Skip to content

Commit 42266b0

Browse files
authored
Merge pull request #4177 from zfoltz/axis_spanning_layout_object_xref_yref_bug
Issue #3755 Bug Fix for: Shapes and Annotations With yref Parameter Not Drawing on Correct Axis
2 parents 58075f4 + f5d2900 commit 42266b0

File tree

3 files changed

+97
-14
lines changed

3 files changed

+97
-14
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
88
- Fixed another compatibility issue with Pandas 2.0, just affecting `px.*(line_close=True)` [[#4190](https://github.com/plotly/plotly.py/pull/4190)]
99
- 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)]
1010
- Sanitize JSON output to prevent an XSS vector when graphs are inserted directly into HTML [[#4196](https://github.com/plotly/plotly.py/pull/4196)]
11+
- 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)]
1112
- Remove `use_2to3` setuptools arg, which is invalid in the latest Python and setuptools versions [[#4206](https://github.com/plotly/plotly.py/pull/4206)]
1213
- Fix [#4066](https://github.com/plotly/plotly.py/issues/4066) JupyterLab v4 giving tiny default graph height [[#4227](https://github.com/plotly/plotly.py/pull/4227)]
1314

Diff for: packages/python/plotly/plotly/basedatatypes.py

+37-14
Original file line numberDiff line numberDiff line change
@@ -1559,17 +1559,31 @@ def _add_annotation_like(
15591559
subplot_type=refs[0].subplot_type,
15601560
)
15611561
)
1562-
if len(refs) == 1 and secondary_y:
1563-
raise ValueError(
1564-
"""
1565-
Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c})
1566-
because subplot does not have a secondary y-axis"""
1567-
)
1568-
if secondary_y:
1569-
xaxis, yaxis = refs[1].layout_keys
1562+
1563+
# 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
1564+
if (
1565+
new_obj.yref is None
1566+
or new_obj.yref == "y"
1567+
or "paper" in new_obj.yref
1568+
or "domain" in new_obj.yref
1569+
):
1570+
if len(refs) == 1 and secondary_y:
1571+
raise ValueError(
1572+
"""
1573+
Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c})
1574+
because subplot does not have a secondary y-axis""".format(
1575+
prop_singular=prop_singular, r=row, c=col
1576+
)
1577+
)
1578+
if secondary_y:
1579+
xaxis, yaxis = refs[1].layout_keys
1580+
else:
1581+
xaxis, yaxis = refs[0].layout_keys
1582+
xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "")
15701583
else:
1571-
xaxis, yaxis = refs[0].layout_keys
1572-
xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "")
1584+
yref = new_obj.yref
1585+
xaxis = refs[0].layout_keys[0]
1586+
xref = xaxis.replace("axis", "")
15731587
# if exclude_empty_subplots is True, check to see if subplot is
15741588
# empty and return if it is
15751589
if exclude_empty_subplots and (
@@ -1591,6 +1605,11 @@ def _add_domain(ax_letter, new_axref):
15911605
new_obj.update(xref=xref, yref=yref)
15921606

15931607
self.layout[prop_plural] += (new_obj,)
1608+
# The 'new_obj.xref' and 'new_obj.yref' parameters need to be reset otherwise it
1609+
# will appear as if user supplied yref params when looping through subplots and
1610+
# will force annotation to be on the axis of the last drawn annotation
1611+
# i.e. they all end up on the same axis.
1612+
new_obj.update(xref=None, yref=None)
15941613

15951614
return self
15961615

@@ -4034,6 +4053,7 @@ def _process_multiple_axis_spanning_shapes(
40344053
row=row,
40354054
col=col,
40364055
exclude_empty_subplots=exclude_empty_subplots,
4056+
yref=shape_kwargs.get("yref", "y"),
40374057
)
40384058
# update xref and yref for the new shapes and annotations
40394059
for layout_obj, n_layout_objs_before in zip(
@@ -4045,10 +4065,13 @@ def _process_multiple_axis_spanning_shapes(
40454065
):
40464066
# this was called intending to add to a single plot (and
40474067
# self.add_{layout_obj} succeeded)
4048-
# however, in the case of a single plot, xref and yref are not
4049-
# specified, so we specify them here so the following routines can work
4050-
# (they need to append " domain" to xref or yref)
4051-
self.layout[layout_obj][-1].update(xref="x", yref="y")
4068+
# however, in the case of a single plot, xref and yref MAY not be
4069+
# specified, IF they are not specified we specify them here so the following routines can work
4070+
# (they need to append " domain" to xref or yref). If they are specified, we leave them alone.
4071+
if self.layout[layout_obj][-1].xref is None:
4072+
self.layout[layout_obj][-1].update(xref="x")
4073+
if self.layout[layout_obj][-1].yref is None:
4074+
self.layout[layout_obj][-1].update(yref="y")
40524075
new_layout_objs = tuple(
40534076
filter(
40544077
lambda x: x is not None,

Diff for: packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py

+59
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import plotly.graph_objs as go
77
from plotly.subplots import make_subplots
8+
89
import pytest
910

1011

@@ -351,6 +352,64 @@ def test_no_exclude_empty_subplots():
351352
assert fig.layout[k][3]["xref"] == "x4" and fig.layout[k][3]["yref"] == "y4"
352353

353354

355+
def test_supplied_yref_on_single_plot_subplot():
356+
### test a (1,1) subplot figure object
357+
fig = make_subplots(1, 1)
358+
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[1, 2, 2, 1]))
359+
fig.add_trace(go.Scatter(x=[1, 2, 3, 4], y=[4, 3, 2, 1], yaxis="y2"))
360+
fig.update_layout(
361+
yaxis=dict(title="yaxis1 title"),
362+
yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"),
363+
)
364+
# add horizontal line on y2. Secondary_y can be True or False when yref is supplied
365+
fig.add_hline(y=3, yref="y2", secondary_y=True)
366+
assert fig.layout["shapes"][0]["yref"] == "y2"
367+
368+
369+
def test_supplied_yref_on_non_subplot_figure_object():
370+
### test a non-subplot figure object from go.Figure
371+
trace1 = go.Scatter(x=[1, 2, 3, 4], y=[1, 2, 2, 1])
372+
trace2 = go.Scatter(x=[1, 2, 3, 4], y=[4, 3, 2, 1], yaxis="y2")
373+
data = [trace1, trace2]
374+
layout = go.Layout(
375+
yaxis=dict(title="yaxis1 title"),
376+
yaxis2=dict(title="yaxis2 title", overlaying="y", side="right"),
377+
)
378+
fig = go.Figure(data=data, layout=layout)
379+
# add horizontal line on y2. Secondary_y can be True or False when yref is supplied
380+
fig.add_hline(y=3, yref="y2", secondary_y=False)
381+
assert fig.layout["shapes"][0]["yref"] == "y2"
382+
383+
384+
def test_supplied_yref_on_multi_plot_subplot():
385+
### test multiple subploted figure object with subplots.make_subplots
386+
fig = make_subplots(
387+
rows=1,
388+
cols=2,
389+
shared_yaxes=False,
390+
specs=[[{"secondary_y": True}, {"secondary_y": True}]],
391+
)
392+
### Add traces to the first subplot
393+
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3]), row=1, col=1)
394+
fig.add_trace(
395+
go.Scatter(x=[1, 2, 3], y=[3, 2, 1], yaxis="y2"), row=1, col=1, secondary_y=True
396+
)
397+
### Add traces to the second subplot
398+
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[1, 2, 3], yaxis="y"), row=1, col=2)
399+
fig.add_trace(
400+
go.Scatter(x=[1, 2, 3], y=[1, 1, 2], yaxis="y2"), row=1, col=2, secondary_y=True
401+
)
402+
# add a horizontal line on both subplots on their respective secondary y.
403+
# When using the subplots.make_subplots() method yref parameter should NOT be supplied per docstring instructions.
404+
# Instead secondary_y specs and secondary_y parameter MUST be True to plot on secondary y
405+
fig.add_hline(y=2, row=1, col=1, secondary_y=True)
406+
fig.add_hline(y=1, row=1, col=2, secondary_y=True)
407+
assert fig.layout["shapes"][0]["yref"] == "y2"
408+
assert fig.layout["shapes"][0]["xref"] == "x domain"
409+
assert fig.layout["shapes"][1]["yref"] == "y4"
410+
assert fig.layout["shapes"][1]["xref"] == "x2 domain"
411+
412+
354413
@pytest.fixture
355414
def select_annotations_integer():
356415
fig = make_subplots(2, 3)

0 commit comments

Comments
 (0)