Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9919594

Browse files
Overlaying 2nd fig on secondary-y has problems
It works but it seems the "start_cell" argument of make_subplots has not been taken into consideration. Also sometimes it adds annotations twice?
1 parent bf7cf9b commit 9919594

File tree

2 files changed

+70
-14
lines changed

2 files changed

+70
-14
lines changed

proto/px_overlay/multilayered_data_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import test_data
22
import numpy as np
33
import plotly.express as px
4-
from px_combine import px_combine_secondary_y, px_simple_combine
4+
from px_overlay import px_simple_overlay
55

66
df = test_data.multilayered_data(d_divs=[2, 3, 4, 2], rwalk=0.1)
77
print(df)
@@ -26,11 +26,11 @@
2626
x=0.25, y=0.5, xref="x domain", yref="y domain", row=2, col=3, text="yo"
2727
)
2828
figs[1].add_annotation(
29-
x=0.5, y=0.35, xref="x domain", yref="y domain", row=1, col=2, text="budday"
29+
x=0.5, y=0.35, xref="x domain", yref="y", row=1, col=2, text="budday"
3030
)
3131
figs[0].layout.barmode = "group"
3232
figs[1].layout.barmode = "relative"
33-
final_fig = px_simple_combine(*figs)
33+
final_fig = px_simple_overlay(*figs, fig1_secondary_y=True)
3434
for fig in figs:
3535
fig.show()
3636
final_fig.show()

proto/px_overlay/px_overlay.py

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# Prototype for px.combine
1+
# Prototype for px.overlay
22
# Combine 2 figures containing subplots
33
# Run as
4-
# python px_combine.py
4+
# python px_overlay.py
55

66
import plotly.express as px
77
import plotly.graph_objects as go
@@ -111,7 +111,9 @@ def find_subplot_axes(fig, row, col, secondary_y=False):
111111
nrows, ncols = fig_grid_ref_shape(fig)
112112
try:
113113
sps = fig._grid_ref[row - 1][col - 1]
114-
except IndexError:
114+
except (IndexError, TypeError):
115+
# IndexError if fig has _grid_ref but not requested row or column,
116+
# TypeError if fig has no _grid_ref (it is None)
115117
raise IndexError(
116118
"Figure does not have a subplot at the requested row or column."
117119
)
@@ -142,7 +144,15 @@ def _check_is_secondary_y(sp):
142144
return sp.layout_keys
143145

144146

145-
def map_axis_pair(old_fig, new_fig, axpair, make_axis_ref=True):
147+
def map_axis_pair(
148+
old_fig,
149+
new_fig,
150+
axpair,
151+
new_row=None,
152+
new_col=None,
153+
new_secondary_y=None,
154+
make_axis_ref=True,
155+
):
146156
"""
147157
Find the axes on the new figure that will give the same subplot and
148158
possibly secondary y axis as on the old figure. This can only
@@ -151,9 +161,14 @@ def map_axis_pair(old_fig, new_fig, axpair, make_axis_ref=True):
151161
columns and secondary y-axes.
152162
if make_axis_ref is True, axis is removed from the resulting strings, e.g., xaxis2 -> x2
153163
"""
164+
if None in axpair:
165+
raise ValueError("Cannot map axis whose value is None.")
154166
if axpair == ("paper", "paper"):
155-
return ax
167+
return axpair
156168
row, col, secondary_y = axis_pair_to_row_col(old_fig, axpair)
169+
row = new_row if new_row is not None else row
170+
col = new_col if new_col is not None else col
171+
secondary_y = new_secondary_y if new_secondary_y is not None else secondary_y
157172
newaxpair = find_subplot_axes(new_fig, row, col, secondary_y)
158173
axpair_extras = [" domain" if ax.endswith("domain") else "" for ax in axpair]
159174
newaxpair = tuple(ax + extra for ax, extra in zip(newaxpair, axpair_extras))
@@ -162,7 +177,27 @@ def map_axis_pair(old_fig, new_fig, axpair, make_axis_ref=True):
162177
return newaxpair
163178

