From 76a026e4c4d5825910da0e463ef090347ea92b2f Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Mon, 6 May 2019 18:02:42 -0400 Subject: [PATCH] Support **kwargs in update_traces and update_{subplot} methods --- codegen/figure.py | 11 +- plotly/basedatatypes.py | 11 +- plotly/graph_objs/_figure.py | 84 +++++++++--- plotly/graph_objs/_figurewidget.py | 84 +++++++++--- .../test_update_subplots.py | 16 ++- .../test_update_objects/test_update_traces.py | 121 +++++++----------- 6 files changed, 198 insertions(+), 129 deletions(-) diff --git a/codegen/figure.py b/codegen/figure.py index 7e5d32af1f3..d60f2c7b762 100644 --- a/codegen/figure.py +++ b/codegen/figure.py @@ -218,7 +218,8 @@ def for_each_{singular_name}(self, fn, selector=None, row=None, col=None): return self - def update_{plural_name}(self, patch, selector=None, row=None, col=None): + def update_{plural_name}( + self, patch=None, selector=None, row=None, col=None, **kwargs): \"\"\" Perform a property update operation on all {singular_name} objects that satisfy the specified selection criteria @@ -239,7 +240,11 @@ def update_{plural_name}(self, patch, selector=None, row=None, col=None): To select {singular_name} objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all {singular_name} objects are selected. - + **kwargs + Additional property updates to apply to each selected + {singular_name} object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self @@ -247,7 +252,7 @@ def update_{plural_name}(self, patch, selector=None, row=None, col=None): \"\"\" for obj in self.select_{plural_name}( selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self""") diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py index 6bab9c79a38..cd8cd7ea909 100644 --- a/plotly/basedatatypes.py +++ b/plotly/basedatatypes.py @@ -759,14 +759,15 @@ def for_each_trace(self, fn, selector=None, row=None, col=None): return self - def update_traces(self, patch, selector=None, row=None, col=None): + def update_traces( + self, patch=None, selector=None, row=None, col=None, **kwargs): """ Perform a property update operation on all traces that satisfy the specified selection criteria Parameters ---------- - patch: dict + patch: dict or None (default None) Dictionary of property updates to be applied to all traces that satisfy the selection criteria. selector: dict or None (default None) @@ -780,6 +781,10 @@ def update_traces(self, patch, selector=None, row=None, col=None): To select traces by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all traces are selected. + **kwargs + Additional property updates to apply to each selected trace. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. Returns ------- @@ -787,7 +792,7 @@ def update_traces(self, patch, selector=None, row=None, col=None): Returns the Figure object that the method was called on """ for trace in self.select_traces(selector=selector, row=row, col=col): - trace.update(patch) + trace.update(patch, **kwargs) return self def _select_layout_subplots_by_prefix( diff --git a/plotly/graph_objs/_figure.py b/plotly/graph_objs/_figure.py index 3ac55bc02f3..8cc8ef26242 100644 --- a/plotly/graph_objs/_figure.py +++ b/plotly/graph_objs/_figure.py @@ -12366,7 +12366,9 @@ def for_each_geo(self, fn, selector=None, row=None, col=None): return self - def update_geos(self, patch, selector=None, row=None, col=None): + def update_geos( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all geo objects that satisfy the specified selection criteria @@ -12387,14 +12389,18 @@ def update_geos(self, patch, selector=None, row=None, col=None): To select geo objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all geo objects are selected. - + **kwargs + Additional property updates to apply to each selected + geo object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_geos(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12462,7 +12468,9 @@ def for_each_mapbox(self, fn, selector=None, row=None, col=None): return self - def update_mapboxes(self, patch, selector=None, row=None, col=None): + def update_mapboxes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all mapbox objects that satisfy the specified selection criteria @@ -12483,14 +12491,18 @@ def update_mapboxes(self, patch, selector=None, row=None, col=None): To select mapbox objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all mapbox objects are selected. - + **kwargs + Additional property updates to apply to each selected + mapbox object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_mapboxes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12558,7 +12570,9 @@ def for_each_polar(self, fn, selector=None, row=None, col=None): return self - def update_polars(self, patch, selector=None, row=None, col=None): + def update_polars( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all polar objects that satisfy the specified selection criteria @@ -12579,14 +12593,18 @@ def update_polars(self, patch, selector=None, row=None, col=None): To select polar objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all polar objects are selected. - + **kwargs + Additional property updates to apply to each selected + polar object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_polars(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12654,7 +12672,9 @@ def for_each_scene(self, fn, selector=None, row=None, col=None): return self - def update_scenes(self, patch, selector=None, row=None, col=None): + def update_scenes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all scene objects that satisfy the specified selection criteria @@ -12675,14 +12695,18 @@ def update_scenes(self, patch, selector=None, row=None, col=None): To select scene objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all scene objects are selected. - + **kwargs + Additional property updates to apply to each selected + scene object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_scenes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12750,7 +12774,9 @@ def for_each_ternary(self, fn, selector=None, row=None, col=None): return self - def update_ternaries(self, patch, selector=None, row=None, col=None): + def update_ternaries( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all ternary objects that satisfy the specified selection criteria @@ -12771,14 +12797,18 @@ def update_ternaries(self, patch, selector=None, row=None, col=None): To select ternary objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all ternary objects are selected. - + **kwargs + Additional property updates to apply to each selected + ternary object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_ternaries(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12846,7 +12876,9 @@ def for_each_xaxis(self, fn, selector=None, row=None, col=None): return self - def update_xaxes(self, patch, selector=None, row=None, col=None): + def update_xaxes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all xaxis objects that satisfy the specified selection criteria @@ -12867,14 +12899,18 @@ def update_xaxes(self, patch, selector=None, row=None, col=None): To select xaxis objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all xaxis objects are selected. - + **kwargs + Additional property updates to apply to each selected + xaxis object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_xaxes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12942,7 +12978,9 @@ def for_each_yaxis(self, fn, selector=None, row=None, col=None): return self - def update_yaxes(self, patch, selector=None, row=None, col=None): + def update_yaxes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all yaxis objects that satisfy the specified selection criteria @@ -12963,13 +13001,17 @@ def update_yaxes(self, patch, selector=None, row=None, col=None): To select yaxis objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all yaxis objects are selected. - + **kwargs + Additional property updates to apply to each selected + yaxis object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_yaxes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self diff --git a/plotly/graph_objs/_figurewidget.py b/plotly/graph_objs/_figurewidget.py index 1b6831c416e..71698ae0740 100644 --- a/plotly/graph_objs/_figurewidget.py +++ b/plotly/graph_objs/_figurewidget.py @@ -12366,7 +12366,9 @@ def for_each_geo(self, fn, selector=None, row=None, col=None): return self - def update_geos(self, patch, selector=None, row=None, col=None): + def update_geos( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all geo objects that satisfy the specified selection criteria @@ -12387,14 +12389,18 @@ def update_geos(self, patch, selector=None, row=None, col=None): To select geo objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all geo objects are selected. - + **kwargs + Additional property updates to apply to each selected + geo object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_geos(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12462,7 +12468,9 @@ def for_each_mapbox(self, fn, selector=None, row=None, col=None): return self - def update_mapboxes(self, patch, selector=None, row=None, col=None): + def update_mapboxes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all mapbox objects that satisfy the specified selection criteria @@ -12483,14 +12491,18 @@ def update_mapboxes(self, patch, selector=None, row=None, col=None): To select mapbox objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all mapbox objects are selected. - + **kwargs + Additional property updates to apply to each selected + mapbox object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_mapboxes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12558,7 +12570,9 @@ def for_each_polar(self, fn, selector=None, row=None, col=None): return self - def update_polars(self, patch, selector=None, row=None, col=None): + def update_polars( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all polar objects that satisfy the specified selection criteria @@ -12579,14 +12593,18 @@ def update_polars(self, patch, selector=None, row=None, col=None): To select polar objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all polar objects are selected. - + **kwargs + Additional property updates to apply to each selected + polar object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_polars(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12654,7 +12672,9 @@ def for_each_scene(self, fn, selector=None, row=None, col=None): return self - def update_scenes(self, patch, selector=None, row=None, col=None): + def update_scenes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all scene objects that satisfy the specified selection criteria @@ -12675,14 +12695,18 @@ def update_scenes(self, patch, selector=None, row=None, col=None): To select scene objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all scene objects are selected. - + **kwargs + Additional property updates to apply to each selected + scene object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_scenes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12750,7 +12774,9 @@ def for_each_ternary(self, fn, selector=None, row=None, col=None): return self - def update_ternaries(self, patch, selector=None, row=None, col=None): + def update_ternaries( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all ternary objects that satisfy the specified selection criteria @@ -12771,14 +12797,18 @@ def update_ternaries(self, patch, selector=None, row=None, col=None): To select ternary objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all ternary objects are selected. - + **kwargs + Additional property updates to apply to each selected + ternary object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_ternaries(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12846,7 +12876,9 @@ def for_each_xaxis(self, fn, selector=None, row=None, col=None): return self - def update_xaxes(self, patch, selector=None, row=None, col=None): + def update_xaxes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all xaxis objects that satisfy the specified selection criteria @@ -12867,14 +12899,18 @@ def update_xaxes(self, patch, selector=None, row=None, col=None): To select xaxis objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all xaxis objects are selected. - + **kwargs + Additional property updates to apply to each selected + xaxis object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_xaxes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self @@ -12942,7 +12978,9 @@ def for_each_yaxis(self, fn, selector=None, row=None, col=None): return self - def update_yaxes(self, patch, selector=None, row=None, col=None): + def update_yaxes( + self, patch=None, selector=None, row=None, col=None, **kwargs + ): """ Perform a property update operation on all yaxis objects that satisfy the specified selection criteria @@ -12963,13 +13001,17 @@ def update_yaxes(self, patch, selector=None, row=None, col=None): To select yaxis objects by row and column, the Figure must have been created using plotly.subplots.make_subplots. If None (the default), all yaxis objects are selected. - + **kwargs + Additional property updates to apply to each selected + yaxis object. If a property is specified in + both patch and in **kwargs then the one in **kwargs + takes precedence. Returns ------- self Returns the Figure object that the method was called on """ for obj in self.select_yaxes(selector=selector, row=row, col=col): - obj.update(patch) + obj.update(patch, **kwargs) return self diff --git a/plotly/tests/test_core/test_update_objects/test_update_subplots.py b/plotly/tests/test_core/test_update_objects/test_update_subplots.py index d435cd1ca9f..7ad874021e4 100644 --- a/plotly/tests/test_core/test_update_objects/test_update_subplots.py +++ b/plotly/tests/test_core/test_update_objects/test_update_subplots.py @@ -287,8 +287,8 @@ def test_select_by_type_and_grid_and_selector(self): selector={'angularaxis.rotation': 0}) def assert_update_subplots( - self, subplot_type, subplots_name, expected_nums, patch, - selector=None, row=None, col=None, test_no_grid=False): + self, subplot_type, subplots_name, expected_nums, patch=None, + selector=None, row=None, col=None, test_no_grid=False, **kwargs): update_fn = getattr(Figure, 'update_' + subplots_name) @@ -300,7 +300,7 @@ def check_update(fig): # perform update_* update_res = update_fn( - fig, patch, selector=selector, row=row, col=col) + fig, patch, selector=selector, row=row, col=col, **kwargs) self.assertIs(update_res, fig) # Build expected layout keys @@ -316,7 +316,7 @@ def check_update(fig): if k in expected_keys: # Make sure sure there is an initial difference self.assertNotEqual(orig_obj, new_obj) - orig_obj.update(patch) + orig_obj.update(patch, **kwargs) self.assertEqual(new_obj, orig_obj) @@ -470,3 +470,11 @@ def test_update_by_type_and_grid_and_selector(self): {'radialaxis.title.font.family': 'Rockwell'}, row=2, col=3, selector={'angularaxis.rotation': 0}) + + # kwargs + self.assert_update_subplots( + 'xaxis', 'xaxes', [1, 2], + title_font_family='Courier', + title_font_color='yellow', + row=1, + selector={'title.text': 'A'}) diff --git a/plotly/tests/test_core/test_update_objects/test_update_traces.py b/plotly/tests/test_core/test_update_objects/test_update_traces.py index 36d598d2aa8..70e7a5280d7 100644 --- a/plotly/tests/test_core/test_update_objects/test_update_traces.py +++ b/plotly/tests/test_core/test_update_objects/test_update_traces.py @@ -232,9 +232,8 @@ def test_for_each_trace_lowercase_names(self): # test update_traces # ------------------ - def assert_update_traces( - self, patch, expected_inds, selector=None, row=None, col=None - ): + def assert_update_traces(self, expected_inds, patch=None, selector=None, + row=None, col=None, **kwargs): # Save off original figure fig_orig = copy.deepcopy(self.fig) for trace1, trace2 in zip(fig_orig.data, self.fig.data): @@ -242,7 +241,7 @@ def assert_update_traces( # Perform update update_res = self.fig.update_traces( - patch, selector=selector, row=row, col=col + patch, selector=selector, row=row, col=col, **kwargs ) # Check chaining support @@ -255,102 +254,70 @@ def assert_update_traces( self.assertNotEqual(t_orig, t) # Check that traces are equal after update - t_orig.update(patch) + t_orig.update(patch, **kwargs) # Check that traces are equal self.assertEqual(t_orig, t) def test_update_traces_by_type(self): - self.assert_update_traces( - {'visible': 'legendonly'}, - [0, 2], - selector={'type': 'scatter'} - ) + self.assert_update_traces([0, 2], {'visible': 'legendonly'}, + selector={'type': 'scatter'}) - self.assert_update_traces( - {'visible': 'legendonly'}, - [1], - selector={'type': 'bar'}, - ) + self.assert_update_traces([0, 2], + selector={'type': 'scatter'}, + visible=False) - self.assert_update_traces( - {'colorscale': 'Viridis'}, - [3], - selector={'type': 'heatmap'} - ) + self.assert_update_traces([1], {'visible': 'legendonly'}, + selector={'type': 'bar'}) + + self.assert_update_traces([3], {'colorscale': 'Viridis'}, + selector={'type': 'heatmap'}) # Nest dictionaries - self.assert_update_traces( - {'marker': {'line': {'color': 'yellow'}}}, - [4, 5], - selector={'type': 'scatter3d'} - ) + self.assert_update_traces([4, 5], + {'marker': {'line': {'color': 'yellow'}}}, + selector={'type': 'scatter3d'}) # dot syntax - self.assert_update_traces( - {'marker.line.color': 'cyan'}, - [4, 5], - selector={'type': 'scatter3d'} - ) + self.assert_update_traces([4, 5], {'marker.line.color': 'cyan'}, + selector={'type': 'scatter3d'}) # underscore syntax - self.assert_update_traces( - dict(marker_line_color='pink'), - [4, 5], - selector={'type': 'scatter3d'} - ) + self.assert_update_traces([4, 5], dict(marker_line_color='pink'), + selector={'type': 'scatter3d'}) - self.assert_update_traces( - {'line': {'dash': 'dot'}}, - [6, 7], - selector={'type': 'scatterpolar'} - ) + # underscore syntax with kwarg + self.assert_update_traces([4, 5], + selector={'type': 'scatter3d'}, + marker_line_color='red') + + self.assert_update_traces([6, 7], {'line': {'dash': 'dot'}}, + selector={'type': 'scatterpolar'}) # Nested dictionaries - self.assert_update_traces( - {'dimensions': {1: {'label': 'Dimension 1'}}}, - [8], - selector={'type': 'parcoords'} - ) + self.assert_update_traces([8], { + 'dimensions': {1: {'label': 'Dimension 1'}}}, + selector={'type': 'parcoords'}) # Dot syntax - self.assert_update_traces( - {'dimensions[1].label': 'Dimension A'}, - [8], - selector={'type': 'parcoords'} - ) + self.assert_update_traces([8], {'dimensions[1].label': 'Dimension A'}, + selector={'type': 'parcoords'}) # underscore syntax # Dot syntax - self.assert_update_traces( - dict(dimensions_1_label='Dimension X'), - [8], - selector={'type': 'parcoords'} - ) + self.assert_update_traces([8], dict(dimensions_1_label='Dimension X'), + selector={'type': 'parcoords'}) - self.assert_update_traces( - {'hoverinfo': 'label+percent'}, - [], selector={'type': 'pie'} - ) + self.assert_update_traces([], {'hoverinfo': 'label+percent'}, + selector={'type': 'pie'}) def test_update_traces_by_grid_and_selector(self): - self.assert_update_traces( - {'marker.size': 5}, - [4, 6], - selector={'marker.color': 'green'}, - col=2 - ) + self.assert_update_traces([4, 6], {'marker.size': 5}, + selector={'marker.color': 'green'}, col=2) - self.assert_update_traces( - {'marker.size': 6}, - [0, 4], - selector={'marker.color': 'green'}, - row=1 - ) + self.assert_update_traces([0, 4], {'marker.size': 6}, + selector={'marker.color': 'green'}, row=1) - self.assert_update_traces( - {'marker.size': 6}, - [6], - selector={'marker.color': 'green'}, - row=2, col=2 - ) + self.assert_update_traces([6], {'marker.size': 6}, + selector={'marker.color': 'green'}, row=2, + col=2)