Skip to content

Commit 4d3e325

Browse files
larsonerclaude
andcommitted
feat: style embedded figures as a card on dark pages
Move the dark-theme styling out of sphinx-gallery: sphinx-gallery should not carry plotly-specific CSS, so ship a scoped style with each embed (repr fallback and scraper) instead. The white padded card only shows on dark pages (data-theme toggles or OS preference); on light pages it is invisible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 55bd2a3 commit 4d3e325

4 files changed

Lines changed: 33 additions & 7 deletions

File tree

‎plotly/basedatatypes.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -827,18 +827,19 @@ def _repr_html_(self):
827827
else:
828828
import uuid
829829

830-
from plotly.io._utils import resize_after_load_script
830+
from plotly.io._utils import embed_in_output_card, resize_after_load_script
831831

832832
div_id = str(uuid.uuid4())
833833
# Size like the html renderers do: "100%" height collapses or
834834
# overflows in plain-HTML consumers such as sphinx-gallery.
835-
return self.to_html(
835+
html = self.to_html(
836836
full_html=False,
837837
include_plotlyjs="cdn",
838838
default_width="100%",
839839
default_height=525,
840840
div_id=div_id,
841-
) + resize_after_load_script(div_id)
841+
)
842+
return embed_in_output_card(html + resize_after_load_script(div_id))
842843

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

‎plotly/io/_sg_scraper.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import plotly
1212
from plotly.basedatatypes import BaseFigure
1313
from plotly.io._base_renderers import sphinx_gallery_figures
14-
from plotly.io._utils import resize_after_load_script
14+
from plotly.io._utils import embed_in_output_card, resize_after_load_script
1515

1616
plotly.io.renderers.default = "sphinx_gallery_png"
1717

@@ -159,12 +159,12 @@ def _inline_html(fig_dict):
159159
validate=False,
160160
div_id=div_id,
161161
)
162+
# The figure may draw before the page finishes laying out, ending up
163+
# sized to a container whose width then changes.
164+
html = embed_in_output_card(html + resize_after_load_script(div_id))
162165
html = (
163166
'<div class="output_subarea output_html rendered_html output_result">\n'
164167
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"
168168
"</div>"
169169
)
170170
return "\n.. raw:: html\n\n" + textwrap.indent(html, " ") + "\n"

‎plotly/io/_utils.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,24 @@ def resize_after_load_script(div_id):
110110
'else window.addEventListener("load", fit);'
111111
"})();</script>"
112112
)
113+
114+
115+
def embed_in_output_card(html):
116+
"""Wrap embedded-figure HTML in a card that stays legible on dark pages.
117+
118+
The figure keeps the light background baked into it, so on dark pages an
119+
edge-to-edge white slab results; a white padded card reads as intentional
120+
instead. Both dark selectors are needed: ``data-theme`` covers themes
121+
with an explicit toggle, the media query covers theme-less pages under a
122+
dark OS preference. On light pages the white-on-white card is invisible.
123+
"""
124+
card = "background:#fff;border-radius:0.25rem;padding:0.5rem"
125+
return (
126+
"<style>"
127+
f'html[data-theme="dark"] .plotly-output-card{{{card}}}'
128+
"@media (prefers-color-scheme: dark){"
129+
f'html:not([data-theme="light"]) .plotly-output-card{{{card}}}'
130+
"}"
131+
"</style>"
132+
f'<div class="plotly-output-card">{html}</div>'
133+
)

‎tests/test_io/test_sg_scraper.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ def test_scraper(gallery, image_format):
142142
assert rst.count('class="output_subarea') == 2
143143
# Fixes up figures drawn while the page was still laying out
144144
assert rst.count("Plotly.Plots.resize") == 2
145+
# Styled as a card so the light-background figure works on dark pages
146+
assert rst.count('class="plotly-output-card"') == 2
145147

146148
# The thumbnail must be a scraped figure rather than a "no image" default,
147149
# and one image per figure in order is what makes `thumbnail_number` work
@@ -264,6 +266,8 @@ def test_repr_html_fallback_size(monkeypatch):
264266
assert 'style="height:525px; width:100%;"' in html
265267
# Fixes up figures drawn while the page was still laying out
266268
assert "Plotly.Plots.resize" in html
269+
# Styled as a card so the light-background figure works on dark pages
270+
assert 'class="plotly-output-card"' in html
267271
fig = go.Figure(layout={"height": 400})
268272
assert 'style="height:400px; width:100%;"' in fig._repr_html_()
269273

0 commit comments

Comments
 (0)