Skip to content

Commit 0c4d8dd

Browse files
authored
Repr html (#2421)
* added _repr_html_ to BaseFigure * added test for _repr_html_ * black * use mimebundle for _repr_html_ and introduce new renderer for sphinx-gallery without orca * added to_mimebundle to remove postscript * added tests * more tests * removed orca renderers from test
1 parent e026ef5 commit 0c4d8dd

File tree

6 files changed

+185
-6
lines changed

6 files changed

+185
-6
lines changed

packages/python/plotly/plotly/basedatatypes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,37 @@ def __repr__(self):
419419

420420
return repr_str
421421

422+
def _repr_html_(self):
423+
"""
424+
Customize html representation
425+
"""
426+
bundle = self._repr_mimebundle_()
427+
if "text/html" in bundle:
428+
return bundle["text/html"]
429+
else:
430+
return self.to_html(full_html=False, include_plotlyjs="cdn")
431+
432+
def _repr_mimebundle_(self, include=None, exclude=None, validate=True, **kwargs):
433+
"""
434+
Return mimebundle corresponding to default renderer.
435+
"""
436+
import plotly.io as pio
437+
438+
renderer_str = pio.renderers.default
439+
renderers = pio._renderers.renderers
440+
renderer_names = renderers._validate_coerce_renderers(renderer_str)
441+
renderers_list = [renderers[name] for name in renderer_names]
442+
from plotly.io._utils import validate_coerce_fig_to_dict
443+
from plotly.io._renderers import MimetypeRenderer
444+
445+
fig_dict = validate_coerce_fig_to_dict(self, validate)
446+
# Mimetype renderers
447+
bundle = {}
448+
for renderer in renderers_list:
449+
if isinstance(renderer, MimetypeRenderer):
450+
bundle.update(renderer.to_mimebundle(fig_dict))
451+
return bundle
452+
422453
def _ipython_display_(self):
423454
"""
424455
Handle rich display of figures in ipython contexts

packages/python/plotly/plotly/io/_base_renderers.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,57 @@ def render(self, fig_dict):
796796
self.displayHTML(html)
797797

798798

799-
class SphinxGalleryRenderer(ExternalRenderer):
799+
class SphinxGalleryHtmlRenderer(HtmlRenderer):
800+
def __init__(
801+
self,
802+
connected=True,
803+
config=None,
804+
auto_play=False,
805+
post_script=None,
806+
animation_opts=None,
807+
):
808+
super(SphinxGalleryHtmlRenderer, self).__init__(
809+
connected=connected,
810+
full_html=False,
811+
requirejs=False,
812+
global_init=False,
813+
config=config,
814+
auto_play=auto_play,
815+
post_script=post_script,
816+
animation_opts=animation_opts,
817+
)
818+
819+
def to_mimebundle(self, fig_dict):
820+
821+
from plotly.io import to_html
822+
823+
if self.requirejs:
824+
include_plotlyjs = "require"
825+
include_mathjax = False
826+
elif self.connected:
827+
include_plotlyjs = "cdn"
828+
include_mathjax = "cdn"
829+
else:
830+
include_plotlyjs = True
831+
include_mathjax = "cdn"
832+
833+
html = to_html(
834+
fig_dict,
835+
config=self.config,
836+
auto_play=self.auto_play,
837+
include_plotlyjs=include_plotlyjs,
838+
include_mathjax=include_mathjax,
839+
full_html=self.full_html,
840+
animation_opts=self.animation_opts,
841+
default_width="100%",
842+
default_height=525,
843+
validate=False,
844+
)
845+
846+
return {"text/html": html}
847+
848+
849+
class SphinxGalleryOrcaRenderer(ExternalRenderer):
800850
def render(self, fig_dict):
801851
stack = inspect.stack()
802852
# Name of script from which plot function was called is retrieved
@@ -809,4 +859,13 @@ def render(self, fig_dict):
809859
filename_png = filename_root + ".png"
810860
figure = return_figure_from_figure_or_data(fig_dict, True)
811861
_ = write_html(fig_dict, file=filename_html)
812-
write_image(figure, filename_png)
862+
try:
863+
write_image(figure, filename_png)
864+
except (ValueError, ImportError):
865+
raise ImportError(
866+
"orca and psutil are required to use the `sphinx-gallery-orca` renderer. "
867+
"See https://plotly.com/python/static-image-export/ for instructions on"
868+
"how to install orca. Alternatively, you can use the `sphinx-gallery`"
869+
"renderer (note that png thumbnails can only be generated with"
870+
"the `sphinx-gallery-orca` renderer)."
871+
)

packages/python/plotly/plotly/io/_renderers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
PdfRenderer,
2525
BrowserRenderer,
2626
IFrameRenderer,
27-
SphinxGalleryRenderer,
27+
SphinxGalleryHtmlRenderer,
28+
SphinxGalleryOrcaRenderer,
2829
CoCalcRenderer,
2930
DatabricksRenderer,
3031
)
@@ -430,7 +431,8 @@ def show(fig, renderer=None, validate=True, **kwargs):
430431
renderers["chromium"] = BrowserRenderer(config=config, using="chromium")
431432
renderers["iframe"] = IFrameRenderer(config=config, include_plotlyjs=True)
432433
renderers["iframe_connected"] = IFrameRenderer(config=config, include_plotlyjs="cdn")
433-
renderers["sphinx_gallery"] = SphinxGalleryRenderer()
434+
renderers["sphinx_gallery"] = SphinxGalleryHtmlRenderer()
435+
renderers["sphinx_gallery_png"] = SphinxGalleryOrcaRenderer()
434436

435437
# Set default renderer
436438
# --------------------

packages/python/plotly/plotly/io/_sg_scraper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from glob import glob
88
import shutil
99

10-
plotly.io.renderers.default = "sphinx_gallery"
10+
plotly.io.renderers.default = "sphinx_gallery_png"
1111

1212

1313
def plotly_sg_scraper(block, block_vars, gallery_conf, **kwargs):

packages/python/plotly/plotly/tests/test_io/test_renderers.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,90 @@ def test_reject_invalid_renderer(renderer):
287287
)
288288
def test_accept_valid_renderer(renderer):
289289
pio.renderers.default = renderer
290+
291+
292+
@pytest.mark.parametrize(
293+
"renderer",
294+
plotly_mimetype_renderers
295+
+ ["notebook", "notebook_connected", "browser", "notebook+plotly_mimetype"],
296+
)
297+
def test_repr_html(renderer):
298+
pio.renderers.default = renderer
299+
fig = go.Figure()
300+
fig.update_layout(template=None)
301+
str_html = fig._repr_html_()
302+
bundle = fig._repr_mimebundle_()
303+
# id number of figure
304+
id_html = str_html.split('document.getElementById("')[1].split('")')[0]
305+
id_pattern = "cd462b94-79ce-42a2-887f-2650a761a144"
306+
template = (
307+
'<div>\n \n <script type="text/javascript">'
308+
"window.PlotlyConfig = {MathJaxConfig: 'local'};</script>\n "
309+
'<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> \n '
310+
'<div id="cd462b94-79ce-42a2-887f-2650a761a144" class="plotly-graph-div" '
311+
'style="height:100%; width:100%;"></div>\n <script type="text/javascript">'
312+
"\n \n window.PLOTLYENV=window.PLOTLYENV || {};"
313+
'\n \n if (document.getElementById("cd462b94-79ce-42a2-887f-2650a761a144"))'
314+
" {\n Plotly.newPlot(\n 'cd462b94-79ce-42a2-887f-2650a761a144',"
315+
'\n [],\n {"template": {}},'
316+
'\n {"responsive": true}\n )\n };'
317+
"\n \n </script>\n </div>"
318+
)
319+
if "text/html" in bundle:
320+
str_bundle = bundle["text/html"]
321+
id_bundle = str_bundle.split('document.getElementById("')[1].split('")')[0]
322+
assert str_html.replace(id_html, "") == str_bundle.replace(id_bundle, "")
323+
else:
324+
assert str_html.replace(id_html, "") == template.replace(id_pattern, "")
325+
326+
327+
all_renderers_without_orca = [
328+
"plotly_mimetype",
329+
"jupyterlab",
330+
"nteract",
331+
"vscode",
332+
"notebook",
333+
"notebook_connected",
334+
"kaggle",
335+
"azure",
336+
"colab",
337+
"cocalc",
338+
"databricks",
339+
"json",
340+
"browser",
341+
"firefox",
342+
"chrome",
343+
"chromium",
344+
"iframe",
345+
"iframe_connected",
346+
"sphinx_gallery",
347+
]
348+
349+
350+
@pytest.mark.parametrize("renderer_str", all_renderers_without_orca)
351+
def test_repr_mimebundle(renderer_str):
352+
pio.renderers.default = renderer_str
353+
fig = go.Figure()
354+
fig.update_layout(template=None)
355+
bundle = fig._repr_mimebundle_()
356+
renderer = pio.renderers[renderer_str]
357+
from plotly.io._renderers import MimetypeRenderer
358+
359+
if isinstance(renderer, MimetypeRenderer):
360+
ref_bundle = renderer.to_mimebundle(fig.to_dict())
361+
for key in bundle:
362+
if "getElementById" in bundle[key]:
363+
id1 = bundle[key].split('document.getElementById("')[1].split('")')[0]
364+
id2 = (
365+
ref_bundle[key].split('document.getElementById("')[1].split('")')[0]
366+
)
367+
assert bundle[key].replace(id1, "") == ref_bundle[key].replace(id2, "")
368+
else:
369+
assert bundle == {}
370+
371+
372+
def test_repr_mimebundle_mixed_renderer(fig1):
373+
pio.renderers.default = "notebook+plotly_mimetype"
374+
assert set(fig1._repr_mimebundle_().keys()) == set(
375+
{"application/vnd.plotly.v1+json", "text/html"}
376+
)

packages/python/plotly/plotly/tests/test_orca/test_sg_scraper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_scraper():
4444
from plotly.io._sg_scraper import plotly_sg_scraper
4545

4646
# test that monkey-patching worked ok
47-
assert plotly.io.renderers.default == "sphinx_gallery"
47+
assert plotly.io.renderers.default == "sphinx_gallery_png"
4848
# Use dummy values for arguments of plotly_sg_scraper
4949
block = "" # we don't need actually code
5050
import tempfile

0 commit comments

Comments
 (0)