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

539 lines
16 KiB
Python

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