diff --git a/python-package/lets_plot/plot/core.py b/python-package/lets_plot/plot/core.py index c5a23d712ec..9cbfcde5527 100644 --- a/python-package/lets_plot/plot/core.py +++ b/python-package/lets_plot/plot/core.py @@ -130,43 +130,8 @@ def layer(geom=None, stat=None, data=None, mapping=None, position=None, **kwargs return LayerSpec(**locals()) -def _cleanup_spec_list(obj): - assert isinstance(obj, list) - - if all(isinstance(item, (dict, type(None))) for item in obj): - return [v for v in map(lambda item: _cleanup_spec(item), obj) if v is not None] - - # ignore non-dict list or it may produce unexpected result: [['asd'], [None]] => [['asd'],[]] - return obj - - -def _cleanup_spec(obj: dict): - assert isinstance(obj, dict) - res = {} - for k, v in obj.items(): - if v is None: - continue - - if k in ['data', 'map']: - res[k] = v - continue - - if isinstance(v, list): - if k in ['scales', 'layers', 'feature-list', 'tooltip_formats', 'tooltip_lines', 'tooltip_variables']: - clean = _cleanup_spec_list(v) - if len(clean) > 0: - res[k] = clean - else: - res[k] = v - - elif isinstance(v, dict): - clean = _cleanup_spec(v) - # Empty corr_plot layer adds geom with default visuals, if remove - no layer will be added at all - if len(clean) > 0 or k in ['point_params', 'tile_params', 'label_params']: - res[k] = clean - else: - res[k] = v - return res +def _filter_none(original: dict) -> dict: + return {k: v for k, v in original.items() if v is not None} # @@ -184,7 +149,7 @@ def _specs_to_dict(opts_raw): else: opts[k] = v - return _cleanup_spec(opts) + return _filter_none(opts) class FeatureSpec(): @@ -425,8 +390,7 @@ def as_dict(self): d['kind'] = self.kind d['scales'] = [scale.as_dict() for scale in self.__scales] d['layers'] = [layer.as_dict() for layer in self.__layers] - - return _cleanup_spec(d) + return d def __str__(self): result = ['plot'] @@ -567,7 +531,7 @@ def elements(self): def as_dict(self): elements = [{e.kind: e.as_dict()} for e in self.__elements] - return _cleanup_spec({'feature-list': elements}) + return {'feature-list': elements} def __add__(self, other): if isinstance(other, DummySpec): diff --git a/python-package/lets_plot/plot/plot.py b/python-package/lets_plot/plot/plot.py index 7c239e9c456..2bad714266a 100644 --- a/python-package/lets_plot/plot/plot.py +++ b/python-package/lets_plot/plot/plot.py @@ -6,7 +6,7 @@ from lets_plot._global_settings import has_global_value, get_global_val, MAX_WIDTH, MAX_HEIGHT from lets_plot.geo_data_internals.utils import is_geocoder -from lets_plot.plot.core import FeatureSpec, _cleanup_spec +from lets_plot.plot.core import FeatureSpec from lets_plot.plot.core import PlotSpec from lets_plot.plot.util import as_annotated_data @@ -211,7 +211,7 @@ def item_as_dict(item): return result d['items'] = [item_as_dict(item) for item in self.items] - return _cleanup_spec(d) + return d def _repr_html_(self): """ diff --git a/python-package/lets_plot/plot/tooltip.py b/python-package/lets_plot/plot/tooltip.py index 2e43b1edcc5..d023018b507 100644 --- a/python-package/lets_plot/plot/tooltip.py +++ b/python-package/lets_plot/plot/tooltip.py @@ -4,7 +4,7 @@ from typing import List -from lets_plot.plot.core import FeatureSpec, _cleanup_spec +from lets_plot.plot.core import FeatureSpec, _filter_none # # Tooltips @@ -116,7 +116,7 @@ def as_dict(self): d['tooltip_min_width'] = self._tooltip_min_width d['tooltip_color'] = self._tooltip_color d['tooltip_variables'] = self._tooltip_variables - return _cleanup_spec(d) + return _filter_none(d) def format(self, field=None, format=None): """ diff --git a/python-package/test/plot/test_assembly.py b/python-package/test/plot/test_assembly.py index 7ebc21325a5..93356b381bc 100644 --- a/python-package/test/plot/test_assembly.py +++ b/python-package/test/plot/test_assembly.py @@ -17,7 +17,13 @@ def test_plot_geom_geom(): expect = { 'kind': 'plot', - 'layers': [{'geom': 'geom1'}, {'geom': 'geom2'}] + 'mapping': {}, + 'data_meta': {}, + 'layers': [ + geom1.as_dict(), + geom2.as_dict() + ], + 'scales': [] } assert (plot + geom1 + geom2).as_dict() == expect @@ -32,8 +38,11 @@ def test_plot_geom_scale(): expect = { 'kind': 'plot', - 'layers': [{'geom': 'geom'}], - 'scales': [{'aesthetic': 'A'}]} + 'mapping': {}, + 'data_meta': {}, + 'layers': [geom.as_dict()], + 'scales': [scale.as_dict()] + } assert (plot + geom + scale).as_dict() == expect assert (plot + scale + geom).as_dict() == expect @@ -45,8 +54,12 @@ def test_plot_ggtitle(): ggtitle = gg.labs(title="New plot title") expect = { + 'ggtitle': {'text': 'New plot title'}, 'kind': 'plot', - 'ggtitle': {'text': 'New plot title'} + 'data_meta': {}, + 'layers': [], + 'mapping': {}, + 'scales': [] } assert (plot + ggtitle).as_dict() == expect diff --git a/python-package/test/plot/test_geom.py b/python-package/test/plot/test_geom.py index 738408d43d6..f9f385bdd06 100644 --- a/python-package/test/plot/test_geom.py +++ b/python-package/test/plot/test_geom.py @@ -16,12 +16,15 @@ class TestWithListAndDictArgs: mapping_arg = gg.aes('X') expected[0] = dict( geom='n', + data_meta={}, mapping=mapping_arg.as_dict() ) # II expected[1] = dict( geom='n', + mapping={}, + data_meta={}, arrow={'angle': 0, 'length': 1, 'ends': 'a', 'type': 'b', 'name': 'arrow'} ) diff --git a/python-package/test/plot/test_geom_image.py b/python-package/test/plot/test_geom_image.py index 621a53219a4..a87183a0dd7 100644 --- a/python-package/test/plot/test_geom_image.py +++ b/python-package/test/plot/test_geom_image.py @@ -12,6 +12,7 @@ def _image_spec(width, height, href): return dict( + data_meta={}, geom='image', # image_spec=dict( # width=width, diff --git a/python-package/test/plot/test_ggplot.py b/python-package/test/plot/test_ggplot.py index eb6f40ba076..cf0c7eba0a6 100644 --- a/python-package/test/plot/test_ggplot.py +++ b/python-package/test/plot/test_ggplot.py @@ -6,15 +6,15 @@ import lets_plot as gg -data = [1, 2] +data = {'a': [1, 2], 'b': [3, 4]} mapping_empty = gg.aes() mapping_x = gg.aes('X') -result_empty = {'kind': 'plot'} -result_data = {'data': data, 'kind': 'plot'} -result_data_mapping_empty = {'data': data, 'kind': 'plot'} -result_data_mapping_x = {'data': data, 'mapping': {'x': 'X'}, 'kind': 'plot'} -result_mapping_x = {'mapping': {'x': 'X'}, 'kind': 'plot'} +result_empty = {'kind': 'plot', 'mapping': {}, 'data_meta': {}, 'layers': [], 'scales': []} +result_data = {'data': data, 'kind': 'plot', 'mapping': {}, 'data_meta': {}, 'layers': [], 'scales': []} +result_data_mapping_empty = {'data': data, 'kind': 'plot', 'mapping': {}, 'data_meta': {}, 'layers': [], 'scales': []} +result_data_mapping_x = {'data': data, 'mapping': {'x': 'X'}, 'data_meta': {}, 'kind': 'plot', 'layers': [], 'scales': []} +result_mapping_x = {'mapping': {'x': 'X'}, 'data_meta': {}, 'kind': 'plot', 'layers': [], 'scales': []} @pytest.mark.parametrize('args,expected', [ diff --git a/python-package/test/plot/test_ggsize.py b/python-package/test/plot/test_ggsize.py index 22ae5d3d4c8..b31ce2bda69 100644 --- a/python-package/test/plot/test_ggsize.py +++ b/python-package/test/plot/test_ggsize.py @@ -9,4 +9,4 @@ # noinspection SpellCheckingInspection def test_ggsize(): spec = gg.ggplot() + gg.ggsize(5, 10) - assert spec.as_dict() == {'kind': 'plot', 'ggsize': {'height': 10, 'width': 5}} + assert spec.as_dict() == {'kind': 'plot', 'ggsize': {'height': 10, 'width': 5}, 'mapping': {}, 'data_meta': {}, 'layers': [], 'scales': []} diff --git a/python-package/test/plot/test_scale.py b/python-package/test/plot/test_scale.py index 66d91464e5b..366f29de513 100644 --- a/python-package/test/plot/test_scale.py +++ b/python-package/test/plot/test_scale.py @@ -30,7 +30,7 @@ def test_scale(args_list, args_dict, expected): def test_labs_empty(): spec = gg.labs() - assert len(spec.as_dict()) == 0 + assert spec.as_dict()['feature-list'] == [] def test_plot_title():