Skip to content

Commit 3be34f7

Browse files
emmanuellejonmmease
authored andcommitted
Scraper function for sphinx-gallery, in plotly.io (#1577)
* scraper function for sphinx-gallery, in plotly.io. scraper function + skeleton of sphinx doc with minimal gallery (two examples) * added psutil dep to tox.ini * python 2 compatibility * added orca dep to package.json for tests * removed typo * corrected bug * python 2 compatibility * added electron * try to configure orca * Moved test file so that it's tested by the orca build only. * use custom renderer for sphinx-gallery * removed sphinx files which are now in plotly-sphinx-gallery + changed name of renderer sphinx -> sphinx-gallery, and changed wording of documentation * Modified test with new renderer name * try to fix py2 compatibility * write_html instead of offline.plot * change import order
1 parent dc11111 commit 3be34f7

File tree

6 files changed

+189
-3
lines changed

6 files changed

+189
-3
lines changed

plotly/io/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
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
1113

12-
from ._html import to_html, write_html

plotly/io/_base_renderers.py

+19-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')
@@ -637,3 +638,20 @@ def render(self, fig_dict):
637638
validate=False,
638639
)
639640
open_html_in_browser(html, self.using, self.new, self.autoraise)
641+
642+
643+
class SphinxGalleryRenderer(ExternalRenderer):
644+
645+
def render(self, fig_dict):
646+
stack = inspect.stack()
647+
# Name of script from which plot function was called is retrieved
648+
try:
649+
filename = stack[3].filename # let's hope this is robust...
650+
except: #python 2
651+
filename = stack[3][1]
652+
filename_root, _ = os.path.splitext(filename)
653+
filename_html = filename_root + '.html'
654+
filename_png = filename_root + '.png'
655+
figure = return_figure_from_figure_or_data(fig_dict, True)
656+
_ = write_html(fig_dict, file=filename_html)
657+
write_image(figure, filename_png)

plotly/io/_renderers.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from plotly.io._base_renderers import (
1313
MimetypeRenderer, ExternalRenderer, PlotlyRenderer, NotebookRenderer,
1414
KaggleRenderer, ColabRenderer, JsonRenderer, PngRenderer, JpegRenderer,
15-
SvgRenderer, PdfRenderer, BrowserRenderer, IFrameRenderer)
15+
SvgRenderer, PdfRenderer, BrowserRenderer, IFrameRenderer,
16+
SphinxGalleryRenderer)
1617
from plotly.io._utils import validate_coerce_fig_to_dict
1718

1819
ipython = optional_imports.get_module('IPython')
@@ -394,6 +395,7 @@ def show(fig, renderer=None, validate=True, **kwargs):
394395
renderers['chrome'] = BrowserRenderer(config=config, using='chrome')
395396
renderers['chromium'] = BrowserRenderer(config=config, using='chromium')
396397
renderers['iframe'] = IFrameRenderer(config=config)
398+
renderers['sphinx_gallery'] = SphinxGalleryRenderer()
397399

398400
# Set default renderer
399401
# --------------------

plotly/io/_sg_scraper.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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,
51+
'*.png')))
52+
htmls = sorted(glob(os.path.join(examples_dirs,
53+
'*.html')))
54+
image_path_iterator = block_vars['image_path_iterator']
55+
image_names = list()
56+
seen = set()
57+
for html, png in zip(htmls, pngs):
58+
if png not in seen:
59+
seen |= set(png)
60+
this_image_path_png = next(image_path_iterator)
61+
this_image_path_html = (os.path.splitext(
62+
this_image_path_png)[0] + '.html')
63+
image_names.append(this_image_path_html)
64+
shutil.move(png, this_image_path_png)
65+
shutil.move(html, this_image_path_html)
66+
# Use the `figure_rst` helper function to generate rST for image files
67+
return figure_rst(image_names, gallery_conf['src_dir'])
68+
69+
70+
def figure_rst(figure_list, sources_dir):
71+
"""Generate RST for a list of PNG filenames.
72+
73+
Depending on whether we have one or more figures, we use a
74+
single rst call to 'image' or a horizontal list.
75+
76+
Parameters
77+
----------
78+
figure_list : list
79+
List of strings of the figures' absolute paths.
80+
sources_dir : str
81+
absolute path of Sphinx documentation sources
82+
83+
Returns
84+
-------
85+
images_rst : str
86+
rst code to embed the images in the document
87+
"""
88+
89+
figure_paths = [os.path.relpath(figure_path, sources_dir)
90+
.replace(os.sep, '/').lstrip('/')
91+
for figure_path in figure_list]
92+
images_rst = ""
93+
figure_name = figure_paths[0]
94+
ext = os.path.splitext(figure_name)[1]
95+
figure_path = os.path.join('images', os.path.basename(figure_name))
96+
images_rst = SINGLE_HTML % figure_path
97+
return images_rst
98+
99+
100+
SINGLE_HTML = """
101+
.. raw:: html
102+
:file: %s
103+
"""
104+
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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(
35+
x=random_x,
36+
y=random_y_0,
37+
mode='markers',
38+
name='Above',
39+
)
40+
41+
fig = go.Figure(data=[trace_0])
42+
plotly.io.show(fig)
43+
44+
45+
def test_scraper():
46+
from plotly.io._sg_scraper import plotly_sg_scraper
47+
# test that monkey-patching worked ok
48+
assert plotly.io.renderers.default == 'sphinx_gallery'
49+
# Use dummy values for arguments of plotly_sg_scraper
50+
block = '' # we don't need actually code
51+
import tempfile
52+
tempdir = tempfile.mkdtemp()
53+
gallery_conf = {'src_dir':tempdir,
54+
'examples_dirs':'plotly/tests/test_orca'}
55+
names = iter(['0', '1', '2'])
56+
block_vars = {'image_path_iterator':names}
57+
execute_plotly_example()
58+
res = plotly_sg_scraper(block, block_vars, gallery_conf)
59+
shutil.rmtree(tempdir)
60+
assert ".. raw:: html" in res

tox.ini

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ deps=
7474
optional: matplotlib==2.2.3
7575
optional: xarray==0.10.9
7676
optional: scikit-image==0.13.1
77+
optional: psutil==5.6.2
7778
plot_ly: pandas==0.23.2
7879
plot_ly: numpy==1.14.3
7980
plot_ly: ipywidgets==7.2.0

0 commit comments

Comments
 (0)