first commit

This commit is contained in:
Ayxan
2022-05-23 00:16:32 +04:00
commit d660f2a4ca
24786 changed files with 4428337 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
from _plotly_utils.importers import relative_import
import sys
from typing import TYPE_CHECKING
if sys.version_info < (3, 7) or TYPE_CHECKING:
from ._kaleido import to_image, write_image, full_figure_for_development
from . import orca, kaleido
from . import json
from ._json import to_json, from_json, read_json, write_json
from ._templates import templates, to_templated
from ._html import to_html, write_html
from ._renderers import renderers, show
from . import base_renderers
__all__ = [
"to_image",
"write_image",
"orca",
"json",
"to_json",
"from_json",
"read_json",
"write_json",
"templates",
"to_templated",
"to_html",
"write_html",
"renderers",
"show",
"base_renderers",
"full_figure_for_development",
]
else:
__all__, __getattr__, __dir__ = relative_import(
__name__,
[".orca", ".kaleido", ".json", ".base_renderers"],
[
"._kaleido.to_image",
"._kaleido.write_image",
"._kaleido.full_figure_for_development",
"._json.to_json",
"._json.from_json",
"._json.read_json",
"._json.write_json",
"._templates.templates",
"._templates.to_templated",
"._html.to_html",
"._html.write_html",
"._renderers.renderers",
"._renderers.show",
],
)
# Set default template (for < 3.7 this is done in ploty/__init__.py)
from plotly.io import templates
templates._default = "plotly"

View File

