Repository: altair-viz/altair
Pull request #1641 (2019-08-04 19:31:07)

Test resolve() and facet() methods
Addresses part of #1637


Patch:
diff --git a/altair/vegalite/v3/api.py b/altair/vegalite/v3/api.py
index 8e9fc1ba..44663262 100644
--- a/altair/vegalite/v3/api.py
+++ b/altair/vegalite/v3/api.py
@@ -1354,6 +1354,50 @@ class TopLevelMixin(mixins.ConfigMethodMixin):
               files=files, jupyter_warning=jupyter_warning,
               open_browser=open_browser, http_server=http_server)
 
+    @utils.use_signature(core.Resolve)
+    def _set_resolve(self, **kwargs):
+        """Copy the chart and update the resolve property with kwargs"""
+        if not hasattr(self, 'resolve'):
+            raise ValueError("{} object has no attribute "
+                             "'resolve'".format(self.__class__))
+        copy = self.copy(deep=['resolve'])
+        if copy.resolve is Undefined:
+            copy.resolve = core.Resolve()
+        for key, val in kwargs.items():
+            copy.resolve[key] = val
+        return copy
+
+    @utils.use_signature(core.AxisResolveMap)
+    def resolve_axis(self, *args, **kwargs):
+        return self._set_resolve(axis=core.AxisResolveMap(*args, **kwargs))
+
+    @utils.use_signature(core.LegendResolveMap)
+    def resolve_legend(self, *args, **kwargs):
+        return self._set_resolve(legend=core.LegendResolveMap(*args, **kwargs))
+
+    @utils.use_signature(core.ScaleResolveMap)
+    def resolve_scale(self, *args, **kwargs):
+        return self._set_resolve(scale=core.ScaleResolveMap(*args, **kwargs))
+
+
+class _EncodingMixin(object):
+    @utils.use_signature(core.FacetedEncoding)
+    def encode(self, *args, **kwargs):
+        # Convert args to kwargs based on their types.
+        kwargs = utils.infer_encoding_types(args, kwargs, channels)
+
+        # get a copy of the dict representation of the previous encoding
+        copy = self.copy(deep=['encoding'])
+        encoding = copy._get('encoding', {})
+        if isinstance(encoding, core.VegaLiteSchema):
+            encoding = {k: v for k, v in encoding._kwds.items()
+                        if v is not Undefined}
+
+        # update with the new encodings, and apply them to the copy
+        encoding.update(kwargs)
+        copy.encoding = core.FacetedEncoding(**encoding)
+        return copy
+
     def facet(self, facet=Undefined, row=Undefined, column=Undefined, data=Undefined,
               columns=Undefined, **kwargs):
         """Create a facet chart from the current chart.
@@ -1405,51 +1449,6 @@ class TopLevelMixin(mixins.ConfigMethodMixin):
         return FacetChart(spec=self, facet=facet, data=data, columns=columns, **kwargs)
 
 
-class _ResolveMixin(object):
-    @utils.use_signature(core.Resolve)
-    def _set_resolve(self, **kwargs):
-        """Copy the chart and update the resolve property with kwargs"""
-        if not hasattr(self, 'resolve'):
-            raise ValueError("{} object has no attribute "
-                             "'resolve'".format(self.__class__))
-        copy = self.copy(deep=['resolve'])
-        if copy.resolve is Undefined:
-            copy.resolve = core.Resolve()
-        for key, val in kwargs.items():
-            copy.resolve[key] = val
-        return copy
-
-    @utils.use_signature(core.AxisResolveMap)
-    def resolve_axis(self, *args, **kwargs):
-        return self._set_resolve(axis=core.AxisResolveMap(*args, **kwargs))
-
-    @utils.use_signature(core.LegendResolveMap)
-    def resolve_legend(self, *args, **kwargs):
-        return self._set_resolve(legend=core.LegendResolveMap(*args, **kwargs))
-
-    @utils.use_signature(core.ScaleResolveMap)
-    def resolve_scale(self, *args, **kwargs):
-        return self._set_resolve(scale=core.ScaleResolveMap(*args, **kwargs))
-
-
-class _EncodingMixin(object):
-    @utils.use_signature(core.FacetedEncoding)
-    def encode(self, *args, **kwargs):
-        # Convert args to kwargs based on their types.
-        kwargs = utils.infer_encoding_types(args, kwargs, channels)
-
-        # get a copy of the dict representation of the previous encoding
-        copy = self.copy(deep=['encoding'])
-        encoding = copy._get('encoding', {})
-        if isinstance(encoding, core.VegaLiteSchema):
-            encoding = {k: v for k, v in encoding._kwds.items()
-                        if v is not Undefined}
-
-        # update with the new encodings, and apply them to the copy
-        encoding.update(kwargs)
-        copy.encoding = core.FacetedEncoding(**encoding)
-        return copy
-
 
 class Chart(TopLevelMixin, _EncodingMixin, mixins.MarkMethodMixin,
             core.TopLevelUnitSpec):
@@ -1642,7 +1641,7 @@ def _check_if_can_be_layered(spec):
 
 
 @utils.use_signature(core.TopLevelRepeatSpec)
-class RepeatChart(TopLevelMixin, _ResolveMixin, core.TopLevelRepeatSpec):
+class RepeatChart(TopLevelMixin, core.TopLevelRepeatSpec):
     """A chart repeated across rows and columns with small changes"""
     def __init__(self, data=Undefined, spec=Undefined, repeat=Undefined, **kwargs):
         _check_if_valid_subspec(spec, 'RepeatChart')
@@ -1700,7 +1699,7 @@ def repeat(repeater='repeat'):
 
 
 @utils.use_signature(core.TopLevelConcatSpec)
-class ConcatChart(TopLevelMixin, _ResolveMixin, core.TopLevelConcatSpec):
+class ConcatChart(TopLevelMixin, core.TopLevelConcatSpec):
     """A chart with horizontally-concatenated facets"""
     def __init__(self, data=Undefined, concat=(), columns=Undefined, **kwargs):
         # TODO: move common data to top level?
@@ -1737,7 +1736,7 @@ def concat(*charts, **kwargs):
 
 
 @utils.use_signature(core.TopLevelHConcatSpec)
-class HConcatChart(TopLevelMixin, _ResolveMixin, core.TopLevelHConcatSpec):
+class HConcatChart(TopLevelMixin, core.TopLevelHConcatSpec):
     """A chart with horizontally-concatenated facets"""
     def __init__(self, data=Undefined, hconcat=(), **kwargs):
         # TODO: move common data to top level?
@@ -1773,7 +1772,7 @@ def hconcat(*charts, **kwargs):
 
 
 @utils.use_signature(core.TopLevelVConcatSpec)
-class VConcatChart(TopLevelMixin, _ResolveMixin, core.TopLevelVConcatSpec):
+class VConcatChart(TopLevelMixin, core.TopLevelVConcatSpec):
     """A chart with vertically-concatenated facets"""
     def __init__(self, data=Undefined, vconcat=(), **kwargs):
         # TODO: move common data to top level?
@@ -1809,7 +1808,7 @@ def vconcat(*charts, **kwargs):
 
 
 @utils.use_signature(core.TopLevelLayerSpec)
-class LayerChart(TopLevelMixin, _ResolveMixin, _EncodingMixin, core.TopLevelLayerSpec):
+class LayerChart(TopLevelMixin, _EncodingMixin, core.TopLevelLayerSpec):
     """A Chart with layers within a single panel"""
     def __init__(self, data=Undefined, layer=(), **kwargs):
         # TODO: move common data to top level?
@@ -1880,7 +1879,7 @@ def layer(*charts, **kwargs):
 
 
 @utils.use_signature(core.TopLevelFacetSpec)
-class FacetChart(TopLevelMixin, _ResolveMixin, core.TopLevelFacetSpec):
+class FacetChart(TopLevelMixin, core.TopLevelFacetSpec):
     """A Chart with layers within a single panel"""
     def __init__(self, data=Undefined, spec=Undefined, facet=Undefined, **kwargs):
         _check_if_valid_subspec(spec, 'FacetChart')


Test patch:
diff --git a/altair/vegalite/v3/tests/test_api.py b/altair/vegalite/v3/tests/test_api.py
index f0e4b849..60b8fb2e 100644
--- a/altair/vegalite/v3/tests/test_api.py
+++ b/altair/vegalite/v3/tests/test_api.py
@@ -30,6 +30,37 @@ OP_DICT = {
 }
 
 
+def _make_chart_type(chart_type):
+    data = pd.DataFrame({
+        'x': [28, 55, 43, 91, 81, 53, 19, 87],
+        'y': [43, 91, 81, 53, 19, 87, 52, 28],
+        'color': list('AAAABBBB'),
+    })
+    base = alt.Chart(data).mark_point().encode(
+        x='x',
+        y='y',
+        color='color',
+    )
+
+    if chart_type in ['layer', 'hconcat', 'vconcat', 'concat']:
+        func = getattr(alt, chart_type)
+        return func(base.mark_square(), base.mark_circle())
+    elif chart_type == 'facet':
+        return base.facet('color')
+    elif chart_type == 'facet_encoding':
+        return base.encode(facet='color')
+    elif chart_type == 'repeat':
+        return base.encode(
+            alt.X(alt.repeat(), type='quantitative')
+        ).repeat(
+            ['x', 'y']
+        )
+    elif chart_type == 'chart':
+        return base
+    else:
+        raise ValueError("chart_type='{}' is not recognized".format(chart_type))
+
+
 @pytest.fixture
 def basic_chart():
     data = pd.DataFrame({
@@ -767,3 +798,45 @@ def test_layer_errors():
     with pytest.raises(ValueError) as err:
         alt.layer(simple_chart) + facet_chart2
     assert str(err.value) == "Faceted charts cannot be layered."
+
+
+@pytest.mark.parametrize(
+    'chart_type',
+    ['layer', 'hconcat', 'vconcat', 'concat', 'facet', 'facet_encoding', 'repeat']
+)
+def test_resolve(chart_type):
+    chart = _make_chart_type(chart_type)
+    chart = chart.resolve_scale(
+        x='independent',
+    ).resolve_legend(
+        color='independent'
+    ).resolve_axis(
+        y='independent'
+    )
+    dct = chart.to_dict()
+    assert dct['resolve'] == {
+        'scale': {'x': 'independent'},
+        'legend': {'color': 'independent'},
+        'axis': {'y': 'independent'},
+    }
+
+
+# TODO: test vconcat, hconcat, concat when schema allows them.
+# This is blocked by https://github.com/vega/vega-lite/issues/5261
+@pytest.mark.parametrize('chart_type', ['chart', 'layer', 'facet_encoding'])
+@pytest.mark.parametrize('facet_arg', [None, 'facet', 'row', 'column'])
+def test_facet(chart_type, facet_arg):
+    chart = _make_chart_type(chart_type)
+    if facet_arg is None:
+        chart = chart.facet('color:N', columns=2)
+    else:
+        chart = chart.facet(**{facet_arg: 'color:N', 'columns': 2})
+    dct = chart.to_dict()
+
+    assert 'spec' in dct
+    assert dct['columns'] == 2
+    expected = {'field': 'color', 'type': 'nominal'}
+    if facet_arg is None or facet_arg == 'facet':
+        assert dct['facet'] == expected
+    else:
+        assert dct['facet'][facet_arg] == expected