altair icon indicating copy to clipboard operation
altair copied to clipboard

Multiple selections not working when one is empty

Open Alan-Penkar opened this issue 6 years ago • 12 comments

Below is a minimal adaptation of this example.

I am trying to incorporate an interval filter and a legend filter. What I want is the coloring in the scatter plot to be colored if both selections are met. This works fine when both selections are null and works fine when both selections are not null. The issue I'm having is that using the brush1 & brush2 seems to return no selection if brush1 XOR brush2 is null. I would have though the empty='all' being default should take care of this, but there appears to be odd behavior in the & that negates this? Any help would be appreciated (btw I am using Jupyter Lab and already updated vega as suggested in issue 1710 ).

import altair as alt
from vega_datasets import data

source = data.cars()

brush1 = alt.selection_interval()
brush2 = alt.selection_multi(fields=['Origin'], empty='all')

color = alt.condition(brush1 & brush2, alt.Color('Origin:N', legend=None), alt.value('lightgray'))

base = alt.Chart(source)
points = base.mark_circle().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=color
).add_selection(
    brush1
).properties(width=500)

legend = base.mark_point(size=100).encode(
    y=alt.Y('Origin:N', axis=alt.Axis(orient='right', ticks=False, grid=False)),
    color=alt.condition(brush2, alt.Color('Origin:N', legend=None), alt.value('lightgray'))
).add_selection(brush2)

bars = base.mark_bar().encode(
    y='Origin:N',
    color=alt.condition(brush2, alt.Color('Origin:N', legend=None), alt.value('lightgray')),
    x='count(Origin):Q'
).transform_filter(
    brush1
)

alt.vconcat(alt.hconcat(points, legend), bars)

Alan-Penkar avatar Oct 29 '19 21:10 Alan-Penkar

org_sel is undefined in your example

jakevdp avatar Oct 29 '19 23:10 jakevdp

Sorry about that- I lazily cleaned up some naming in the github dialog box and missed a name change. I updated the code above and confirmed it's working (issue still present, but no variable naming problems).

Alan-Penkar avatar Oct 30 '19 00:10 Alan-Penkar

@Alan-Penkar could you find a solution for this? This seems so basic but at the same time it just doesn't work

victornoel avatar Nov 14 '19 15:11 victornoel

Sadly never did, I just had to remove some functionality. Would definitely be curious if this is doable.

Alan-Penkar avatar Nov 14 '19 16:11 Alan-Penkar

Yep, the same happens when you use on selection_single for example, but with multiple fields and binding to inputs: all must have a value, if not it just doesn't work.

victornoel avatar Nov 14 '19 16:11 victornoel

I think it's a Vega-Lite bug: the empty='none' and empty='all' specifications don't seem to influence logical operations over selections. I'd suggest opening a Vega-Lite issue.

jakevdp avatar Nov 14 '19 17:11 jakevdp

@jakevdp thx, I created it here: https://github.com/vega/vega-lite/issues/5553

victornoel avatar Nov 15 '19 08:11 victornoel

Here's a workaround for anyone experiencing this issue (which for me persists as of altair 4.1.0).

Suppose you have two selection_singles combined as follows:

filtered = base.add_selection(
    one_select, two_select
).transform_filter(
    edit_select & drd_select
)

The above chart does not initialize properly until and unless the user toggles both select dropdowns. Nevertheless, providing an init for both selects solves the issue and the chart functions perfectly from initialization on:

ones = df['one'].unique()
one_select = alt.selection_single(
    fields=['one'],
    bind=one_dropdown,
    init={'one': ones[0]},
    name="DRD",
)

akarve avatar Aug 27 '21 00:08 akarve

https://github.com/vega/vega-lite/issues/5553 has been closed, but this issue remains.

Rephrasing the issue: if two selectors s1 and s2 are combined into s = s1 & s2, then exactly one of s1 or s2 being empty makes s considered false. However, s works as expected when they are either both empty, or both non-empty.

m-legrand avatar Sep 10 '21 14:09 m-legrand

Here's a workaround for anyone experiencing this issue (which for me persists as of altair 4.1.0).

Suppose you two selection_singles combined as follows:

filtered = base.add_selection(
    one_select, two_select
).transform_filter(
    edit_select & drd_select
)

The easiest workaround for this use case is to avoid using & altogether by chaining:

filtered = base.add_selection(s1, s2).transform_filter(s1).transform_filter(s2)

Unfortunately I didn't find a workaround for setting some encoding, e.g.

filtered = base.add_selection(s1, s2).encode(
    color=alt.condition(s1 & s2, alt.ColorValue("firebrick"), alt.ColorValue("lightgray"))  # <- doesn't work properly
)

I tried

filtered = base.add_selection(s1, s2).encode(
    color=alt.condition(
        s1, 
        alt.condition(s2, alt.ColorValue("firebrick"), alt.ColorValue("lightgray")), 
        alt.ColorValue("lightgray"),
    )  
)

But I then get a SchemaValidationError:

SchemaValidationError: Invalid specification

        altair.vegalite.v4.schema.channels.Color, validating 'additionalProperties'

        Additional properties are not allowed ('selection' was unexpected)

m-legrand avatar Sep 10 '21 14:09 m-legrand

I actually found a very dirty hack to work around the problem above if anyone needs it:

bg = base.add_selection(s1, s2).encode(color=alt.ColorValue("lightgray"))
fg = base.encode(color=alt.ColorValue("firebrick")).transform_filter(s1).transform_filter(s2)
chart = (bg + fg)

m-legrand avatar Sep 10 '21 14:09 m-legrand

By setting empty parameter of selection object to 'none' and initializing the selection (init parameter) with all possible values solves the problem (also for setting the encodings).

s1 = alt.selection_multi(fields=['one'], empty='none', init=[{'one': item} for item in df['one'].unique().tolist()])
s2 = alt.selection_multi(fields=['two'], empty='none', init=[{'two': item} for item in df['two'].unique().tolist()])

filtered = base.add_selection(s1, s2).encode(
    color=alt.condition(s1 & s2, alt.ColorValue("firebrick"), alt.ColorValue("lightgray"))
)

younos avatar Sep 30 '21 10:09 younos

The example in the first post works for me with Altair 5, presumably because https://github.com/vega/vega-lite/issues/5553 got merged and is now used. Feel free to reopen if needed!

binste avatar May 26 '23 19:05 binste