896 lines
26 KiB
Python
896 lines
26 KiB
Python
|
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
|
||
|
try:
|
||
|
cell_number = list(ip.history_manager.get_tail(1))[0][1] + 1 if ip else 0
|
||
|
except Exception:
|
||
|
cell_number = 0
|
||
|
return "{dirname}/figure_{cell_number}.html".format(
|
||
|
dirname=self.html_directory, cell_number=cell_number
|
||
|
)
|
||
|
|
||
|
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)."
|
||
|
)
|