@@ -0,0 +1,894 @@
from __future__ import absolute_import
import base64
import json
import webbrowser
import inspect
import os
from os.path import isdir
from plotly import utils, optional_imports
from plotly.io import to_json, to_image, write_image, write_html
from plotly.io._orca import ensure_server
from plotly.io._utils import plotly_cdn_url
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")
try:
from http.server import BaseHTTPRequestHandler, HTTPServer
except ImportError:
# Python 2.7
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class BaseRenderer(object):
"""
Base class for all renderers
"""
def activate(self):
pass
def __repr__(self):
try:
init_sig = inspect.signature(self.__init__)
init_args = list(init_sig.parameters.keys())
except AttributeError:
# Python 2.7
argspec = inspect.getargspec(self.__init__)
init_args = [a for a in argspec.args if a != "self"]
return "{cls}({attrs})\n{doc}".format(
cls=self.__class__.__name__,
attrs=", ".join("{}={!r}".format(k, self.__dict__[k]) for k in init_args),
doc=self.__doc__,
)
def __hash__(self):
# Constructor args fully define uniqueness
return hash(repr(self))
class MimetypeRenderer(BaseRenderer):
"""
Base class for all mime type renderers
"""
def to_mimebundle(self, fig_dict):
raise NotImplementedError()
class JsonRenderer(MimetypeRenderer):
"""
Renderer to display figures as JSON hierarchies. This renderer is
compatible with JupyterLab and VSCode.
mime type: 'application/json'
"""
def to_mimebundle(self, fig_dict):
value = json.loads(to_json(fig_dict, validate=False, remove_uids=False))
return {"application/json": value}
# Plotly mimetype
class PlotlyRenderer(MimetypeRenderer):
"""
Renderer to display figures using the plotly mime type. This renderer is
compatible with JupyterLab (using the @jupyterlab/plotly-extension),
VSCode, and nteract.
mime type: 'application/vnd.plotly.v1+json'
"""
def __init__(self, config=None):
self.config = dict(config) if config else {}
def to_mimebundle(self, fig_dict):
config = _get_jconfig(self.config)
if config:
fig_dict["config"] = config
json_compatible_fig_dict = json.loads(
to_json(fig_dict, validate=False, remove_uids=False)
)
return {"application/vnd.plotly.v1+json": json_compatible_fig_dict}
# Static Image
class ImageRenderer(MimetypeRenderer):
"""
Base class for all static image renderers
"""
def __init__(
self,
mime_type,
b64_encode=False,
format=None,
width=None,
height=None,
scale=None,
engine="auto",
):
self.mime_type = mime_type
self.b64_encode = b64_encode
self.format = format
self.width = width
self.height = height
self.scale = scale
self.engine = engine
def to_mimebundle(self, fig_dict):
image_bytes = to_image(
fig_dict,
format=self.format,
width=self.width,
height=self.height,
scale=self.scale,
validate=False,
engine=self.engine,
)
if self.b64_encode:
image_str = base64.b64encode(image_bytes).decode("utf8")
else:
image_str = image_bytes.decode("utf8")
return {self.mime_type: image_str}
class PngRenderer(ImageRenderer):
"""
Renderer to display figures as static PNG images. This renderer requires
either the kaleido package or the orca command-line utility and is broadly
compatible across IPython environments (classic Jupyter Notebook, JupyterLab,
QtConsole, VSCode, PyCharm, etc) and nbconvert targets (HTML, PDF, etc.).
mime type: 'image/png'
"""
def __init__(self, width=None, height=None, scale=None, engine="auto"):
super(PngRenderer, self).__init__(
mime_type="image/png",
b64_encode=True,
format="png",
width=width,
height=height,
scale=scale,
engine=engine,
)
class SvgRenderer(ImageRenderer):
"""
Renderer to display figures as static SVG images. This renderer requires
either the kaleido package or the orca command-line utility and is broadly
compatible across IPython environments (classic Jupyter Notebook, JupyterLab,
QtConsole, VSCode, PyCharm, etc) and nbconvert targets (HTML, PDF, etc.).
mime type: 'image/svg+xml'
"""
def __init__(self, width=None, height=None, scale=None, engine="auto"):
super(SvgRenderer, self).__init__(
mime_type="image/svg+xml",
b64_encode=False,
format="svg",
width=width,
height=height,
scale=scale,
engine=engine,
)
class JpegRenderer(ImageRenderer):
"""
Renderer to display figures as static JPEG images. This renderer requires
either the kaleido package or the orca command-line utility and is broadly
compatible across IPython environments (classic Jupyter Notebook, JupyterLab,
QtConsole, VSCode, PyCharm, etc) and nbconvert targets (HTML, PDF, etc.).
mime type: 'image/jpeg'
"""
def __init__(self, width=None, height=None, scale=None, engine="auto"):
super(JpegRenderer, self).__init__(
mime_type="image/jpeg",
b64_encode=True,
format="jpg",
width=width,
height=height,
scale=scale,
engine=engine,
)
class PdfRenderer(ImageRenderer):
"""
Renderer to display figures as static PDF images. This renderer requires
either the kaleido package or the orca command-line utility and is compatible
with JupyterLab and the LaTeX-based nbconvert export to PDF.
mime type: 'application/pdf'
"""
def __init__(self, width=None, height=None, scale=None, engine="auto"):
super(PdfRenderer, self).__init__(
mime_type="application/pdf",
b64_encode=True,
format="pdf",
width=width,
height=height,
scale=scale,
engine=engine,
)
# HTML
# Build script to set global PlotlyConfig object. This must execute before
# plotly.js is loaded.
_window_plotly_config = """\
window.PlotlyConfig = {MathJaxConfig: 'local'};"""
_mathjax_config = """\
if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}"""
class HtmlRenderer(MimetypeRenderer):
"""
Base class for all HTML mime type renderers
mime type: 'text/html'
"""
def __init__(
self,
connected=False,
full_html=False,
requirejs=True,
global_init=False,
config=None,
auto_play=False,
post_script=None,
animation_opts=None,
):
self.config = dict(config) if config else {}
self.auto_play = auto_play
self.connected = connected
self.global_init = global_init
self.requirejs = requirejs
self.full_html = full_html
self.animation_opts = animation_opts
self.post_script = post_script
def activate(self):
if self.global_init:
if not ipython_display:
raise ValueError(
"The {cls} class requires ipython but it is not installed".format(
cls=self.__class__.__name__
)
)
if not self.requirejs:
raise ValueError("global_init is only supported with requirejs=True")
if self.connected:
# Connected so we configure requirejs with the plotly CDN
script = """\
<script type="text/javascript">
{win_config}
{mathjax_config}
if (typeof require !== 'undefined') {{
require.undef("plotly");
requirejs.config({{
paths: {{
'plotly': ['{plotly_cdn}']
}}
}});
require(['plotly'], function(Plotly) {{
window._Plotly = Plotly;
}});
}}
</script>
""".format(
win_config=_window_plotly_config,
mathjax_config=_mathjax_config,
plotly_cdn=plotly_cdn_url().rstrip(".js"),
)
else:
# If not connected then we embed a copy of the plotly.js
# library in the notebook
script = """\
<script type="text/javascript">
{win_config}
{mathjax_config}
if (typeof require !== 'undefined') {{
require.undef("plotly");
define('plotly', function(require, exports, module) {{
{script}
}});
require(['plotly'], function(Plotly) {{
window._Plotly = Plotly;
}});
}}
</script>
""".format(
script=get_plotlyjs(),
win_config=_window_plotly_config,
mathjax_config=_mathjax_config,
)
ipython_display.display_html(script, raw=True)
def to_mimebundle(self, fig_dict):
from plotly.io import to_html
if self.requirejs:
include_plotlyjs = "require"
include_mathjax = False
elif self.connected:
include_plotlyjs = "cdn"
include_mathjax = "cdn"
else:
include_plotlyjs = True
include_mathjax = "cdn"
# build post script
post_script = [
"""
var gd = document.getElementById('{plot_id}');
var x = new MutationObserver(function (mutations, observer) {{
var display = window.getComputedStyle(gd).display;
if (!display || display === 'none') {{
console.log([gd, 'removed!']);
Plotly.purge(gd);
observer.disconnect();
}}
}});
// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
x.observe(notebookContainer, {childList: true});
}}
// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
x.observe(outputEl, {childList: true});
}}
"""
]
# Add user defined post script
if self.post_script:
if not isinstance(self.post_script, (list, tuple)):
post_script.append(self.post_script)
else:
post_script.extend(self.post_script)
html = to_html(
fig_dict,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=include_plotlyjs,
include_mathjax=include_mathjax,
post_script=post_script,
full_html=self.full_html,
animation_opts=self.animation_opts,
default_width="100%",
default_height=525,
validate=False,
)
return {"text/html": html}
class NotebookRenderer(HtmlRenderer):
"""
Renderer to display interactive figures in the classic Jupyter Notebook.
This renderer is also useful for notebooks that will be converted to
HTML using nbconvert/nbviewer as it will produce standalone HTML files
that include interactive figures.
This renderer automatically performs global notebook initialization when
activated.
mime type: 'text/html'
"""
def __init__(
self,
connected=False,
config=None,
auto_play=False,
post_script=None,
animation_opts=None,
):
super(NotebookRenderer, self).__init__(
connected=connected,
full_html=False,
requirejs=True,
global_init=True,
config=config,
auto_play=auto_play,
post_script=post_script,
animation_opts=animation_opts,
)
class KaggleRenderer(HtmlRenderer):
"""
Renderer to display interactive figures in Kaggle Notebooks.
Same as NotebookRenderer but with connected=True so that the plotly.js
bundle is loaded from a CDN rather than being embedded in the notebook.
This renderer is enabled by default when running in a Kaggle notebook.
mime type: 'text/html'
"""
def __init__(
self, config=None, auto_play=False, post_script=None, animation_opts=None
):
super(KaggleRenderer, self).__init__(
connected=True,
full_html=False,
requirejs=True,
global_init=True,
config=config,
auto_play=auto_play,
post_script=post_script,
animation_opts=animation_opts,
)
class AzureRenderer(HtmlRenderer):
"""
Renderer to display interactive figures in Azure Notebooks.
Same as NotebookRenderer but with connected=True so that the plotly.js
bundle is loaded from a CDN rather than being embedded in the notebook.
This renderer is enabled by default when running in an Azure notebook.
mime type: 'text/html'
"""
def __init__(
self, config=None, auto_play=False, post_script=None, animation_opts=None
):
super(AzureRenderer, self).__init__(
connected=True,
full_html=False,
requirejs=True,
global_init=True,
config=config,
auto_play=auto_play,
post_script=post_script,
animation_opts=animation_opts,
)
class ColabRenderer(HtmlRenderer):
"""
Renderer to display interactive figures in Google Colab Notebooks.
This renderer is enabled by default when running in a Colab notebook.
mime type: 'text/html'
"""
def __init__(
self, config=None, auto_play=False, post_script=None, animation_opts=None
):
super(ColabRenderer, self).__init__(
connected=True,
full_html=True,
requirejs=False,
global_init=False,
config=config,
auto_play=auto_play,
post_script=post_script,
animation_opts=animation_opts,
)
class IFrameRenderer(MimetypeRenderer):
"""
Renderer to display interactive figures using an IFrame. HTML
representations of Figures are saved to an `iframe_figures/` directory and
iframe HTML elements that reference these files are inserted into the
notebook.
With this approach, neither plotly.js nor the figure data are embedded in
the notebook, so this is a good choice for notebooks that contain so many
large figures that basic operations (like saving and opening) become
very slow.
Notebooks using this renderer will display properly when exported to HTML
as long as the `iframe_figures/` directory is placed in the same directory
as the exported html file.
Note that the HTML files in `iframe_figures/` are numbered according to
the IPython cell execution count and so they will start being overwritten
each time the kernel is restarted. This directory may be deleted whenever
the kernel is restarted and it will be automatically recreated.
mime type: 'text/html'
"""
def __init__(
self,
config=None,
auto_play=False,
post_script=None,
animation_opts=None,
include_plotlyjs=True,
html_directory="iframe_figures",
):
self.config = config
self.auto_play = auto_play
self.post_script = post_script
self.animation_opts = animation_opts
self.include_plotlyjs = include_plotlyjs
self.html_directory = html_directory
def to_mimebundle(self, fig_dict):
from plotly.io import write_html
# Make iframe size slightly larger than figure size to avoid
# having iframe have its own scroll bar.
iframe_buffer = 20
layout = fig_dict.get("layout", {})
if layout.get("width", False):
iframe_width = str(layout["width"] + iframe_buffer) + "px"
else:
iframe_width = "100%"
if layout.get("height", False):
iframe_height = layout["height"] + iframe_buffer
else:
iframe_height = str(525 + iframe_buffer) + "px"
# Build filename using ipython cell number
filename = self.build_filename()
# Make directory for
try:
os.makedirs(self.html_directory)
except OSError as error:
if not isdir(self.html_directory):
raise
write_html(
fig_dict,
filename,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=self.include_plotlyjs,
include_mathjax="cdn",
auto_open=False,
post_script=self.post_script,
animation_opts=self.animation_opts,
default_width="100%",
default_height=525,
validate=False,
)
# Build IFrame
iframe_html = """\
<iframe
scrolling="no"
width="{width}"
height="{height}"
src="{src}"
frameborder="0"
allowfullscreen
></iframe>
""".format(
width=iframe_width, height=iframe_height, src=self.build_url(filename)
)
return {"text/html": iframe_html}
def build_filename(self):
ip = IPython.get_ipython() if IPython else None
cell_number = list(ip.history_manager.get_tail(1))[0][1] + 1 if ip else 0
filename = "{dirname}/figure_{cell_number}.html".format(
dirname=self.html_directory, cell_number=cell_number
)
return filename
def build_url(self, filename):
return filename
class CoCalcRenderer(IFrameRenderer):
_render_count = 0
def build_filename(self):
filename = "{dirname}/figure_{render_count}.html".format(
dirname=self.html_directory, render_count=CoCalcRenderer._render_count
)
CoCalcRenderer._render_count += 1
return filename
def build_url(self, filename):
return "{filename}?fullscreen=kiosk".format(filename=filename)
class ExternalRenderer(BaseRenderer):
"""
Base class for external renderers. ExternalRenderer subclasses
do not display figures inline in a notebook environment, but render
figures by some external means (e.g. a separate browser tab).
Unlike MimetypeRenderer subclasses, ExternalRenderer subclasses are not
invoked when a figure is asked to display itself in the notebook.
Instead, they are invoked when the plotly.io.show function is called
on a figure.
"""
def render(self, fig):
raise NotImplementedError()
def open_html_in_browser(html, using=None, new=0, autoraise=True):
"""
Display html in a web browser without creating a temp file.
Instantiates a trivial http server and uses the webbrowser module to
open a URL to retrieve html from that server.
Parameters
----------
html: str
HTML string to display
using, new, autoraise:
See docstrings in webbrowser.get and webbrowser.open
"""
if isinstance(html, str):
html = html.encode("utf8")
browser = None
if using is None:
browser = webbrowser.get(None)
else:
if not isinstance(using, tuple):
using = (using,)
for browser_key in using:
try:
browser = webbrowser.get(browser_key)
if browser is not None:
break
except webbrowser.Error:
pass
if browser is None:
raise ValueError("Can't locate a browser with key in " + str(using))
class OneShotRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
bufferSize = 1024 * 1024
for i in range(0, len(html), bufferSize):
self.wfile.write(html[i : i + bufferSize])
def log_message(self, format, *args):
# Silence stderr logging
pass
server = HTTPServer(("127.0.0.1", 0), OneShotRequestHandler)
browser.open(
"http://127.0.0.1:%s" % server.server_port, new=new, autoraise=autoraise
)
server.handle_request()
class BrowserRenderer(ExternalRenderer):
"""
Renderer to display interactive figures in an external web browser.
This renderer will open a new browser window or tab when the
plotly.io.show function is called on a figure.
This renderer has no ipython/jupyter dependencies and is a good choice
for use in environments that do not support the inline display of
interactive figures.
mime type: 'text/html'
"""
def __init__(
self,
config=None,
auto_play=False,
using=None,
new=0,
autoraise=True,
post_script=None,
animation_opts=None,
):
self.config = config
self.auto_play = auto_play
self.using = using
self.new = new
self.autoraise = autoraise
self.post_script = post_script
self.animation_opts = animation_opts
def render(self, fig_dict):
from plotly.io import to_html
html = to_html(
fig_dict,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=True,
include_mathjax="cdn",
post_script=self.post_script,
full_html=True,
animation_opts=self.animation_opts,
default_width="100%",
default_height="100%",
validate=False,
)
open_html_in_browser(html, self.using, self.new, self.autoraise)
class DatabricksRenderer(ExternalRenderer):
def __init__(
self,
config=None,
auto_play=False,
post_script=None,
animation_opts=None,
include_plotlyjs="cdn",
):
self.config = config
self.auto_play = auto_play
self.post_script = post_script
self.animation_opts = animation_opts
self.include_plotlyjs = include_plotlyjs
self._displayHTML = None
@property
def displayHTML(self):
import inspect
if self._displayHTML is None:
for frame in inspect.getouterframes(inspect.currentframe()):
global_names = set(frame.frame.f_globals)
# Check for displayHTML plus a few others to reduce chance of a false
# hit.
if all(v in global_names for v in ["displayHTML", "display", "spark"]):
self._displayHTML = frame.frame.f_globals["displayHTML"]
break
if self._displayHTML is None:
raise EnvironmentError(
"""
Unable to detect the Databricks displayHTML function. The 'databricks' renderer is only
supported when called from within the Databricks notebook environment."""
)
return self._displayHTML
def render(self, fig_dict):
from plotly.io import to_html
html = to_html(
fig_dict,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=self.include_plotlyjs,
include_mathjax="cdn",
post_script=self.post_script,
full_html=True,
animation_opts=self.animation_opts,
default_width="100%",
default_height="100%",
validate=False,
)
# displayHTML is a Databricks notebook built-in function
self.displayHTML(html)
class SphinxGalleryHtmlRenderer(HtmlRenderer):
def __init__(
self,
connected=True,
config=None,
auto_play=False,
post_script=None,
animation_opts=None,
):
super(SphinxGalleryHtmlRenderer, self).__init__(
connected=connected,
full_html=False,
requirejs=False,
global_init=False,
config=config,
auto_play=auto_play,
post_script=post_script,
animation_opts=animation_opts,
)
def to_mimebundle(self, fig_dict):
from plotly.io import to_html
if self.requirejs:
include_plotlyjs = "require"
include_mathjax = False
elif self.connected:
include_plotlyjs = "cdn"
include_mathjax = "cdn"
else:
include_plotlyjs = True
include_mathjax = "cdn"
html = to_html(
fig_dict,
config=self.config,
auto_play=self.auto_play,
include_plotlyjs=include_plotlyjs,
include_mathjax=include_mathjax,
full_html=self.full_html,
animation_opts=self.animation_opts,
default_width="100%",
default_height=525,
validate=False,
)
return {"text/html": html}
class SphinxGalleryOrcaRenderer(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, include_plotlyjs="cdn")
try:
write_image(figure, filename_png)
except (ValueError, ImportError):
raise ImportError(
"orca and psutil are required to use the `sphinx-gallery-orca` renderer. "
"See https://plotly.com/python/static-image-export/ for instructions on "
"how to install orca. Alternatively, you can use the `sphinx-gallery` "
"renderer (note that png thumbnails can only be generated with "
"the `sphinx-gallery-orca` renderer)."
)

View File

