aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/plotly/io/_templates.py
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
committersotech117 <michael_foiani@brown.edu>2025-07-31 17:27:24 -0400
commit5bf22fc7e3c392c8bd44315ca2d06d7dca7d084e (patch)
tree8dacb0f195df1c0788d36dd0064f6bbaa3143ede /venv/lib/python3.8/site-packages/plotly/io/_templates.py
parentb832d364da8c2efe09e3f75828caf73c50d01ce3 (diff)
add code for analysis of data
Diffstat (limited to 'venv/lib/python3.8/site-packages/plotly/io/_templates.py')
-rw-r--r--venv/lib/python3.8/site-packages/plotly/io/_templates.py492
1 files changed, 492 insertions, 0 deletions
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