Skip to content

Commit c17b7e3

Browse files
Center 3D subplot titles on their scenes
1 parent 086c2c1 commit c17b7e3

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

‎plotly/matplotlylib/renderer.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ def __init__(self):
146146
self.scene_ct = 0
147147
self.current_3d_subplot = None
148148
self.current_is_3d = False
149+
self.all_3d = False
149150
self.mpl_x_bounds = (0, 1)
150151
self.mpl_y_bounds = (0, 1)
151152
self.msg = "Initialized PlotlyRenderer\n"
@@ -289,10 +290,10 @@ def open_figure(self, fig, props):
289290
)
290291
self.plotly_fig["layout"].paper_bgcolor = _export_color(props["figbg"])
291292
self.mpl_x_bounds, self.mpl_y_bounds = mpltools.get_axes_bounds(fig)
292-
all_3d = fig.get_axes() and all(
293+
self.all_3d = bool(fig.get_axes()) and all(
293294
getattr(ax, "name", None) == "3d" for ax in fig.get_axes()
294295
)
295-
if all_3d:
296+
if self.all_3d:
296297
has_title = any(
297298
getattr(ax, "title", None) and ax.title.get_text()
298299
for ax in fig.get_axes()
@@ -1361,6 +1362,11 @@ def draw_title(self, **props):
13611362
self.msg += " More than one subplot, adding title as annotation\n"
13621363
x_px, y_px = props["mplobj"].get_transform().transform(props["position"])
13631364
x, y = mpltools.display_to_paper(x_px, y_px, self.plotly_fig["layout"])
1365+
if self.all_3d:
1366+
# the scene domains of all-3d figures span the horizontal
1367+
# axes bounds of the figure
1368+
x0, x1 = self.mpl_x_bounds
1369+
x = (x_px / self.mpl_fig.bbox.width - x0) / (x1 - x0)
13641370
annotation = go.layout.Annotation(
13651371
text=props["text"],
13661372
font=go.layout.annotation.Font(

‎plotly/matplotlylib/tests/test_renderer.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,36 @@ def test_plot3d_shorthand_axis_colors():
16481648
assert axis.tickfont.color == "#FF7F0E"
16491649

16501650

1651+
@pytest.mark.parametrize("nrows,ncols", [(1, 2), (2, 2)])
1652+
def test_plot3d_subplot_titles_centered_on_their_scenes(nrows, ncols):
1653+
"""In figures of 3D subplots, each title sits at the horizontal
1654+
position across its scene domain that the matplotlib title has
1655+
across its axes."""
1656+
fig = plt.figure(figsize=(4 * ncols, 4 * nrows))
1657+
axes = []
1658+
for i in range(nrows * ncols):
1659+
ax = fig.add_subplot(nrows, ncols, i + 1, projection="3d")
1660+
ax.plot([0, 1], [0, 1], [0, 1])
1661+
ax.set_title("title {0}".format(i))
1662+
axes.append(ax)
1663+
1664+
plotly_fig = tls.mpl_to_plotly(fig)
1665+
1666+
layout = plotly_fig.layout
1667+
scenes = [layout.scene] + [
1668+
layout["scene{0}".format(i + 1)] for i in range(1, len(axes))
1669+
]
1670+
for ax, scene, annotation in zip(axes, scenes, layout.annotations):
1671+
x_px, _ = ax.title.get_transform().transform(ax.title.get_position())
1672+
bounds = ax.get_position()
1673+
mpl_rel_x = (x_px / fig.bbox.width - bounds.x0) / bounds.width
1674+
rel_x = (annotation.x - scene.domain.x[0]) / (
1675+
scene.domain.x[1] - scene.domain.x[0]
1676+
)
1677+
assert annotation.text == ax.get_title()
1678+
assert rel_x == pytest.approx(mpl_rel_x, abs=1e-6)
1679+
1680+
16511681
def _add_scatter3d(ax):
16521682
ax.scatter([0, 1], [0, 1], [0, 1])
16531683

0 commit comments

Comments
 (0)