Skip to content

Commit

Permalink
Merge pull request #1716 from mattijn/fix-composing-selections
Browse files Browse the repository at this point in the history
ENH: Recommit removed commits #1707
  • Loading branch information
jakevdp authored Oct 2, 2019
2 parents a8633e4 + 63cc795 commit 907f63b
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
6 changes: 3 additions & 3 deletions altair/vegalite/v3/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,17 +177,17 @@ def to_dict(self):
return {'selection': self.name}

def __invert__(self):
return core.SelectionNot(**{'not': self.name})
return Selection(core.SelectionNot(**{'not': self.name}), self.selection)

def __and__(self, other):
if isinstance(other, Selection):
other = other.name
return core.SelectionAnd(**{'and': [self.name, other]})
return Selection(core.SelectionAnd(**{'and': [self.name, other]}), self.selection)

def __or__(self, other):
if isinstance(other, Selection):
other = other.name
return core.SelectionOr(**{'or': [self.name, other]})
return Selection(core.SelectionOr(**{'or': [self.name, other]}), self.selection)

def __getattr__(self, field_name):
return expr.core.GetAttrExpression(self.name, field_name)
Expand Down
19 changes: 16 additions & 3 deletions altair/vegalite/v3/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,12 @@ def test_selection():
assert set(chart.selection.keys()) == {'selec_1', 'selec_2', 'selec_3'}

# test logical operations
assert isinstance(single & multi, alt.SelectionAnd)
assert isinstance(single | multi, alt.SelectionOr)
assert isinstance(~single, alt.SelectionNot)
assert isinstance(single & multi, alt.Selection)
assert isinstance(single | multi, alt.Selection)
assert isinstance(~single, alt.Selection)
assert isinstance((single & multi)[0].group, alt.SelectionAnd)
assert isinstance((single | multi)[0].group, alt.SelectionOr)
assert isinstance((~single)[0].group, alt.SelectionNot)

# test that default names increment (regression for #1454)
sel1 = alt.selection_single()
Expand Down Expand Up @@ -475,6 +478,16 @@ def test_filter_transform_selection_predicates():
chart = base.transform_filter(selector1 | selector2)
assert chart.to_dict()['transform'] == [{'filter': {'selection': {'or': ['s1', 's2']}}}]

chart = base.transform_filter(selector1 | ~selector2)
assert chart.to_dict()['transform'] == [{'filter': {'selection': {'or': ['s1', {'not': 's2'}]}}}]

chart = base.transform_filter(~selector1 | ~selector2)
assert chart.to_dict()['transform'] == [{'filter': {'selection': {'or': [{'not': 's1'}, {'not': 's2'}]}}}]

chart = base.transform_filter(~(selector1 & selector2))
assert chart.to_dict()['transform'] == [{'filter': {'selection': {'not': {'and': ['s1', 's2']}}}}]



def test_resolve_methods():
chart = alt.LayerChart().resolve_axis(x='shared', y='independent')
Expand Down
48 changes: 48 additions & 0 deletions doc/user_guide/interactions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,54 @@ over them with your mouse:
multi_mouseover = alt.selection_multi(on='mouseover', toggle=False, empty='none')
make_example(multi_mouseover)

Composing Multiple Selections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Altair also supports combining multiple selections using the ``&``, ``|``
and ``~`` for respectively ``AND``, ``OR`` and ``NOT`` logical composition
operands.

In the following example there are two people who can make an interval
selection in the chart. The person Alex makes a selection box when the
alt-key (macOS: option-key) is selected and Morgan can make a selection
box when the shift-key is selected.
We use the alt.Brushconfig() to give the selection box of Morgan a different
style.
Now, we color the rectangles when they fall within Alex's or Morgan's
selection.

.. altair-plot::

alex = alt.selection_interval(
on="[mousedown[event.altKey], mouseup] > mousemove",
name='alex'
)
morgan = alt.selection_interval(
on="[mousedown[event.shiftKey], mouseup] > mousemove",
mark=alt.BrushConfig(fill="#fdbb84", fillOpacity=0.5, stroke="#e34a33"),
name='morgan'
)

alt.Chart(cars).mark_rect().encode(
x='Cylinders:O',
y='Origin:O',
color=alt.condition(alex | morgan, 'count()', alt.ColorValue("grey"))
).add_selection(
alex, morgan
).properties(
width=300,
height=180
)

With these operators, selections can be combined in arbitrary ways:

- ``~(alex & morgan)``: to select the rectangles that fall outside
Alex's and Morgan's selections.

- ``alex | ~morgan``: to select the rectangles that fall within Alex's
selection or outside the selection of Morgan


Selection Targets: Fields and Encodings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For any but the simplest selections, the user needs to think about exactly
Expand Down

0 comments on commit 907f63b

Please sign in to comment.