Skip to content

Commit 5e84da1

Browse files
committed
Merge sphinx gallery scraper commit into v4_integration
2 parents 39daa70 + 3be34f7 commit 5e84da1

File tree

5 files changed

+179
-3
lines changed

5 files changed

+179
-3
lines changed

Diff for: packages/python/plotly/plotly/io/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from ._templates import templates, to_templated
77

8+
from ._html import to_html, write_html
9+
810
from ._renderers import renderers, show
911

1012
from . import base_renderers
11-
12-
from ._html import to_html, write_html

Diff for: packages/python/plotly/plotly/io/_base_renderers.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
import os
77

88
import six
9-
from plotly.io import to_json, to_image
9+
from plotly.io import to_json, to_image, write_image, write_html
1010
from plotly import utils, optional_imports
1111
from plotly.io._orca import ensure_server
1212
from plotly.offline.offline import _get_jconfig, get_plotlyjs
13+
from plotly.tools import return_figure_from_figure_or_data
1314

1415
ipython_display = optional_imports.get_module("IPython.display")
1516
IPython = optional_imports.get_module("IPython")
@@ -704,3 +705,19 @@ def render(self, fig_dict):
704705
validate=False,
705706
)
706707
open_html_in_browser(html, self.using, self.new, self.autoraise)
708+
709+
710+
class SphinxGalleryRenderer(ExternalRenderer):
711+
def render(self, fig_dict):
712+
stack = inspect.stack()
713+
# Name of script from which plot function was called is retrieved
714+
try:
715+
filename = stack[3].filename # let's hope this is robust...
716+
except: # python 2
717+
filename = stack[3][1]
718+
filename_root, _ = os.path.splitext(filename)
719+
filename_html = filename_root + ".html"
720+
filename_png = filename_root + ".png"
721+
figure = return_figure_from_figure_or_data(fig_dict, True)
722+
_ = write_html(fig_dict, file=filename_html)
723+
write_image(figure, filename_png)

Diff for: packages/python/plotly/plotly/io/_renderers.py

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
PdfRenderer,
2525
BrowserRenderer,
2626
IFrameRenderer,
27+
SphinxGalleryRenderer,
2728
)
2829
from plotly.io._utils import validate_coerce_fig_to_dict
2930

@@ -425,6 +426,7 @@ def show(fig, renderer=None, validate=True, **kwargs):
425426
renderers["chromium"] = BrowserRenderer(config=config, using="chromium")
426427
renderers["iframe"] = IFrameRenderer(config=config, include_plotlyjs=True)
427428
renderers["iframe_connected"] = IFrameRenderer(config=config, include_plotlyjs="cdn")
429+
renderers["sphinx_gallery"] = SphinxGalleryRenderer()
428430

429431
# Set default renderer
430432
# --------------------