@@ -0,0 +1,550 @@
import uuid
import os
from pathlib import Path
import webbrowser
from _plotly_utils.optional_imports import get_module
from plotly.io._utils import validate_coerce_fig_to_dict, plotly_cdn_url
from plotly.offline.offline import _get_jconfig, get_plotlyjs
from plotly import utils
_json = get_module("json")
# Build script to set global PlotlyConfig object. This must execute before
# plotly.js is loaded.
_window_plotly_config = """\
<script type="text/javascript">\
window.PlotlyConfig = {MathJaxConfig: 'local'};\
</script>"""
_mathjax_config = """\
<script type="text/javascript">\
if (window.MathJax && window.MathJax.Hub && && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}\
</script>"""
def to_html(
fig,
config=None,
auto_play=True,
include_plotlyjs=True,
include_mathjax=False,
post_script=None,
full_html=True,
animation_opts=None,
default_width="100%",
default_height="100%",
validate=True,
div_id=None,
):
"""
Convert a figure to an HTML string representation.
Parameters
----------
fig:
Figure object or dict representing a figure
config: dict or None (default None)
Plotly.js figure config options
auto_play: bool (default=True)
Whether to automatically start the animation sequence on page load
if the figure contains frames. Has no effect if the figure does not
contain frames.
include_plotlyjs: bool or string (default True)
Specifies how the plotly.js library is included/loaded in the output
div string.
If True, a script tag containing the plotly.js source code (~3MB)
is included in the output. HTML files generated with this option are
fully self-contained and can be used offline.
If 'cdn', a script tag that references the plotly.js CDN is included
in the output. The url used is versioned to match the bundled plotly.js.
HTML files generated with this option are about 3MB smaller than those
generated with include_plotlyjs=True, but they require an active
internet connection in order to load the plotly.js library.
If 'directory', a script tag is included that references an external
plotly.min.js bundle that is assumed to reside in the same
directory as the HTML file.
If 'require', Plotly.js is loaded using require.js. This option
assumes that require.js is globally available and that it has been
globally configured to know how to find Plotly.js as 'plotly'.
This option is not advised when full_html=True as it will result
in a non-functional html file.
If a string that ends in '.js', a script tag is included that
references the specified path. This approach can be used to point
the resulting HTML file to an alternative CDN or local bundle.
If False, no script tag referencing plotly.js is included. This is
useful when the resulting div string will be placed inside an HTML
document that already loads plotly.js. This option is not advised
when full_html=True as it will result in a non-functional html file.
include_mathjax: bool or string (default False)
Specifies how the MathJax.js library is included in the output html
div string. MathJax is required in order to display labels
with LaTeX typesetting.
If False, no script tag referencing MathJax.js will be included in the
output.
If 'cdn', a script tag that references a MathJax CDN location will be
included in the output. HTML div strings generated with this option
will be able to display LaTeX typesetting as long as internet access
is available.
If a string that ends in '.js', a script tag is included that
references the specified path. This approach can be used to point the
resulting HTML div string to an alternative CDN.
post_script: str or list or None (default None)
JavaScript snippet(s) to be included in the resulting div just after
plot creation. The string(s) may include '{plot_id}' placeholders
that will then be replaced by the `id` of the div element that the
plotly.js figure is associated with. One application for this script
is to install custom plotly.js event handlers.
full_html: bool (default True)
If True, produce a string containing a complete HTML document
starting with an <html> tag. If False, produce a string containing
a single <div> element.
animation_opts: dict or None (default None)
dict of custom animation parameters to be passed to the function
Plotly.animate in Plotly.js. See
https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js
for available options. Has no effect if the figure does not contain
frames, or auto_play is False.
default_width, default_height: number or str (default '100%')
The default figure width/height to use if the provided figure does not
specify its own layout.width/layout.height property. May be
specified in pixels as an integer (e.g. 500), or as a css width style
string (e.g. '500px', '100%').
validate: bool (default True)
True if the figure should be validated before being converted to
JSON, False otherwise.
div_id: str (default None)
If provided, this is the value of the id attribute of the div tag. If None, the
id attribute is a UUID.
Returns
-------
str
Representation of figure as an HTML div string
"""
from plotly.io.json import to_json_plotly
# ## Validate figure ##
fig_dict = validate_coerce_fig_to_dict(fig, validate)
# ## Generate div id ##
plotdivid = div_id or str(uuid.uuid4())
# ## Serialize figure ##
jdata = to_json_plotly(fig_dict.get("data", []))
jlayout = to_json_plotly(fig_dict.get("layout", {}))
if fig_dict.get("frames", None):
jframes = to_json_plotly(fig_dict.get("frames", []))
else:
jframes = None
# ## Serialize figure config ##
config = _get_jconfig(config)
# Set responsive
config.setdefault("responsive", True)
# Get div width/height
layout_dict = fig_dict.get("layout", {})
template_dict = fig_dict.get("layout", {}).get("template", {}).get("layout", {})
div_width = layout_dict.get("width", template_dict.get("width", default_width))
div_height = layout_dict.get("height", template_dict.get("height", default_height))
# Add 'px' suffix to numeric widths
try:
float(div_width)
except (ValueError, TypeError):
pass
else:
div_width = str(div_width) + "px"
try:
float(div_height)
except (ValueError, TypeError):
pass
else:
div_height = str(div_height) + "px"
# ## Get platform URL ##
if config.get("showLink", False) or config.get("showSendToCloud", False):
# Figure is going to include a Chart Studio link or send-to-cloud button,
# So we need to configure the PLOTLYENV.BASE_URL property
base_url_line = """
window.PLOTLYENV.BASE_URL='{plotly_platform_url}';\
""".format(
plotly_platform_url=config.get("plotlyServerURL", "https://plot.ly")
)
else:
# Figure is not going to include a Chart Studio link or send-to-cloud button,
# In this case we don't want https://plot.ly to show up anywhere in the HTML
# output
config.pop("plotlyServerURL", None)
config.pop("linkText", None)
config.pop("showLink", None)
base_url_line = ""
# ## Build script body ##
# This is the part that actually calls Plotly.js
# build post script snippet(s)
then_post_script = ""
if post_script:
if not isinstance(post_script, (list, tuple)):
post_script = [post_script]
for ps in post_script:
then_post_script += """.then(function(){{
{post_script}
}})""".format(
post_script=ps.replace("{plot_id}", plotdivid)
)
then_addframes = ""
then_animate = ""
if jframes:
then_addframes = """.then(function(){{
Plotly.addFrames('{id}', {frames});
}})""".format(
id=plotdivid, frames=jframes
)
if auto_play:
if animation_opts:
animation_opts_arg = ", " + _json.dumps(animation_opts)
else:
animation_opts_arg = ""
then_animate = """.then(function(){{
Plotly.animate('{id}', null{animation_opts});
}})""".format(
id=plotdivid, animation_opts=animation_opts_arg
)
# Serialize config dict to JSON
jconfig = _json.dumps(config)
script = """\
if (document.getElementById("{id}")) {{\
Plotly.newPlot(\
"{id}",\
{data},\
{layout},\
{config}\
){then_addframes}{then_animate}{then_post_script}\
}}""".format(
id=plotdivid,
data=jdata,
layout=jlayout,
config=jconfig,
then_addframes=then_addframes,
then_animate=then_animate,
then_post_script=then_post_script,
)
# ## Handle loading/initializing plotly.js ##
include_plotlyjs_orig = include_plotlyjs
if isinstance(include_plotlyjs, str):
include_plotlyjs = include_plotlyjs.lower()
# Start/end of requirejs block (if any)
require_start = ""
require_end = ""
# Init and load
load_plotlyjs = ""
# Init plotlyjs. This block needs to run before plotly.js is loaded in
# order for MathJax configuration to work properly
if include_plotlyjs == "require":
require_start = 'require(["plotly"], function(Plotly) {'
require_end = "});"
elif include_plotlyjs == "cdn":
load_plotlyjs = """\
{win_config}
<script src="{cdn_url}"></script>\
""".format(
win_config=_window_plotly_config, cdn_url=plotly_cdn_url()
)
elif include_plotlyjs == "directory":
load_plotlyjs = """\
{win_config}
<script src="plotly.min.js"></script>\
""".format(
win_config=_window_plotly_config
)
elif isinstance(include_plotlyjs, str) and include_plotlyjs.endswith(".js"):
load_plotlyjs = """\
{win_config}
<script src="{url}"></script>\
""".format(
win_config=_window_plotly_config, url=include_plotlyjs_orig
)
elif include_plotlyjs:
load_plotlyjs = """\
{win_config}
<script type="text/javascript">{plotlyjs}</script>\
""".format(
win_config=_window_plotly_config, plotlyjs=get_plotlyjs()
)
# ## Handle loading/initializing MathJax ##
include_mathjax_orig = include_mathjax
if isinstance(include_mathjax, str):
include_mathjax = include_mathjax.lower()
mathjax_template = """\
<script src="{url}?config=TeX-AMS-MML_SVG"></script>"""
if include_mathjax == "cdn":
mathjax_script = (
mathjax_template.format(
url=(
"https://cdnjs.cloudflare.com" "/ajax/libs/mathjax/2.7.5/MathJax.js"
)
)
+ _mathjax_config
)
elif isinstance(include_mathjax, str) and include_mathjax.endswith(".js"):
mathjax_script = (
mathjax_template.format(url=include_mathjax_orig) + _mathjax_config
)
elif not include_mathjax:
mathjax_script = ""
else:
raise ValueError(
"""\
Invalid value of type {typ} received as the include_mathjax argument
Received value: {val}
include_mathjax may be specified as False, 'cdn', or a string ending with '.js'
""".format(
typ=type(include_mathjax), val=repr(include_mathjax)
)
)
plotly_html_div = """\
<div>\
{mathjax_script}\
{load_plotlyjs}\
<div id="{id}" class="plotly-graph-div" \
style="height:{height}; width:{width};"></div>\
<script type="text/javascript">\
{require_start}\
window.PLOTLYENV=window.PLOTLYENV || {{}};{base_url_line}\
{script};\
{require_end}\
</script>\
</div>""".format(
mathjax_script=mathjax_script,
load_plotlyjs=load_plotlyjs,
id=plotdivid,
width=div_width,
height=div_height,
base_url_line=base_url_line,
require_start=require_start,
script=script,
require_end=require_end,
).strip()
if full_html:
return """\
<html>
<head><meta charset="utf-8" /></head>
<body>
{div}
</body>
</html>""".format(
div=plotly_html_div
)
else:
return plotly_html_div
def write_html(
fig,
file,
config=None,
auto_play=True,
include_plotlyjs=True,
include_mathjax=False,
post_script=None,
full_html=True,
animation_opts=None,
validate=True,
default_width="100%",
default_height="100%",
auto_open=False,
div_id=None,
):
"""
Write a figure to an HTML file representation
Parameters
----------
fig:
Figure object or dict representing a figure
file: str or writeable
A string representing a local file path or a writeable object
(e.g. a pathlib.Path object or an open file descriptor)
config: dict or None (default None)
Plotly.js figure config options
auto_play: bool (default=True)
Whether to automatically start the animation sequence on page load
if the figure contains frames. Has no effect if the figure does not
contain frames.
include_plotlyjs: bool or string (default True)
Specifies how the plotly.js library is included/loaded in the output
div string.
If True, a script tag containing the plotly.js source code (~3MB)
is included in the output. HTML files generated with this option are
fully self-contained and can be used offline.
If 'cdn', a script tag that references the plotly.js CDN is included
in the output. The url used is versioned to match the bundled plotly.js.
HTML files generated with this option are about 3MB smaller than those
generated with include_plotlyjs=True, but they require an active
internet connection in order to load the plotly.js library.
If 'directory', a script tag is included that references an external
plotly.min.js bundle that is assumed to reside in the same
directory as the HTML file. If `file` is a string to a local file
path and `full_html` is True, then the plotly.min.js bundle is copied
into the directory of the resulting HTML file. If a file named
plotly.min.js already exists in the output directory then this file
is left unmodified and no copy is performed. HTML files generated
with this option can be used offline, but they require a copy of
the plotly.min.js bundle in the same directory. This option is
useful when many figures will be saved as HTML files in the same
directory because the plotly.js source code will be included only
once per output directory, rather than once per output file.
If 'require', Plotly.js is loaded using require.js. This option
assumes that require.js is globally available and that it has been
globally configured to know how to find Plotly.js as 'plotly'.
This option is not advised when full_html=True as it will result
in a non-functional html file.
If a string that ends in '.js', a script tag is included that
references the specified path. This approach can be used to point
the resulting HTML file to an alternative CDN or local bundle.
If False, no script tag referencing plotly.js is included. This is
useful when the resulting div string will be placed inside an HTML
document that already loads plotly.js. This option is not advised
when full_html=True as it will result in a non-functional html file.
include_mathjax: bool or string (default False)
Specifies how the MathJax.js library is included in the output html
div string. MathJax is required in order to display labels
with LaTeX typesetting.
If False, no script tag referencing MathJax.js will be included in the
output.
If 'cdn', a script tag that references a MathJax CDN location will be
included in the output. HTML div strings generated with this option
will be able to display LaTeX typesetting as long as internet access
is available.
If a string that ends in '.js', a script tag is included that
references the specified path. This approach can be used to point the
resulting HTML div string to an alternative CDN.
post_script: str or list or None (default None)
JavaScript snippet(s) to be included in the resulting div just after
plot creation. The string(s) may include '{plot_id}' placeholders
that will then be replaced by the `id` of the div element that the
plotly.js figure is associated with. One application for this script
is to install custom plotly.js event handlers.
full_html: bool (default True)
If True, produce a string containing a complete HTML document
starting with an <html> tag. If False, produce a string containing
a single <div> element.
animation_opts: dict or None (default None)
dict of custom animation parameters to be passed to the function
Plotly.animate in Plotly.js. See
https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js
for available options. Has no effect if the figure does not contain
frames, or auto_play is False.
default_width, default_height: number or str (default '100%')
The default figure width/height to use if the provided figure does not
specify its own layout.width/layout.height property. May be
specified in pixels as an integer (e.g. 500), or as a css width style
string (e.g. '500px', '100%').
validate: bool (default True)
True if the figure should be validated before being converted to
JSON, False otherwise.
auto_open: bool (default True)
If True, open the saved file in a web browser after saving.
This argument only applies if `full_html` is True.
div_id: str (default None)
If provided, this is the value of the id attribute of the div tag. If None, the
id attribute is a UUID.
Returns
-------
str
Representation of figure as an HTML div string
"""
# Build HTML string
html_str = to_html(
fig,
config=config,
auto_play=auto_play,
include_plotlyjs=include_plotlyjs,
include_mathjax=include_mathjax,
post_script=post_script,
full_html=full_html,
animation_opts=animation_opts,
default_width=default_width,
default_height=default_height,
validate=validate,
div_id=div_id,
)
# Check if file is a string
if isinstance(file, str):
# Use the standard pathlib constructor to make a pathlib object.
path = Path(file)
elif isinstance(file, Path): # PurePath is the most general pathlib object.
# `file` is already a pathlib object.
path = file
else:
# We could not make a pathlib object out of file. Either `file` is an open file
# descriptor with a `write()` method or it's an invalid object.
path = None
# Write HTML string
if path is not None:
path.write_text(html_str)
else:
file.write(html_str)
# Check if we should copy plotly.min.js to output directory
if path is not None and full_html and include_plotlyjs == "directory":
bundle_path = path.parent / "plotly.min.js"
if not bundle_path.exists():
bundle_path.write_text(get_plotlyjs())
# Handle auto_open
if path is not None and full_html and auto_open:
url = path.absolute().as_uri()
webbrowser.open(url)

