wg-backend-django/dell-env/lib/python3.11/site-packages/plotly/io/_base_renderers.py
2023-10-30 14:40:43 +07:00

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)."
)