164179

165-
def px_simple_combine(fig0, fig1, fig1_secondary_y=False):
180+
def map_annotation_like_obj_axis(oldfig, newfig, an, force_secondary_y=False):
181+
"""
182+
Take an annotation-like object with xref and yref referring to axes in oldfig
183+
and map them to axes in newfig. This makes it possible to map an annotation
184+
to the same subplot row, column or secondary y in a new plot even if they do
185+
not have matching subplots.
186+
If force_secondary_y is True, attempt is made to map the annotation to a
187+
secondary y axis in the new figure.
188+
Returns the new annotation. Note that it has not been added to newfig, the
189+
caller must then do this if it wants it added to newfig.
190+
"""
191+
oldaxpair = tuple([an[ref] for ref in ["xref", "yref"]])
192+
newaxpair = map_axis_pair(
193+
oldfig, newfig, oldaxpair, new_secondary_y=force_secondary_y
194+
)
195+
newan = an.__class__(an)
196+
newan["xref"], newan["yref"] = newaxpair
197+
return newan
198+
199+
200+
def px_simple_overlay(fig0, fig1, fig1_secondary_y=False):
166201
"""
167202
Combines two figures by just using the layout of the first figure and
168203
appending the data of the second figure.
@@ -177,7 +212,7 @@ def px_simple_combine(fig0, fig1, fig1_secondary_y=False):
177212
grid_ref_shape = fig_grid_ref_shape(fig0)
178213
if grid_ref_shape != fig_grid_ref_shape(fig1):
179214
raise ValueError(
180-
"Only two figures with the same subplot geometry can be combined."
215+
"Only two figures with the same subplot geometry can be overlayd."
181216
)
182217
# reflow the colors
183218
colorway = fig0.layout.template.layout.colorway
@@ -209,7 +244,28 @@ def px_simple_combine(fig0, fig1, fig1_secondary_y=False):
209244
tr, row=r + 1, col=c + 1, secondary_y=(fig1_secondary_y and (f == fig1))
210245
)
211246
# TODO: How to preserve axis sizes when adding secondary y?
212-
# TODO: How to put annotations on the correct subplot when using secondary y?
247+
248+
# Map the axes of the annotation-like objects to the new figure. Map the
249+
# fig1 objects to the secondary-y if requested.
250+
selectors = product(
251+
[fig0, fig1],
252+
[
253+
go.Figure.select_annotations,
254+
go.Figure.select_shapes,
255+
go.Figure.select_layout_images,
256+
],
257+
)
258+
adders = product(
259+
[(fig, False), (fig, fig1_secondary_y)],
260+
[go.Figure.add_annotation, go.Figure.add_shape, go.Figure.add_layout_image],
261+
)
262+
for (oldfig, selector), ((newfig, secy), adder) in zip(selectors, adders):
263+
for ann in selector(oldfig):
264+
newann = map_annotation_like_obj_axis(
265+
oldfig, newfig, ann, force_secondary_y=secy
266+
)
267+
adder(newfig, newann)
268+
213269
# fig.update_layout(fig0.layout)
214270
# title will be wrong
215271
fig.layout.title = None
@@ -270,17 +326,17 @@ def get_first_set_barmode(figs):
270326
df = test_data.aug_tips()
271327

272328

273-
def simple_combine_example():
329+
def simple_overlay_example():
274330
fig0 = px.scatter(df, x="total_bill", y="tip", facet_row="sex", facet_col="smoker")
275331
fig1 = px.histogram(
276332
df, x="total_bill", y="tip", facet_row="sex", facet_col="smoker"
277333
)
278334
fig1.update_traces(marker_color="red")
279-
fig = px_simple_combine(fig0, fig1)
335+
fig = px_simple_overlay(fig0, fig1)
280336
fig.update_layout(title="Simple figure combination")
281337
return fig
282338

283339

284340
if __name__ == "__main__":
285-
fig_simple = simple_combine_example()
341+
fig_simple = simple_overlay_example()
286342
fig_simple.show()

0 commit comments

Comments
 (0)