View File

@@ -0,0 +1,574 @@
from __future__ import absolute_import
import json
import decimal
import datetime
from pathlib import Path
from plotly.io._utils import validate_coerce_fig_to_dict, validate_coerce_output_type
from _plotly_utils.optional_imports import get_module
from _plotly_utils.basevalidators import ImageUriValidator
# Orca configuration class
# ------------------------
class JsonConfig(object):
_valid_engines = ("json", "orjson", "auto")
def __init__(self):
self._default_engine = "auto"
@property
def default_engine(self):
return self._default_engine
@default_engine.setter
def default_engine(self, val):
if val not in JsonConfig._valid_engines:
raise ValueError(
"Supported JSON engines include {valid}\n"
" Received {val}".format(valid=JsonConfig._valid_engines, val=val)
)
if val == "orjson":
self.validate_orjson()
self._default_engine = val
@classmethod
def validate_orjson(cls):
orjson = get_module("orjson")
if orjson is None:
raise ValueError("The orjson engine requires the orjson package")
config = JsonConfig()
def coerce_to_strict(const):
"""
This is used to ultimately *encode* into strict JSON, see `encode`
"""
# before python 2.7, 'true', 'false', 'null', were include here.
if const in ("Infinity", "-Infinity", "NaN"):
return None
else:
return const
def to_json_plotly(plotly_object, pretty=False, engine=None):
"""
Convert a plotly/Dash object to a JSON string representation
Parameters
----------
plotly_object:
A plotly/Dash object represented as a dict, graph_object, or Dash component
pretty: bool (default False)
True if JSON representation should be pretty-printed, False if
representation should be as compact as possible.
engine: str (default None)
The JSON encoding engine to use. One of:
- "json" for an engine based on the built-in Python json module
- "orjson" for a faster engine that requires the orjson package
- "auto" for the "orjson" engine if available, otherwise "json"
If not specified, the default engine is set to the current value of
plotly.io.json.config.default_engine.
Returns
-------
str
Representation of input object as a JSON string
See Also
--------
to_json : Convert a plotly Figure to JSON with validation
"""
orjson = get_module("orjson", should_load=True)
# Determine json engine
if engine is None:
engine = config.default_engine
if engine == "auto":
if orjson is not None:
engine = "orjson"
else:
engine = "json"
elif engine not in ["orjson", "json"]:
raise ValueError("Invalid json engine: %s" % engine)
modules = {
"sage_all": get_module("sage.all", should_load=False),
"np": get_module("numpy", should_load=False),
"pd": get_module("pandas", should_load=False),
"image": get_module("PIL.Image", should_load=False),
}
# Dump to a JSON string and return
# --------------------------------
if engine == "json":
opts = {}
if pretty:
opts["indent"] = 2
else:
# Remove all whitespace
opts["separators"] = (",", ":")
from _plotly_utils.utils import PlotlyJSONEncoder
return json.dumps(plotly_object, cls=PlotlyJSONEncoder, **opts)
elif engine == "orjson":
JsonConfig.validate_orjson()
opts = orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY
if pretty:
opts |= orjson.OPT_INDENT_2
# Plotly
try:
plotly_object = plotly_object.to_plotly_json()
except AttributeError:
pass
# Try without cleaning
try:
return orjson.dumps(plotly_object, option=opts).decode("utf8")
except TypeError:
pass
cleaned = clean_to_json_compatible(
plotly_object,
numpy_allowed=True,
datetime_allowed=True,
modules=modules,
)
return orjson.dumps(cleaned, option=opts).decode("utf8")
def to_json(fig, validate=True, pretty=False, remove_uids=True, engine=None):
"""
Convert a figure to a JSON string representation
Parameters
----------
fig:
Figure object or dict representing a figure
validate: bool (default True)
True if the figure should be validated before being converted to
JSON, False otherwise.
pretty: bool (default False)
True if JSON representation should be pretty-printed, False if
representation should be as compact as possible.
remove_uids: bool (default True)
True if trace UIDs should be omitted from the JSON representation
engine: str (default None)
The JSON encoding engine to use. One of:
- "json" for an engine based on the built-in Python json module
- "orjson" for a faster engine that requires the orjson package
- "auto" for the "orjson" engine if available, otherwise "json"
If not specified, the default engine is set to the current value of
plotly.io.json.config.default_engine.
Returns
-------
str
Representation of figure as a JSON string
See Also
--------
to_json_plotly : Convert an arbitrary plotly graph_object or Dash component to JSON
"""
# Validate figure
# ---------------
fig_dict = validate_coerce_fig_to_dict(fig, validate)
# Remove trace uid
# ----------------
if remove_uids:
for trace in fig_dict.get("data", []):
trace.pop("uid", None)
return to_json_plotly(fig_dict, pretty=pretty, engine=engine)
def write_json(fig, file, validate=True, pretty=False, remove_uids=True, engine=None):
"""
Convert a figure to JSON and write it to a file or writeable
object
Parameters
----------
fig:
Figure object or dict representing a figure
file: str or writeable
A string representing a local file path or a writeable object
(e.g. a pathlib.Path object or an open file descriptor)
pretty: bool (default False)
True if JSON representation should be pretty-printed, False if
representation should be as compact as possible.
remove_uids: bool (default True)
True if trace UIDs should be omitted from the JSON representation
engine: str (default None)
The JSON encoding engine to use. One of:
- "json" for an engine based on the built-in Python json module
- "orjson" for a faster engine that requires the orjson package
- "auto" for the "orjson" engine if available, otherwise "json"
If not specified, the default engine is set to the current value of
plotly.io.json.config.default_engine.
Returns
-------
None
"""
# Get JSON string
# ---------------
# Pass through validate argument and let to_json handle validation logic
json_str = to_json(
fig, validate=validate, pretty=pretty, remove_uids=remove_uids, engine=engine
)
# Try to cast `file` as a pathlib object `path`.
# ----------------------------------------------
if isinstance(file, str):
# Use the standard Path constructor to make a pathlib object.
path = Path(file)
elif isinstance(file, Path):
# `file` is already a Path object.
path = file
else:
# We could not make a Path object out of file. Either `file` is an open file
# descriptor with a `write()` method or it's an invalid object.
path = None
# Open file
# ---------
if path is None:
# We previously failed to make sense of `file` as a pathlib object.
# Attempt to write to `file` as an open file descriptor.
try:
file.write(json_str)
return
except AttributeError:
pass
raise ValueError(
"""
The 'file' argument '{file}' is not a string, pathlib.Path object, or file descriptor.
""".format(
file=file
)
)
else:
# We previously succeeded in interpreting `file` as a pathlib object.
# Now we can use `write_bytes()`.
path.write_text(json_str)
def from_json_plotly(value, engine=None):
"""
Parse JSON string using the specified JSON engine
Parameters
----------
value: str or bytes
A JSON string or bytes object
engine: str (default None)
The JSON decoding engine to use. One of:
- if "json", parse JSON using built in json module
- if "orjson", parse using the faster orjson module, requires the orjson
package
- if "auto" use orjson module if available, otherwise use the json module
If not specified, the default engine is set to the current value of
plotly.io.json.config.default_engine.
Returns
-------
dict
See Also
--------
from_json_plotly : Parse JSON with plotly conventions into a dict
"""
orjson = get_module("orjson", should_load=True)
# Validate value
# --------------
if not isinstance(value, (str, bytes)):
raise ValueError(
"""
from_json_plotly requires a string or bytes argument but received value of type {typ}
Received value: {value}""".format(
typ=type(value), value=value
)
)
# Determine json engine
if engine is None:
engine = config.default_engine
if engine == "auto":
if orjson is not None:
engine = "orjson"
else:
engine = "json"
elif engine not in ["orjson", "json"]:
raise ValueError("Invalid json engine: %s" % engine)
if engine == "orjson":
JsonConfig.validate_orjson()
# orjson handles bytes input natively
value_dict = orjson.loads(value)
else:
# decode bytes to str for built-in json module
if isinstance(value, bytes):
value = value.decode("utf-8")
value_dict = json.loads(value)
return value_dict
def from_json(value, output_type="Figure", skip_invalid=False, engine=None):
"""
Construct a figure from a JSON string
Parameters
----------
value: str or bytes
String or bytes object containing the JSON representation of a figure
output_type: type or str (default 'Figure')
The output figure type or type name.
One of: graph_objs.Figure, 'Figure', graph_objs.FigureWidget, 'FigureWidget'
skip_invalid: bool (default False)
False if invalid figure properties should result in an exception.
True if invalid figure properties should be silently ignored.
engine: str (default None)
The JSON decoding engine to use. One of:
- if "json", parse JSON using built in json module
- if "orjson", parse using the faster orjson module, requires the orjson
package
- if "auto" use orjson module if available, otherwise use the json module
If not specified, the default engine is set to the current value of
plotly.io.json.config.default_engine.
Raises
------
ValueError
if value is not a string, or if skip_invalid=False and value contains
invalid figure properties
Returns
-------
Figure or FigureWidget
"""
# Decode JSON
# -----------
fig_dict = from_json_plotly(value, engine=engine)
# Validate coerce output type
# ---------------------------
cls = validate_coerce_output_type(output_type)
# Create and return figure
# ------------------------
fig = cls(fig_dict, skip_invalid=skip_invalid)
return fig
def read_json(file, output_type="Figure", skip_invalid=False, engine=None):
"""
Construct a figure from the JSON contents of a local file or readable
Python object
Parameters
----------
file: str or readable
A string containing the path to a local file or a read-able Python
object (e.g. a pathlib.Path object or an open file descriptor)
output_type: type or str (default 'Figure')
The output figure type or type name.
One of: graph_objs.Figure, 'Figure', graph_objs.FigureWidget, 'FigureWidget'
skip_invalid: bool (default False)
False if invalid figure properties should result in an exception.
True if invalid figure properties should be silently ignored.
engine: str (default None)
The JSON decoding engine to use. One of:
- if "json", parse JSON using built in json module
- if "orjson", parse using the faster orjson module, requires the orjson
package
- if "auto" use orjson module if available, otherwise use the json module
If not specified, the default engine is set to the current value of
plotly.io.json.config.default_engine.
Returns
-------
Figure or FigureWidget
"""
# Try to cast `file` as a pathlib object `path`.
# -------------------------
# ----------------------------------------------
file_is_str = isinstance(file, str)
if isinstance(file, str):
# Use the standard Path constructor to make a pathlib object.
path = Path(file)
elif isinstance(file, Path):
# `file` is already a Path object.
path = file
else:
# We could not make a Path object out of file. Either `file` is an open file
# descriptor with a `write()` method or it's an invalid object.
path = None
# Read file contents into JSON string
# -----------------------------------
if path is not None:
json_str = path.read_text()
else:
json_str = file.read()
# Construct and return figure
# ---------------------------
return from_json(
json_str, skip_invalid=skip_invalid, output_type=output_type, engine=engine
)
def clean_to_json_compatible(obj, **kwargs):
# Try handling value as a scalar value that we have a conversion for.
# Return immediately if we know we've hit a primitive value
# Bail out fast for simple scalar types
if isinstance(obj, (int, float, str)):
return obj
if isinstance(obj, dict):
return {k: clean_to_json_compatible(v, **kwargs) for k, v in obj.items()}
elif isinstance(obj, (list, tuple)):
if obj:
# Must process list recursively even though it may be slow
return [clean_to_json_compatible(v, **kwargs) for v in obj]
# unpack kwargs
numpy_allowed = kwargs.get("numpy_allowed", False)
datetime_allowed = kwargs.get("datetime_allowed", False)
modules = kwargs.get("modules", {})
sage_all = modules["sage_all"]
np = modules["np"]
pd = modules["pd"]
image = modules["image"]
# Sage
if sage_all is not None:
if obj in sage_all.RR:
return float(obj)
elif obj in sage_all.ZZ:
return int(obj)
# numpy
if np is not None:
if obj is np.ma.core.masked:
return float("nan")
elif isinstance(obj, np.ndarray):
if numpy_allowed and obj.dtype.kind in ("b", "i", "u", "f"):
return np.ascontiguousarray(obj)
elif obj.dtype.kind == "M":
# datetime64 array
return np.datetime_as_string(obj).tolist()
elif obj.dtype.kind == "U":
return obj.tolist()
elif obj.dtype.kind == "O":
# Treat object array as a lists, continue processing
obj = obj.tolist()
elif isinstance(obj, np.datetime64):
return str(obj)
# pandas
if pd is not None:
if obj is pd.NaT:
return None
elif isinstance(obj, (pd.Series, pd.DatetimeIndex)):
if numpy_allowed and obj.dtype.kind in ("b", "i", "u", "f"):
return np.ascontiguousarray(obj.values)
elif obj.dtype.kind == "M":
if isinstance(obj, pd.Series):
dt_values = obj.dt.to_pydatetime().tolist()
else: # DatetimeIndex
dt_values = obj.to_pydatetime().tolist()
if not datetime_allowed:
# Note: We don't need to handle dropping timezones here because
# numpy's datetime64 doesn't support them and pandas's tz_localize
# above drops them.
for i in range(len(dt_values)):
dt_values[i] = dt_values[i].isoformat()
return dt_values
# datetime and date
try:
# Need to drop timezone for scalar datetimes. Don't need to convert
# to string since engine can do that
obj = obj.to_pydatetime()
except (TypeError, AttributeError):
pass
if not datetime_allowed:
try:
return obj.isoformat()
except (TypeError, AttributeError):
pass
elif isinstance(obj, datetime.datetime):
return obj
# Try .tolist() convertible, do not recurse inside
try:
return obj.tolist()
except AttributeError:
pass
# Do best we can with decimal
if isinstance(obj, decimal.Decimal):
return float(obj)
# PIL
if image is not None and isinstance(obj, image.Image):
return ImageUriValidator.pil_image_to_uri(obj)
# Plotly
try:
obj = obj.to_plotly_json()
except AttributeError:
pass
# Recurse into lists and dictionaries
if isinstance(obj, dict):
return {k: clean_to_json_compatible(v, **kwargs) for k, v in obj.items()}
elif isinstance(obj, (list, tuple)):
if obj:
# Must process list recursively even though it may be slow
return [clean_to_json_compatible(v, **kwargs) for v in obj]
return obj

