From b451f071448ccb4a9e4142b7ec7aac5b247c8cb7 Mon Sep 17 00:00:00 2001 From: Ivan Kupriyanov Date: Fri, 12 Nov 2021 00:52:05 +0300 Subject: [PATCH 1/4] to_dict cleanup - remove empty dicts/lists, all None entries, keep empty dicts for corr_plot layers, skip cleanup for 'data' dicts. --- python-package/lets_plot/plot/core.py | 31 ++++++++++++++++++++++- python-package/test/plot/test_assembly.py | 26 +++---------------- python-package/test/plot/test_ggplot.py | 10 ++++---- python-package/test/plot/test_ggsize.py | 8 +----- python-package/test/plot/test_theme.py | 2 +- 5 files changed, 41 insertions(+), 36 deletions(-) diff --git a/python-package/lets_plot/plot/core.py b/python-package/lets_plot/plot/core.py index bbba48055ff..2b710a5ad9d 100644 --- a/python-package/lets_plot/plot/core.py +++ b/python-package/lets_plot/plot/core.py @@ -386,7 +386,36 @@ 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 d + + # Empty corr_plot layer adds geom with default visuals, if remove - no layer will be added at all + keep_if_empty = ['point_params', 'tile_params', 'label_params'] + + def cleanup(o): + if isinstance(o, list): + return [cleanup(item) for item in o] + + if isinstance(o, dict): + res = {} + for k, v in o.items(): + if v is None: + continue + + if k == 'data': + res[k] = v + continue + + if isinstance(v, (dict, list)): + clean = cleanup(v) + if len(clean) > 0 or k in keep_if_empty: + res[k] = clean + else: + res[k] = v + return res + + else: + return o + + return cleanup(d) def __str__(self): result = ['plot'] diff --git a/python-package/test/plot/test_assembly.py b/python-package/test/plot/test_assembly.py index 2efa857abcc..7ebc21325a5 100644 --- a/python-package/test/plot/test_assembly.py +++ b/python-package/test/plot/test_assembly.py @@ -17,14 +17,8 @@ def test_plot_geom_geom(): expect = { 'kind': 'plot', - 'data': None, - 'data_meta': {}, - 'mapping': {'x': None, 'y': None}, - 'layers': [ - geom1.as_dict(), - geom2.as_dict() - ], - 'scales': []} + 'layers': [{'geom': 'geom1'}, {'geom': 'geom2'}] + } assert (plot + geom1 + geom2).as_dict() == expect assert (plot + (geom1 + geom2)).as_dict() == expect @@ -38,15 +32,8 @@ def test_plot_geom_scale(): expect = { 'kind': 'plot', - 'data': None, - 'data_meta': {}, - 'mapping': {'x': None, 'y': None}, - 'layers': [ - geom.as_dict() - ], - 'scales': [ - scale.as_dict() - ]} + 'layers': [{'geom': 'geom'}], + 'scales': [{'aesthetic': 'A'}]} assert (plot + geom + scale).as_dict() == expect assert (plot + scale + geom).as_dict() == expect @@ -59,11 +46,6 @@ def test_plot_ggtitle(): expect = { 'kind': 'plot', - 'data': None, - 'mapping': {'x': None, 'y': None}, - 'data_meta': {}, - 'layers': [], - 'scales': [], 'ggtitle': {'text': 'New plot title'} } diff --git a/python-package/test/plot/test_ggplot.py b/python-package/test/plot/test_ggplot.py index 51341304386..eb6f40ba076 100644 --- a/python-package/test/plot/test_ggplot.py +++ b/python-package/test/plot/test_ggplot.py @@ -10,11 +10,11 @@ mapping_empty = gg.aes() mapping_x = gg.aes('X') -result_empty = {'data': None, 'data_meta': {}, 'mapping': {'x': None, 'y': None}, 'layers': [], 'scales': [], 'kind': 'plot'} -result_data = {'data': data, 'data_meta': {}, 'mapping': {'x': None, 'y': None}, 'layers': [], 'scales': [], 'kind': 'plot'} -result_data_mapping_empty = {'data': data, 'data_meta': {}, 'mapping': mapping_empty.as_dict(), 'layers': [], 'scales': [], 'kind': 'plot'} -result_data_mapping_x = {'data': data, 'data_meta': {}, 'mapping': mapping_x.as_dict(), 'layers': [], 'scales': [], 'kind': 'plot'} -result_mapping_x = {'data': None, 'data_meta': {}, 'mapping': mapping_x.as_dict(), 'layers': [], 'scales': [], 'kind': 'plot'} +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'} @pytest.mark.parametrize('args,expected', [ diff --git a/python-package/test/plot/test_ggsize.py b/python-package/test/plot/test_ggsize.py index bec2c38f585..22ae5d3d4c8 100644 --- a/python-package/test/plot/test_ggsize.py +++ b/python-package/test/plot/test_ggsize.py @@ -9,10 +9,4 @@ # noinspection SpellCheckingInspection def test_ggsize(): spec = gg.ggplot() + gg.ggsize(5, 10) - assert spec.as_dict() == {'data': None, - 'data_meta': {}, - 'mapping': {'x': None, 'y': None}, - 'kind': 'plot', - 'ggsize': {'height': 10, 'width': 5}, - 'layers': [], - 'scales': []} + assert spec.as_dict() == {'kind': 'plot', 'ggsize': {'height': 10, 'width': 5}} diff --git a/python-package/test/plot/test_theme.py b/python-package/test/plot/test_theme.py index d4be977bc1c..05712c9d340 100644 --- a/python-package/test/plot/test_theme.py +++ b/python-package/test/plot/test_theme.py @@ -6,7 +6,7 @@ from lets_plot import element_rect from lets_plot.plot import theme from lets_plot.plot.geom import _geom -from lets_plot.plot.theme_classic_ import theme_classic +from lets_plot.plot.theme_set import theme_classic def test_theme_options_should_be_merged(): From bd04ec886c3e70c1629e032573d90b96a5fafe70 Mon Sep 17 00:00:00 2001 From: Ivan Kupriyanov Date: Wed, 17 Nov 2021 00:29:55 +0300 Subject: [PATCH 2/4] Specs cleanup --- python-package/lets_plot/plot/core.py | 67 +++++++++++---------- python-package/lets_plot/plot/plot.py | 4 +- python-package/lets_plot/plot/tooltip.py | 5 +- python-package/test/plot/test_aes.py | 2 +- python-package/test/plot/test_geom.py | 17 +----- python-package/test/plot/test_geom_image.py | 11 +--- python-package/test/plot/test_scale.py | 5 +- 7 files changed, 45 insertions(+), 66 deletions(-) diff --git a/python-package/lets_plot/plot/core.py b/python-package/lets_plot/plot/core.py index 2b710a5ad9d..3f355b9b803 100644 --- a/python-package/lets_plot/plot/core.py +++ b/python-package/lets_plot/plot/core.py @@ -130,6 +130,39 @@ def layer(geom=None, stat=None, data=None, mapping=None, position=None, **kwargs return LayerSpec(**locals()) +def _cleanup(obj): + # Empty corr_plot layer adds geom with default visuals, if remove - no layer will be added at all + keep_if_empty = ['point_params', 'tile_params', 'label_params'] + + if 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, (dict, list)): + clean = _cleanup(v) + if len(clean) > 0 or k in keep_if_empty: + res[k] = clean + else: + res[k] = v + return res + + if isinstance(obj, list): + if all(isinstance(item, (dict, type(None))) for item in obj): + return [v for v in map(lambda item: _cleanup(item), obj) if v is not None] + + # ignore non-dict list or it may produce unexpected result: [['asd'], [None]] => [['asd'],[]] + return obj + + else: + return obj + + # # ----------------------------------- # Specs @@ -145,7 +178,7 @@ def _specs_to_dict(opts_raw): else: opts[k] = v - return opts + return _cleanup(opts) class FeatureSpec(): @@ -387,35 +420,7 @@ def as_dict(self): d['scales'] = [scale.as_dict() for scale in self.__scales] d['layers'] = [layer.as_dict() for layer in self.__layers] - # Empty corr_plot layer adds geom with default visuals, if remove - no layer will be added at all - keep_if_empty = ['point_params', 'tile_params', 'label_params'] - - def cleanup(o): - if isinstance(o, list): - return [cleanup(item) for item in o] - - if isinstance(o, dict): - res = {} - for k, v in o.items(): - if v is None: - continue - - if k == 'data': - res[k] = v - continue - - if isinstance(v, (dict, list)): - clean = cleanup(v) - if len(clean) > 0 or k in keep_if_empty: - res[k] = clean - else: - res[k] = v - return res - - else: - return o - - return cleanup(d) + return _cleanup(d) def __str__(self): result = ['plot'] @@ -556,7 +561,7 @@ def elements(self): def as_dict(self): elements = [{e.kind: e.as_dict()} for e in self.__elements] - return {'feature-list': elements} + return _cleanup({'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 2bad714266a..73e4773c6f9 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 +from lets_plot.plot.core import FeatureSpec, _cleanup 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 d + return _cleanup(d) def _repr_html_(self): """ diff --git a/python-package/lets_plot/plot/tooltip.py b/python-package/lets_plot/plot/tooltip.py index 55bc9ebf131..382ac902ef8 100644 --- a/python-package/lets_plot/plot/tooltip.py +++ b/python-package/lets_plot/plot/tooltip.py @@ -2,9 +2,10 @@ # Copyright (c) 2020. JetBrains s.r.o. # Use of this source code is governed by the MIT license that can be found in the LICENSE file. -from .core import FeatureSpec from typing import List +from lets_plot.plot.core import FeatureSpec, _cleanup + # # Tooltips # @@ -115,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 d + return _cleanup(d) def format(self, field=None, format=None): """ diff --git a/python-package/test/plot/test_aes.py b/python-package/test/plot/test_aes.py index 10aa435b5c6..f234a718e70 100644 --- a/python-package/test/plot/test_aes.py +++ b/python-package/test/plot/test_aes.py @@ -8,7 +8,7 @@ class TestWithListArgs: - result_empty = {'x': None, 'y': None} + result_empty = {} result_xy = {'x': 'xVar', 'y': 'yVar'} @pytest.mark.parametrize('args,expected', [ diff --git a/python-package/test/plot/test_geom.py b/python-package/test/plot/test_geom.py index 2176fa60b9b..738408d43d6 100644 --- a/python-package/test/plot/test_geom.py +++ b/python-package/test/plot/test_geom.py @@ -16,27 +16,12 @@ class TestWithListAndDictArgs: mapping_arg = gg.aes('X') expected[0] = dict( geom='n', - mapping=mapping_arg.as_dict(), - data=None, - stat=None, - position=None, - show_legend=None, - sampling=None, - tooltips=None, - data_meta= {}, + mapping=mapping_arg.as_dict() ) # II expected[1] = dict( geom='n', - mapping={'x': None, 'y': None}, - data=None, - stat=None, - position=None, - show_legend=None, - sampling=None, - tooltips=None, - 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 6bcb27253f8..621a53219a4 100644 --- a/python-package/test/plot/test_geom_image.py +++ b/python-package/test/plot/test_geom_image.py @@ -20,21 +20,12 @@ def _image_spec(width, height, href): # bytes=bytes # ), href=href, - data=None, mapping=dict( - x=None, - y=None, xmin=[-0.5], ymin=[-0.5], xmax=[width - 1 + 0.5], ymax=[height - 1 + 0.5], - ), - stat=None, - position=None, - show_legend=None, - data_meta={}, - sampling=None, - tooltips=None, + ) ) diff --git a/python-package/test/plot/test_scale.py b/python-package/test/plot/test_scale.py index 5937eb28d19..66d91464e5b 100644 --- a/python-package/test/plot/test_scale.py +++ b/python-package/test/plot/test_scale.py @@ -13,12 +13,9 @@ def gen_scale_args(): pos_args = ['a', 'n'] # aesthetic pos_args_as_dict = {'aesthetic': 'a', 'name': 'n'} - pos_args_def_as_dict = {'breaks': None, 'labels': None, 'limits': None, 'expand': None, 'na_value': None, - 'guide': None, 'trans': None, 'format': None} other_args = {'other1': 1, 'other2': 2} expected = pos_args_as_dict.copy() - expected.update(pos_args_def_as_dict) expected.update(other_args) return pos_args, other_args, expected @@ -33,7 +30,7 @@ def test_scale(args_list, args_dict, expected): def test_labs_empty(): spec = gg.labs() - assert spec.as_dict()['feature-list'] == [] + assert len(spec.as_dict()) == 0 def test_plot_title(): From 5c0a56ca0340de3c6070d8349102fdcb94967d26 Mon Sep 17 00:00:00 2001 From: Ivan Kupriyanov Date: Mon, 22 Nov 2021 17:59:53 +0300 Subject: [PATCH 3/4] Cleanup only whitelisted lists --- python-package/lets_plot/plot/core.py | 60 ++++++++++--------- python-package/lets_plot/plot/plot.py | 4 +- python-package/lets_plot/plot/tooltip.py | 4 +- python-package/test/plot/test_geom_livemap.py | 18 ++++++ 4 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 python-package/test/plot/test_geom_livemap.py diff --git a/python-package/lets_plot/plot/core.py b/python-package/lets_plot/plot/core.py index 3f355b9b803..c5a23d712ec 100644 --- a/python-package/lets_plot/plot/core.py +++ b/python-package/lets_plot/plot/core.py @@ -130,37 +130,43 @@ def layer(geom=None, stat=None, data=None, mapping=None, position=None, **kwargs return LayerSpec(**locals()) -def _cleanup(obj): - # Empty corr_plot layer adds geom with default visuals, if remove - no layer will be added at all - keep_if_empty = ['point_params', 'tile_params', 'label_params'] +def _cleanup_spec_list(obj): + assert isinstance(obj, list) - if isinstance(obj, dict): - res = {} - for k, v in obj.items(): - if v is None: - continue + 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] - if k in ['data', 'map']: - res[k] = v - continue + # 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, (dict, list)): - clean = _cleanup(v) - if len(clean) > 0 or k in keep_if_empty: + 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 - return res - if isinstance(obj, list): - if all(isinstance(item, (dict, type(None))) for item in obj): - return [v for v in map(lambda item: _cleanup(item), obj) if v is not None] - - # ignore non-dict list or it may produce unexpected result: [['asd'], [None]] => [['asd'],[]] - return obj - - else: - return obj + 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 # @@ -178,7 +184,7 @@ def _specs_to_dict(opts_raw): else: opts[k] = v - return _cleanup(opts) + return _cleanup_spec(opts) class FeatureSpec(): @@ -420,7 +426,7 @@ def as_dict(self): d['scales'] = [scale.as_dict() for scale in self.__scales] d['layers'] = [layer.as_dict() for layer in self.__layers] - return _cleanup(d) + return _cleanup_spec(d) def __str__(self): result = ['plot'] @@ -561,7 +567,7 @@ def elements(self): def as_dict(self): elements = [{e.kind: e.as_dict()} for e in self.__elements] - return _cleanup({'feature-list': elements}) + return _cleanup_spec({'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 73e4773c6f9..7c239e9c456 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 +from lets_plot.plot.core import FeatureSpec, _cleanup_spec 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(d) + return _cleanup_spec(d) def _repr_html_(self): """ diff --git a/python-package/lets_plot/plot/tooltip.py b/python-package/lets_plot/plot/tooltip.py index 382ac902ef8..2e43b1edcc5 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 +from lets_plot.plot.core import FeatureSpec, _cleanup_spec # # 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(d) + return _cleanup_spec(d) def format(self, field=None, format=None): """ diff --git a/python-package/test/plot/test_geom_livemap.py b/python-package/test/plot/test_geom_livemap.py new file mode 100644 index 00000000000..19eff4ea040 --- /dev/null +++ b/python-package/test/plot/test_geom_livemap.py @@ -0,0 +1,18 @@ +# Copyright (c) 2021. JetBrains s.r.o. +# Use of this source code is governed by the MIT license that can be found in the LICENSE file. +import pytest + +import lets_plot as gg + + +@pytest.mark.parametrize('args,expected', [ + ('foo', [['foo'], None]), + (['foo'], [['foo'], None]), + (['foo', 'bar'], [['foo'], ['bar']]), + ([['foo', 'bar']], [['foo', 'bar'], None]), + ([['foo', 'bar'], ['baz', 'qux']], [['foo', 'bar'], ['baz', 'qux']]) +]) +def test_map_join(args, expected): + # ggplot is required - it normalizes map_join on before_append + spec = gg.ggplot() + gg.geom_livemap(map_join=args, tooltips=gg.layer_tooltips()) + assert spec.as_dict()['layers'][0]['map_join'] == expected From 7e3cb2bdc1ea853d8c6c2b93a2e191c8098d681e Mon Sep 17 00:00:00 2001 From: Ivan Kupriyanov Date: Wed, 15 Dec 2021 23:46:32 +0300 Subject: [PATCH 4/4] Simplified cleanup --- python-package/lets_plot/plot/core.py | 46 +++------------------ python-package/lets_plot/plot/plot.py | 4 +- python-package/lets_plot/plot/tooltip.py | 4 +- python-package/test/plot/test_assembly.py | 21 ++++++++-- python-package/test/plot/test_geom.py | 3 ++ python-package/test/plot/test_geom_image.py | 1 + python-package/test/plot/test_ggplot.py | 12 +++--- python-package/test/plot/test_ggsize.py | 2 +- python-package/test/plot/test_scale.py | 2 +- 9 files changed, 38 insertions(+), 57 deletions(-) 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():