From 9d350bc880a88e3dd2e0d0afc02c1f71b9884e0a Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 30 Oct 2020 12:06:47 -0400 Subject: [PATCH 1/5] go.Figure now has set_subplots Calling `fig.set_subplots(2,3)` works just like fig=make_subplots(2,3). Also accepts the same keywords arguments as make_subplots. Fails if fig already has subplots. Tests still need to be added. --- .../python/plotly/plotly/basedatatypes.py | 17 +++++++++++++++ packages/python/plotly/plotly/subplots.py | 21 ++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index f2033cf359a..0c1bba8cfb1 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -14,6 +14,7 @@ from .optional_imports import get_module from . import shapeannotation +from . import subplots # Create Undefined sentinel value # - Setting a property to None removes any existing value @@ -3926,6 +3927,22 @@ def _subplot_not_empty(self, xref, yref, selector="all"): ) return ret + def set_subplots(self, rows=None, cols=None, **make_subplots_args): + """ + Add subplots to this figure. If the figure already contains subplots, + then this throws an error. Accepts any keyword arguments that + plotly.subplots.make_subplots accepts. + """ + # rows, cols provided so that this can be called like + # fig.set_subplots(2,3), say + if rows is not None: + make_subplots_args["rows"] = rows + if cols is not None: + make_subplots_args["cols"] = cols + if self._has_subplots(): + raise ValueError("This figure already has subplots.") + return subplots.make_subplots(figure=self, **make_subplots_args) + class BasePlotlyType(object): """ diff --git a/packages/python/plotly/plotly/subplots.py b/packages/python/plotly/plotly/subplots.py index 652467ff6a4..f7d5a0441c5 100644 --- a/packages/python/plotly/plotly/subplots.py +++ b/packages/python/plotly/plotly/subplots.py @@ -60,6 +60,7 @@ def make_subplots( row_titles=None, x_title=None, y_title=None, + figure=None, **kwargs ): """ @@ -226,7 +227,15 @@ def make_subplots( y_title: str or None (default None) Title to place to the left of the left column of subplots, - centered vertically + centered vertically + + figure: go.Figure or None (default None) + If None, a new go.Figure instance will be created and its axes will be + populated with those corresponding to the requested subplot geometry and + this new figure will be returned. + If a go.Figure instance, the axes will be added to the + layout of this figure and this figure will be returned. If the figure + already contains axes, they will be overwritten. Examples -------- @@ -809,13 +818,15 @@ def _checks(item, defaults): print(grid_str) # Build resulting figure - fig = go.Figure(layout=layout) + if figure is None: + figure = go.Figure() + figure.update_layout(layout) # Attach subplot grid info to the figure - fig.__dict__["_grid_ref"] = grid_ref - fig.__dict__["_grid_str"] = grid_str + figure.__dict__["_grid_ref"] = grid_ref + figure.__dict__["_grid_str"] = grid_str - return fig + return figure def _configure_shared_axes(layout, grid_ref, specs, x_or_y, shared, row_dir): From c006c1f979dccda44703e371151faee758be1c30 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 30 Oct 2020 12:56:49 -0400 Subject: [PATCH 2/5] Tests for go.Figure.set_subplots and figure argument to make_subplots --- .../test_core/test_graph_objs/test_figure.py | 18 ++++++++++++++++++ .../test_subplots/test_make_subplots.py | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py index 57bd060b59a..c6daee026eb 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py +++ b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import plotly.graph_objects as go +from plotly.subplots import make_subplots from plotly.tests.utils import TestCaseNoTemplate @@ -199,3 +200,20 @@ def test_update_overwrite_data(self): fig.to_plotly_json()["data"], [{"type": "scatter", "y": [1, 3, 2], "line": {"color": "yellow"}}], ) + + def test_set_subplots(self): + # Test that it works the same as make_subplots for a simple call + fig0 = go.Figure() + fig0_sp = make_subplots(2, 2) + fig0.set_subplots(2, 2) + assert fig0.layout == fig0_sp.layout + # Test that it accepts the same arguments as make_subplots + fig1 = go.Figure() + fig1.set_subplots(rows=2, cols=2, horizontal_spacing=0.25, vertical_spacing=0.1) + fig1_sp = make_subplots( + rows=2, cols=2, horizontal_spacing=0.25, vertical_spacing=0.1 + ) + assert fig1.layout == fig1_sp.layout + # Test that calling on a figure that already has subplots throws an error. + with self.assertRaisesRegex(ValueError, "^This figure already has subplots\.$"): + fig1.set_subplots(2, 3) diff --git a/packages/python/plotly/plotly/tests/test_core/test_subplots/test_make_subplots.py b/packages/python/plotly/plotly/tests/test_core/test_subplots/test_make_subplots.py index 0b699859c47..b5978c0e4bd 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_subplots/test_make_subplots.py +++ b/packages/python/plotly/plotly/tests/test_core/test_subplots/test_make_subplots.py @@ -1924,3 +1924,13 @@ def test_secondary_y_subplots(self): expected.update_traces(uid=None) self.assertEqual(fig.to_plotly_json(), expected.to_plotly_json()) + + def test_if_passed_figure(self): + # assert it returns the same figure it was passed + fig = Figure() + figsp = subplots.make_subplots(2, 2, figure=fig) + assert id(fig) == id(figsp) + # assert the layout is the same when it returns its own figure + fig2sp = subplots.make_subplots(2, 2) + assert id(fig2sp) != id(figsp) + assert fig2sp.layout == figsp.layout From a62225ae02c2a1a5b5aeae097058c7e6e724b01d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 30 Oct 2020 13:31:38 -0400 Subject: [PATCH 3/5] docs and changelog for fig.set_subplots --- CHANGELOG.md | 4 ++++ doc/python/subplots.md | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fadd9e371e5..60cda725b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [4.13.0] - UNRELEASED +### Added + +- `go.Figure` now has a `set_subplots` method to set subplots on an already + existing figure. ## [4.12.1] - UNRELEASED diff --git a/doc/python/subplots.md b/doc/python/subplots.md index 1687a2ae800..0552b3ebec6 100644 --- a/doc/python/subplots.md +++ b/doc/python/subplots.md @@ -581,6 +581,26 @@ fig = go.Figure(data=data, layout=layout) fig.show() ``` +#### Setting Subplots on a Figure Directly + +_new in 4.13_ + +Subplots can be added to an already existing figure, provided it doesn't already +have subplots. `go.Figure.set_subplots` accepts all the same arguments as +`plotly.subplots.make_subplots`. + +```python +import plotly.graph_objects as go +fig = go.Figure().set_subplots(2, 3, horizontal_spacing=0.1) +``` + +is equivalent to: + +```python +from plotly.subplots import make_subplots +fig = make_subplots(2, 3, horizontal_spacing=0.1) +``` + #### Reference All of the x-axis properties are found here: https://plotly.com/python/reference/XAxis/ All of the y-axis properties are found here: https://plotly.com/python/reference/YAxis/ From b8e23c4e44769907e7b8eaed7943311b8daa4007 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 30 Oct 2020 16:26:47 -0400 Subject: [PATCH 4/5] python 2 unittest doesn't support assertRaisesRegex --- .../plotly/tests/test_core/test_graph_objs/test_figure.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py index c6daee026eb..97b0f8a7921 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py +++ b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py @@ -215,5 +215,10 @@ def test_set_subplots(self): ) assert fig1.layout == fig1_sp.layout # Test that calling on a figure that already has subplots throws an error. - with self.assertRaisesRegex(ValueError, "^This figure already has subplots\.$"): + raised = False + try: fig1.set_subplots(2, 3) + except ValueError as e: + assert e.args[0] == "This figure already has subplots." + raised = True + assert raised From 18166956c284cb784e4ea393bee84ce5941331fa Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 4 Nov 2020 13:04:13 -0500 Subject: [PATCH 5/5] Use pytest.raises with match argument in set_subplots tests --- .../test_core/test_graph_objs/test_figure.py | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py index 97b0f8a7921..a1a5523fc43 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py +++ b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_figure.py @@ -3,6 +3,7 @@ import plotly.graph_objects as go from plotly.subplots import make_subplots from plotly.tests.utils import TestCaseNoTemplate +import pytest class FigureTest(TestCaseNoTemplate): @@ -201,24 +202,20 @@ def test_update_overwrite_data(self): [{"type": "scatter", "y": [1, 3, 2], "line": {"color": "yellow"}}], ) - def test_set_subplots(self): - # Test that it works the same as make_subplots for a simple call - fig0 = go.Figure() - fig0_sp = make_subplots(2, 2) - fig0.set_subplots(2, 2) - assert fig0.layout == fig0_sp.layout - # Test that it accepts the same arguments as make_subplots - fig1 = go.Figure() - fig1.set_subplots(rows=2, cols=2, horizontal_spacing=0.25, vertical_spacing=0.1) - fig1_sp = make_subplots( - rows=2, cols=2, horizontal_spacing=0.25, vertical_spacing=0.1 - ) - assert fig1.layout == fig1_sp.layout - # Test that calling on a figure that already has subplots throws an error. - raised = False - try: - fig1.set_subplots(2, 3) - except ValueError as e: - assert e.args[0] == "This figure already has subplots." - raised = True - assert raised + +def test_set_subplots(): + # Test that it works the same as make_subplots for a simple call + fig0 = go.Figure() + fig0_sp = make_subplots(2, 2) + fig0.set_subplots(2, 2) + assert fig0.layout == fig0_sp.layout + # Test that it accepts the same arguments as make_subplots + fig1 = go.Figure() + fig1.set_subplots(rows=2, cols=2, horizontal_spacing=0.25, vertical_spacing=0.1) + fig1_sp = make_subplots( + rows=2, cols=2, horizontal_spacing=0.25, vertical_spacing=0.1 + ) + assert fig1.layout == fig1_sp.layout + # Test that calling on a figure that already has subplots throws an error. + with pytest.raises(ValueError, match=r"^This figure already has subplots\.$"): + fig1.set_subplots(2, 3)