aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/plotly/io
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.8/site-packages/plotly/io')
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/__init__.py68
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_base_renderers.py846
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_defaults.py19
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_html.py517
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_json.py594
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_kaleido.py872
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_orca.py1670
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_renderers.py567
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_sg_scraper.py100
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_templates.py492
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_utils.py93
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/base_renderers.py17
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/json.py10
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/kaleido.py12
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/orca.py9
15 files changed, 5886 insertions, 0 deletions
diff --git a/venv/lib/python3.8/site-packages/plotly/io/__init__.py b/venv/lib/python3.8/site-packages/plotly/io/__init__.py
new file mode 100644
index 0000000..87f9c3a
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/__init__.py
@@ -0,0 +1,68 @@
+# 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
+
+ __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",
+ ]
+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",
+ ],
+ )
+
+ # Set default template (for < 3.7 this is done in ploty/__init__.py)
+ from plotly.io import templates
+
+ templates._default = "plotly"
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_base_renderers.py b/venv/lib/python3.8/site-packages/plotly/io/_base_renderers.py
new file mode 100644
index 0000000..b413aee
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_base_renderers.py
@@ -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="auto"):
+ super(PngRenderer, self).__init__(
+ mime_type="image/png",
+ b64_encode=True,
+ format="png",
+ width=width,
+ height=height,
+ scale=scale,
+ engine=engine,
+ )
+
+
+class SvgRenderer(ImageRenderer):
+ """
+ Renderer to display figures as static SVG images. This renderer requires
+ either the kaleido package or the orca command-line utility and is broadly
+ compatible across IPython environments (classic Jupyter Notebook, JupyterLab,
+ QtConsole, VSCode, PyCharm, etc) and nbconvert targets (HTML, PDF, etc.).
+
+ mime type: 'image/svg+xml'
+ """
+
+ def __init__(self, width=None, height=None, scale=None, engine="auto"):
+ super(SvgRenderer, self).__init__(
+ mime_type="image/svg+xml",
+ b64_encode=False,
+ format="svg",
+ width=width,
+ height=height,
+ scale=scale,
+ engine=engine,
+ )
+
+
+class JpegRenderer(ImageRenderer):
+ """
+ Renderer to display figures as static JPEG images. This renderer requires
+ either the kaleido package or the orca command-line utility and is broadly
+ compatible across IPython environments (classic Jupyter Notebook, JupyterLab,
+ QtConsole, VSCode, PyCharm, etc) and nbconvert targets (HTML, PDF, etc.).
+
+ mime type: 'image/jpeg'
+ """
+
+ def __init__(self, width=None, height=None, scale=None, engine="auto"):
+ super(JpegRenderer, self).__init__(
+ mime_type="image/jpeg",
+ b64_encode=True,
+ format="jpg",
+ width=width,
+ height=height,
+ scale=scale,
+ engine=engine,
+ )
+
+
+class PdfRenderer(ImageRenderer):
+ """
+ Renderer to display figures as static PDF images. This renderer requires
+ either the kaleido package or the orca command-line utility and is compatible
+ with JupyterLab and the LaTeX-based nbconvert export to PDF.
+
+ mime type: 'application/pdf'
+ """
+
+ def __init__(self, width=None, height=None, scale=None, engine="auto"):
+ super(PdfRenderer, self).__init__(
+ mime_type="application/pdf",
+ b64_encode=True,
+ format="pdf",
+ width=width,
+ height=height,
+ scale=scale,
+ engine=engine,
+ )
+
+
+# HTML
+# Build script to set global PlotlyConfig object. This must execute before
+# plotly.js is loaded.
+_window_plotly_config = """\
+window.PlotlyConfig = {MathJaxConfig: 'local'};"""
+
+_mathjax_config = """\
+if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}"""
+
+
+class HtmlRenderer(MimetypeRenderer):
+ """
+ Base class for all HTML mime type renderers
+
+ mime type: 'text/html'
+ """
+
+ def __init__(
+ self,
+ connected=False,
+ full_html=False,
+ 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)."
+ )
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_defaults.py b/venv/lib/python3.8/site-packages/plotly/io/_defaults.py
new file mode 100644
index 0000000..c36530c
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_defaults.py
@@ -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()
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_html.py b/venv/lib/python3.8/site-packages/plotly/io/_html.py
new file mode 100644
index 0000000..3e7b89c
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_html.py
@@ -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)
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_json.py b/venv/lib/python3.8/site-packages/plotly/io/_json.py
new file mode 100644
index 0000000..e4324d1
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_json.py
@@ -0,0 +1,594 @@
+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
+
+ Parameters
+ ----------
+ fig:
+ Figure object or dict representing a figure
+
+ file: str or writeable
+ A string representing a local file path or a writeable object
+ (e.g. a pathlib.Path object or an open file descriptor)
+
+ pretty: bool (default False)
+ True if JSON representation should be pretty-printed, False if
+ representation should be as compact as possible.
+
+ remove_uids: bool (default True)
+ True if trace UIDs should be omitted from the JSON representation
+
+ engine: str (default None)
+ The JSON encoding engine to use. One of:
+ - "json" for an engine based on the built-in Python json module
+ - "orjson" for a faster engine that requires the orjson package
+ - "auto" for the "orjson" engine if available, otherwise "json"
+ If not specified, the default engine is set to the current value of
+ plotly.io.json.config.default_engine.
+ Returns
+ -------
+ None
+ """
+
+ # Get JSON string
+ # ---------------
+ # Pass through validate argument and let to_json handle validation logic
+ json_str = to_json(
+ fig, validate=validate, pretty=pretty, remove_uids=remove_uids, engine=engine
+ )
+
+ # Try to cast `file` as a pathlib object `path`.
+ # ----------------------------------------------
+ if isinstance(file, str):
+ # Use the standard Path constructor to make a pathlib object.
+ path = Path(file)
+ elif isinstance(file, Path):
+ # `file` is already a Path object.
+ path = file
+ else:
+ # We could not make a Path object out of file. Either `file` is an open file
+ # descriptor with a `write()` method or it's an invalid object.
+ path = None
+
+ # Open file
+ # ---------
+ if path is None:
+ # We previously failed to make sense of `file` as a pathlib object.
+ # Attempt to write to `file` as an open file descriptor.
+ try:
+ file.write(json_str)
+ return
+ except AttributeError:
+ pass
+ raise ValueError(
+ """
+The 'file' argument '{file}' is not a string, pathlib.Path object, or file descriptor.
+""".format(file=file)
+ )
+ else:
+ # We previously succeeded in interpreting `file` as a pathlib object.
+ # Now we can use `write_bytes()`.
+ path.write_text(json_str)
+
+
+def from_json_plotly(value, engine=None):
+ """
+ Parse JSON string using the specified JSON engine
+
+ Parameters
+ ----------
+ value: str or bytes
+ A JSON string or bytes object
+
+ engine: str (default None)
+ The JSON decoding engine to use. One of:
+ - if "json", parse JSON using built in json module
+ - if "orjson", parse using the faster orjson module, requires the orjson
+ package
+ - if "auto" use orjson module if available, otherwise use the json module
+
+ If not specified, the default engine is set to the current value of
+ plotly.io.json.config.default_engine.
+
+ Returns
+ -------
+ dict
+
+ See Also
+ --------
+ from_json_plotly : Parse JSON with plotly conventions into a dict
+ """
+ orjson = get_module("orjson", should_load=True)
+
+ # Validate value
+ # --------------
+ if not isinstance(value, (str, bytes)):
+ raise ValueError(
+ """
+from_json_plotly requires a string or bytes argument but received value of type {typ}
+ Received value: {value}""".format(typ=type(value), value=value)
+ )
+
+ # Determine json engine
+ if engine is None:
+ engine = config.default_engine
+
+ if engine == "auto":
+ if orjson is not None:
+ engine = "orjson"
+ else:
+ engine = "json"
+ elif engine not in ["orjson", "json"]:
+ raise ValueError("Invalid json engine: %s" % engine)
+
+ if engine == "orjson":
+ JsonConfig.validate_orjson()
+ # orjson handles bytes input natively
+ value_dict = orjson.loads(value)
+ else:
+ # decode bytes to str for built-in json module
+ if isinstance(value, bytes):
+ value = value.decode("utf-8")
+ value_dict = json.loads(value)
+
+ return value_dict
+
+
+def from_json(value, output_type="Figure", skip_invalid=False, engine=None):
+ """
+ Construct a figure from a JSON string
+
+ Parameters
+ ----------
+ value: str or bytes
+ String or bytes object containing the JSON representation of a figure
+
+ output_type: type or str (default 'Figure')
+ The output figure type or type name.
+ One of: graph_objs.Figure, 'Figure', graph_objs.FigureWidget, 'FigureWidget'
+
+ skip_invalid: bool (default False)
+ False if invalid figure properties should result in an exception.
+ True if invalid figure properties should be silently ignored.
+
+ engine: str (default None)
+ The JSON decoding engine to use. One of:
+ - if "json", parse JSON using built in json module
+ - if "orjson", parse using the faster orjson module, requires the orjson
+ package
+ - if "auto" use orjson module if available, otherwise use the json module
+
+ If not specified, the default engine is set to the current value of
+ plotly.io.json.config.default_engine.
+
+ Raises
+ ------
+ ValueError
+ if value is not a string, or if skip_invalid=False and value contains
+ invalid figure properties
+
+ Returns
+ -------
+ Figure or FigureWidget
+ """
+
+ # Decode JSON
+ # -----------
+ fig_dict = from_json_plotly(value, engine=engine)
+
+ # Validate coerce output type
+ # ---------------------------
+ cls = validate_coerce_output_type(output_type)
+
+ # Create and return figure
+ # ------------------------
+ fig = cls(fig_dict, skip_invalid=skip_invalid)
+ return fig
+
+
+def read_json(file, output_type="Figure", skip_invalid=False, engine=None):
+ """
+ Construct a figure from the JSON contents of a local file or readable
+ Python object
+
+ Parameters
+ ----------
+ file: str or readable
+ A string containing the path to a local file or a read-able Python
+ object (e.g. a pathlib.Path object or an open file descriptor)
+
+ output_type: type or str (default 'Figure')
+ The output figure type or type name.
+ One of: graph_objs.Figure, 'Figure', graph_objs.FigureWidget, 'FigureWidget'
+
+ skip_invalid: bool (default False)
+ False if invalid figure properties should result in an exception.
+ True if invalid figure properties should be silently ignored.
+
+ engine: str (default None)
+ The JSON decoding engine to use. One of:
+ - if "json", parse JSON using built in json module
+ - if "orjson", parse using the faster orjson module, requires the orjson
+ package
+ - if "auto" use orjson module if available, otherwise use the json module
+
+ If not specified, the default engine is set to the current value of
+ plotly.io.json.config.default_engine.
+
+ Returns
+ -------
+ Figure or FigureWidget
+ """
+
+ # Try to cast `file` as a pathlib object `path`.
+ 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
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_kaleido.py b/venv/lib/python3.8/site-packages/plotly/io/_kaleido.py
new file mode 100644
index 0000000..6775c33
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_kaleido.py
@@ -0,0 +1,872 @@
+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 get_chrome() -> None:
+ """
+ Install Google Chrome for Kaleido (Required for Plotly image export).
+ This function can be run from the command line using the command `plotly_get_chrome`
+ defined in pyproject.toml
+ """
+
+ 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
+ user_specified_path = False
+ 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)
+ user_specified_path = True
+ else:
+ 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.
+"""
+ )
+
+ # 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 = kaleido.get_chrome_sync(path=chrome_install_path)
+ print("Chrome installed successfully.")
+ print(f"The Chrome executable is now located at: {exe_path}")
+
+
+__all__ = ["to_image", "write_image", "scope", "full_figure_for_development"]
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_orca.py b/venv/lib/python3.8/site-packages/plotly/io/_orca.py
new file mode 100644
index 0000000..2984210
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_orca.py
@@ -0,0 +1,1670 @@
+import atexit
+import functools
+import json
+import os
+import random
+import socket
+import subprocess
+import sys
+import threading
+import time
+import warnings
+from contextlib import contextmanager
+from copy import copy
+from pathlib import Path
+from shutil import which
+
+import plotly
+from plotly.files import PLOTLY_DIR, ensure_writable_plotly_dir
+from plotly.io._utils import validate_coerce_fig_to_dict
+from plotly.optional_imports import get_module
+
+psutil = get_module("psutil")
+
+# Valid image format constants
+# ----------------------------
+valid_formats = ("png", "jpeg", "webp", "svg", "pdf", "eps")
+format_conversions = {fmt: fmt for fmt in valid_formats}
+format_conversions.update({"jpg": "jpeg"})
+
+
+# Utility functions
+# -----------------
+def raise_format_value_error(val):
+ raise ValueError(
+ """
+Invalid value of type {typ} receive as an image format specification.
+ Received value: {v}
+
+An image format must be specified as one of the following string values:
+ {valid_formats}""".format(
+ typ=type(val), v=val, valid_formats=sorted(format_conversions.keys())
+ )
+ )
+
+
+def validate_coerce_format(fmt):
+ """
+ Validate / coerce a user specified image format, and raise an informative
+ exception if format is invalid.
+
+ Parameters
+ ----------
+ fmt
+ A value that may or may not be a valid image format string.
+
+ Returns
+ -------
+ str or None
+ A valid image format string as supported by orca. This may not
+ be identical to the input image designation. For example,
+ the resulting string will always be lower case and 'jpg' is
+ converted to 'jpeg'.
+
+ If the input format value is None, then no exception is raised and
+ None is returned.
+
+ Raises
+ ------
+ ValueError
+ if the input `fmt` cannot be interpreted as a valid image format.
+ """
+
+ # Let None pass through
+ if fmt is None:
+ return None
+
+ # Check format type
+ if not isinstance(fmt, str) or not fmt:
+ raise_format_value_error(fmt)
+
+ # Make lower case
+ fmt = fmt.lower()
+
+ # Remove leading period, if any.
+ # For example '.png' is accepted and converted to 'png'
+ if fmt[0] == ".":
+ fmt = fmt[1:]
+
+ # Check string value
+ if fmt not in format_conversions:
+ raise_format_value_error(fmt)
+
+ # Return converted string specification
+ return format_conversions[fmt]
+
+
+def find_open_port():
+ """
+ Use the socket module to find an open port.
+
+ Returns
+ -------
+ int
+ An open port
+ """
+ s = socket.socket()
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.bind(("", 0))
+ _, port = s.getsockname()
+ s.close()
+
+ return port
+
+
+def retry(min_wait=5, max_wait=10, max_delay=60000):
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ start_time = time.time()
+
+ while True:
+ try:
+ return func(*args, **kwargs)
+ except Exception as e:
+ elapsed_time = time.time() - start_time
+ if elapsed_time * 1000 >= max_delay:
+ raise TimeoutError(
+ f"Retry limit of {max_delay} milliseconds reached."
+ ) from e
+
+ wait_time = random.uniform(min_wait, max_wait)
+ print(f"Retrying in {wait_time:.2f} seconds due to {e}...")
+ time.sleep(wait_time)
+
+ return wrapper
+
+ return decorator
+
+
+# Orca configuration class
+# ------------------------
+class OrcaConfig(object):
+ """
+ Singleton object containing the current user defined configuration
+ properties for orca.
+
+ These parameters may optionally be saved to the user's ~/.plotly
+ directory using the `save` method, in which case they are automatically
+ restored in future sessions.
+ """
+
+ def __init__(self):
+ # Initialize properties dict
+ self._props = {}
+
+ # Compute absolute path to the 'plotly/package_data/' directory
+ root_dir = os.path.dirname(os.path.abspath(plotly.__file__))
+ self.package_dir = os.path.join(root_dir, "package_data")
+
+ # Load pre-existing configuration
+ self.reload(warn=False)
+
+ # Compute constants
+ plotlyjs = os.path.join(self.package_dir, "plotly.min.js")
+ self._constants = {
+ "plotlyjs": plotlyjs,
+ "config_file": os.path.join(PLOTLY_DIR, ".orca"),
+ }
+
+ def restore_defaults(self, reset_server=True):
+ """
+ Reset all orca configuration properties to their default values
+ """
+ self._props = {}
+
+ if reset_server:
+ # Server must restart before setting is active
+ reset_status()
+
+ def update(self, d={}, **kwargs):
+ """
+ Update one or more properties from a dict or from input keyword
+ arguments.
+
+ Parameters
+ ----------
+ d: dict
+ Dictionary from property names to new property values.
+
+ kwargs
+ Named argument value pairs where the name is a configuration
+ property name and the value is the new property value.
+
+ Returns
+ -------
+ None
+
+ Examples
+ --------
+ Update configuration properties using a dictionary
+
+ >>> import plotly.io as pio
+ >>> pio.orca.config.update({'timeout': 30, 'default_format': 'svg'})
+
+ Update configuration properties using keyword arguments
+
+ >>> pio.orca.config.update(timeout=30, default_format='svg'})
+ """
+ # Combine d and kwargs
+ if not isinstance(d, dict):
+ raise ValueError(
+ """
+The first argument to update must be a dict, \
+but received value of type {typ}l
+ Received value: {val}""".format(typ=type(d), val=d)
+ )
+
+ updates = copy(d)
+ updates.update(kwargs)
+
+ # Validate keys
+ for k in updates:
+ if k not in self._props:
+ raise ValueError("Invalid property name: {k}".format(k=k))
+
+ # Apply keys
+ for k, v in updates.items():
+ setattr(self, k, v)
+
+ def reload(self, warn=True):
+ """
+ Reload orca settings from ~/.plotly/.orca, if any.
+
+ Note: Settings are loaded automatically when plotly is imported.
+ This method is only needed if the setting are changed by some outside
+ process (e.g. a text editor) during an interactive session.
+
+ Parameters
+ ----------
+ warn: bool
+ If True, raise informative warnings if settings cannot be restored.
+ If False, do not raise warnings if setting cannot be restored.
+
+ Returns
+ -------
+ None
+ """
+ if os.path.exists(self.config_file):
+ # ### Load file into a string ###
+ try:
+ with open(self.config_file, "r") as f:
+ orca_str = f.read()
+ except Exception:
+ if warn:
+ warnings.warn(
+ """\
+Unable to read orca configuration file at {path}""".format(path=self.config_file)
+ )
+ return
+
+ # ### Parse as JSON ###
+ try:
+ orca_props = json.loads(orca_str)
+ except ValueError:
+ if warn:
+ warnings.warn(
+ """\
+Orca configuration file at {path} is not valid JSON""".format(path=self.config_file)
+ )
+ return
+
+ # ### Update _props ###
+ for k, v in orca_props.items():
+ self._props[k] = v
+
+ elif warn:
+ warnings.warn(
+ """\
+Orca configuration file at {path} not found""".format(path=self.config_file)
+ )
+
+ def save(self):
+ """
+ Attempt to save current settings to disk, so that they are
+ automatically restored for future sessions.
+
+ This operation requires write access to the path returned by
+ in the `config_file` property.
+
+ Returns
+ -------
+ None
+ """
+ if ensure_writable_plotly_dir():
+ with open(self.config_file, "w") as f:
+ json.dump(self._props, f, indent=4)
+ else:
+ warnings.warn(
+ """\
+Failed to write orca configuration file at '{path}'""".format(path=self.config_file)
+ )
+
+ @property
+ def server_url(self):
+ """
+ The server URL to use for an external orca server, or None if orca
+ should be managed locally
+
+ Overrides executable, port, timeout, mathjax, topojson,
+ and mapbox_access_token
+
+ Returns
+ -------
+ str or None
+ """
+ return self._props.get("server_url", None)
+
+ @server_url.setter
+ def server_url(self, val):
+ if val is None:
+ self._props.pop("server_url", None)
+ return
+ if not isinstance(val, str):
+ raise ValueError(
+ """
+The server_url property must be a string, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+
+ if not val.startswith("http://") and not val.startswith("https://"):
+ val = "http://" + val
+
+ shutdown_server()
+ self.executable = None
+ self.port = None
+ self.timeout = None
+ self.mathjax = None
+ self.topojson = None
+ self.mapbox_access_token = None
+ self._props["server_url"] = val
+
+ @property
+ def port(self):
+ """
+ The specific port to use to communicate with the orca server, or
+ None if the port is to be chosen automatically.
+
+ If an orca server is active, the port in use is stored in the
+ plotly.io.orca.status.port property.
+
+ Returns
+ -------
+ int or None
+ """
+ return self._props.get("port", None)
+
+ @port.setter
+ def port(self, val):
+ if val is None:
+ self._props.pop("port", None)
+ return
+ if not isinstance(val, int):
+ raise ValueError(
+ """
+The port property must be an integer, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+
+ self._props["port"] = val
+
+ @property
+ def executable(self):
+ """
+ The name or full path of the orca executable.
+
+ - If a name (e.g. 'orca'), then it should be the name of an orca
+ executable on the PATH. The directories on the PATH can be
+ displayed by running the following command:
+
+ >>> import os
+ >>> print(os.environ.get('PATH').replace(os.pathsep, os.linesep))
+
+ - If a full path (e.g. '/path/to/orca'), then
+ it should be the full path to an orca executable. In this case
+ the executable does not need to reside on the PATH.
+
+ If an orca server has been validated, then the full path to the
+ validated orca executable is stored in the
+ plotly.io.orca.status.executable property.
+
+ Returns
+ -------
+ str
+ """
+ executable_list = self._props.get("executable_list", ["orca"])
+ if executable_list is None:
+ return None
+ else:
+ return " ".join(executable_list)
+
+ @executable.setter
+ def executable(self, val):
+ if val is None:
+ self._props.pop("executable", None)
+ else:
+ if not isinstance(val, str):
+ raise ValueError(
+ """
+The executable property must be a string, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ if isinstance(val, str):
+ val = [val]
+ self._props["executable_list"] = val
+
+ # Server and validation must restart before setting is active
+ reset_status()
+
+ @property
+ def timeout(self):
+ """
+ The number of seconds of inactivity required before the orca server
+ is shut down.
+
+ For example, if timeout is set to 20, then the orca
+ server will shutdown once is has not been used for at least
+ 20 seconds. If timeout is set to None, then the server will not be
+ automatically shut down due to inactivity.
+
+ Regardless of the value of timeout, a running orca server may be
+ manually shut down like this:
+
+ >>> import plotly.io as pio
+ >>> pio.orca.shutdown_server()
+
+ Returns
+ -------
+ int or float or None
+ """
+ return self._props.get("timeout", None)
+
+ @timeout.setter
+ def timeout(self, val):
+ if val is None:
+ self._props.pop("timeout", None)
+ else:
+ if not isinstance(val, (int, float)):
+ raise ValueError(
+ """
+The timeout property must be a number, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["timeout"] = val
+
+ # Server must restart before setting is active
+ shutdown_server()
+
+ @property
+ def default_width(self):
+ """
+ The default width to use on image export. This value is only
+ applied if no width value is supplied to the plotly.io
+ to_image or write_image functions.
+
+ Returns
+ -------
+ int or None
+ """
+ return self._props.get("default_width", None)
+
+ @default_width.setter
+ def default_width(self, val):
+ if val is None:
+ self._props.pop("default_width", None)
+ return
+ if not isinstance(val, int):
+ raise ValueError(
+ """
+The default_width property must be an int, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["default_width"] = val
+
+ @property
+ def default_height(self):
+ """
+ The default height to use on image export. This value is only
+ applied if no height value is supplied to the plotly.io
+ to_image or write_image functions.
+
+ Returns
+ -------
+ int or None
+ """
+ return self._props.get("default_height", None)
+
+ @default_height.setter
+ def default_height(self, val):
+ if val is None:
+ self._props.pop("default_height", None)
+ return
+ if not isinstance(val, int):
+ raise ValueError(
+ """
+The default_height property must be an int, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["default_height"] = val
+
+ @property
+ def default_format(self):
+ """
+ The default image format to use on image export.
+
+ Valid image formats strings are:
+ - 'png'
+ - 'jpg' or 'jpeg'
+ - 'webp'
+ - 'svg'
+ - 'pdf'
+ - 'eps' (Requires the poppler library to be installed)
+
+ This value is only applied if no format value is supplied to the
+ plotly.io to_image or write_image functions.
+
+ Returns
+ -------
+ str or None
+ """
+ return self._props.get("default_format", "png")
+
+ @default_format.setter
+ def default_format(self, val):
+ if val is None:
+ self._props.pop("default_format", None)
+ return
+
+ val = validate_coerce_format(val)
+ self._props["default_format"] = val
+
+ @property
+ def default_scale(self):
+ """
+ The default image scaling factor to use on image export.
+ This value is only applied if no scale value is supplied to the
+ plotly.io to_image or write_image functions.
+
+ Returns
+ -------
+ int or None
+ """
+ return self._props.get("default_scale", 1)
+
+ @default_scale.setter
+ def default_scale(self, val):
+ if val is None:
+ self._props.pop("default_scale", None)
+ return
+ if not isinstance(val, (int, float)):
+ raise ValueError(
+ """
+The default_scale property must be a number, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["default_scale"] = val
+
+ @property
+ def topojson(self):
+ """
+ Path to the topojson files needed to render choropleth traces.
+
+ If None, topojson files from the plot.ly CDN are used.
+
+ Returns
+ -------
+ str
+ """
+ return self._props.get("topojson", None)
+
+ @topojson.setter
+ def topojson(self, val):
+ if val is None:
+ self._props.pop("topojson", None)
+ else:
+ if not isinstance(val, str):
+ raise ValueError(
+ """
+The topojson property must be a string, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["topojson"] = val
+
+ # Server must restart before setting is active
+ shutdown_server()
+
+ @property
+ def mathjax(self):
+ """
+ Path to the MathJax bundle needed to render LaTeX characters
+
+ Returns
+ -------
+ str
+ """
+ return self._props.get(
+ "mathjax",
+ "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js",
+ )
+
+ @mathjax.setter
+ def mathjax(self, val):
+ if val is None:
+ self._props.pop("mathjax", None)
+ else:
+ if not isinstance(val, str):
+ raise ValueError(
+ """
+The mathjax property must be a string, but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["mathjax"] = val
+
+ # Server must restart before setting is active
+ shutdown_server()
+
+ @property
+ def mapbox_access_token(self):
+ """
+ Mapbox access token required to render mapbox traces.
+
+ Returns
+ -------
+ str
+ """
+ return self._props.get("mapbox_access_token", None)
+
+ @mapbox_access_token.setter
+ def mapbox_access_token(self, val):
+ if val is None:
+ self._props.pop("mapbox_access_token", None)
+ else:
+ if not isinstance(val, str):
+ raise ValueError(
+ """
+The mapbox_access_token property must be a string, \
+but received value of type {typ}.
+ Received value: {val}""".format(typ=type(val), val=val)
+ )
+ self._props["mapbox_access_token"] = val
+
+ # Server must restart before setting is active
+ shutdown_server()
+
+ @property
+ def use_xvfb(self):
+ dflt = "auto"
+ return self._props.get("use_xvfb", dflt)
+
+ @use_xvfb.setter
+ def use_xvfb(self, val):
+ valid_vals = [True, False, "auto"]
+ if val is None:
+ self._props.pop("use_xvfb", None)
+ else:
+ if val not in valid_vals:
+ raise ValueError(
+ """
+The use_xvfb property must be one of {valid_vals}
+ Received value of type {typ}: {val}""".format(
+ valid_vals=valid_vals, typ=type(val), val=repr(val)
+ )
+ )
+
+ self._props["use_xvfb"] = val
+
+ # Server and validation must restart before setting is active
+ reset_status()
+
+ @property
+ def plotlyjs(self):
+ """
+ The plotly.js bundle being used for image rendering.
+
+ Returns
+ -------
+ str
+ """
+ return self._constants.get("plotlyjs", None)
+
+ @property
+ def config_file(self):
+ """
+ Path to orca configuration file
+
+ Using the `plotly.io.config.save()` method will save the current
+ configuration settings to this file. Settings in this file are
+ restored at the beginning of each sessions.
+
+ Returns
+ -------
+ str
+ """
+ return os.path.join(PLOTLY_DIR, ".orca")
+
+ def __repr__(self):
+ """
+ Display a nice representation of the current orca configuration.
+ """
+ return """\
+orca configuration
+------------------
+ server_url: {server_url}
+ executable: {executable}
+ port: {port}
+ timeout: {timeout}
+ default_width: {default_width}
+ default_height: {default_height}
+ default_scale: {default_scale}
+ default_format: {default_format}
+ mathjax: {mathjax}
+ topojson: {topojson}
+ mapbox_access_token: {mapbox_access_token}
+ use_xvfb: {use_xvfb}
+
+constants
+---------
+ plotlyjs: {plotlyjs}
+ config_file: {config_file}
+
+""".format(
+ server_url=self.server_url,
+ port=self.port,
+ executable=self.executable,
+ timeout=self.timeout,
+ default_width=self.default_width,
+ default_height=self.default_height,
+ default_scale=self.default_scale,
+ default_format=self.default_format,
+ mathjax=self.mathjax,
+ topojson=self.topojson,
+ mapbox_access_token=self.mapbox_access_token,
+ plotlyjs=self.plotlyjs,
+ config_file=self.config_file,
+ use_xvfb=self.use_xvfb,
+ )
+
+
+# Make config a singleton object
+# ------------------------------
+config = OrcaConfig()
+del OrcaConfig
+
+
+# Orca status class
+# ------------------------
+class OrcaStatus(object):
+ """
+ Class to store information about the current status of the orca server.
+ """
+
+ _props = {
+ "state": "unvalidated", # or 'validated' or 'running'
+ "executable_list": None,
+ "version": None,
+ "pid": None,
+ "port": None,
+ "command": None,
+ }
+
+ @property
+ def state(self):
+ """
+ A string representing the state of the orca server process
+
+ One of:
+ - unvalidated: The orca executable has not yet been searched for or
+ tested to make sure its valid.
+ - validated: The orca executable has been located and tested for
+ validity, but it is not running.
+ - running: The orca server process is currently running.
+ """
+ return self._props["state"]
+
+ @property
+ def executable(self):
+ """
+ If the `state` property is 'validated' or 'running', this property
+ contains the full path to the orca executable.
+
+ This path can be specified explicitly by setting the `executable`
+ property of the `plotly.io.orca.config` object.
+
+ This property will be None if the `state` is 'unvalidated'.
+ """
+ executable_list = self._props["executable_list"]
+ if executable_list is None:
+ return None
+ else:
+ return " ".join(executable_list)
+
+ @property
+ def version(self):
+ """
+ If the `state` property is 'validated' or 'running', this property
+ contains the version of the validated orca executable.
+
+ This property will be None if the `state` is 'unvalidated'.
+ """
+ return self._props["version"]
+
+ @property
+ def pid(self):
+ """
+ The process id of the orca server process, if any. This property
+ will be None if the `state` is not 'running'.
+ """
+ return self._props["pid"]
+
+ @property
+ def port(self):
+ """
+ The port number that the orca server process is listening to, if any.
+ This property will be None if the `state` is not 'running'.
+
+ This port can be specified explicitly by setting the `port`
+ property of the `plotly.io.orca.config` object.
+ """
+ return self._props["port"]
+
+ @property
+ def command(self):
+ """
+ The command arguments used to launch the running orca server, if any.
+ This property will be None if the `state` is not 'running'.
+ """
+ return self._props["command"]
+
+ def __repr__(self):
+ """
+ Display a nice representation of the current orca server status.
+ """
+ return """\
+orca status
+-----------
+ state: {state}
+ executable: {executable}
+ version: {version}
+ port: {port}
+ pid: {pid}
+ command: {command}
+
+""".format(
+ executable=self.executable,
+ version=self.version,
+ port=self.port,
+ pid=self.pid,
+ state=self.state,
+ command=self.command,
+ )
+
+
+# Make status a singleton object
+# ------------------------------
+status = OrcaStatus()
+del OrcaStatus
+
+
+@contextmanager
+def orca_env():
+ """
+ Context manager to clear and restore environment variables that are
+ problematic for orca to function properly
+
+ NODE_OPTIONS: When this variable is set, orca <v1.2 will have a
+ segmentation fault due to an electron bug.
+ See: https://github.com/electron/electron/issues/12695
+
+ ELECTRON_RUN_AS_NODE: When this environment variable is set the call
+ to orca is transformed into a call to nodejs.
+ See https://github.com/plotly/orca/issues/149#issuecomment-443506732
+ """
+ clear_env_vars = ["NODE_OPTIONS", "ELECTRON_RUN_AS_NODE", "LD_PRELOAD"]
+ orig_env_vars = {}
+
+ try:
+ # Clear and save
+ orig_env_vars.update(
+ {var: os.environ.pop(var) for var in clear_env_vars if var in os.environ}
+ )
+ yield
+ finally:
+ # Restore
+ for var, val in orig_env_vars.items():
+ os.environ[var] = val
+
+
+# Public orca server interaction functions
+# ----------------------------------------
+def validate_executable():
+ """
+ Attempt to find and validate the orca executable specified by the
+ `plotly.io.orca.config.executable` property.
+
+ If the `plotly.io.orca.status.state` property is 'validated' or 'running'
+ then this function does nothing.
+
+ How it works:
+ - First, it searches the system PATH for an executable that matches the
+ name or path specified in the `plotly.io.orca.config.executable`
+ property.
+ - Then it runs the executable with the `--help` flag to make sure
+ it's the plotly orca executable
+ - Then it runs the executable with the `--version` flag to check the
+ orca version.
+
+ If all of these steps are successful then the `status.state` property
+ is set to 'validated' and the `status.executable` and `status.version`
+ properties are populated
+
+ Returns
+ -------
+ None
+ """
+ # Check state
+ # -----------
+ if status.state != "unvalidated":
+ # Nothing more to do
+ return
+
+ # Initialize error messages
+ # -------------------------
+ install_location_instructions = """\
+If you haven't installed orca yet, you can do so using conda as follows:
+
+ $ conda install -c plotly plotly-orca
+
+Alternatively, see other installation methods in the orca project README at
+https://github.com/plotly/orca
+
+After installation is complete, no further configuration should be needed.
+
+If you have installed orca, then for some reason plotly.py was unable to
+locate it. In this case, set the `plotly.io.orca.config.executable`
+property to the full path of your orca executable. For example:
+
+ >>> plotly.io.orca.config.executable = '/path/to/orca'
+
+After updating this executable property, try the export operation again.
+If it is successful then you may want to save this configuration so that it
+will be applied automatically in future sessions. You can do this as follows:
+
+ >>> plotly.io.orca.config.save()
+
+If you're still having trouble, feel free to ask for help on the forums at
+https://community.plot.ly/c/api/python
+"""
+
+ # Try to find an executable
+ # -------------------------
+ # Search for executable name or path in config.executable
+ executable = which(config.executable)
+ path = os.environ.get("PATH", os.defpath)
+ formatted_path = path.replace(os.pathsep, "\n ")
+
+ if executable is None:
+ raise ValueError(
+ """
+The orca executable is required to export figures as static images,
+but it could not be found on the system path.
+
+Searched for executable '{executable}' on the following path:
+ {formatted_path}
+
+{instructions}""".format(
+ executable=config.executable,
+ formatted_path=formatted_path,
+ instructions=install_location_instructions,
+ )
+ )
+
+ # Check if we should run with Xvfb
+ # --------------------------------
+ xvfb_args = [
+ "--auto-servernum",
+ "--server-args",
+ "-screen 0 640x480x24 +extension RANDR +extension GLX",
+ executable,
+ ]
+
+ if config.use_xvfb:
+ # Use xvfb
+ xvfb_run_executable = which("xvfb-run")
+ if not xvfb_run_executable:
+ raise ValueError(
+ """
+The plotly.io.orca.config.use_xvfb property is set to True, but the
+xvfb-run executable could not be found on the system path.
+
+Searched for the executable 'xvfb-run' on the following path:
+ {formatted_path}""".format(formatted_path=formatted_path)
+ )
+
+ executable_list = [xvfb_run_executable] + xvfb_args
+ elif (
+ config.use_xvfb == "auto"
+ and sys.platform.startswith("linux")
+ and not os.environ.get("DISPLAY")
+ and which("xvfb-run")
+ ):
+ # use_xvfb is 'auto', we're on linux without a display server,
+ # and xvfb-run is available. Use it.
+ xvfb_run_executable = which("xvfb-run")
+ executable_list = [xvfb_run_executable] + xvfb_args
+ else:
+ # Do not use xvfb
+ executable_list = [executable]
+
+ # Run executable with --help and see if it's our orca
+ # ---------------------------------------------------
+ invalid_executable_msg = """
+The orca executable is required in order to export figures as static images,
+but the executable that was found at '{executable}'
+does not seem to be a valid plotly orca executable. Please refer to the end of
+this message for details on what went wrong.
+
+{instructions}""".format(
+ executable=executable, instructions=install_location_instructions
+ )
+
+ # ### Run with Popen so we get access to stdout and stderr
+ with orca_env():
+ p = subprocess.Popen(
+ executable_list + ["--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+
+ help_result, help_error = p.communicate()
+
+ if p.returncode != 0:
+ err_msg = (
+ invalid_executable_msg
+ + """
+Here is the error that was returned by the command
+ $ {executable} --help
+
+[Return code: {returncode}]
+{err_msg}
+""".format(
+ executable=" ".join(executable_list),
+ err_msg=help_error.decode("utf-8"),
+ returncode=p.returncode,
+ )
+ )
+
+ # Check for Linux without X installed.
+ if sys.platform.startswith("linux") and not os.environ.get("DISPLAY"):
+ err_msg += """\
+Note: When used on Linux, orca requires an X11 display server, but none was
+detected. Please install Xvfb and configure plotly.py to run orca using Xvfb
+as follows:
+
+ >>> import plotly.io as pio
+ >>> pio.orca.config.use_xvfb = True
+
+You can save this configuration for use in future sessions as follows:
+
+ >>> pio.orca.config.save()
+
+See https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml
+for more info on Xvfb
+"""
+ raise ValueError(err_msg)
+
+ if not help_result:
+ raise ValueError(
+ invalid_executable_msg
+ + """
+The error encountered is that no output was returned by the command
+ $ {executable} --help
+""".format(executable=" ".join(executable_list))
+ )
+
+ if "Plotly's image-exporting utilities" not in help_result.decode("utf-8"):
+ raise ValueError(
+ invalid_executable_msg
+ + """
+The error encountered is that unexpected output was returned by the command
+ $ {executable} --help
+
+{help_result}
+""".format(executable=" ".join(executable_list), help_result=help_result)
+ )
+
+ # Get orca version
+ # ----------------
+ # ### Run with Popen so we get access to stdout and stderr
+ with orca_env():
+ p = subprocess.Popen(
+ executable_list + ["--version"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ version_result, version_error = p.communicate()
+
+ if p.returncode != 0:
+ raise ValueError(
+ invalid_executable_msg
+ + """
+An error occurred while trying to get the version of the orca executable.
+Here is the command that plotly.py ran to request the version
+ $ {executable} --version
+
+This command returned the following error:
+
+[Return code: {returncode}]
+{err_msg}
+ """.format(
+ executable=" ".join(executable_list),
+ err_msg=version_error.decode("utf-8"),
+ returncode=p.returncode,
+ )
+ )
+
+ if not version_result:
+ raise ValueError(
+ invalid_executable_msg
+ + """
+The error encountered is that no version was reported by the orca executable.
+Here is the command that plotly.py ran to request the version:
+
+ $ {executable} --version
+""".format(executable=" ".join(executable_list))
+ )
+ else:
+ version_result = version_result.decode()
+
+ status._props["executable_list"] = executable_list
+ status._props["version"] = version_result.strip()
+ status._props["state"] = "validated"
+
+
+def reset_status():
+ """
+ Shutdown the running orca server, if any, and reset the orca status
+ to unvalidated.
+
+ This command is only needed if the desired orca executable is changed
+ during an interactive session.
+
+ Returns
+ -------
+ None
+ """
+ shutdown_server()
+ status._props["executable_list"] = None
+ status._props["version"] = None
+ status._props["state"] = "unvalidated"
+
+
+# Initialze process control variables
+# -----------------------------------
+orca_lock = threading.Lock()
+orca_state = {"proc": None, "shutdown_timer": None}
+
+
+# Shutdown
+# --------
+# The @atexit.register annotation ensures that the shutdown function is
+# is run when the Python process is terminated
+@atexit.register
+def cleanup():
+ shutdown_server()
+
+
+def shutdown_server():
+ """
+ Shutdown the running orca server process, if any
+
+ Returns
+ -------
+ None
+ """
+ # Use double-check locking to make sure the properties of orca_state
+ # are updated consistently across threads.
+ if orca_state["proc"] is not None:
+ with orca_lock:
+ if orca_state["proc"] is not None:
+ # We use psutil to kill all child processes of the main orca
+ # process. This prevents any zombie processes from being
+ # left over, and it saves us from needing to write
+ # OS-specific process management code here.
+
+ parent = psutil.Process(orca_state["proc"].pid)
+ for child in parent.children(recursive=True):
+ try:
+ child.terminate()
+ except Exception:
+ # We tried, move on
+ pass
+
+ try:
+ # Kill parent process
+ orca_state["proc"].terminate()
+
+ # Wait for the process to shutdown
+ orca_state["proc"].wait()
+ except Exception:
+ # We tried, move on
+ pass
+
+ # Update our internal process management state
+ orca_state["proc"] = None
+
+ if orca_state["shutdown_timer"] is not None:
+ orca_state["shutdown_timer"].cancel()
+ orca_state["shutdown_timer"] = None
+
+ orca_state["port"] = None
+
+ # Update orca.status so the user has an accurate view
+ # of the state of the orca server
+ status._props["state"] = "validated"
+ status._props["pid"] = None
+ status._props["port"] = None
+ status._props["command"] = None
+
+
+# Launch or get server
+def ensure_server():
+ """
+ Start an orca server if none is running. If a server is already running,
+ then reset the timeout countdown
+
+ Returns
+ -------
+ None
+ """
+
+ # Validate psutil
+ if psutil is None:
+ raise ValueError(
+ """\
+Image generation requires the psutil package.
+
+Install using pip:
+ $ pip install psutil
+
+Install using conda:
+ $ conda install psutil
+"""
+ )
+
+ # Validate requests
+ if not get_module("requests"):
+ raise ValueError(
+ """\
+Image generation requires the requests package.
+
+Install using pip:
+ $ pip install requests
+
+Install using conda:
+ $ conda install requests
+"""
+ )
+
+ if not config.server_url:
+ # Validate orca executable only if server_url is not provided
+ if status.state == "unvalidated":
+ validate_executable()
+ # Acquire lock to make sure that we keep the properties of orca_state
+ # consistent across threads
+ with orca_lock:
+ # Cancel the current shutdown timer, if any
+ if orca_state["shutdown_timer"] is not None:
+ orca_state["shutdown_timer"].cancel()
+
+ # Start a new server process if none is active
+ if orca_state["proc"] is None:
+ # Determine server port
+ if config.port is None:
+ orca_state["port"] = find_open_port()
+ else:
+ orca_state["port"] = config.port
+
+ # Build orca command list
+ cmd_list = status._props["executable_list"] + [
+ "serve",
+ "-p",
+ str(orca_state["port"]),
+ "--plotly",
+ config.plotlyjs,
+ "--graph-only",
+ ]
+
+ if config.topojson:
+ cmd_list.extend(["--topojson", config.topojson])
+
+ if config.mathjax:
+ cmd_list.extend(["--mathjax", config.mathjax])
+
+ if config.mapbox_access_token:
+ cmd_list.extend(
+ ["--mapbox-access-token", config.mapbox_access_token]
+ )
+
+ # Create subprocess that launches the orca server on the
+ # specified port.
+ DEVNULL = open(os.devnull, "wb")
+ with orca_env():
+ stderr = DEVNULL if "CI" in os.environ else None # fix for CI
+ orca_state["proc"] = subprocess.Popen(
+ cmd_list, stdout=DEVNULL, stderr=stderr
+ )
+
+ # Update orca.status so the user has an accurate view
+ # of the state of the orca server
+ status._props["state"] = "running"
+ status._props["pid"] = orca_state["proc"].pid
+ status._props["port"] = orca_state["port"]
+ status._props["command"] = cmd_list
+
+ # Create new shutdown timer if a timeout was specified
+ if config.timeout is not None:
+ t = threading.Timer(config.timeout, shutdown_server)
+ # Make it a daemon thread so that exit won't wait for timer to
+ # complete
+ t.daemon = True
+ t.start()
+ orca_state["shutdown_timer"] = t
+
+
+@retry(min_wait=5, max_wait=10, max_delay=60000)
+def request_image_with_retrying(**kwargs):
+ """
+ Helper method to perform an image request to a running orca server process
+ with retrying logic.
+ """
+ from requests import post
+ from plotly.io.json import to_json_plotly
+
+ if config.server_url:
+ server_url = config.server_url
+ else:
+ server_url = "http://{hostname}:{port}".format(
+ hostname="localhost", port=orca_state["port"]
+ )
+
+ request_params = {k: v for k, v in kwargs.items() if v is not None}
+ json_str = to_json_plotly(request_params)
+ response = post(server_url + "/", data=json_str)
+
+ if response.status_code == 522:
+ # On "522: client socket timeout", return server and keep trying
+ shutdown_server()
+ ensure_server()
+ raise OSError("522: client socket timeout")
+
+ return response
+
+
+def to_image(fig, format=None, width=None, height=None, scale=None, validate=True):
+ """
+ Convert a figure to a static image bytes string
+
+ Parameters
+ ----------
+ fig:
+ Figure object or dict representing a figure
+
+ format: str or None
+ The desired image format. One of
+ - 'png'
+ - 'jpg' or 'jpeg'
+ - 'webp'
+ - 'svg'
+ - 'pdf'
+ - 'eps' (Requires the poppler library to be installed)
+
+ If not specified, will default to `plotly.io.config.default_format`
+
+ 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.config.default_width`
+
+ 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.config.default_height`
+
+ 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.config.default_scale`
+
+ validate: bool
+ True if the figure should be validated before being converted to
+ an image, False otherwise.
+
+ Returns
+ -------
+ bytes
+ The image data
+ """
+ # Make sure orca sever is running
+ # -------------------------------
+ ensure_server()
+
+ # Handle defaults
+ # ---------------
+ # Apply configuration defaults to unspecified arguments
+ if format is None:
+ format = config.default_format
+
+ format = validate_coerce_format(format)
+
+ if scale is None:
+ scale = config.default_scale
+
+ if width is None:
+ width = config.default_width
+
+ if height is None:
+ height = config.default_height
+
+ # Validate figure
+ # ---------------
+ fig_dict = validate_coerce_fig_to_dict(fig, validate)
+
+ # Request image from server
+ # -------------------------
+ try:
+ response = request_image_with_retrying(
+ figure=fig_dict, format=format, scale=scale, width=width, height=height
+ )
+ except OSError:
+ # Get current status string
+ status_str = repr(status)
+
+ if config.server_url:
+ raise ValueError(
+ """
+Plotly.py was unable to communicate with the orca server at {server_url}
+
+Please check that the server is running and accessible.
+""".format(server_url=config.server_url)
+ )
+
+ else:
+ # Check if the orca server process exists
+ pid_exists = psutil.pid_exists(status.pid)
+
+ # Raise error message based on whether the server process existed
+ if pid_exists:
+ raise ValueError(
+ """
+For some reason plotly.py was unable to communicate with the
+local orca server process, even though the server process seems to be running.
+
+Please review the process and connection information below:
+
+{info}
+""".format(info=status_str)
+ )
+ else:
+ # Reset the status so that if the user tries again, we'll try to
+ # start the server again
+ reset_status()
+ raise ValueError(
+ """
+For some reason the orca server process is no longer running.
+
+Please review the process and connection information below:
+
+{info}
+plotly.py will attempt to start the local server process again the next time
+an image export operation is performed.
+""".format(info=status_str)
+ )
+
+ # Check response
+ # --------------
+ if response.status_code == 200:
+ # All good
+ return response.content
+ else:
+ # ### Something went wrong ###
+ err_message = """
+The image request was rejected by the orca conversion utility
+with the following error:
+ {status}: {msg}
+""".format(status=response.status_code, msg=response.content.decode("utf-8"))
+
+ # ### Try to be helpful ###
+ # Status codes from /src/component/plotly-graph/constants.js in the
+ # orca code base.
+ # statusMsg: {
+ # 400: 'invalid or malformed request syntax',
+ # 522: client socket timeout
+ # 525: 'plotly.js error',
+ # 526: 'plotly.js version 1.11.0 or up required',
+ # 530: 'image conversion error'
+ # }
+ if response.status_code == 400 and isinstance(fig, dict) and not validate:
+ err_message += """
+Try setting the `validate` argument to True to check for errors in the
+figure specification"""
+ elif response.status_code == 525:
+ any_mapbox = any(
+ [
+ trace.get("type", None) == "scattermapbox"
+ for trace in fig_dict.get("data", [])
+ ]
+ )
+ if any_mapbox and config.mapbox_access_token is None:
+ err_message += """
+Exporting scattermapbox traces requires a mapbox access token.
+Create a token in your mapbox account and then set it using:
+
+>>> plotly.io.orca.config.mapbox_access_token = 'pk.abc...'
+
+If you would like this token to be applied automatically in
+future sessions, then save your orca configuration as follows:
+
+>>> plotly.io.orca.config.save()
+"""
+ elif response.status_code == 530 and format == "eps":
+ err_message += """
+Exporting to EPS format requires the poppler library. You can install
+poppler on MacOS or Linux with:
+
+ $ conda install poppler
+
+Or, you can install it on MacOS using homebrew with:
+
+ $ brew install poppler
+
+Or, you can install it on Linux using your distribution's package manager to
+install the 'poppler-utils' package.
+
+Unfortunately, we don't yet know of an easy way to install poppler on Windows.
+"""
+ raise ValueError(err_message)
+
+
+def write_image(
+ fig, file, format=None, scale=None, width=None, height=None, validate=True
+):
+ """
+ Convert a figure to a static image and write it to a file or writeable
+ object
+
+ Parameters
+ ----------
+ fig:
+ Figure object or dict representing a figure
+
+ file: str or writeable
+ A string representing a local file path or a writeable object
+ (e.g. a pathlib.Path object or an open file descriptor)
+
+ format: str or None
+ The desired image format. One of
+ - 'png'
+ - 'jpg' or 'jpeg'
+ - 'webp'
+ - 'svg'
+ - 'pdf'
+ - 'eps' (Requires the poppler library to be installed)
+
+ 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.config.default_format`
+
+ 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.config.default_width`
+
+ 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.config.default_height`
+
+ 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.config.default_scale`
+
+ validate: bool
+ True if the figure should be validated before being converted to
+ an image, False otherwise.
+
+ Returns
+ -------
+ None
+ """
+
+ # Try to cast `file` as a pathlib object `path`.
+ # ----------------------------------------------
+ if isinstance(file, str):
+ # Use the standard Path constructor to make a pathlib object.
+ path = Path(file)
+ elif isinstance(file, Path):
+ # `file` is already a Path object.
+ path = file
+ else:
+ # We could not make a Path object out of file. Either `file` is an open file
+ # descriptor with a `write()` method or it's an invalid object.
+ path = None
+
+ # Infer format if not specified
+ # -----------------------------
+ if path is not None and format is None:
+ ext = path.suffix
+ if ext:
+ format = ext.lstrip(".")
+ else:
+ raise ValueError(
+ """
+Cannot infer image type from output path '{file}'.
+Please add a file extension or specify the type using the format parameter.
+For example:
+
+ >>> import plotly.io as pio
+ >>> pio.write_image(fig, file_path, format='png')
+""".format(file=file)
+ )
+
+ # Request image
+ # -------------
+ # Do this first so we don't create a file if image conversion fails
+ img_data = to_image(
+ fig, format=format, scale=scale, width=width, height=height, validate=validate
+ )
+
+ # Open file
+ # ---------
+ if path is None:
+ # We previously failed to make sense of `file` as a pathlib object.
+ # Attempt to write to `file` as an open file descriptor.
+ try:
+ file.write(img_data)
+ return
+ except AttributeError:
+ pass
+ raise ValueError(
+ """
+The 'file' argument '{file}' is not a string, pathlib.Path object, or file descriptor.
+""".format(file=file)
+ )
+ else:
+ # We previously succeeded in interpreting `file` as a pathlib object.
+ # Now we can use `write_bytes()`.
+ path.write_bytes(img_data)
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_renderers.py b/venv/lib/python3.8/site-packages/plotly/io/_renderers.py
new file mode 100644
index 0000000..9ddd1db
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_renderers.py
@@ -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
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_sg_scraper.py b/venv/lib/python3.8/site-packages/plotly/io/_sg_scraper.py
new file mode 100644
index 0000000..af15b7d
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_sg_scraper.py
@@ -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
+"""
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_templates.py b/venv/lib/python3.8/site-packages/plotly/io/_templates.py
new file mode 100644
index 0000000..160ee7c
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_templates.py
@@ -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
diff --git a/venv/lib/python3.8/site-packages/plotly/io/_utils.py b/venv/lib/python3.8/site-packages/plotly/io/_utils.py
new file mode 100644
index 0000000..4d27e03
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/_utils.py
@@ -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,
+ )
diff --git a/venv/lib/python3.8/site-packages/plotly/io/base_renderers.py b/venv/lib/python3.8/site-packages/plotly/io/base_renderers.py
new file mode 100644
index 0000000..78c1d86
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/base_renderers.py
@@ -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,
+)
diff --git a/venv/lib/python3.8/site-packages/plotly/io/json.py b/venv/lib/python3.8/site-packages/plotly/io/json.py
new file mode 100644
index 0000000..86c320d
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/json.py
@@ -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,
+)
diff --git a/venv/lib/python3.8/site-packages/plotly/io/kaleido.py b/venv/lib/python3.8/site-packages/plotly/io/kaleido.py
new file mode 100644
index 0000000..c086ea3
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/kaleido.py
@@ -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,
+)
diff --git a/venv/lib/python3.8/site-packages/plotly/io/orca.py b/venv/lib/python3.8/site-packages/plotly/io/orca.py
new file mode 100644
index 0000000..4fd5c19
--- /dev/null
+++ b/venv/lib/python3.8/site-packages/plotly/io/orca.py
@@ -0,0 +1,9 @@
+# ruff: noqa: F401
+from ._orca import (
+ ensure_server,
+ shutdown_server,
+ validate_executable,
+ reset_status,
+ config,
+ status,
+)