View File

@@ -0,0 +1,354 @@
from __future__ import absolute_import
import os
import json
from pathlib import Path
import plotly
from plotly.io._utils import validate_coerce_fig_to_dict
try:
from kaleido.scopes.plotly import PlotlyScope
scope = PlotlyScope()
# Compute absolute path to the 'plotly/package_data/' directory
root_dir = os.path.dirname(os.path.abspath(plotly.__file__))
package_dir = os.path.join(root_dir, "package_data")
scope.plotlyjs = os.path.join(package_dir, "plotly.min.js")
if scope.mathjax is None:
scope.mathjax = (
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js"
)
except ImportError:
PlotlyScope = None
scope = None
def to_image(
fig, format=None, width=None, height=None, scale=None, validate=True, engine="auto"
):
"""
Convert a figure to a static image bytes string
Parameters
----------
fig:
Figure object or dict representing a figure
format: str or None
The desired image format. One of
- 'png'
- 'jpg' or 'jpeg'
- 'webp'
- 'svg'
- 'pdf'
- 'eps' (Requires the poppler library to be installed and on the PATH)
If not specified, will default to:
- `plotly.io.kaleido.scope.default_format` if engine is "kaleido"
- `plotly.io.orca.config.default_format` if engine is "orca"
width: int or None
The width of the exported image in layout pixels. If the `scale`
property is 1.0, this will also be the width of the exported image
in physical pixels.
If not specified, will default to:
- `plotly.io.kaleido.scope.default_width` if engine is "kaleido"
- `plotly.io.orca.config.default_width` if engine is "orca"
height: int or None
The height of the exported image in layout pixels. If the `scale`
property is 1.0, this will also be the height of the exported image
in physical pixels.
If not specified, will default to:
- `plotly.io.kaleido.scope.default_height` if engine is "kaleido"
- `plotly.io.orca.config.default_height` if engine is "orca"
scale: int or float or None
The scale factor to use when exporting the figure. A scale factor
larger than 1.0 will increase the image resolution with respect
to the figure's layout pixel dimensions. Whereas as scale factor of
less than 1.0 will decrease the image resolution.
If not specified, will default to:
- `plotly.io.kaleido.scope.default_scale` if engine is "kaleido"
- `plotly.io.orca.config.default_scale` if engine is "orca"
validate: bool
True if the figure should be validated before being converted to
an image, False otherwise.
engine: str
Image export engine to use:
- "kaleido": Use Kaleido for image export
- "orca": Use Orca for image export
- "auto" (default): Use Kaleido if installed, otherwise use orca
Returns
-------
bytes
The image data
"""
# Handle engine
# -------------
if engine == "auto":
if scope is not None:
# Default to kaleido if available
engine = "kaleido"
else:
# See if orca is available
from ._orca import validate_executable
try:
validate_executable()
engine = "orca"
except:
# If orca not configured properly, make sure we display the error
# message advising the installation of kaleido
engine = "kaleido"
if engine == "orca":
# Fall back to legacy orca image export path
from ._orca import to_image as to_image_orca
return to_image_orca(
fig,
format=format,
width=width,
height=height,
scale=scale,
validate=validate,
)
elif engine != "kaleido":
raise ValueError(
"Invalid image export engine specified: {engine}".format(
engine=repr(engine)
)
)
# Raise informative error message if Kaleido is not installed
if scope is None:
raise ValueError(
"""
Image export using the "kaleido" engine requires the kaleido package,
which can be installed using pip:
$ pip install -U kaleido
"""
)
# Validate figure
# ---------------
fig_dict = validate_coerce_fig_to_dict(fig, validate)
img_bytes = scope.transform(
fig_dict, format=format, width=width, height=height, scale=scale
)
return img_bytes
def write_image(
fig,
file,
format=None,
scale=None,
width=None,
height=None,
validate=True,
engine="auto",
):
"""
Convert a figure to a static image and write it to a file or writeable
object
Parameters
----------
fig:
Figure object or dict representing a figure
file: str or writeable
A string representing a local file path or a writeable object
(e.g. a pathlib.Path object or an open file descriptor)
format: str or None
The desired image format. One of
- 'png'
- 'jpg' or 'jpeg'
- 'webp'
- 'svg'
- 'pdf'
- 'eps' (Requires the poppler library to be installed and on the PATH)
If not specified and `file` is a string then this will default to the
file extension. If not specified and `file` is not a string then this
will default to:
- `plotly.io.kaleido.scope.default_format` if engine is "kaleido"
- `plotly.io.orca.config.default_format` if engine is "orca"
width: int or None
The width of the exported image in layout pixels. If the `scale`
property is 1.0, this will also be the width of the exported image
in physical pixels.
If not specified, will default to:
- `plotly.io.kaleido.scope.default_width` if engine is "kaleido"
- `plotly.io.orca.config.default_width` if engine is "orca"
height: int or None
The height of the exported image in layout pixels. If the `scale`
property is 1.0, this will also be the height of the exported image
in physical pixels.
If not specified, will default to:
- `plotly.io.kaleido.scope.default_height` if engine is "kaleido"
- `plotly.io.orca.config.default_height` if engine is "orca"
scale: int or float or None
The scale factor to use when exporting the figure. A scale factor
larger than 1.0 will increase the image resolution with respect
to the figure's layout pixel dimensions. Whereas as scale factor of
less than 1.0 will decrease the image resolution.
If not specified, will default to:
- `plotly.io.kaleido.scope.default_scale` if engine is "kaleido"
- `plotly.io.orca.config.default_scale` if engine is "orca"
validate: bool
True if the figure should be validated before being converted to
an image, False otherwise.
engine: str
Image export engine to use:
- "kaleido": Use Kaleido for image export
- "orca": Use Orca for image export
- "auto" (default): Use Kaleido if installed, otherwise use orca
Returns
-------
None
"""
# Try to cast `file` as a pathlib object `path`.
# ----------------------------------------------
if isinstance(file, str):
# Use the standard Path constructor to make a pathlib object.
path = Path(file)
elif isinstance(file, Path):
# `file` is already a Path object.
path = file
else:
# We could not make a Path object out of file. Either `file` is an open file
# descriptor with a `write()` method or it's an invalid object.
path = None
# Infer format if not specified
# -----------------------------
if path is not None and format is None:
ext = path.suffix
if ext:
format = ext.lstrip(".")
else:
raise ValueError(
"""
Cannot infer image type from output path '{file}'.
Please add a file extension or specify the type using the format parameter.
For example:
>>> import plotly.io as pio
>>> pio.write_image(fig, file_path, format='png')
""".format(
file=file
)
)
# Request image
# -------------
# Do this first so we don't create a file if image conversion fails
img_data = to_image(
fig,
format=format,
scale=scale,
width=width,
height=height,
validate=validate,
engine=engine,
)
# Open file
# ---------
if path is None:
# We previously failed to make sense of `file` as a pathlib object.
# Attempt to write to `file` as an open file descriptor.
try:
file.write(img_data)
return
except AttributeError:
pass
raise ValueError(
"""
The 'file' argument '{file}' is not a string, pathlib.Path object, or file descriptor.
""".format(
file=file
)
)
else:
# We previously succeeded in interpreting `file` as a pathlib object.
# Now we can use `write_bytes()`.
path.write_bytes(img_data)
def full_figure_for_development(fig, warn=True, as_dict=False):
"""
Compute default values for all attributes not specified in the input figure and
returns the output as a "full" figure. This function calls Plotly.js via Kaleido
to populate unspecified attributes. This function is intended for interactive use
during development to learn more about how Plotly.js computes default values and is
not generally necessary or recommended for production use.
Parameters
----------
fig:
Figure object or dict representing a figure
warn: bool
If False, suppress warnings about not using this in production.
as_dict: bool
If True, output is a dict with some keys that go.Figure can't parse.
If False, output is a go.Figure with unparseable keys skipped.
Returns
-------
plotly.graph_objects.Figure or dict
The full figure
"""
# Raise informative error message if Kaleido is not installed
if scope is None:
raise ValueError(
"""
Full figure generation requires the kaleido package,
which can be installed using pip:
$ pip install -U kaleido
"""
)
if warn:
import warnings
warnings.warn(
"full_figure_for_development is not recommended or necessary for "
"production use in most circumstances. \n"
"To suppress this warning, set warn=False"
)
fig = json.loads(scope.transform(fig, format="json").decode("utf-8"))
if as_dict:
return fig
else:
import plotly.graph_objects as go
return go.Figure(fig, skip_invalid=True)
__all__ = ["to_image", "write_image", "scope", "full_figure_for_development"]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,538 @@
from __future__ import absolute_import, division
import textwrap
from copy import copy
import os
from distutils.version import LooseVersion
from plotly import optional_imports
from plotly.io._base_renderers import (
MimetypeRenderer,
ExternalRenderer,
PlotlyRenderer,
NotebookRenderer,
KaggleRenderer,
AzureRenderer,
ColabRenderer,
JsonRenderer,
PngRenderer,
JpegRenderer,
SvgRenderer,
PdfRenderer,
BrowserRenderer,
IFrameRenderer,
SphinxGalleryHtmlRenderer,
SphinxGalleryOrcaRenderer,
CoCalcRenderer,
DatabricksRenderer,
)
from plotly.io._utils import validate_coerce_fig_to_dict
ipython = optional_imports.get_module("IPython")
ipython_display = optional_imports.get_module("IPython.display")
nbformat = optional_imports.get_module("nbformat")
# Renderer configuration class
# -----------------------------
class RenderersConfig(object):
"""
Singleton object containing the current renderer configurations
"""
def __init__(self):
self._renderers = {}
self._default_name = None
self._default_renderers = []
self._render_on_display = False
self._to_activate = []
# ### Magic methods ###
# Make this act as a dict of renderers
def __len__(self):
return len(self._renderers)
def __contains__(self, item):
return item in self._renderers
def __iter__(self):
return iter(self._renderers)
def __getitem__(self, item):
renderer = self._renderers[item]
return renderer
def __setitem__(self, key, value):
if not isinstance(value, (MimetypeRenderer, ExternalRenderer)):
raise ValueError(
"""\
Renderer must be a subclass of MimetypeRenderer or ExternalRenderer.
Received value with type: {typ}""".format(
typ=type(value)
)
)
self._renderers[key] = value
def __delitem__(self, key):
# Remove template
del self._renderers[key]
# Check if we need to remove it as the default
if self._default == key:
self._default = None
def keys(self):
return self._renderers.keys()
def items(self):
return self._renderers.items()
def update(self, d={}, **kwargs):
"""
Update one or more renderers from a dict or from input keyword
arguments.
Parameters
----------
d: dict
Dictionary from renderer names to new renderer objects.
kwargs
Named argument value pairs where the name is a renderer name
and the value is a new renderer object
"""
for k, v in dict(d, **kwargs).items():
self[k] = v
# ### Properties ###
@property
def default(self):
"""
The default renderer, or None if no there is no default
If not None, the default renderer is used to render
figures when the `plotly.io.show` function is called on a Figure.
If `plotly.io.renderers.render_on_display` is True, then the default
renderer will also be used to display Figures automatically when
displayed in the Jupyter Notebook
Multiple renderers may be registered by separating their names with
'+' characters. For example, to specify rendering compatible with
the classic Jupyter Notebook, JupyterLab, and PDF export:
>>> import plotly.io as pio
>>> pio.renderers.default = 'notebook+jupyterlab+pdf'
The names of available renderers may be retrieved with:
>>> import plotly.io as pio
>>> list(pio.renderers)
Returns
-------
str
"""
return self._default_name
@default.setter
def default(self, value):
# Handle None
if not value:
# _default_name should always be a string so we can do
# pio.renderers.default.split('+')
self._default_name = ""
self._default_renderers = []
return
# Store defaults name and list of renderer(s)
renderer_names = self._validate_coerce_renderers(value)
self._default_name = value
self._default_renderers = [self[name] for name in renderer_names]
# Register renderers for activation before their next use
self._to_activate = list(self._default_renderers)
@property
def render_on_display(self):
"""
If True, the default mimetype renderers will be used to render
figures when they are displayed in an IPython context.
Returns
-------
bool
"""
return self._render_on_display
@render_on_display.setter
def render_on_display(self, val):
self._render_on_display = bool(val)
def _activate_pending_renderers(self, cls=object):
"""
Activate all renderers that are waiting in the _to_activate list
Parameters
----------
cls
Only activate renders that are subclasses of this class
"""
to_activate_with_cls = [
r for r in self._to_activate if cls and isinstance(r, cls)
]
while to_activate_with_cls:
# Activate renderers from left to right so that right-most
# renderers take precedence
renderer = to_activate_with_cls.pop(0)
renderer.activate()
self._to_activate = [
r for r in self._to_activate if not (cls and isinstance(r, cls))
]
def _validate_coerce_renderers(self, renderers_string):
"""
Input a string and validate that it contains the names of one or more
valid renderers separated on '+' characters. If valid, return
a list of the renderer names
Parameters
----------
renderers_string: str
Returns
-------
list of str
"""
# Validate value
if not isinstance(renderers_string, str):
raise ValueError("Renderer must be specified as a string")
renderer_names = renderers_string.split("+")
invalid = [name for name in renderer_names if name not in self]
if invalid:
raise ValueError(
"""
Invalid named renderer(s) received: {}""".format(
str(invalid)
)
)
return renderer_names
def __repr__(self):
return """\
Renderers configuration
-----------------------
Default renderer: {default}
Available renderers:
{available}
""".format(
default=repr(self.default), available=self._available_renderers_str()
)
def _available_renderers_str(self):
"""
Return nicely wrapped string representation of all
available renderer names
"""
available = "\n".join(
textwrap.wrap(
repr(list(self)),
width=79 - 8,
initial_indent=" " * 8,
subsequent_indent=" " * 9,
)
)
return available
def _build_mime_bundle(self, fig_dict, renderers_string=None, **kwargs):
"""
Build a mime bundle dict containing a kev/value pair for each
MimetypeRenderer specified in either the default renderer string,
or in the supplied renderers_string argument.
Note that this method skips any renderers that are not subclasses
of MimetypeRenderer.
Parameters
----------
fig_dict: dict
Figure dictionary
renderers_string: str or None (default None)
Renderer string to process rather than the current default
renderer string
Returns
-------
dict
"""
if renderers_string:
renderer_names = self._validate_coerce_renderers(renderers_string)
renderers_list = [self[name] for name in renderer_names]
# Activate these non-default renderers
for renderer in renderers_list:
if isinstance(renderer, MimetypeRenderer):
renderer.activate()
else:
# Activate any pending default renderers
self._activate_pending_renderers(cls=MimetypeRenderer)
renderers_list = self._default_renderers
bundle = {}
for renderer in renderers_list:
if isinstance(renderer, MimetypeRenderer):
renderer = copy(renderer)
for k, v in kwargs.items():
if hasattr(renderer, k):
setattr(renderer, k, v)
bundle.update(renderer.to_mimebundle(fig_dict))
return bundle
def _perform_external_rendering(self, fig_dict, renderers_string=None, **kwargs):
"""
Perform external rendering for each ExternalRenderer specified
in either the default renderer string, or in the supplied
renderers_string argument.
Note that this method skips any renderers that are not subclasses
of ExternalRenderer.
Parameters
----------
fig_dict: dict
Figure dictionary
renderers_string: str or None (default None)
Renderer string to process rather than the current default
renderer string
Returns
-------
None
"""
if renderers_string:
renderer_names = self._validate_coerce_renderers(renderers_string)
renderers_list = [self[name] for name in renderer_names]
# Activate these non-default renderers
for renderer in renderers_list:
if isinstance(renderer, ExternalRenderer):
renderer.activate()
else:
self._activate_pending_renderers(cls=ExternalRenderer)
renderers_list = self._default_renderers
for renderer in renderers_list:
if isinstance(renderer, ExternalRenderer):
renderer = copy(renderer)
for k, v in kwargs.items():
if hasattr(renderer, k):
setattr(renderer, k, v)
renderer.render(fig_dict)
# Make renderers a singleton object
# ---------------------------------
renderers = RenderersConfig()
del RenderersConfig
# Show
def show(fig, renderer=None, validate=True, **kwargs):
"""
Show a figure using either the default renderer(s) or the renderer(s)
specified by the renderer argument
Parameters
----------
fig: dict of Figure
The Figure object or figure dict to display
renderer: str or None (default None)
A string containing the names of one or more registered renderers
(separated by '+' characters) or None. If None, then the default
renderers specified in plotly.io.renderers.default are used.
validate: bool (default True)
True if the figure should be validated before being shown,
False otherwise.
width: int or float
An integer or float that determines the number of pixels wide the
plot is. The default is set in plotly.js.
height: int or float
An integer or float that determines the number of pixels wide the
plot is. The default is set in plotly.js.
config: dict
A dict of parameters to configure the figure. The defaults are set
in plotly.js.
Returns
-------
None
"""
fig_dict = validate_coerce_fig_to_dict(fig, validate)
# Mimetype renderers
bundle = renderers._build_mime_bundle(fig_dict, renderers_string=renderer, **kwargs)
if bundle:
if not ipython_display:
raise ValueError(
"Mime type rendering requires ipython but it is not installed"
)
if not nbformat or LooseVersion(nbformat.__version__) < LooseVersion("4.2.0"):
raise ValueError(
"Mime type rendering requires nbformat>=4.2.0 but it is not installed"
)
ipython_display.display(bundle, raw=True)
# external renderers
renderers._perform_external_rendering(fig_dict, renderers_string=renderer, **kwargs)
# Register renderers
# ------------------
# Plotly mime type
plotly_renderer = PlotlyRenderer()
renderers["plotly_mimetype"] = plotly_renderer
renderers["jupyterlab"] = plotly_renderer
renderers["nteract"] = plotly_renderer
renderers["vscode"] = plotly_renderer
# HTML-based
config = {}
renderers["notebook"] = NotebookRenderer(config=config)
renderers["notebook_connected"] = NotebookRenderer(config=config, connected=True)
renderers["kaggle"] = KaggleRenderer(config=config)
renderers["azure"] = AzureRenderer(config=config)
renderers["colab"] = ColabRenderer(config=config)
renderers["cocalc"] = CoCalcRenderer()
renderers["databricks"] = DatabricksRenderer()
# JSON
renderers["json"] = JsonRenderer()
# Static Image
renderers["png"] = PngRenderer()
jpeg_renderer = JpegRenderer()
renderers["jpeg"] = jpeg_renderer
renderers["jpg"] = jpeg_renderer
renderers["svg"] = SvgRenderer()
renderers["pdf"] = PdfRenderer()
# External
renderers["browser"] = BrowserRenderer(config=config)
renderers["firefox"] = BrowserRenderer(config=config, using=("firefox"))
renderers["chrome"] = BrowserRenderer(config=config, using=("chrome", "google-chrome"))
renderers["chromium"] = BrowserRenderer(
config=config, using=("chromium", "chromium-browser")
)
renderers["iframe"] = IFrameRenderer(config=config, include_plotlyjs=True)
renderers["iframe_connected"] = IFrameRenderer(config=config, include_plotlyjs="cdn")
renderers["sphinx_gallery"] = SphinxGalleryHtmlRenderer()
renderers["sphinx_gallery_png"] = SphinxGalleryOrcaRenderer()
# Set default renderer
# --------------------
# Version 4 renderer configuration
default_renderer = None
# Handle the PLOTLY_RENDERER environment variable
env_renderer = os.environ.get("PLOTLY_RENDERER", None)
if env_renderer:
try:
renderers._validate_coerce_renderers(env_renderer)
except ValueError:
raise ValueError(
"""
Invalid named renderer(s) specified in the 'PLOTLY_RENDERER'
environment variable: {env_renderer}""".format(
env_renderer=env_renderer
)
)
default_renderer = env_renderer
elif ipython and ipython.get_ipython():
# Try to detect environment so that we can enable a useful
# default renderer
if not default_renderer:
try:
import google.colab
default_renderer = "colab"
except ImportError:
pass
# Check if we're running in a Kaggle notebook
if not default_renderer and os.path.exists("/kaggle/input"):
default_renderer = "kaggle"
# Check if we're running in an Azure Notebook
if not default_renderer and "AZURE_NOTEBOOKS_HOST" in os.environ:
default_renderer = "azure"
# Check if we're running in VSCode
if not default_renderer and "VSCODE_PID" in os.environ:
default_renderer = "vscode"
# Check if we're running in nteract
if not default_renderer and "NTERACT_EXE" in os.environ:
default_renderer = "nteract"
# Check if we're running in CoCalc
if not default_renderer and "COCALC_PROJECT_ID" in os.environ:
default_renderer = "cocalc"
if not default_renderer and "DATABRICKS_RUNTIME_VERSION" in os.environ:
default_renderer = "databricks"
# Check if we're running in spyder and orca is installed
if not default_renderer and "SPYDER_ARGS" in os.environ:
try:
from plotly.io.orca import validate_executable
validate_executable()
default_renderer = "svg"
except ValueError:
# orca not found
pass
# Check if we're running in ipython terminal
if not default_renderer and (
ipython.get_ipython().__class__.__name__ == "TerminalInteractiveShell"
):
default_renderer = "browser"
# Fallback to renderer combination that will work automatically
# in the classic notebook (offline), jupyterlab, nteract, vscode, and
# nbconvert HTML export.
if not default_renderer:
default_renderer = "plotly_mimetype+notebook"
else:
# If ipython isn't available, try to display figures in the default
# browser
import webbrowser
try:
webbrowser.get()
default_renderer = "browser"
except webbrowser.Error:
# Default browser could not be loaded
pass
renderers.render_on_display = True
renderers.default = default_renderer

