Skip to content

Commit 54e8884

Browse files
committed
Calculate coordinate mapping after drawing figure
To calculate accurate coordinate mappings for matplotlib (& plotnine) plots in display coordinates, the figure should know the location of it's subplots. The location determined by the subplot parameters (subplot_params) and they come from one or more of these sources, listed from lowest to highest priority: 1. rcParams 2. plt.subplots(..., gridspec_kw={..., **subplot_params}) 3. fig.subplot_adjust(**subplot_params) 4. layout engine The layout engine has the last say as it dynamically calculates subplot_params and calls fig.subplot_adjust() to set them. There are two things to consider: 1. Only layout engines that are "adjust_compatible" calculate subplot_params 2. Matplotlib runs the layout engine only when drawing the figure This PR ensures that there is an "adjust_compatible" layout engine and the plot is drawn/saved before the coordinate mappings are calculated. closes has2k1/plotnine#738
1 parent a4ab950 commit 54e8884

File tree

1 file changed

+31
-13
lines changed

1 file changed

+31
-13
lines changed

shiny/render/_try_render_plot.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,16 +159,27 @@ def try_render_matplotlib(
159159
)
160160
fig.set_dpi(ppi_out * pixelratio)
161161

162-
# Suppress the message `UserWarning: The figure layout has changed to tight`
163-
with warnings.catch_warnings():
164-
warnings.filterwarnings(
165-
action="ignore",
166-
category=UserWarning,
167-
message="The figure layout has changed to tight",
162+
# Calculating accurate coordinate mappings requires that the layout engine
163+
# (if there is one) adjusts the figure's subplot parameters.
164+
# e.g. "tight" layout.
165+
# When there is no layout engine, "tight" layout is often helpful
166+
layout_engine = fig.get_layout_engine()
167+
if layout_engine:
168+
if not layout_engine.adjust_compatible:
169+
# In most cases, this branch will override the constained layout.
170+
# which is usually a very deliberate choice by the user
171+
fig.set_layout_engine( # pyright: ignore[reportUnknownMemberType]
172+
layout="tight"
173+
)
174+
warnings.warn(
175+
f"'{type(layout_engine)}' layout engine is not compatible with shiny. "
176+
"The figure layout has been changed to tight.",
177+
stacklevel=1,
178+
)
179+
else:
180+
fig.set_layout_engine( # pyright: ignore[reportUnknownMemberType]
181+
layout="tight"
168182
)
169-
plt.tight_layout() # pyright: ignore[reportUnknownMemberType]
170-
171-
coordmap = get_coordmap(fig)
172183

173184
with io.BytesIO() as buf:
174185
fig.savefig( # pyright: ignore[reportUnknownMemberType]
@@ -181,6 +192,10 @@ def try_render_matplotlib(
181192
data = base64.b64encode(buf.read())
182193
data_str = data.decode("utf-8")
183194

195+
# Calculating accurate coordinate mappings requires the figure to be
196+
# drawn/saved first, which runs the layout engine.
197+
coordmap = get_coordmap(fig)
198+
184199
res: ImgData = {
185200
"src": "data:image/png;base64," + data_str,
186201
"width": width_attr,
@@ -343,17 +358,20 @@ def try_render_plotnine(
343358
verbose=False,
344359
**kwargs,
345360
)
346-
coordmap = get_coordmap_plotnine(
347-
x,
348-
res.figure, # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType, reportGeneralTypeIssues]
349-
)
350361
res.figure.savefig( # pyright: ignore[reportUnknownMemberType, reportGeneralTypeIssues]
351362
**res.kwargs # pyright: ignore[reportUnknownMemberType, reportGeneralTypeIssues]
352363
)
353364
buf.seek(0)
354365
data = base64.b64encode(buf.read())
355366
data_str = data.decode("utf-8")
356367

368+
# Calculating accurate coordinate mappings requires the figure to be
369+
# drawn/saved first, which runs the layout engine.
370+
coordmap = get_coordmap_plotnine(
371+
x,
372+
res.figure, # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType, reportGeneralTypeIssues]
373+
)
374+
357375
res: ImgData = {
358376
"src": "data:image/png;base64," + data_str,
359377
"width": w_attr,

0 commit comments

Comments
 (0)