From cc9480313b5e032e7c87e759a25472776bfd07e7 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Fri, 21 Apr 2023 16:15:02 -0600 Subject: [PATCH 01/16] fixed bug for issue #3755 when the figure object contains subplots --- .../python/plotly/plotly/basedatatypes.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index bf5eac3dfa..489010be63 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1563,13 +1563,28 @@ def _add_annotation_like( raise ValueError( """ Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c}) -because subplot does not have a secondary y-axis""" +because subplot does not have a secondary y-axis""".format( + prop_singular=prop_singular, r=row, c=col + ) ) - if secondary_y: - xaxis, yaxis = refs[1].layout_keys + # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys + if new_obj.xref is None: + if secondary_y: + xaxis = refs[1].layout_keys[0] + else: + xaxis = refs[0].layout_keys[0] + xref = xaxis.replace("axis", "") + else: + xref = new_obj.xref + # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys + if new_obj.yref is None: + if secondary_y: + yaxis = refs[1].layout_keys[1] + else: + yaxis = refs[0].layout_keys[1] + yref = yaxis.replace("axis", "") else: - xaxis, yaxis = refs[0].layout_keys - xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") + yref = new_obj.yref # if exclude_empty_subplots is True, check to see if subplot is # empty and return if it is if exclude_empty_subplots and ( From ebb3808223d647c15bfef7ed4c647149e348811a Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Fri, 21 Apr 2023 16:21:45 -0600 Subject: [PATCH 02/16] fixed bug for issue #3755 when the figure object does not contain subplots --- packages/python/plotly/plotly/basedatatypes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 489010be63..e9e3338bda 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -4060,10 +4060,13 @@ def _process_multiple_axis_spanning_shapes( ): # this was called intending to add to a single plot (and # self.add_{layout_obj} succeeded) - # however, in the case of a single plot, xref and yref are not - # specified, so we specify them here so the following routines can work + # however, in the case of a single plot, xref and yref MAY not be + # specified, IF they are not specified we specify them here so the following routines can work # (they need to append " domain" to xref or yref) - self.layout[layout_obj][-1].update(xref="x", yref="y") + if self.layout[layout_obj][-1].xref is None: + self.layout[layout_obj][-1].update(xref="x") + if self.layout[layout_obj][-1].yref is None: + self.layout[layout_obj][-1].update(yref="y") new_layout_objs = tuple( filter( lambda x: x is not None, From d0522f5ed6eed9153e04cc4b5f8038210a79bbd0 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Fri, 21 Apr 2023 17:29:03 -0600 Subject: [PATCH 03/16] incorrect use of 'is' changed to '==' --- packages/python/plotly/plotly/basedatatypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index e9e3338bda..04bc45080c 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1568,7 +1568,7 @@ def _add_annotation_like( ) ) # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys - if new_obj.xref is None: + if new_obj.xref == None: if secondary_y: xaxis = refs[1].layout_keys[0] else: @@ -1577,7 +1577,7 @@ def _add_annotation_like( else: xref = new_obj.xref # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys - if new_obj.yref is None: + if new_obj.yref == None: if secondary_y: yaxis = refs[1].layout_keys[1] else: From 24ad4c7af3028b19f659afdfd231df02168a3526 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Sat, 22 Apr 2023 08:42:38 -0600 Subject: [PATCH 04/16] added some conditionals to help find bug in subplot shapes --- debug.py | 41 ++++++++++++++++++ debugtesting.py | 43 +++++++++++++++++++ .../python/plotly/plotly/basedatatypes.py | 15 ++++++- 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 debug.py create mode 100644 debugtesting.py diff --git a/debug.py b/debug.py new file mode 100644 index 0000000000..06511a2b57 --- /dev/null +++ b/debug.py @@ -0,0 +1,41 @@ +import plotly.express as px +import plotly.graph_objects as go +import numpy as np +from plotly import subplots + +test_subplots = True +use_OP = False + +df = px.data.iris() +if use_OP: + fig = px.scatter(df, x="petal_length", y="petal_width") + fig.add_traces(go.Scatter(y=np.arange(1, 7), mode="lines+markers", yaxis="y2")) + + fig.update_layout( + yaxis2=dict( + title="yaxis2 title", + overlaying="y", + side="right", + ) + ) +else: + trace1 = go.Scatter(x=df["petal_length"], y=df["petal_width"], mode="markers") + trace2 = go.Scatter( + x=[0, 1, 2, 3, 4, 5], y=np.arange(1, 7), mode="lines+markers", yaxis="y2" + ) + data = [trace1, trace2] + layout = go.Layout( + yaxis=dict(title="yaxis1"), + yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"), + ) + fig = go.Figure(data=data, layout=layout) +if test_subplots: + fig = subplots.make_subplots(rows=1, cols=2, shared_yaxes=False) + +fig.add_hline( + y=2, + line_dash="dash", + line_color="Red", # yref='y2' + # secondary_y=True +) +fig.show() diff --git a/debugtesting.py b/debugtesting.py new file mode 100644 index 0000000000..71a9bd47e0 --- /dev/null +++ b/debugtesting.py @@ -0,0 +1,43 @@ +import plotly.express as px +import plotly.graph_objects as go +import numpy as np +from plotly.subplots import make_subplots + + +for k, fun, d, fun2, d2 in [ + ( + "shapes", + go.Figure.add_shape, + dict(type="rect", x0=1.5, x1=2.5, y0=3.5, y1=4.5), + # add a different type to make the check easier (otherwise we might + # mix up the objects added before and after fun was run) + go.Figure.add_annotation, + dict(x=1, y=2, text="A"), + ), + ( + "annotations", + go.Figure.add_annotation, + dict(x=1, y=2, text="A"), + go.Figure.add_layout_image, + dict(x=3, y=4, sizex=2, sizey=3, source="test"), + ), + ( + "images", + go.Figure.add_layout_image, + dict(x=3, y=4, sizex=2, sizey=3, source="test"), + go.Figure.add_shape, + dict(type="rect", x0=1.5, x1=2.5, y0=3.5, y1=4.5), + ), +]: + # make a figure where not all the subplots are populated + fig = make_subplots(2, 2) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[5, 1, 2]), row=1, col=1) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, -7]), row=2, col=2) + fun2(fig, d2, row=1, col=2) + # add a thing to all subplots but make sure it only goes on the + # plots without data or layout objects + fun(fig, d, row="all", col="all", exclude_empty_subplots="anything_truthy") + assert len(fig.layout[k]) == 3 + assert fig.layout[k][0]["xref"] == "x" and fig.layout[k][0]["yref"] == "y" + assert fig.layout[k][1]["xref"] == "x2" and fig.layout[k][1]["yref"] == "y2" + assert fig.layout[k][2]["xref"] == "x4" and fig.layout[k][2]["yref"] == "y4" diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 04bc45080c..fae8b9b9fc 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1567,6 +1567,7 @@ def _add_annotation_like( prop_singular=prop_singular, r=row, c=col ) ) + #''' # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys if new_obj.xref == None: if secondary_y: @@ -1585,6 +1586,14 @@ def _add_annotation_like( yref = yaxis.replace("axis", "") else: yref = new_obj.yref + #''' + """ + if secondary_y: + xaxis, yaxis = refs[1].layout_keys + else: + xaxis, yaxis = refs[0].layout_keys + xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") + """ # if exclude_empty_subplots is True, check to see if subplot is # empty and return if it is if exclude_empty_subplots and ( @@ -4063,10 +4072,12 @@ def _process_multiple_axis_spanning_shapes( # however, in the case of a single plot, xref and yref MAY not be # specified, IF they are not specified we specify them here so the following routines can work # (they need to append " domain" to xref or yref) - if self.layout[layout_obj][-1].xref is None: + if self.layout[layout_obj][-1].xref == None: self.layout[layout_obj][-1].update(xref="x") - if self.layout[layout_obj][-1].yref is None: + # self.layout[layout_obj][-1].update(xref="x") + if self.layout[layout_obj][-1].yref == None: self.layout[layout_obj][-1].update(yref="y") + # self.layout[layout_obj][-1].update(yref='y') new_layout_objs = tuple( filter( lambda x: x is not None, From 8bb24b9e731de31dd5769ba5a364591eb2000037 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Wed, 26 Apr 2023 15:27:02 -0600 Subject: [PATCH 05/16] fixed bug which was causing shapes to be all drawn on the same axis --- .../python/plotly/plotly/basedatatypes.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index fae8b9b9fc..812631c063 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1559,6 +1559,7 @@ def _add_annotation_like( subplot_type=refs[0].subplot_type, ) ) + ''' if len(refs) == 1 and secondary_y: raise ValueError( """ @@ -1567,26 +1568,33 @@ def _add_annotation_like( prop_singular=prop_singular, r=row, c=col ) ) + ''' + #''' - # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys - if new_obj.xref == None: - if secondary_y: - xaxis = refs[1].layout_keys[0] - else: - xaxis = refs[0].layout_keys[0] - xref = xaxis.replace("axis", "") - else: - xref = new_obj.xref - # If the new_object was created with an xref specified, the specified xref should be used otherwise assign the xref from the layout_keys - if new_obj.yref == None: + # If the new_object was created with an yref specified, the specified yref should be used otherwise assign the xref from the layout_keys + if new_obj.yref == None or new_obj.yref == "y": + if len(refs) == 1 and secondary_y: + raise ValueError( + """ + Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c}) + because subplot does not have a secondary y-axis""".format( + prop_singular=prop_singular, r=row, c=col + ) + ) if secondary_y: yaxis = refs[1].layout_keys[1] + xaxis = refs[1].layout_keys[0] else: yaxis = refs[0].layout_keys[1] + xaxis = refs[0].layout_keys[0] yref = yaxis.replace("axis", "") + xref = xaxis.replace("axis", "") else: yref = new_obj.yref + xaxis = refs[0].layout_keys[0] + xref = xaxis.replace("axis", "") #''' + """ if secondary_y: xaxis, yaxis = refs[1].layout_keys @@ -1594,6 +1602,7 @@ def _add_annotation_like( xaxis, yaxis = refs[0].layout_keys xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") """ + # if exclude_empty_subplots is True, check to see if subplot is # empty and return if it is if exclude_empty_subplots and ( @@ -1615,6 +1624,10 @@ def _add_domain(ax_letter, new_axref): new_obj.update(xref=xref, yref=yref) self.layout[prop_plural] += (new_obj,) + # The 'new_obj.xref' and 'new_obj.yref' parameters need to be reset otherwise it + # will appear as if user supplied yref params and will force annotation to + # be on the axis of the last drawn annotation (they all end up on the same axis) + new_obj.update(xref=None, yref=None) return self From 1d1db35c9ca6ab0daa8a10b2bb259896c1416309 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Wed, 26 Apr 2023 15:28:52 -0600 Subject: [PATCH 06/16] removed some no longer needed files that were being used to find the bug on the previous commit --- debug.py | 41 ----------------------------------------- debugtesting.py | 43 ------------------------------------------- 2 files changed, 84 deletions(-) delete mode 100644 debug.py delete mode 100644 debugtesting.py diff --git a/debug.py b/debug.py deleted file mode 100644 index 06511a2b57..0000000000 --- a/debug.py +++ /dev/null @@ -1,41 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import numpy as np -from plotly import subplots - -test_subplots = True -use_OP = False - -df = px.data.iris() -if use_OP: - fig = px.scatter(df, x="petal_length", y="petal_width") - fig.add_traces(go.Scatter(y=np.arange(1, 7), mode="lines+markers", yaxis="y2")) - - fig.update_layout( - yaxis2=dict( - title="yaxis2 title", - overlaying="y", - side="right", - ) - ) -else: - trace1 = go.Scatter(x=df["petal_length"], y=df["petal_width"], mode="markers") - trace2 = go.Scatter( - x=[0, 1, 2, 3, 4, 5], y=np.arange(1, 7), mode="lines+markers", yaxis="y2" - ) - data = [trace1, trace2] - layout = go.Layout( - yaxis=dict(title="yaxis1"), - yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"), - ) - fig = go.Figure(data=data, layout=layout) -if test_subplots: - fig = subplots.make_subplots(rows=1, cols=2, shared_yaxes=False) - -fig.add_hline( - y=2, - line_dash="dash", - line_color="Red", # yref='y2' - # secondary_y=True -) -fig.show() diff --git a/debugtesting.py b/debugtesting.py deleted file mode 100644 index 71a9bd47e0..0000000000 --- a/debugtesting.py +++ /dev/null @@ -1,43 +0,0 @@ -import plotly.express as px -import plotly.graph_objects as go -import numpy as np -from plotly.subplots import make_subplots - - -for k, fun, d, fun2, d2 in [ - ( - "shapes", - go.Figure.add_shape, - dict(type="rect", x0=1.5, x1=2.5, y0=3.5, y1=4.5), - # add a different type to make the check easier (otherwise we might - # mix up the objects added before and after fun was run) - go.Figure.add_annotation, - dict(x=1, y=2, text="A"), - ), - ( - "annotations", - go.Figure.add_annotation, - dict(x=1, y=2, text="A"), - go.Figure.add_layout_image, - dict(x=3, y=4, sizex=2, sizey=3, source="test"), - ), - ( - "images", - go.Figure.add_layout_image, - dict(x=3, y=4, sizex=2, sizey=3, source="test"), - go.Figure.add_shape, - dict(type="rect", x0=1.5, x1=2.5, y0=3.5, y1=4.5), - ), -]: - # make a figure where not all the subplots are populated - fig = make_subplots(2, 2) - fig.add_trace(go.Scatter(x=[1, 2, 3], y=[5, 1, 2]), row=1, col=1) - fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, -7]), row=2, col=2) - fun2(fig, d2, row=1, col=2) - # add a thing to all subplots but make sure it only goes on the - # plots without data or layout objects - fun(fig, d, row="all", col="all", exclude_empty_subplots="anything_truthy") - assert len(fig.layout[k]) == 3 - assert fig.layout[k][0]["xref"] == "x" and fig.layout[k][0]["yref"] == "y" - assert fig.layout[k][1]["xref"] == "x2" and fig.layout[k][1]["yref"] == "y2" - assert fig.layout[k][2]["xref"] == "x4" and fig.layout[k][2]["yref"] == "y4" From d95d8ee7fbef3075f20c4f81ac92ba91c0f432c1 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Wed, 26 Apr 2023 16:35:04 -0600 Subject: [PATCH 07/16] added tests --- .../test_update_annotations.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py index 23c3af2796..58af8cb6fc 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -5,6 +5,7 @@ import plotly.graph_objs as go from plotly.subplots import make_subplots +import plotly.express as px import pytest @@ -351,6 +352,61 @@ def test_no_exclude_empty_subplots(): assert fig.layout[k][3]["xref"] == "x4" and fig.layout[k][3]["yref"] == "y4" +def test_supplied_yref_on_single_plot_subplot(): + ### test a (1,1) subplot figure object from px.scatter + fig = px.scatter(x=[1, 2, 3, 4], y=[1, 2, 2, 1]) + fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[4, 3, 2, 1], yaxis="y2")) + fig.update_layout( + yaxis=dict(title="yaxis1 title"), + yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"), + ) + # add horizontal line on y2. Secondary_y can be True or False + fig.add_hline(y=3, yref="y2", secondary_y=True) + assert fig.layout["shapes"][0]["yref"] == "y2" + + +def test_supplied_yref_on_non_subplot_figure_object(): + ### test a non-subplot figure object from go.Figure + trace1 = go.Scatter(x=[1, 2, 3, 4], y=[1, 2, 2, 1]) + trace2 = go.Scatter(x=[1, 2, 3, 4], y=[4, 3, 2, 1], yaxis="y2") + data = [trace1, trace2] + layout = go.Layout( + yaxis=dict(title="yaxis1 title"), + yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"), + ) + fig = go.Figure(data=data, layout=layout) + # add horizontal line on y2. Secondary_y can be True or False + fig.add_hline(y=3, yref="y2", secondary_y=False) + assert fig.layout["shapes"][0]["yref"] == "y2" + + +def test_supplied_yref_on_multi_plot_subplot(): + ### test multiple subploted figure object with subplots.make_subplots + fig = make_subplots( + rows=1, + cols=2, + shared_yaxes=False, + specs=[[{"secondary_y": True}, {"secondary_y": True}]], + ) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3]), row=1, col=1) + fig.add_trace( + go.Scatter(x=[1, 2, 3], y=[3, 2, 1], yaxis="y2"), row=1, col=1, secondary_y=True + ) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3], yaxis="y"), row=1, col=2) + fig.add_trace( + go.Scatter(x=[1, 2, 3], y=[1, 1, 2], yaxis="y2"), row=1, col=2, secondary_y=True + ) + # add a horizontal line on both subplots secondary y. + # When using the subplots.make_subplots() method yref parameter should not be supplied to add_hline() + # Instead Secondary_y MUST be True to plot on secondary y + fig.add_hline(y=2, row=1, col=1, secondary_y=True) + fig.add_hline(y=1, row=1, col=2, secondary_y=True) + assert fig.layout["shapes"][0]["yref"] == "y2" + assert fig.layout["shapes"][0]["xref"] == "x domain" + assert fig.layout["shapes"][1]["yref"] == "y4" + assert fig.layout["shapes"][1]["xref"] == "x2 domain" + + @pytest.fixture def select_annotations_integer(): fig = make_subplots(2, 3) From f92dc6e1089e12559c8610c086a271d01be74d87 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Wed, 26 Apr 2023 17:54:31 -0600 Subject: [PATCH 08/16] added comments and cleaned up code --- .../python/plotly/plotly/basedatatypes.py | 39 ++++--------------- .../test_update_annotations.py | 8 ++-- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 812631c063..380a3e740b 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1559,19 +1559,8 @@ def _add_annotation_like( subplot_type=refs[0].subplot_type, ) ) - ''' - if len(refs) == 1 and secondary_y: - raise ValueError( - """ -Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c}) -because subplot does not have a secondary y-axis""".format( - prop_singular=prop_singular, r=row, c=col - ) - ) - ''' - #''' - # If the new_object was created with an yref specified, the specified yref should be used otherwise assign the xref from the layout_keys + # If the new_object was created with an yref specified, the specified yref should be used otherwise assign the xref and yref from the layout_keys if new_obj.yref == None or new_obj.yref == "y": if len(refs) == 1 and secondary_y: raise ValueError( @@ -1582,27 +1571,14 @@ def _add_annotation_like( ) ) if secondary_y: - yaxis = refs[1].layout_keys[1] - xaxis = refs[1].layout_keys[0] + xaxis, yaxis = refs[1].layout_keys else: - yaxis = refs[0].layout_keys[1] - xaxis = refs[0].layout_keys[0] - yref = yaxis.replace("axis", "") - xref = xaxis.replace("axis", "") + xaxis, yaxis = refs[0].layout_keys + xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") else: yref = new_obj.yref xaxis = refs[0].layout_keys[0] xref = xaxis.replace("axis", "") - #''' - - """ - if secondary_y: - xaxis, yaxis = refs[1].layout_keys - else: - xaxis, yaxis = refs[0].layout_keys - xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") - """ - # if exclude_empty_subplots is True, check to see if subplot is # empty and return if it is if exclude_empty_subplots and ( @@ -1625,8 +1601,9 @@ def _add_domain(ax_letter, new_axref): self.layout[prop_plural] += (new_obj,) # The 'new_obj.xref' and 'new_obj.yref' parameters need to be reset otherwise it - # will appear as if user supplied yref params and will force annotation to - # be on the axis of the last drawn annotation (they all end up on the same axis) + # will appear as if user supplied yref params when looping through subplots and + # will force annotation to be on the axis of the last drawn annotation + # i.e. they all end up on the same axis. new_obj.update(xref=None, yref=None) return self @@ -4084,7 +4061,7 @@ def _process_multiple_axis_spanning_shapes( # self.add_{layout_obj} succeeded) # however, in the case of a single plot, xref and yref MAY not be # specified, IF they are not specified we specify them here so the following routines can work - # (they need to append " domain" to xref or yref) + # (they need to append " domain" to xref or yref). If they are specified, we leave them alone. if self.layout[layout_obj][-1].xref == None: self.layout[layout_obj][-1].update(xref="x") # self.layout[layout_obj][-1].update(xref="x") diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py index 58af8cb6fc..56e6a1cdb7 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -388,17 +388,19 @@ def test_supplied_yref_on_multi_plot_subplot(): shared_yaxes=False, specs=[[{"secondary_y": True}, {"secondary_y": True}]], ) + ### Add traces to the first subplot fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3]), row=1, col=1) fig.add_trace( go.Scatter(x=[1, 2, 3], y=[3, 2, 1], yaxis="y2"), row=1, col=1, secondary_y=True ) + ### Add traces to the second subplot fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3], yaxis="y"), row=1, col=2) fig.add_trace( go.Scatter(x=[1, 2, 3], y=[1, 1, 2], yaxis="y2"), row=1, col=2, secondary_y=True ) - # add a horizontal line on both subplots secondary y. - # When using the subplots.make_subplots() method yref parameter should not be supplied to add_hline() - # Instead Secondary_y MUST be True to plot on secondary y + # add a horizontal line on both subplots on their respective secondary y. + # When using the subplots.make_subplots() method yref parameter should NOT be supplied per docstring instructions. + # Instead secondary_y specs and secondary_y parameter MUST be True to plot on secondary y fig.add_hline(y=2, row=1, col=1, secondary_y=True) fig.add_hline(y=1, row=1, col=2, secondary_y=True) assert fig.layout["shapes"][0]["yref"] == "y2" From 9f0650910517a9d696412589a3c396d2f953427e Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Wed, 26 Apr 2023 18:05:05 -0600 Subject: [PATCH 09/16] more comments --- .../test_core/test_update_objects/test_update_annotations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py index 56e6a1cdb7..7c35c0aee5 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -360,7 +360,7 @@ def test_supplied_yref_on_single_plot_subplot(): yaxis=dict(title="yaxis1 title"), yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"), ) - # add horizontal line on y2. Secondary_y can be True or False + # add horizontal line on y2. Secondary_y can be True or False when yref is supplied fig.add_hline(y=3, yref="y2", secondary_y=True) assert fig.layout["shapes"][0]["yref"] == "y2" @@ -375,7 +375,7 @@ def test_supplied_yref_on_non_subplot_figure_object(): yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"), ) fig = go.Figure(data=data, layout=layout) - # add horizontal line on y2. Secondary_y can be True or False + # add horizontal line on y2. Secondary_y can be True or False when yref is supplied fig.add_hline(y=3, yref="y2", secondary_y=False) assert fig.layout["shapes"][0]["yref"] == "y2" From 746b227d95300b2f928834b82ef3f4d7cc9ac94d Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Wed, 26 Apr 2023 20:07:15 -0600 Subject: [PATCH 10/16] removed import plotly express from test --- .../test_update_objects/test_update_annotations.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py index 7c35c0aee5..d1c81a3113 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -5,7 +5,7 @@ import plotly.graph_objs as go from plotly.subplots import make_subplots -import plotly.express as px + import pytest @@ -353,8 +353,9 @@ def test_no_exclude_empty_subplots(): def test_supplied_yref_on_single_plot_subplot(): - ### test a (1,1) subplot figure object from px.scatter - fig = px.scatter(x=[1, 2, 3, 4], y=[1, 2, 2, 1]) + ### test a (1,1) subplot figure object + fig = make_subplots(1, 1) + fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[1, 2, 2, 1])) fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[4, 3, 2, 1], yaxis="y2")) fig.update_layout( yaxis=dict(title="yaxis1 title"), From 9f8c92d1c70298e7a1cb300ed2a07f3aed63ae9a Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Fri, 5 May 2023 11:59:46 -0400 Subject: [PATCH 11/16] comment tweaks and remove commented-out code --- packages/python/plotly/plotly/basedatatypes.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 380a3e740b..eb3e581092 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1560,7 +1560,8 @@ def _add_annotation_like( ) ) - # If the new_object was created with an yref specified, the specified yref should be used otherwise assign the xref and yref from the layout_keys + # If new_obj was created with yref specified, the specified yref + # should be used, otherwise assign xref and yref from layout_keys if new_obj.yref == None or new_obj.yref == "y": if len(refs) == 1 and secondary_y: raise ValueError( @@ -4062,12 +4063,10 @@ def _process_multiple_axis_spanning_shapes( # however, in the case of a single plot, xref and yref MAY not be # specified, IF they are not specified we specify them here so the following routines can work # (they need to append " domain" to xref or yref). If they are specified, we leave them alone. - if self.layout[layout_obj][-1].xref == None: + if self.layout[layout_obj][-1].xref is None: self.layout[layout_obj][-1].update(xref="x") - # self.layout[layout_obj][-1].update(xref="x") - if self.layout[layout_obj][-1].yref == None: + if self.layout[layout_obj][-1].yref is None: self.layout[layout_obj][-1].update(yref="y") - # self.layout[layout_obj][-1].update(yref='y') new_layout_objs = tuple( filter( lambda x: x is not None, From 8b2d4bf17633a91f09f0507f5aa4a51de757e1a6 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Tue, 9 May 2023 20:44:39 -0600 Subject: [PATCH 12/16] Fixed issued caused by a paper or domain, yref --- packages/python/plotly/plotly/basedatatypes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 380a3e740b..9e1f46bf68 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1560,8 +1560,13 @@ def _add_annotation_like( ) ) - # If the new_object was created with an yref specified, the specified yref should be used otherwise assign the xref and yref from the layout_keys - if new_obj.yref == None or new_obj.yref == "y": + # If the new_object was created with a yref specified that did not include paper or domain, the specified yref should be used otherwise assign the xref and yref from the layout_keys + if ( + new_obj.yref == None + or new_obj.yref == "y" + or "paper" in new_obj.yref + or "domain" in new_obj.yref + ): if len(refs) == 1 and secondary_y: raise ValueError( """ From 872e3d5558bc4b8686761845c7ce19ae7599eefe Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Tue, 9 May 2023 22:52:42 -0600 Subject: [PATCH 13/16] Added changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f17a4a7e17..2bd64b6a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed another compatibility issue with Pandas 2.0, just affecting `px.*(line_close=True)` [[#4190](https://github.com/plotly/plotly.py/pull/4190)] - Added some rounding to the `make_subplots` function to handle situations where the user-input specs cause the domain to exceed 1 by small amounts https://github.com/plotly/plotly.py/pull/4153 + - Fixed issue with shapes and annotations plotting on the wrong y axis when supplied with a specific axis in the `yref` parameter [[#4177](https://github.com/plotly/plotly.py/pull/4177)] + + ## [5.14.1] - 2023-04-05 From 76788127d73c3620c256b584631ba3830ed51b77 Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Fri, 12 May 2023 10:13:46 -0600 Subject: [PATCH 14/16] fixed issue with annotation_text of an axis_spanning_shape being displayed on the primary y instead of the yref supplied with the axis_spanning_shape --- packages/python/plotly/plotly/basedatatypes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 812631c063..ba7196d127 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -4071,6 +4071,7 @@ def _process_multiple_axis_spanning_shapes( row=row, col=col, exclude_empty_subplots=exclude_empty_subplots, + yref=shape_kwargs["yref"], ) # update xref and yref for the new shapes and annotations for layout_obj, n_layout_objs_before in zip( From c2860de16f00f6a705cf5578c56ebd5d942180ee Mon Sep 17 00:00:00 2001 From: Zach Foltz Date: Fri, 12 May 2023 10:46:08 -0600 Subject: [PATCH 15/16] Added check to catch if no yref was supplied --- packages/python/plotly/plotly/basedatatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 0da2015079..1807212a13 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -4053,7 +4053,7 @@ def _process_multiple_axis_spanning_shapes( row=row, col=col, exclude_empty_subplots=exclude_empty_subplots, - yref=shape_kwargs["yref"], + yref=shape_kwargs["yref"] if "yref" in shape_kwargs.keys() else "y", ) # update xref and yref for the new shapes and annotations for layout_obj, n_layout_objs_before in zip( From a8fa9dd4f711124ce4eef0d16a2a6707672eb8fd Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Wed, 24 May 2023 10:19:05 -0400 Subject: [PATCH 16/16] Apply suggestions from code review --- CHANGELOG.md | 3 +-- packages/python/plotly/plotly/basedatatypes.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3584e56b..8bc032c041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fixed another compatibility issue with Pandas 2.0, just affecting `px.*(line_close=True)` [[#4190](https://github.com/plotly/plotly.py/pull/4190)] - - - Added some rounding to the `make_subplots` function to handle situations where the user-input specs cause the domain to exceed 1 by small amounts https://github.com/plotly/plotly.py/pull/4153 + - Added some rounding to the `make_subplots` function to handle situations where the user-input specs cause the domain to exceed 1 by small amounts [[#4153](https://github.com/plotly/plotly.py/pull/4153)] - Sanitize JSON output to prevent an XSS vector when graphs are inserted directly into HTML [[#4196](https://github.com/plotly/plotly.py/pull/4196)] - Fixed issue with shapes and annotations plotting on the wrong y axis when supplied with a specific axis in the `yref` parameter [[#4177](https://github.com/plotly/plotly.py/pull/4177)] diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 1807212a13..8fa70e0ada 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1562,7 +1562,7 @@ def _add_annotation_like( # If the new_object was created with a yref specified that did not include paper or domain, the specified yref should be used otherwise assign the xref and yref from the layout_keys if ( - new_obj.yref == None + new_obj.yref is None or new_obj.yref == "y" or "paper" in new_obj.yref or "domain" in new_obj.yref @@ -4053,7 +4053,7 @@ def _process_multiple_axis_spanning_shapes( row=row, col=col, exclude_empty_subplots=exclude_empty_subplots, - yref=shape_kwargs["yref"] if "yref" in shape_kwargs.keys() else "y", + yref=shape_kwargs.get("yref", "y"), ) # update xref and yref for the new shapes and annotations for layout_obj, n_layout_objs_before in zip(