View File

@@ -0,0 +1,101 @@
# 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_png"
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_dir = os.path.dirname(block_vars["src_file"])
pngs = sorted(glob(os.path.join(examples_dir, "*.png")))
htmls = sorted(glob(os.path.join(examples_dir, "*.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 = ""
if not figure_paths:
return 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
"""

View File

@@ -0,0 +1,497 @@
from __future__ import absolute_import
import textwrap
import pkgutil
import copy
import os
import json
from functools import reduce
try:
from math import gcd
except ImportError:
# Python 2
from fractions import gcd
# Create Lazy sentinal object to indicate that a template should be loaded
# on-demand from package_data
Lazy = object()
# Templates configuration class
# -----------------------------
class TemplatesConfig(object):
"""
Singleton object containing the current figure templates (aka themes)
"""
def __init__(self):
# Initialize properties dict
self._templates = {}
# Initialize built-in templates
default_templates = [
"ggplot2",
"seaborn",
"simple_white",
"plotly",
"plotly_white",
"plotly_dark",
"presentation",
"xgridoff",
"ygridoff",
"gridon",
"none",
]
for template_name in default_templates:
self._templates[template_name] = Lazy
self._validator = None
self._default = None
# ### Magic methods ###
# Make this act as a dict of templates
def __len__(self):
return len(self._templates)
def __contains__(self, item):
return item in self._templates
def __iter__(self):
return iter(self._templates)
def __getitem__(self, item):
if isinstance(item, str):
template_names = item.split("+")
else:
template_names = [item]
templates = []
for template_name in template_names:
template = self._templates[template_name]
if template is Lazy:
from plotly.graph_objs.layout import Template
if template_name == "none":
# "none" is a special built-in named template that applied no defaults
template = Template(data_scatter=[{}])
self._templates[template_name] = template
else:
# Load template from package data
path = os.path.join(
"package_data", "templates", template_name + ".json"
)
template_str = pkgutil.get_data("plotly", path).decode("utf-8")
template_dict = json.loads(template_str)
template = Template(template_dict, _validate=False)
self._templates[template_name] = template
templates.append(self._templates[template_name])
return self.merge_templates(*templates)
def __setitem__(self, key, value):
self._templates[key] = self._validate(value)
def __delitem__(self, key):
# Remove template
del self._templates[key]
# Check if we need to remove it as the default
if self._default == key:
self._default = None
def _validate(self, value):
if not self._validator:
from plotly.validators.layout import TemplateValidator
self._validator = TemplateValidator()
return self._validator.validate_coerce(value)
def keys(self):
return self._templates.keys()
def items(self):
return self._templates.items()
def update(self, d={}, **kwargs):
"""
Update one or more templates from a dict or from input keyword
arguments.
Parameters
----------
d: dict
Dictionary from template names to new template values.
kwargs
Named argument value pairs where the name is a template name
and the value is a new template value.
"""
for k, v in dict(d, **kwargs).items():
self[k] = v
# ### Properties ###
@property
def default(self):
"""
The name of the default template, or None if no there is no default
If not None, the default template is automatically applied to all
figures during figure construction if no explicit template is
specified.
The names of available templates may be retrieved with:
>>> import plotly.io as pio
>>> list(pio.templates)
Returns
-------
str
"""
return self._default
@default.setter
def default(self, value):
# Validate value
# Could be a Template object, the key of a registered template,
# Or a string containing the names of multiple templates joined on
# '+' characters
self._validate(value)
self._default = value
def __repr__(self):
return """\
Templates configuration
-----------------------
Default template: {default}
Available templates:
{available}
""".format(
default=repr(self.default), available=self._available_templates_str()
)
def _available_templates_str(self):
"""
Return nicely wrapped string representation of all
available template names
"""
available = "\n".join(
textwrap.wrap(
repr(list(self)),
width=79 - 8,
initial_indent=" " * 8,
subsequent_indent=" " * 9,
)
)
return available
def merge_templates(self, *args):
"""
Merge a collection of templates into a single combined template.
Templates are process from left to right so if multiple templates
specify the same propery, the right-most template will take
precedence.
Parameters
----------
args: list of Template
Zero or more template objects (or dicts with compatible properties)
Returns
-------
template:
A combined template object
Examples
--------
>>> pio.templates.merge_templates(
... go.layout.Template(layout={'font': {'size': 20}}),
... go.layout.Template(data={'scatter': [{'mode': 'markers'}]}),
... go.layout.Template(layout={'font': {'family': 'Courier'}}))
layout.Template({
'data': {'scatter': [{'mode': 'markers', 'type': 'scatter'}]},
'layout': {'font': {'family': 'Courier', 'size': 20}}
})
"""
if args:
return reduce(self._merge_2_templates, args)
else:
from plotly.graph_objs.layout import Template
return Template()
def _merge_2_templates(self, template1, template2):
"""
Helper function for merge_templates that merges exactly two templates
Parameters
----------
template1: Template
template2: Template
Returns
-------
Template:
merged template
"""
# Validate/copy input templates
result = self._validate(template1)
other = self._validate(template2)
# Cycle traces
for trace_type in result.data:
result_traces = result.data[trace_type]
other_traces = other.data[trace_type]
if result_traces and other_traces:
lcm = (
len(result_traces)
* len(other_traces)
// gcd(len(result_traces), len(other_traces))
)
# Cycle result traces
result.data[trace_type] = result_traces * (lcm // len(result_traces))
# Cycle other traces
other.data[trace_type] = other_traces * (lcm // len(other_traces))
# Perform update
result.update(other)
return result
# Make config a singleton object
# ------------------------------
templates = TemplatesConfig()
del TemplatesConfig
# Template utilities
# ------------------
def walk_push_to_template(fig_obj, template_obj, skip):
"""
Move style properties from fig_obj to template_obj.
Parameters
----------
fig_obj: plotly.basedatatypes.BasePlotlyType
template_obj: plotly.basedatatypes.BasePlotlyType
skip: set of str
Set of names of properties to skip
"""
from _plotly_utils.basevalidators import (
CompoundValidator,
CompoundArrayValidator,
is_array,
)
for prop in list(fig_obj._props):
if prop == "template" or prop in skip:
# Avoid infinite recursion
continue
fig_val = fig_obj[prop]
template_val = template_obj[prop]
validator = fig_obj._get_validator(prop)
if isinstance(validator, CompoundValidator):
walk_push_to_template(fig_val, template_val, skip)
if not fig_val._props:
# Check if we can remove prop itself
fig_obj[prop] = None
elif isinstance(validator, CompoundArrayValidator) and fig_val:
template_elements = list(template_val)
template_element_names = [el.name for el in template_elements]
template_propdefaults = template_obj[prop[:-1] + "defaults"]
for fig_el in fig_val:
element_name = fig_el.name
if element_name:
# No properties are skipped inside a named array element
skip = set()
if fig_el.name in template_element_names:
item_index = template_element_names.index(fig_el.name)
template_el = template_elements[item_index]
walk_push_to_template(fig_el, template_el, skip)
else:
template_el = fig_el.__class__()
walk_push_to_template(fig_el, template_el, skip)
template_elements.append(template_el)
template_element_names.append(fig_el.name)
# Restore element name
# since it was pushed to template above
fig_el.name = element_name
else:
walk_push_to_template(fig_el, template_propdefaults, skip)
template_obj[prop] = template_elements
elif not validator.array_ok or not is_array(fig_val):
# Move property value from figure to template
template_obj[prop] = fig_val
try:
fig_obj[prop] = None
except ValueError:
# Property cannot be set to None, move on.
pass
def to_templated(fig, skip=("title", "text")):
"""
Return a copy of a figure where all styling properties have been moved
into the figure's template. The template property of the resulting figure
may then be used to set the default styling of other figures.
Parameters
----------
fig
Figure object or dict representing a figure
skip
A collection of names of properties to skip when moving properties to
the template. Defaults to ('title', 'text') so that the text
of figure titles, axis titles, and annotations does not become part of
the template
Examples
--------
Imports
>>> import plotly.graph_objs as go
>>> import plotly.io as pio
Construct a figure with large courier text
>>> fig = go.Figure(layout={'title': 'Figure Title',
... 'font': {'size': 20, 'family': 'Courier'},
... 'template':"none"})
>>> fig # doctest: +NORMALIZE_WHITESPACE
Figure({
'data': [],
'layout': {'font': {'family': 'Courier', 'size': 20},
'template': '...', 'title': {'text': 'Figure Title'}}
})
Convert to a figure with a template. Note how the 'font' properties have
been moved into the template property.
>>> templated_fig = pio.to_templated(fig)
>>> templated_fig.layout.template
layout.Template({
'layout': {'font': {'family': 'Courier', 'size': 20}}
})
>>> templated_fig
Figure({
'data': [], 'layout': {'template': '...', 'title': {'text': 'Figure Title'}}
})
Next create a new figure with this template
>>> fig2 = go.Figure(layout={
... 'title': 'Figure 2 Title',
... 'template': templated_fig.layout.template})
>>> fig2.layout.template
layout.Template({
'layout': {'font': {'family': 'Courier', 'size': 20}}
})
The default font in fig2 will now be size 20 Courier.
Next, register as a named template...
>>> pio.templates['large_courier'] = templated_fig.layout.template
and specify this template by name when constructing a figure.
>>> go.Figure(layout={
... 'title': 'Figure 3 Title',
... 'template': 'large_courier'}) # doctest: +ELLIPSIS
Figure(...)
Finally, set this as the default template to be applied to all new figures
>>> pio.templates.default = 'large_courier'
>>> fig = go.Figure(layout={'title': 'Figure 4 Title'})
>>> fig.layout.template
layout.Template({
'layout': {'font': {'family': 'Courier', 'size': 20}}
})
Returns
-------
go.Figure
"""
# process fig
from plotly.basedatatypes import BaseFigure
from plotly.graph_objs import Figure
if not isinstance(fig, BaseFigure):
fig = Figure(fig)
# Process skip
if not skip:
skip = set()
else:
skip = set(skip)
# Always skip uids
skip.add("uid")
# Initialize templated figure with deep copy of input figure
templated_fig = copy.deepcopy(fig)
# Handle layout
walk_push_to_template(
templated_fig.layout, templated_fig.layout.template.layout, skip=skip
)
# Handle traces
trace_type_indexes = {}
for trace in list(templated_fig.data):
template_index = trace_type_indexes.get(trace.type, 0)
# Extend template traces if necessary
template_traces = list(templated_fig.layout.template.data[trace.type])
while len(template_traces) <= template_index:
# Append empty trace
template_traces.append(trace.__class__())
# Get corresponding template trace
template_trace = template_traces[template_index]
# Perform push properties to template
walk_push_to_template(trace, template_trace, skip=skip)
# Update template traces in templated_fig
templated_fig.layout.template.data[trace.type] = template_traces
# Update trace_type_indexes
trace_type_indexes[trace.type] = template_index + 1
# Remove useless trace arrays
any_non_empty = False
for trace_type in templated_fig.layout.template.data:
traces = templated_fig.layout.template.data[trace_type]
is_empty = [trace.to_plotly_json() == {"type": trace_type} for trace in traces]
if all(is_empty):
templated_fig.layout.template.data[trace_type] = None
else:
any_non_empty = True
# Check if we can remove the data altogether key
if not any_non_empty:
templated_fig.layout.template.data = None
return templated_fig

View File

@@ -0,0 +1,52 @@
from __future__ import absolute_import
import plotly
import plotly.graph_objs as go
from plotly.offline import get_plotlyjs_version
def validate_coerce_fig_to_dict(fig, validate):
from plotly.basedatatypes import BaseFigure
if isinstance(fig, BaseFigure):
fig_dict = fig.to_dict()
elif isinstance(fig, dict):
if validate:
# This will raise an exception if fig is not a valid plotly figure
fig_dict = plotly.graph_objs.Figure(fig).to_plotly_json()
else:
fig_dict = fig
elif hasattr(fig, "to_plotly_json"):
fig_dict = fig.to_plotly_json()
else:
raise ValueError(
"""
The fig parameter must be a dict or Figure.
Received value of type {typ}: {v}""".format(
typ=type(fig), v=fig
)
)
return fig_dict
def validate_coerce_output_type(output_type):
if output_type == "Figure" or output_type == go.Figure:
cls = go.Figure
elif output_type == "FigureWidget" or (
hasattr(go, "FigureWidget") and output_type == go.FigureWidget
):
cls = go.FigureWidget
else:
raise ValueError(
"""
Invalid output type: {output_type}
Must be one of: 'Figure', 'FigureWidget'"""
)
return cls
def plotly_cdn_url(cdn_ver=get_plotlyjs_version()):
"""Return a valid plotly CDN url."""
return "https://cdn.plot.ly/plotly-{cdn_ver}.min.js".format(
cdn_ver=cdn_ver,
)

View File

@@ -0,0 +1,16 @@
from ._base_renderers import (
MimetypeRenderer,
PlotlyRenderer,
JsonRenderer,
ImageRenderer,
PngRenderer,
SvgRenderer,
PdfRenderer,
JpegRenderer,
HtmlRenderer,
ColabRenderer,
KaggleRenderer,
NotebookRenderer,
ExternalRenderer,
BrowserRenderer,
)

View File

@@ -0,0 +1,9 @@
from ._json import (
to_json,
write_json,
from_json,
read_json,
config,
to_json_plotly,
from_json_plotly,
)

View File

@@ -0,0 +1 @@
from ._kaleido import to_image, write_image, scope

View File

@@ -0,0 +1,8 @@
from ._orca import (
ensure_server,
shutdown_server,
validate_executable,
reset_status,
config,
status,
)