Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scraper function for sphinx-gallery, in plotly.io #1577

Merged
merged 17 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion plotly/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

from ._templates import templates, to_templated

from ._html import to_html, write_html

from ._renderers import renderers, show

from . import base_renderers

from ._html import to_html, write_html
20 changes: 19 additions & 1 deletion plotly/io/_base_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import os

import six
from plotly.io import to_json, to_image
from plotly.io import to_json, to_image, write_image, write_html
from plotly import utils, optional_imports
from plotly.io._orca import ensure_server
from plotly.offline.offline import _get_jconfig, get_plotlyjs
from plotly.tools import return_figure_from_figure_or_data

ipython_display = optional_imports.get_module('IPython.display')
IPython = optional_imports.get_module('IPython')
Expand Down Expand Up @@ -637,3 +638,20 @@ def render(self, fig_dict):
validate=False,
)
open_html_in_browser(html, self.using, self.new, self.autoraise)


class SphinxGalleryRenderer(ExternalRenderer):

def render(self, fig_dict):
stack = inspect.stack()
# Name of script from which plot function was called is retrieved
try:
filename = stack[3].filename # let's hope this is robust...
except: #python 2
filename = stack[3][1]
filename_root, _ = os.path.splitext(filename)
filename_html = filename_root + '.html'
filename_png = filename_root + '.png'
figure = return_figure_from_figure_or_data(fig_dict, True)
_ = write_html(fig_dict, file=filename_html)
write_image(figure, filename_png)
4 changes: 3 additions & 1 deletion plotly/io/_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from plotly.io._base_renderers import (
MimetypeRenderer, ExternalRenderer, PlotlyRenderer, NotebookRenderer,
KaggleRenderer, ColabRenderer, JsonRenderer, PngRenderer, JpegRenderer,
SvgRenderer, PdfRenderer, BrowserRenderer, IFrameRenderer)
SvgRenderer, PdfRenderer, BrowserRenderer, IFrameRenderer,
SphinxGalleryRenderer)
from plotly.io._utils import validate_coerce_fig_to_dict

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

# Set default renderer
# --------------------
Expand Down
104 changes: 104 additions & 0 deletions plotly/io/_sg_scraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# This module defines an image scraper for sphinx-gallery
# https://sphinx-gallery.github.io/
# which can be used by projects using plotly in their documentation.
import inspect, os

import plotly
from glob import glob
import shutil

plotly.io.renderers.default = 'sphinx_gallery'


def plotly_sg_scraper(block, block_vars, gallery_conf, **kwargs):
"""Scrape Plotly figures for galleries of examples using
sphinx-gallery.

Examples should use ``plotly.io.show()`` to display the figure with
the custom sphinx_gallery renderer.

Since the sphinx_gallery renderer generates both html and static png
files, we simply crawl these files and give them the appropriate path.

Parameters
----------
block : tuple
A tuple containing the (label, content, line_number) of the block.
block_vars : dict
Dict of block variables.
gallery_conf : dict
Contains the configuration of Sphinx-Gallery
**kwargs : dict
Additional keyword arguments to pass to
:meth:`~matplotlib.figure.Figure.savefig`, e.g. ``format='svg'``.
The ``format`` kwarg in particular is used to set the file extension
of the output file (currently only 'png' and 'svg' are supported).

Returns
-------
rst : str
The ReSTructuredText that will be rendered to HTML containing
the images.

Notes
-----
Add this function to the image scrapers
"""
examples_dirs = gallery_conf['examples_dirs']
if isinstance(examples_dirs, (list, tuple)):
examples_dirs = examples_dirs[0]
pngs = sorted(glob(os.path.join(examples_dirs,
'*.png')))
htmls = sorted(glob(os.path.join(examples_dirs,
'*.html')))
image_path_iterator = block_vars['image_path_iterator']
image_names = list()
seen = set()
for html, png in zip(htmls, pngs):
if png not in seen:
seen |= set(png)
this_image_path_png = next(image_path_iterator)
this_image_path_html = (os.path.splitext(
this_image_path_png)[0] + '.html')
image_names.append(this_image_path_html)
shutil.move(png, this_image_path_png)
shutil.move(html, this_image_path_html)
# Use the `figure_rst` helper function to generate rST for image files
return figure_rst(image_names, gallery_conf['src_dir'])


def figure_rst(figure_list, sources_dir):
"""Generate RST for a list of PNG filenames.

Depending on whether we have one or more figures, we use a
single rst call to 'image' or a horizontal list.

Parameters
----------
figure_list : list
List of strings of the figures' absolute paths.
sources_dir : str
absolute path of Sphinx documentation sources

Returns
-------
images_rst : str
rst code to embed the images in the document
"""

figure_paths = [os.path.relpath(figure_path, sources_dir)
.replace(os.sep, '/').lstrip('/')
for figure_path in figure_list]
images_rst = ""
figure_name = figure_paths[0]
ext = os.path.splitext(figure_name)[1]
figure_path = os.path.join('images', os.path.basename(figure_name))
images_rst = SINGLE_HTML % figure_path
return images_rst


SINGLE_HTML = """
.. raw:: html
:file: %s
"""

60 changes: 60 additions & 0 deletions plotly/tests/test_orca/test_sg_scraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import plotly
import os
import shutil
import pytest


# Fixtures
# --------
@pytest.fixture()
def setup():
# Reset orca state
plotly.io.orca.config.restore_defaults(reset_server=False)


# Run setup before every test function in this file
pytestmark = pytest.mark.usefixtures("setup")


def execute_plotly_example():
"""
Some typical code which would go inside a gallery example.
"""
import plotly.graph_objs as go

# Create random data with numpy
import numpy as np

N = 200
random_x = np.random.randn(N)
random_y_0 = np.random.randn(N)
random_y_1 = np.random.randn(N) - 1

# Create traces
trace_0 = go.Scatter(
x=random_x,
y=random_y_0,
mode='markers',
name='Above',
)

fig = go.Figure(data=[trace_0])
plotly.io.show(fig)


def test_scraper():
from plotly.io._sg_scraper import plotly_sg_scraper
# test that monkey-patching worked ok
assert plotly.io.renderers.default == 'sphinx_gallery'
# Use dummy values for arguments of plotly_sg_scraper
block = '' # we don't need actually code
import tempfile
tempdir = tempfile.mkdtemp()
gallery_conf = {'src_dir':tempdir,
'examples_dirs':'plotly/tests/test_orca'}
names = iter(['0', '1', '2'])
block_vars = {'image_path_iterator':names}
execute_plotly_example()
res = plotly_sg_scraper(block, block_vars, gallery_conf)
shutil.rmtree(tempdir)
assert ".. raw:: html" in res
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ deps=
optional: matplotlib==2.2.3
optional: xarray==0.10.9
optional: scikit-image==0.13.1
optional: psutil==5.6.2
plot_ly: pandas==0.23.2
plot_ly: numpy==1.14.3
plot_ly: ipywidgets==7.2.0
Expand Down