Diff for: packages/python/plotly/plotly/io/_sg_scraper.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# This module defines an image scraper for sphinx-gallery
2+
# https://sphinx-gallery.github.io/
3+
# which can be used by projects using plotly in their documentation.
4+
import inspect, os
5+
6+
import plotly
7+
from glob import glob
8+
import shutil
9+
10+
plotly.io.renderers.default = "sphinx_gallery"
11+
12+
13+
def plotly_sg_scraper(block, block_vars, gallery_conf, **kwargs):
14+
"""Scrape Plotly figures for galleries of examples using
15+
sphinx-gallery.
16+
17+
Examples should use ``plotly.io.show()`` to display the figure with
18+
the custom sphinx_gallery renderer.
19+
20+
Since the sphinx_gallery renderer generates both html and static png
21+
files, we simply crawl these files and give them the appropriate path.
22+
23+
Parameters
24+
----------
25+
block : tuple
26+
A tuple containing the (label, content, line_number) of the block.
27+
block_vars : dict
28+
Dict of block variables.
29+
gallery_conf : dict
30+
Contains the configuration of Sphinx-Gallery
31+
**kwargs : dict
32+
Additional keyword arguments to pass to
33+
:meth:`~matplotlib.figure.Figure.savefig`, e.g. ``format='svg'``.
34+
The ``format`` kwarg in particular is used to set the file extension
35+
of the output file (currently only 'png' and 'svg' are supported).
36+
37+
Returns
38+
-------
39+
rst : str
40+
The ReSTructuredText that will be rendered to HTML containing
41+
the images.
42+
43+
Notes
44+
-----
45+
Add this function to the image scrapers
46+
"""
47+
examples_dirs = gallery_conf["examples_dirs"]
48+
if isinstance(examples_dirs, (list, tuple)):
49+
examples_dirs = examples_dirs[0]
50+
pngs = sorted(glob(os.path.join(examples_dirs, "*.png")))
51+
htmls = sorted(glob(os.path.join(examples_dirs, "*.html")))
52+
image_path_iterator = block_vars["image_path_iterator"]
53+
image_names = list()
54+
seen = set()
55+
for html, png in zip(htmls, pngs):
56+
if png not in seen:
57+
seen |= set(png)
58+
this_image_path_png = next(image_path_iterator)
59+
this_image_path_html = os.path.splitext(this_image_path_png)[0] + ".html"
60+
image_names.append(this_image_path_html)
61+
shutil.move(png, this_image_path_png)
62+
shutil.move(html, this_image_path_html)
63+
# Use the `figure_rst` helper function to generate rST for image files
64+
return figure_rst(image_names, gallery_conf["src_dir"])
65+
66+
67+
def figure_rst(figure_list, sources_dir):
68+
"""Generate RST for a list of PNG filenames.
69+
70+
Depending on whether we have one or more figures, we use a
71+
single rst call to 'image' or a horizontal list.
72+
73+
Parameters
74+
----------
75+
figure_list : list
76+
List of strings of the figures' absolute paths.
77+
sources_dir : str
78+
absolute path of Sphinx documentation sources
79+
80+
Returns
81+
-------
82+
images_rst : str
83+
rst code to embed the images in the document
84+
"""
85+
86+
figure_paths = [
87+
os.path.relpath(figure_path, sources_dir).replace(os.sep, "/").lstrip("/")
88+
for figure_path in figure_list
89+
]
90+
images_rst = ""
91+
figure_name = figure_paths[0]
92+
ext = os.path.splitext(figure_name)[1]
93+
figure_path = os.path.join("images", os.path.basename(figure_name))
94+
images_rst = SINGLE_HTML % figure_path
95+
return images_rst
96+
97+
98+
SINGLE_HTML = """
99+
.. raw:: html
100+
:file: %s
101+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import plotly
2+
import os
3+
import shutil
4+
import pytest
5+
6+
7+
# Fixtures
8+
# --------
9+
@pytest.fixture()
10+
def setup():
11+
# Reset orca state
12+
plotly.io.orca.config.restore_defaults(reset_server=False)
13+
14+
15+
# Run setup before every test function in this file
16+
pytestmark = pytest.mark.usefixtures("setup")
17+
18+
19+
def execute_plotly_example():
20+
"""
21+
Some typical code which would go inside a gallery example.
22+
"""
23+
import plotly.graph_objs as go
24+
25+
# Create random data with numpy
26+
import numpy as np
27+
28+
N = 200
29+
random_x = np.random.randn(N)
30+
random_y_0 = np.random.randn(N)
31+
random_y_1 = np.random.randn(N) - 1
32+
33+
# Create traces
34+
trace_0 = go.Scatter(x=random_x, y=random_y_0, mode="markers", name="Above")
35+
36+
fig = go.Figure(data=[trace_0])
37+
plotly.io.show(fig)
38+
39+
40+
def test_scraper():
41+
from plotly.io._sg_scraper import plotly_sg_scraper
42+
43+
# test that monkey-patching worked ok
44+
assert plotly.io.renderers.default == "sphinx_gallery"
45+
# Use dummy values for arguments of plotly_sg_scraper
46+
block = "" # we don't need actually code
47+
import tempfile
48+
49+
tempdir = tempfile.mkdtemp()
50+
gallery_conf = {"src_dir": tempdir, "examples_dirs": "plotly/tests/test_orca"}
51+
names = iter(["0", "1", "2"])
52+
block_vars = {"image_path_iterator": names}
53+
execute_plotly_example()
54+
res = plotly_sg_scraper(block, block_vars, gallery_conf)
55+
shutil.rmtree(tempdir)
56+
assert ".. raw:: html" in res

0 commit comments

Comments
 (0)