Skip to content

Commit 55bd2a3

Browse files
larsonerclaude
andcommitted
fix: resize embedded figures once the page finishes loading
A figure whose inline script draws while the page is still being parsed can be sized to a container whose width changes by the time loading finishes (e.g. pydata-sphinx-theme's secondary sidebar comes after the article in the DOM), leaving it clipped until a window resize. Append a per-figure script to the _repr_html_ fallback and the sphinx-gallery scraper embeds that calls Plotly.Plots.resize once on window load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 76d3aae commit 55bd2a3

4 files changed

Lines changed: 39 additions & 2 deletions

File tree

‎plotly/basedatatypes.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,14 +825,20 @@ def _repr_html_(self):
825825
if "text/html" in bundle:
826826
return bundle["text/html"]
827827
else:
828+
import uuid
829+
830+
from plotly.io._utils import resize_after_load_script
831+
832+
div_id = str(uuid.uuid4())
828833
# Size like the html renderers do: "100%" height collapses or
829834
# overflows in plain-HTML consumers such as sphinx-gallery.
830835
return self.to_html(
831836
full_html=False,
832837
include_plotlyjs="cdn",
833838
default_width="100%",
834839
default_height=525,
835-
)
840+
div_id=div_id,
841+
) + resize_after_load_script(div_id)
836842

837843
def _repr_mimebundle_(self, include=None, exclude=None, validate=True, **kwargs):
838844
"""

‎plotly/io/_sg_scraper.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import logging
77
import os
88
import textwrap
9+
import uuid
910

1011
import plotly
1112
from plotly.basedatatypes import BaseFigure
1213
from plotly.io._base_renderers import sphinx_gallery_figures
14+
from plotly.io._utils import resize_after_load_script
1315

1416
plotly.io.renderers.default = "sphinx_gallery_png"
1517

@@ -147,17 +149,22 @@ def _inline_html(fig_dict):
147149
The figure is wrapped in the same div that sphinx-gallery wraps captured
148150
HTML reprs in, so that themes can style both kinds of embed alike.
149151
"""
152+
div_id = str(uuid.uuid4())
150153
html = plotly.io.to_html(
151154
fig_dict,
152155
include_plotlyjs="cdn",
153156
full_html=False,
154157
default_width="100%",
155158
default_height=525,
156159
validate=False,
160+
div_id=div_id,
157161
)
158162
html = (
159163
'<div class="output_subarea output_html rendered_html output_result">\n'
160164
f"{html}\n"
165+
# The figure may draw before the page finishes laying out, ending up
166+
# sized to a container whose width then changes.
167+
f"{resize_after_load_script(div_id)}\n"
161168
"</div>"
162169
)
163170
return "\n.. raw:: html\n\n" + textwrap.indent(html, " ") + "\n"

‎plotly/io/_utils.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,22 @@ def plotly_cdn_url(cdn_ver=get_plotlyjs_version()):
9191
return "https://cdn.plot.ly/plotly-{cdn_ver}.min.js".format(
9292
cdn_ver=cdn_ver,
9393
)
94+
95+
96+
def resize_after_load_script(div_id):
97+
"""Script that resizes a plotly div once the page finishes loading.
98+
99+
A figure drawn while its page is still loading (e.g. one embedded in a
100+
sphinx-gallery page) can be sized to a container whose width changes by
101+
the time loading finishes, so fix it up once afterwards.
102+
"""
103+
return (
104+
'<script>(function() {'
105+
"function fit() {"
106+
f'var gd = document.getElementById("{div_id}");'
107+
"if (window.Plotly && gd) Plotly.Plots.resize(gd);"
108+
"}"
109+
'if (document.readyState === "complete") fit();'
110+
'else window.addEventListener("load", fit);'
111+
"})();</script>"
112+
)

‎tests/test_io/test_sg_scraper.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ def test_scraper(gallery, image_format):
140140
assert rst.count("plotly-graph-div") == 2
141141
# The wrapper sphinx-gallery uses for HTML reprs, so themes can style both
142142
assert rst.count('class="output_subarea') == 2
143+
# Fixes up figures drawn while the page was still laying out
144+
assert rst.count("Plotly.Plots.resize") == 2
143145

144146
# The thumbnail must be a scraped figure rather than a "no image" default,
145147
# and one image per figure in order is what makes `thumbnail_number` work
@@ -258,7 +260,10 @@ def test_repr_html_fallback_size(monkeypatch):
258260
with no set height.
259261
"""
260262
monkeypatch.setattr(pio.renderers, "default", "sphinx_gallery_png")
261-
assert 'style="height:525px; width:100%;"' in go.Figure()._repr_html_()
263+
html = go.Figure()._repr_html_()
264+
assert 'style="height:525px; width:100%;"' in html
265+
# Fixes up figures drawn while the page was still laying out
266+
assert "Plotly.Plots.resize" in html
262267
fig = go.Figure(layout={"height": 400})
263268
assert 'style="height:400px; width:100%;"' in fig._repr_html_()
264269

0 commit comments

Comments
 (0)