done
This commit is contained in:
70
lib/python3.11/site-packages/plotly/io/__init__.py
Normal file
70
lib/python3.11/site-packages/plotly/io/__init__.py
Normal file
@ -0,0 +1,70 @@
|
||||
# ruff: noqa: F401
|
||||
|
||||
from _plotly_utils.importers import relative_import
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._kaleido import (
|
||||
to_image,
|
||||
write_image,
|
||||
write_images,
|
||||
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
|
||||
from ._kaleido import defaults, get_chrome
|
||||
|
||||
__all__ = [
|
||||
"to_image",
|
||||
"write_image",
|
||||
"write_images",
|
||||
"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",
|
||||
"defaults",
|
||||
"get_chrome",
|
||||
]
|
||||
else:
|
||||
__all__, __getattr__, __dir__ = relative_import(
|
||||
__name__,
|
||||
[".orca", ".kaleido", ".json", ".base_renderers"],
|
||||
[
|
||||
"._kaleido.to_image",
|
||||
"._kaleido.write_image",
|
||||
"._kaleido.write_images",
|
||||
"._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",
|
||||
"._kaleido.defaults",
|
||||
"._kaleido.get_chrome",
|
||||
],
|
||||
)
|
||||
|
||||
# Set default template (for < 3.7 this is done in ploty/__init__.py)
|
||||
from plotly.io import templates
|
||||
|
||||
templates._default = "plotly"
|
846
lib/python3.11/site-packages/plotly/io/_base_renderers.py
Normal file
846
lib/python3.11/site-packages/plotly/io/_base_renderers.py
Normal file
@ -0,0 +1,846 @@
|
||||
import base64
|
||||
import json
|
||||
import webbrowser
|
||||
import inspect
|
||||
import os
|
||||
from os.path import isdir
|
||||
|
||||
from plotly import optional_imports
|
||||
from plotly.io import to_json, to_image, write_image, write_html
|
||||
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 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=None):
|
||||
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=None):
|
||||
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=None):
|
||||
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=None):
|
||||
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,
|
||||
global_init=False,
|
||||
config=None,
|
||||
auto_play=False,
|
||||
post_script=None,
|
||||
animation_opts=None,
|
||||
include_plotlyjs=True,
|
||||
):
|
||||
self.config = dict(config) if config else {}
|
||||
self.auto_play = auto_play
|
||||
self.connected = connected
|
||||
self.global_init = global_init
|
||||
self.full_html = full_html
|
||||
self.animation_opts = animation_opts
|
||||
self.post_script = post_script
|
||||
self.include_plotlyjs = "cdn" if self.connected else include_plotlyjs
|
||||
|
||||
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 self.connected:
|
||||
script = """\
|
||||
<script type="text/javascript">
|
||||
{win_config}
|
||||
{mathjax_config}
|
||||
</script>
|
||||
<script type="module">import \"{plotly_cdn}\"</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}
|
||||
</script>
|
||||
<script>{script}</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
|
||||
|
||||
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=self.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,
|
||||
include_plotlyjs=False,
|
||||
):
|
||||
super(NotebookRenderer, self).__init__(
|
||||
connected=connected,
|
||||
full_html=False,
|
||||
global_init=True,
|
||||
config=config,
|
||||
auto_play=auto_play,
|
||||
post_script=post_script,
|
||||
animation_opts=animation_opts,
|
||||
include_plotlyjs=include_plotlyjs,
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
global_init=True,
|
||||
config=config,
|
||||
auto_play=auto_play,
|
||||
post_script=post_script,
|
||||
animation_opts=animation_opts,
|
||||
include_plotlyjs=False,
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
global_init=True,
|
||||
config=config,
|
||||
auto_play=auto_play,
|
||||
post_script=post_script,
|
||||
animation_opts=animation_opts,
|
||||
include_plotlyjs=False,
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
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:
|
||||
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,
|
||||
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.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 Exception: # 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)."
|
||||
)
|
19
lib/python3.11/site-packages/plotly/io/_defaults.py
Normal file
19
lib/python3.11/site-packages/plotly/io/_defaults.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Default settings for image generation
|
||||
|
||||
|
||||
class _Defaults(object):
|
||||
"""
|
||||
Class to store default settings for image generation.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.default_format = "png"
|
||||
self.default_width = 700
|
||||
self.default_height = 500
|
||||
self.default_scale = 1
|
||||
self.mathjax = None
|
||||
self.topojson = None
|
||||
self.plotlyjs = None
|
||||
|
||||
|
||||
defaults = _Defaults()
|
517
lib/python3.11/site-packages/plotly/io/_html.py
Normal file
517
lib/python3.11/site-packages/plotly/io/_html.py
Normal file
@ -0,0 +1,517 @@
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
import webbrowser
|
||||
import hashlib
|
||||
import base64
|
||||
|
||||
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
|
||||
|
||||
_json = get_module("json")
|
||||
|
||||
|
||||
def _generate_sri_hash(content):
|
||||
"""Generate SHA256 hash for SRI (Subresource Integrity)"""
|
||||
if isinstance(content, str):
|
||||
content = content.encode("utf-8")
|
||||
sha256_hash = hashlib.sha256(content).digest()
|
||||
return "sha256-" + base64.b64encode(sha256_hash).decode("utf-8")
|
||||
|
||||
|
||||
# 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 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()
|
||||
|
||||
# Init and load
|
||||
load_plotlyjs = ""
|
||||
|
||||
if include_plotlyjs == "cdn":
|
||||
# Generate SRI hash from the bundled plotly.js content
|
||||
plotlyjs_content = get_plotlyjs()
|
||||
sri_hash = _generate_sri_hash(plotlyjs_content)
|
||||
|
||||
load_plotlyjs = """\
|
||||
{win_config}
|
||||
<script charset="utf-8" src="{cdn_url}" integrity="{integrity}" crossorigin="anonymous"></script>\
|
||||
""".format(
|
||||
win_config=_window_plotly_config,
|
||||
cdn_url=plotly_cdn_url(),
|
||||
integrity=sri_hash,
|
||||
)
|
||||
|
||||
elif include_plotlyjs == "directory":
|
||||
load_plotlyjs = """\
|
||||
{win_config}
|
||||
<script charset="utf-8" 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 charset="utf-8" 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">\
|
||||
window.PLOTLYENV=window.PLOTLYENV || {{}};{base_url_line}\
|
||||
{script};\
|
||||
</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,
|
||||
script=script,
|
||||
).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 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
|
||||
-------
|
||||
None
|
||||
"""
|
||||
|
||||
# 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:
|
||||
# To use a different file encoding, pass a file descriptor
|
||||
path.write_text(html_str, "utf-8")
|
||||
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(), encoding="utf-8")
|
||||
|
||||
# Handle auto_open
|
||||
if path is not None and full_html and auto_open:
|
||||
url = path.absolute().as_uri()
|
||||
webbrowser.open(url)
|
598
lib/python3.11/site-packages/plotly/io/_json.py
Normal file
598
lib/python3.11/site-packages/plotly/io/_json.py
Normal file
@ -0,0 +1,598 @@
|
||||
import json
|
||||
import decimal
|
||||
import datetime
|
||||
import warnings
|
||||
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
|
||||
|
||||
|
||||
_swap_json = (
|
||||
("<", "\\u003c"),
|
||||
(">", "\\u003e"),
|
||||
("/", "\\u002f"),
|
||||
)
|
||||
_swap_orjson = _swap_json + (
|
||||
("\u2028", "\\u2028"),
|
||||
("\u2029", "\\u2029"),
|
||||
)
|
||||
|
||||
|
||||
def _safe(json_str, _swap):
|
||||
out = json_str
|
||||
for unsafe_char, safe_char in _swap:
|
||||
if unsafe_char in out:
|
||||
out = out.replace(unsafe_char, safe_char)
|
||||
return out
|
||||
|
||||
|
||||
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 _safe(
|
||||
json.dumps(plotly_object, cls=PlotlyJSONEncoder, **opts), _swap_json
|
||||
)
|
||||
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 _safe(
|
||||
orjson.dumps(plotly_object, option=opts).decode("utf8"), _swap_orjson
|
||||
)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
cleaned = clean_to_json_compatible(
|
||||
plotly_object,
|
||||
numpy_allowed=True,
|
||||
datetime_allowed=True,
|
||||
modules=modules,
|
||||
)
|
||||
return _safe(orjson.dumps(cleaned, option=opts).decode("utf8"), _swap_orjson)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Note: A figure converted to JSON with one version of Plotly.py may not be compatible with another version.
|
||||
|
||||
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.
|
||||
|
||||
Note: A figure converted to JSON with one version of Plotly.py may not be compatible with another version.
|
||||
|
||||
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`.
|
||||
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 or obj is pd.NA:
|
||||
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):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FutureWarning)
|
||||
# Series.dt.to_pydatetime will return Index[object]
|
||||
# https://github.com/pandas-dev/pandas/pull/52459
|
||||
dt_values = np.array(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
|
900
lib/python3.11/site-packages/plotly/io/_kaleido.py
Normal file
900
lib/python3.11/site-packages/plotly/io/_kaleido.py
Normal file
@ -0,0 +1,900 @@
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Union, List
|
||||
import importlib.metadata as importlib_metadata
|
||||
from packaging.version import Version
|
||||
import warnings
|
||||
|
||||
import plotly
|
||||
from plotly.io._utils import validate_coerce_fig_to_dict, broadcast_args_to_dicts
|
||||
from plotly.io._defaults import defaults
|
||||
|
||||
ENGINE_SUPPORT_TIMELINE = "September 2025"
|
||||
ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS = True
|
||||
|
||||
PLOTLY_GET_CHROME_ERROR_MSG = """
|
||||
|
||||
Kaleido requires Google Chrome to be installed.
|
||||
|
||||
Either download and install Chrome yourself following Google's instructions for your operating system,
|
||||
or install it from your terminal by running:
|
||||
|
||||
$ plotly_get_chrome
|
||||
|
||||
"""
|
||||
|
||||
KALEIDO_DEPRECATION_MSG = f"""
|
||||
Support for Kaleido versions less than 1.0.0 is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}.
|
||||
Please upgrade Kaleido to version 1.0.0 or greater (`pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`).
|
||||
"""
|
||||
ORCA_DEPRECATION_MSG = f"""
|
||||
Support for the Orca engine is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}.
|
||||
Please install Kaleido (`pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`) to use the Kaleido engine.
|
||||
"""
|
||||
ENGINE_PARAM_DEPRECATION_MSG = f"""
|
||||
Support for the 'engine' argument is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}.
|
||||
Kaleido will be the only supported engine at that time.
|
||||
"""
|
||||
|
||||
_KALEIDO_AVAILABLE = None
|
||||
_KALEIDO_MAJOR = None
|
||||
|
||||
|
||||
def kaleido_scope_default_warning_func(x):
|
||||
return f"""
|
||||
Use of plotly.io.kaleido.scope.{x} is deprecated and support will be removed after {ENGINE_SUPPORT_TIMELINE}.
|
||||
Please use plotly.io.defaults.{x} instead.
|
||||
"""
|
||||
|
||||
|
||||
def bad_attribute_error_msg_func(x):
|
||||
return f"""
|
||||
Attribute plotly.io.defaults.{x} is not valid.
|
||||
Also, use of plotly.io.kaleido.scope.* is deprecated and support will be removed after {ENGINE_SUPPORT_TIMELINE}.
|
||||
Please use plotly.io.defaults.* instead.
|
||||
"""
|
||||
|
||||
|
||||
def kaleido_available() -> bool:
|
||||
"""
|
||||
Returns True if any version of Kaleido is installed, otherwise False.
|
||||
"""
|
||||
global _KALEIDO_AVAILABLE
|
||||
global _KALEIDO_MAJOR
|
||||
if _KALEIDO_AVAILABLE is not None:
|
||||
return _KALEIDO_AVAILABLE
|
||||
try:
|
||||
import kaleido # noqa: F401
|
||||
|
||||
_KALEIDO_AVAILABLE = True
|
||||
except ImportError:
|
||||
_KALEIDO_AVAILABLE = False
|
||||
return _KALEIDO_AVAILABLE
|
||||
|
||||
|
||||
def kaleido_major() -> int:
|
||||
"""
|
||||
Returns the major version number of Kaleido if it is installed,
|
||||
otherwise raises a ValueError.
|
||||
"""
|
||||
global _KALEIDO_MAJOR
|
||||
if _KALEIDO_MAJOR is not None:
|
||||
return _KALEIDO_MAJOR
|
||||
if not kaleido_available():
|
||||
raise ValueError("Kaleido is not installed.")
|
||||
else:
|
||||
_KALEIDO_MAJOR = Version(importlib_metadata.version("kaleido")).major
|
||||
return _KALEIDO_MAJOR
|
||||
|
||||
|
||||
try:
|
||||
if kaleido_available() and kaleido_major() < 1:
|
||||
# Kaleido v0
|
||||
import kaleido
|
||||
from kaleido.scopes.plotly import PlotlyScope
|
||||
|
||||
# Show a deprecation warning if the old method of setting defaults is used
|
||||
class PlotlyScopeWrapper(PlotlyScope):
|
||||
def __setattr__(self, name, value):
|
||||
if name in defaults.__dict__:
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(
|
||||
kaleido_scope_default_warning_func(name),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__setattr__(name, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if hasattr(defaults, name):
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(
|
||||
kaleido_scope_default_warning_func(name),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return super().__getattr__(name)
|
||||
|
||||
# Ensure the new method of setting defaults is backwards compatible with Kaleido v0
|
||||
# DefaultsBackwardsCompatible sets the attributes on `scope` object at the same time
|
||||
# as they are set on the `defaults` object
|
||||
class DefaultsBackwardsCompatible(defaults.__class__):
|
||||
def __init__(self, scope):
|
||||
self._scope = scope
|
||||
super().__init__()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if not name == "_scope":
|
||||
if (
|
||||
hasattr(self._scope, name)
|
||||
and getattr(self._scope, name) != value
|
||||
):
|
||||
setattr(self._scope, name, value)
|
||||
super().__setattr__(name, value)
|
||||
|
||||
scope = PlotlyScopeWrapper()
|
||||
defaults = DefaultsBackwardsCompatible(scope)
|
||||
# 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:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"ignore", message=r".*scope\.mathjax.*", category=DeprecationWarning
|
||||
)
|
||||
scope.mathjax = (
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js"
|
||||
)
|
||||
else:
|
||||
# Kaleido v1
|
||||
import kaleido
|
||||
|
||||
# Show a deprecation warning if the old method of setting defaults is used
|
||||
class DefaultsWrapper:
|
||||
def __getattr__(self, name):
|
||||
if hasattr(defaults, name):
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(
|
||||
kaleido_scope_default_warning_func(name),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return getattr(defaults, name)
|
||||
else:
|
||||
raise AttributeError(bad_attribute_error_msg_func(name))
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if hasattr(defaults, name):
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(
|
||||
kaleido_scope_default_warning_func(name),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
setattr(defaults, name, value)
|
||||
else:
|
||||
raise AttributeError(bad_attribute_error_msg_func(name))
|
||||
|
||||
scope = DefaultsWrapper()
|
||||
|
||||
except ImportError:
|
||||
PlotlyScope = None
|
||||
scope = None
|
||||
|
||||
|
||||
def as_path_object(file: Union[str, Path]) -> Union[Path, None]:
|
||||
"""
|
||||
Cast the `file` argument, which may be either a string or a Path object,
|
||||
to a Path object.
|
||||
If `file` is neither a string nor a Path object, None will be returned.
|
||||
"""
|
||||
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
|
||||
return path
|
||||
|
||||
|
||||
def infer_format(path: Union[Path, None], format: Union[str, None]) -> Union[str, None]:
|
||||
if path is not None and format is None:
|
||||
ext = path.suffix
|
||||
if ext:
|
||||
format = ext.lstrip(".")
|
||||
else:
|
||||
raise ValueError(
|
||||
f"""
|
||||
Cannot infer image type from output path '{path}'.
|
||||
Please specify the type using the format parameter, or add a file extension.
|
||||
For example:
|
||||
|
||||
>>> import plotly.io as pio
|
||||
>>> pio.write_image(fig, file_path, format='png')
|
||||
"""
|
||||
)
|
||||
return format
|
||||
|
||||
|
||||
def to_image(
|
||||
fig: Union[dict, plotly.graph_objects.Figure],
|
||||
format: Union[str, None] = None,
|
||||
width: Union[int, None] = None,
|
||||
height: Union[int, None] = None,
|
||||
scale: Union[int, float, None] = None,
|
||||
validate: bool = True,
|
||||
# Deprecated
|
||||
engine: Union[str, None] = None,
|
||||
) -> bytes:
|
||||
"""
|
||||
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' (deprecated) (Requires the poppler library to be installed and on the PATH)
|
||||
|
||||
If not specified, will default to:
|
||||
- `plotly.io.defaults.default_format` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_format` if engine is "orca" (deprecated)
|
||||
|
||||
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.defaults.default_width` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_width` if engine is "orca" (deprecated)
|
||||
|
||||
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.defaults.default_height` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_height` if engine is "orca" (deprecated)
|
||||
|
||||
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.defaults.default_scale` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_scale` if engine is "orca" (deprecated)
|
||||
|
||||
validate: bool
|
||||
True if the figure should be validated before being converted to
|
||||
an image, False otherwise.
|
||||
|
||||
engine (deprecated): str
|
||||
Image export engine to use. This parameter is deprecated and Orca engine support will be
|
||||
dropped in the next major Plotly version. Until then, the following values are supported:
|
||||
- "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 is not None:
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(
|
||||
ENGINE_PARAM_DEPRECATION_MSG, DeprecationWarning, stacklevel=2
|
||||
)
|
||||
else:
|
||||
engine = "auto"
|
||||
|
||||
if engine == "auto":
|
||||
if kaleido_available():
|
||||
# Default to kaleido if available
|
||||
engine = "kaleido"
|
||||
else:
|
||||
# See if orca is available
|
||||
from ._orca import validate_executable
|
||||
|
||||
try:
|
||||
validate_executable()
|
||||
engine = "orca"
|
||||
except Exception:
|
||||
# If orca not configured properly, make sure we display the error
|
||||
# message advising the installation of kaleido
|
||||
engine = "kaleido"
|
||||
|
||||
if engine == "orca":
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(ORCA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
|
||||
# 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(f"Invalid image export engine specified: {repr(engine)}")
|
||||
|
||||
# Raise informative error message if Kaleido is not installed
|
||||
if not kaleido_available():
|
||||
raise ValueError(
|
||||
"""
|
||||
Image export using the "kaleido" engine requires the Kaleido package,
|
||||
which can be installed using pip:
|
||||
|
||||
$ pip install --upgrade kaleido
|
||||
"""
|
||||
)
|
||||
|
||||
# Convert figure to dict (and validate if requested)
|
||||
fig_dict = validate_coerce_fig_to_dict(fig, validate)
|
||||
|
||||
# Request image bytes
|
||||
if kaleido_major() > 0:
|
||||
# Kaleido v1
|
||||
# Check if trying to export to EPS format, which is not supported in Kaleido v1
|
||||
if format == "eps":
|
||||
raise ValueError(
|
||||
f"""
|
||||
EPS export is not supported by Kaleido v1. Please use SVG or PDF instead.
|
||||
You can also downgrade to Kaleido v0, but support for Kaleido v0 will be removed after {ENGINE_SUPPORT_TIMELINE}.
|
||||
To downgrade to Kaleido v0, run:
|
||||
$ pip install 'kaleido<1.0.0'
|
||||
"""
|
||||
)
|
||||
from kaleido.errors import ChromeNotFoundError
|
||||
|
||||
try:
|
||||
kopts = {}
|
||||
if defaults.plotlyjs:
|
||||
kopts["plotlyjs"] = defaults.plotlyjs
|
||||
if defaults.mathjax:
|
||||
kopts["mathjax"] = defaults.mathjax
|
||||
|
||||
# TODO: Refactor to make it possible to use a shared Kaleido instance here
|
||||
img_bytes = kaleido.calc_fig_sync(
|
||||
fig_dict,
|
||||
opts=dict(
|
||||
format=format or defaults.default_format,
|
||||
width=width or defaults.default_width,
|
||||
height=height or defaults.default_height,
|
||||
scale=scale or defaults.default_scale,
|
||||
),
|
||||
topojson=defaults.topojson,
|
||||
kopts=kopts,
|
||||
)
|
||||
except ChromeNotFoundError:
|
||||
raise RuntimeError(PLOTLY_GET_CHROME_ERROR_MSG)
|
||||
|
||||
else:
|
||||
# Kaleido v0
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(KALEIDO_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
|
||||
img_bytes = scope.transform(
|
||||
fig_dict, format=format, width=width, height=height, scale=scale
|
||||
)
|
||||
|
||||
return img_bytes
|
||||
|
||||
|
||||
def write_image(
|
||||
fig: Union[dict, plotly.graph_objects.Figure],
|
||||
file: Union[str, Path],
|
||||
format: Union[str, None] = None,
|
||||
scale: Union[int, float, None] = None,
|
||||
width: Union[int, None] = None,
|
||||
height: Union[int, None] = None,
|
||||
validate: bool = True,
|
||||
# Deprecated
|
||||
engine: Union[str, None] = "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' (deprecated) (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.defaults.default_format` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_format` if engine is "orca" (deprecated)
|
||||
|
||||
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.defaults.default_width` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_width` if engine is "orca" (deprecated)
|
||||
|
||||
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.defaults.default_height` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_height` if engine is "orca" (deprecated)
|
||||
|
||||
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.defaults.default_scale` if engine is "kaleido"
|
||||
- `plotly.io.orca.config.default_scale` if engine is "orca" (deprecated)
|
||||
|
||||
validate: bool
|
||||
True if the figure should be validated before being converted to
|
||||
an image, False otherwise.
|
||||
|
||||
engine (deprecated): str
|
||||
Image export engine to use. This parameter is deprecated and Orca engine support will be
|
||||
dropped in the next major Plotly version. Until then, the following values are supported:
|
||||
- "kaleido": Use Kaleido for image export
|
||||
- "orca": Use Orca for image export
|
||||
- "auto" (default): Use Kaleido if installed, otherwise use Orca
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
# Show Kaleido deprecation warning if needed
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
if (
|
||||
engine in {None, "auto", "kaleido"}
|
||||
and kaleido_available()
|
||||
and kaleido_major() < 1
|
||||
):
|
||||
warnings.warn(KALEIDO_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
|
||||
if engine == "orca":
|
||||
warnings.warn(ORCA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
|
||||
if engine not in {None, "auto"}:
|
||||
warnings.warn(
|
||||
ENGINE_PARAM_DEPRECATION_MSG, DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
# Try to cast `file` as a pathlib object `path`.
|
||||
path = as_path_object(file)
|
||||
|
||||
# Infer image format if not specified
|
||||
format = infer_format(path, format)
|
||||
|
||||
# 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(
|
||||
f"""
|
||||
The 'file' argument '{file}' is not a string, pathlib.Path object, or file descriptor.
|
||||
"""
|
||||
)
|
||||
else:
|
||||
# We previously succeeded in interpreting `file` as a pathlib object.
|
||||
# Now we can use `write_bytes()`.
|
||||
path.write_bytes(img_data)
|
||||
|
||||
|
||||
def write_images(
|
||||
fig: Union[
|
||||
List[Union[dict, plotly.graph_objects.Figure]],
|
||||
Union[dict, plotly.graph_objects.Figure],
|
||||
],
|
||||
file: Union[List[Union[str, Path]], Union[str, Path]],
|
||||
format: Union[List[Union[str, None]], Union[str, None]] = None,
|
||||
scale: Union[List[Union[int, float, None]], Union[int, float, None]] = None,
|
||||
width: Union[List[Union[int, None]], Union[int, None]] = None,
|
||||
height: Union[List[Union[int, None]], Union[int, None]] = None,
|
||||
validate: Union[List[bool], bool] = True,
|
||||
) -> None:
|
||||
"""
|
||||
Write multiple images to files or writeable objects. This is much faster than
|
||||
calling write_image() multiple times. This function can only be used with the Kaleido
|
||||
engine, v1.0.0 or greater.
|
||||
|
||||
This function accepts the same arguments as write_image() (minus the `engine` argument),
|
||||
except that any of the arguments may be either a single value or an iterable of values.
|
||||
If multiple arguments are iterable, they must all have the same length.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fig:
|
||||
List of figure objects or dicts representing a figure.
|
||||
Also accepts a single figure or dict representing a figure.
|
||||
|
||||
file: str, pathlib.Path, or list of (str or pathlib.Path)
|
||||
List of str or pathlib.Path objects representing local file paths to write to.
|
||||
Can also be a single str or pathlib.Path object if fig argument is
|
||||
a single figure or dict representing a figure.
|
||||
|
||||
format: str, None, or list of (str or None)
|
||||
The image format to use for exported images.
|
||||
Supported formats are:
|
||||
- 'png'
|
||||
- 'jpg' or 'jpeg'
|
||||
- 'webp'
|
||||
- 'svg'
|
||||
- 'pdf'
|
||||
|
||||
Use a list to specify formats for each figure or dict in the list
|
||||
provided to the `fig` argument.
|
||||
Specify format as a `str` to apply the same format to all exported images.
|
||||
If not specified, and the corresponding `file` argument has a file extension, then `format` will default to the
|
||||
file extension. Otherwise, will default to `plotly.io.defaults.default_format`.
|
||||
|
||||
width: int, None, or list of (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.
|
||||
|
||||
Use a list to specify widths for each figure or dict in the list
|
||||
provided to the `fig` argument.
|
||||
Specify width as an `int` to apply the same width to all exported images.
|
||||
If not specified, will default to `plotly.io.defaults.default_width`.
|
||||
|
||||
height: int, None, or list of (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.
|
||||
|
||||
Use a list to specify heights for each figure or dict in the list
|
||||
provided to the `fig` argument.
|
||||
Specify height as an `int` to apply the same height to all exported images.
|
||||
If not specified, will default to `plotly.io.defaults.default_height`.
|
||||
|
||||
scale: int, float, None, or list of (int, 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.
|
||||
|
||||
Use a list to specify scale for each figure or dict in the list
|
||||
provided to the `fig` argument.
|
||||
Specify scale as an `int` or `float` to apply the same scale to all exported images.
|
||||
If not specified, will default to `plotly.io.defaults.default_scale`.
|
||||
|
||||
validate: bool or list of bool
|
||||
True if the figure should be validated before being converted to
|
||||
an image, False otherwise.
|
||||
|
||||
Use a list to specify validation setting for each figure in the list
|
||||
provided to the `fig` argument.
|
||||
Specify validate as a boolean to apply the same validation setting to all figures.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
|
||||
# Raise informative error message if Kaleido v1 is not installed
|
||||
if not kaleido_available():
|
||||
raise ValueError(
|
||||
"""
|
||||
The `write_images()` function requires the Kaleido package,
|
||||
which can be installed using pip:
|
||||
|
||||
$ pip install --upgrade kaleido
|
||||
"""
|
||||
)
|
||||
elif kaleido_major() < 1:
|
||||
raise ValueError(
|
||||
f"""
|
||||
You have Kaleido version {Version(importlib_metadata.version("kaleido"))} installed.
|
||||
The `write_images()` function requires the Kaleido package version 1.0.0 or greater,
|
||||
which can be installed using pip:
|
||||
|
||||
$ pip install 'kaleido>=1.0.0'
|
||||
"""
|
||||
)
|
||||
|
||||
# Broadcast arguments into correct format for passing to Kaleido
|
||||
arg_dicts = broadcast_args_to_dicts(
|
||||
fig=fig,
|
||||
file=file,
|
||||
format=format,
|
||||
scale=scale,
|
||||
width=width,
|
||||
height=height,
|
||||
validate=validate,
|
||||
)
|
||||
|
||||
# For each dict:
|
||||
# - convert figures to dicts (and validate if requested)
|
||||
# - try to cast `file` as a Path object
|
||||
for d in arg_dicts:
|
||||
d["fig"] = validate_coerce_fig_to_dict(d["fig"], d["validate"])
|
||||
d["file"] = as_path_object(d["file"])
|
||||
|
||||
# Reshape arg_dicts into correct format for passing to Kaleido
|
||||
# We call infer_format() here rather than above so that the `file` argument
|
||||
# has already been cast to a Path object.
|
||||
# Also insert defaults for any missing arguments as needed
|
||||
kaleido_specs = [
|
||||
dict(
|
||||
fig=d["fig"],
|
||||
path=d["file"],
|
||||
opts=dict(
|
||||
format=infer_format(d["file"], d["format"]) or defaults.default_format,
|
||||
width=d["width"] or defaults.default_width,
|
||||
height=d["height"] or defaults.default_height,
|
||||
scale=d["scale"] or defaults.default_scale,
|
||||
),
|
||||
topojson=defaults.topojson,
|
||||
)
|
||||
for d in arg_dicts
|
||||
]
|
||||
|
||||
from kaleido.errors import ChromeNotFoundError
|
||||
|
||||
try:
|
||||
kopts = {}
|
||||
if defaults.plotlyjs:
|
||||
kopts["plotlyjs"] = defaults.plotlyjs
|
||||
if defaults.mathjax:
|
||||
kopts["mathjax"] = defaults.mathjax
|
||||
kaleido.write_fig_from_object_sync(
|
||||
kaleido_specs,
|
||||
kopts=kopts,
|
||||
)
|
||||
except ChromeNotFoundError:
|
||||
raise RuntimeError(PLOTLY_GET_CHROME_ERROR_MSG)
|
||||
|
||||
|
||||
def full_figure_for_development(
|
||||
fig: Union[dict, plotly.graph_objects.Figure],
|
||||
warn: bool = True,
|
||||
as_dict: bool = False,
|
||||
) -> Union[plotly.graph_objects.Figure, dict]:
|
||||
"""
|
||||
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 not kaleido_available():
|
||||
raise ValueError(
|
||||
"""
|
||||
Full figure generation requires the Kaleido package,
|
||||
which can be installed using pip:
|
||||
|
||||
$ pip install --upgrade kaleido
|
||||
"""
|
||||
)
|
||||
|
||||
if warn:
|
||||
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"
|
||||
)
|
||||
|
||||
if kaleido_available() and kaleido_major() > 0:
|
||||
# Kaleido v1
|
||||
bytes = kaleido.calc_fig_sync(
|
||||
fig,
|
||||
opts=dict(format="json"),
|
||||
)
|
||||
fig = json.loads(bytes.decode("utf-8"))
|
||||
else:
|
||||
# Kaleido v0
|
||||
if ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS:
|
||||
warnings.warn(
|
||||
f"Support for Kaleido versions less than 1.0.0 is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}. "
|
||||
+ "Please upgrade Kaleido to version 1.0.0 or greater (`pip install 'kaleido>=1.0.0'`).",
|
||||
DeprecationWarning,
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
def plotly_get_chrome() -> None:
|
||||
"""
|
||||
Install Google Chrome for Kaleido (Required for Plotly image export).
|
||||
This function is a command-line wrapper for `plotly.io.get_chrome()`.
|
||||
|
||||
When running from the command line, use the command `plotly_get_chrome`;
|
||||
when calling from Python code, use `plotly.io.get_chrome()`.
|
||||
"""
|
||||
|
||||
usage = """
|
||||
Usage: plotly_get_chrome [-y] [--path PATH]
|
||||
|
||||
Installs Google Chrome for Plotly image export.
|
||||
|
||||
Options:
|
||||
-y Skip confirmation prompt
|
||||
--path PATH Specify the path to install Chrome. Must be a path to an existing directory.
|
||||
--help Show this message and exit.
|
||||
"""
|
||||
|
||||
if not kaleido_available() or kaleido_major() < 1:
|
||||
raise ValueError(
|
||||
"""
|
||||
This command requires Kaleido v1.0.0 or greater.
|
||||
Install it using `pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`."
|
||||
"""
|
||||
)
|
||||
|
||||
# Handle command line arguments
|
||||
import sys
|
||||
|
||||
cli_args = sys.argv
|
||||
|
||||
# Handle "-y" flag
|
||||
cli_yes = "-y" in cli_args
|
||||
if cli_yes:
|
||||
cli_args.remove("-y")
|
||||
|
||||
# Handle "--path" flag
|
||||
chrome_install_path = None
|
||||
if "--path" in cli_args:
|
||||
path_index = cli_args.index("--path") + 1
|
||||
if path_index < len(cli_args):
|
||||
chrome_install_path = cli_args[path_index]
|
||||
cli_args.remove("--path")
|
||||
cli_args.remove(chrome_install_path)
|
||||
chrome_install_path = Path(chrome_install_path)
|
||||
|
||||
# If any arguments remain, command syntax was incorrect -- print usage and exit
|
||||
if len(cli_args) > 1:
|
||||
print(usage)
|
||||
sys.exit(1)
|
||||
|
||||
if not cli_yes:
|
||||
print(
|
||||
f"""
|
||||
Plotly will install a copy of Google Chrome to be used for generating static images of plots.
|
||||
Chrome will be installed at: {chrome_install_path}"""
|
||||
)
|
||||
response = input("Do you want to proceed? [y/n] ")
|
||||
if not response or response[0].lower() != "y":
|
||||
print("Cancelled")
|
||||
return
|
||||
print("Installing Chrome for Plotly...")
|
||||
exe_path = get_chrome(chrome_install_path)
|
||||
print("Chrome installed successfully.")
|
||||
print(f"The Chrome executable is now located at: {exe_path}")
|
||||
|
||||
|
||||
def get_chrome(path: Union[str, Path, None] = None) -> Path:
|
||||
"""
|
||||
Get the path to the Chrome executable for Kaleido.
|
||||
This function is used by the `plotly_get_chrome` command line utility.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path: str or Path or None
|
||||
The path to the directory where Chrome should be installed.
|
||||
If None, the default download path will be used.
|
||||
"""
|
||||
if not kaleido_available() or kaleido_major() < 1:
|
||||
raise ValueError(
|
||||
"""
|
||||
This command requires Kaleido v1.0.0 or greater.
|
||||
Install it using `pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`."
|
||||
"""
|
||||
)
|
||||
|
||||
# Use default download path if no path was specified
|
||||
if path:
|
||||
user_specified_path = True
|
||||
chrome_install_path = Path(path) # Ensure it's a Path object
|
||||
else:
|
||||
user_specified_path = False
|
||||
from choreographer.cli.defaults import default_download_path
|
||||
|
||||
chrome_install_path = default_download_path
|
||||
|
||||
# If install path was chosen by user, make sure there is an existing directory
|
||||
# located at chrome_install_path; otherwise fail
|
||||
if user_specified_path:
|
||||
if not chrome_install_path.exists():
|
||||
raise ValueError(
|
||||
f"""
|
||||
The specified install path '{chrome_install_path}' does not exist.
|
||||
Please specify a path to an existing directory using the --path argument,
|
||||
or omit the --path argument to use the default download path.
|
||||
"""
|
||||
)
|
||||
# Make sure the path is a directory
|
||||
if not chrome_install_path.is_dir():
|
||||
raise ValueError(
|
||||
f"""
|
||||
The specified install path '{chrome_install_path}' already exists but is not a directory.
|
||||
Please specify a path to an existing directory using the --path argument,
|
||||
or omit the --path argument to use the default download path.
|
||||
"""
|
||||
)
|
||||
|
||||
return kaleido.get_chrome_sync(path=chrome_install_path)
|
||||
|
||||
|
||||
__all__ = ["to_image", "write_image", "scope", "full_figure_for_development"]
|
1670
lib/python3.11/site-packages/plotly/io/_orca.py
Normal file
1670
lib/python3.11/site-packages/plotly/io/_orca.py
Normal file
File diff suppressed because it is too large
Load Diff
567
lib/python3.11/site-packages/plotly/io/_renderers.py
Normal file
567
lib/python3.11/site-packages/plotly/io/_renderers.py
Normal file
@ -0,0 +1,567 @@
|
||||
import textwrap
|
||||
from copy import copy
|
||||
import os
|
||||
from packaging.version import Version
|
||||
import warnings
|
||||
|
||||
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")
|
||||
|
||||
|
||||
def display_jupyter_version_warnings():
|
||||
parent_process = None
|
||||
try:
|
||||
psutil = optional_imports.get_module("psutil")
|
||||
if psutil is not None:
|
||||
parent_process = psutil.Process().parent().cmdline()[-1]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if parent_process is None:
|
||||
return
|
||||
elif "jupyter-notebook" in parent_process:
|
||||
jupyter_notebook = optional_imports.get_module("notebook")
|
||||
if jupyter_notebook is not None and jupyter_notebook.__version__ < "7":
|
||||
# Add warning about upgrading notebook
|
||||
warnings.warn(
|
||||
f"Plotly version >= 6 requires Jupyter Notebook >= 7 but you have {jupyter_notebook.__version__} installed.\n To upgrade Jupyter Notebook, please run `pip install notebook --upgrade`."
|
||||
)
|
||||
elif "jupyter-lab" in parent_process:
|
||||
jupyter_lab = optional_imports.get_module("jupyterlab")
|
||||
if jupyter_lab is not None and jupyter_lab.__version__ < "3":
|
||||
# Add warning about upgrading jupyterlab
|
||||
warnings.warn(
|
||||
f"Plotly version >= 6 requires JupyterLab >= 3 but you have {jupyter_lab.__version__} installed. To upgrade JupyterLab, please run `pip install jupyterlab --upgrade`."
|
||||
)
|
||||
|
||||
|
||||
# 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 specifying the height of the plot in pixels.
|
||||
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 Version(nbformat.__version__) < Version("4.2.0"):
|
||||
raise ValueError(
|
||||
"Mime type rendering requires nbformat>=4.2.0 but it is not installed"
|
||||
)
|
||||
|
||||
display_jupyter_version_warnings()
|
||||
|
||||
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:
|
||||
# Try to detect environment so that we can enable a useful
|
||||
# default renderer
|
||||
if not default_renderer:
|
||||
try:
|
||||
import google.colab # noqa: F401
|
||||
|
||||
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
|
||||
ipython_info = ipython.get_ipython()
|
||||
shell = ipython_info.__class__.__name__
|
||||
if not default_renderer and (shell == "TerminalInteractiveShell"):
|
||||
default_renderer = "browser"
|
||||
|
||||
# Check if we're running in a Jupyter notebook or JupyterLab
|
||||
if (
|
||||
not default_renderer
|
||||
and (shell == "ZMQInteractiveShell")
|
||||
and (type(ipython_info).__module__.startswith("ipykernel."))
|
||||
):
|
||||
default_renderer = "plotly_mimetype"
|
||||
|
||||
# Fallback to renderer combination that will work automatically
|
||||
# in the jupyter notebook, 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
|
||||
try:
|
||||
import webbrowser
|
||||
|
||||
webbrowser.get()
|
||||
default_renderer = "browser"
|
||||
except Exception:
|
||||
# Many things could have gone wrong
|
||||
# There could not be a webbrowser Python module,
|
||||
# or the module may be a dumb placeholder
|
||||
pass
|
||||
|
||||
renderers.render_on_display = True
|
||||
renderers.default = default_renderer
|
100
lib/python3.11/site-packages/plotly/io/_sg_scraper.py
Normal file
100
lib/python3.11/site-packages/plotly/io/_sg_scraper.py
Normal file
@ -0,0 +1,100 @@
|
||||
# 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.
|
||||
from glob import glob
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import plotly
|
||||
|
||||
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]
|
||||
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
|
||||
"""
|
492
lib/python3.11/site-packages/plotly/io/_templates.py
Normal file
492
lib/python3.11/site-packages/plotly/io/_templates.py
Normal file
@ -0,0 +1,492 @@
|
||||
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.validator_cache import ValidatorCache
|
||||
|
||||
self._validator = ValidatorCache.get_validator("layout", "template")
|
||||
|
||||
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
|
93
lib/python3.11/site-packages/plotly/io/_utils.py
Normal file
93
lib/python3.11/site-packages/plotly/io/_utils.py
Normal file
@ -0,0 +1,93 @@
|
||||
from typing import List
|
||||
|
||||
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 broadcast_args_to_dicts(**kwargs: dict) -> List[dict]:
|
||||
"""
|
||||
Given one or more keyword arguments which may be either a single value or a list of values,
|
||||
return a list of keyword dictionaries by broadcasting the single valuesacross all the dicts.
|
||||
If more than one item in the input is a list, all lists must be the same length.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
**kwargs: dict
|
||||
The keyword arguments
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of dicts
|
||||
A list of dictionaries
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If any of the input lists are not the same length
|
||||
"""
|
||||
# Check that all list arguments have the same length,
|
||||
# and find out what that length is
|
||||
# If there are no list arguments, length is 1
|
||||
list_lengths = [len(v) for v in tuple(kwargs.values()) if isinstance(v, list)]
|
||||
if list_lengths and len(set(list_lengths)) > 1:
|
||||
raise ValueError("All list arguments must have the same length.")
|
||||
list_length = list_lengths[0] if list_lengths else 1
|
||||
|
||||
# Expand all arguments to lists of the same length
|
||||
expanded_kwargs = {
|
||||
k: [v] * list_length if not isinstance(v, list) else v
|
||||
for k, v in kwargs.items()
|
||||
}
|
||||
# Reshape into a list of dictionaries
|
||||
# Each dictionary represents the keyword arguments for a single function call
|
||||
list_of_kwargs = [
|
||||
{k: v[i] for k, v in expanded_kwargs.items()} for i in range(list_length)
|
||||
]
|
||||
|
||||
return list_of_kwargs
|
||||
|
||||
|
||||
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,
|
||||
)
|
17
lib/python3.11/site-packages/plotly/io/base_renderers.py
Normal file
17
lib/python3.11/site-packages/plotly/io/base_renderers.py
Normal file
@ -0,0 +1,17 @@
|
||||
# ruff: noqa: F401
|
||||
from ._base_renderers import (
|
||||
MimetypeRenderer,
|
||||
PlotlyRenderer,
|
||||
JsonRenderer,
|
||||
ImageRenderer,
|
||||
PngRenderer,
|
||||
SvgRenderer,
|
||||
PdfRenderer,
|
||||
JpegRenderer,
|
||||
HtmlRenderer,
|
||||
ColabRenderer,
|
||||
KaggleRenderer,
|
||||
NotebookRenderer,
|
||||
ExternalRenderer,
|
||||
BrowserRenderer,
|
||||
)
|
10
lib/python3.11/site-packages/plotly/io/json.py
Normal file
10
lib/python3.11/site-packages/plotly/io/json.py
Normal file
@ -0,0 +1,10 @@
|
||||
# ruff: noqa: F401
|
||||
from ._json import (
|
||||
to_json,
|
||||
write_json,
|
||||
from_json,
|
||||
read_json,
|
||||
config,
|
||||
to_json_plotly,
|
||||
from_json_plotly,
|
||||
)
|
12
lib/python3.11/site-packages/plotly/io/kaleido.py
Normal file
12
lib/python3.11/site-packages/plotly/io/kaleido.py
Normal file
@ -0,0 +1,12 @@
|
||||
# ruff: noqa: F401
|
||||
from ._kaleido import (
|
||||
to_image,
|
||||
write_image,
|
||||
scope,
|
||||
kaleido_available,
|
||||
kaleido_major,
|
||||
ENABLE_KALEIDO_V0_DEPRECATION_WARNINGS,
|
||||
KALEIDO_DEPRECATION_MSG,
|
||||
ORCA_DEPRECATION_MSG,
|
||||
ENGINE_PARAM_DEPRECATION_MSG,
|
||||
)
|
9
lib/python3.11/site-packages/plotly/io/orca.py
Normal file
9
lib/python3.11/site-packages/plotly/io/orca.py
Normal file
@ -0,0 +1,9 @@
|
||||
# ruff: noqa: F401
|
||||
from ._orca import (
|
||||
ensure_server,
|
||||
shutdown_server,
|
||||
validate_executable,
|
||||
reset_status,
|
||||
config,
|
||||
status,
|
||||
)
|
Reference in New Issue
Block a user