altair
altair copied to clipboard
Multiple selections not working when one is empty
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)
org_sel is undefined in your example
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 could you find a solution for this? This seems so basic but at the same time it just doesn't work
Sadly never did, I just had to remove some functionality. Would definitely be curious if this is doable.
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.
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 thx, I created it here: https://github.com/vega/vega-lite/issues/5553
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",
)
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.
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)
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)
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"))
)
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!