From ce0ae744867d805641db573068a83b1b232eb509 Mon Sep 17 00:00:00 2001 From: mattijn Date: Thu, 26 Sep 2019 16:02:06 +0200 Subject: [PATCH 1/7] composing multiple selections --- doc/user_guide/interactions.rst | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/user_guide/interactions.rst b/doc/user_guide/interactions.rst index 293ded86a..cb1e1dca5 100644 --- a/doc/user_guide/interactions.rst +++ b/doc/user_guide/interactions.rst @@ -250,6 +250,50 @@ 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 an selection box when the control-key is +selected and Morgan can make an 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.ctrlKey], 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: + +- "selection": {"not": {"and": ["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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 6ddeae266060ba55b52c9337cf69d1a8f2504243 Mon Sep 17 00:00:00 2001 From: Mattijn van Hoek Date: Fri, 27 Sep 2019 21:30:27 +0200 Subject: [PATCH 2/7] make operators return instances of Selection --- altair/vegalite/v3/api.py | 6 +++--- altair/vegalite/v3/tests/test_api.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/altair/vegalite/v3/api.py b/altair/vegalite/v3/api.py index faf2b73cc..1899a593f 100644 --- a/altair/vegalite/v3/api.py +++ b/altair/vegalite/v3/api.py @@ -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) diff --git a/altair/vegalite/v3/tests/test_api.py b/altair/vegalite/v3/tests/test_api.py index c8d7995ca..caaa9fc81 100644 --- a/altair/vegalite/v3/tests/test_api.py +++ b/altair/vegalite/v3/tests/test_api.py @@ -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() @@ -475,6 +478,15 @@ 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(): From c421192d418852676629e72eeaf1cc5ec9855bd0 Mon Sep 17 00:00:00 2001 From: Mattijn van Hoek Date: Fri, 27 Sep 2019 21:31:31 +0200 Subject: [PATCH 3/7] update example to use altKey (optionKey) on macOS --- doc/user_guide/interactions.rst | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/doc/user_guide/interactions.rst b/doc/user_guide/interactions.rst index cb1e1dca5..a364e1944 100644 --- a/doc/user_guide/interactions.rst +++ b/doc/user_guide/interactions.rst @@ -250,24 +250,27 @@ 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. +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 an selection box when the control-key is -selected and Morgan can make an 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. +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.ctrlKey], mouseup] > mousemove", + on="[mousedown[event.altKey], mouseup] > mousemove", name='alex' ) morgan = alt.selection_interval( @@ -279,7 +282,7 @@ Now, we color the rectangles when they fall within Alex's or Morgan's selection. alt.Chart(cars).mark_rect().encode( x='Cylinders:O', y='Origin:O', - color=alt.condition(alex | morgan, 'count()', alt.ColorValue("grey")) + color=alt.condition(alex | morgan, 'count()', alt.ColorValue("grey")) ).add_selection( alex, morgan ).properties( @@ -289,10 +292,11 @@ Now, we color the rectangles when they fall within Alex's or Morgan's selection. With these operators, selections can be combined in arbitrary ways: -- "selection": {"not": {"and": ["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 +- ``~(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 From 3841268e2170bba47cb7026c4df18730d6f59730 Mon Sep 17 00:00:00 2001 From: Mattijn van Hoek Date: Tue, 1 Oct 2019 22:48:18 +0200 Subject: [PATCH 4/7] add geojson as datatype --- altair/utils/core.py | 3 ++- altair/utils/tests/test_core.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/altair/utils/core.py b/altair/utils/core.py index ffe8c0889..b41818251 100644 --- a/altair/utils/core.py +++ b/altair/utils/core.py @@ -47,7 +47,8 @@ def infer_dtype(value): TYPECODE_MAP = {'ordinal': 'O', 'nominal': 'N', 'quantitative': 'Q', - 'temporal': 'T'} + 'temporal': 'T', + 'geojson': 'G'} INV_TYPECODE_MAP = {v: k for k, v in TYPECODE_MAP.items()} diff --git a/altair/utils/tests/test_core.py b/altair/utils/tests/test_core.py index bbd0aaab0..c19e0c40e 100644 --- a/altair/utils/tests/test_core.py +++ b/altair/utils/tests/test_core.py @@ -82,11 +82,13 @@ def check(s, **kwargs): check('foobar:nominal', type='nominal', field='foobar') check('foobar:ordinal', type='ordinal', field='foobar') check('foobar:temporal', type='temporal', field='foobar') + check('foobar:geojson', type='geojson', field='foobar') check('foobar:Q', type='quantitative', field='foobar') check('foobar:N', type='nominal', field='foobar') check('foobar:O', type='ordinal', field='foobar') check('foobar:T', type='temporal', field='foobar') + check('foobar:G', type='geojson', field='foobar') # Fields with aggregate and/or type check('average(foobar)', field='foobar', aggregate='average') From fb696eaddd51857335df20436d1b79cbc2ac0220 Mon Sep 17 00:00:00 2001 From: Mattijn van Hoek Date: Tue, 1 Oct 2019 22:48:36 +0200 Subject: [PATCH 5/7] add documentation --- doc/user_guide/encoding.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user_guide/encoding.rst b/doc/user_guide/encoding.rst index cdd24a3dc..d5854b2e2 100644 --- a/doc/user_guide/encoding.rst +++ b/doc/user_guide/encoding.rst @@ -132,6 +132,7 @@ quantitative ``Q`` a continuous real-valued quantity ordinal ``O`` a discrete ordered quantity nominal ``N`` a discrete unordered category temporal ``T`` a time or date value +geojson ``G`` a geographic shape ============ ============== ================================================ If types are not specified for data input as a DataFrame, Altair defaults to From d6ac79d185a35ae7fe5918766a988ded91aa63b3 Mon Sep 17 00:00:00 2001 From: Mattijn van Hoek Date: Tue, 1 Oct 2019 22:49:14 +0200 Subject: [PATCH 6/7] add geographical facet example --- .../us_incomebrackets_by_state_facet.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 altair/examples/us_incomebrackets_by_state_facet.py diff --git a/altair/examples/us_incomebrackets_by_state_facet.py b/altair/examples/us_incomebrackets_by_state_facet.py new file mode 100644 index 000000000..6923a1d8c --- /dev/null +++ b/altair/examples/us_incomebrackets_by_state_facet.py @@ -0,0 +1,30 @@ +""" +US Income by State: Wrapped Facet +--------------------------------- +This example shows how to create a map of income in the US by state, +faceted over income brackets +""" +# category: maps + +import altair as alt +from vega_datasets import data + +states = alt.topo_feature(data.us_10m.url, 'states') +source = data.income.url + +alt.Chart(source).mark_geoshape().encode( + shape='geo:G', + color='pct:Q', + tooltip=['name:N', 'pct:Q'], + facet='group:N' +).transform_lookup( + lookup='id', + from_=alt.LookupData(data=states, key='id'), + as_='geo' +).properties( + width=300, + height=175, + columns=2 +).project( + type='albersUsa' +) \ No newline at end of file From 22d0d045621f3649d384f91c0e7cd8ec8ac84d03 Mon Sep 17 00:00:00 2001 From: Mattijn van Hoek Date: Tue, 1 Oct 2019 23:07:13 +0200 Subject: [PATCH 7/7] removed commits from master --- altair/vegalite/v3/api.py | 6 ++-- altair/vegalite/v3/tests/test_api.py | 19 ++--------- doc/user_guide/interactions.rst | 48 ---------------------------- 3 files changed, 6 insertions(+), 67 deletions(-) diff --git a/altair/vegalite/v3/api.py b/altair/vegalite/v3/api.py index 1899a593f..faf2b73cc 100644 --- a/altair/vegalite/v3/api.py +++ b/altair/vegalite/v3/api.py @@ -177,17 +177,17 @@ def to_dict(self): return {'selection': self.name} def __invert__(self): - return Selection(core.SelectionNot(**{'not': self.name}), self.selection) + return core.SelectionNot(**{'not': self.name}) def __and__(self, other): if isinstance(other, Selection): other = other.name - return Selection(core.SelectionAnd(**{'and': [self.name, other]}), self.selection) + return core.SelectionAnd(**{'and': [self.name, other]}) def __or__(self, other): if isinstance(other, Selection): other = other.name - return Selection(core.SelectionOr(**{'or': [self.name, other]}), self.selection) + return core.SelectionOr(**{'or': [self.name, other]}) def __getattr__(self, field_name): return expr.core.GetAttrExpression(self.name, field_name) diff --git a/altair/vegalite/v3/tests/test_api.py b/altair/vegalite/v3/tests/test_api.py index caaa9fc81..bf8aa7f34 100644 --- a/altair/vegalite/v3/tests/test_api.py +++ b/altair/vegalite/v3/tests/test_api.py @@ -359,12 +359,9 @@ def test_selection(): assert set(chart.selection.keys()) == {'selec_1', 'selec_2', 'selec_3'} # test logical operations - 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) + assert isinstance(single & multi, alt.SelectionAnd) + assert isinstance(single | multi, alt.SelectionOr) + assert isinstance(~single, alt.SelectionNot) # test that default names increment (regression for #1454) sel1 = alt.selection_single() @@ -478,16 +475,6 @@ 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') diff --git a/doc/user_guide/interactions.rst b/doc/user_guide/interactions.rst index a364e1944..293ded86a 100644 --- a/doc/user_guide/interactions.rst +++ b/doc/user_guide/interactions.rst @@ -251,54 +251,6 @@ 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