diff --git a/.circleci/config.yml b/.circleci/config.yml index df2d8378b90..aca6aeeffce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -297,7 +297,7 @@ jobs: - checkout - run: name: Install tox - command: 'sudo pip install tox requests yapf pytz decorator retrying' + command: 'sudo pip install tox requests yapf pytz decorator retrying inflect' - run: name: Update plotlywidget version command: 'python setup.py updateplotlywidgetversion' diff --git a/_plotly_utils/utils.py b/_plotly_utils/utils.py index ab5d03653af..94d37a8b81b 100644 --- a/_plotly_utils/utils.py +++ b/_plotly_utils/utils.py @@ -2,6 +2,7 @@ import decimal import json as _json import sys +import re import pytz @@ -242,3 +243,18 @@ def _decorator(func): func.__doc__ = func.__doc__.format(**names) return func return _decorator + + +def _natural_sort_strings(vals, reverse=False): + + def key(v): + v_parts = re.split(r'(\d+)', v) + for i in range(len(v_parts)): + try: + v_parts[i] = int(v_parts[i]) + except ValueError: + # not an int + pass + return tuple(v_parts) + + return sorted(vals, key=key, reverse=reverse) diff --git a/codegen/__init__.py b/codegen/__init__.py index 42577fcfdbe..f92fc5a8422 100644 --- a/codegen/__init__.py +++ b/codegen/__init__.py @@ -126,6 +126,9 @@ def perform_codegen(): all_layout_nodes = PlotlyNode.get_all_datatype_nodes( plotly_schema, LayoutNode) + subplot_nodes = [node for node in layout_node.child_compound_datatypes + if node.node_data.get('_isSubplotObj', False)] + # ### FrameNode ### compound_frame_nodes = PlotlyNode.get_all_compound_datatype_nodes( plotly_schema, FrameNode) @@ -187,7 +190,8 @@ def perform_codegen(): base_traces_node, data_validator, layout_validator, - frame_validator) + frame_validator, + subplot_nodes) # Write datatype __init__.py files # -------------------------------- diff --git a/codegen/datatypes.py b/codegen/datatypes.py index 8fd52d305ac..01777086735 100644 --- a/codegen/datatypes.py +++ b/codegen/datatypes.py @@ -103,6 +103,48 @@ def build_datatype_py(node): class {datatype_class}(_{node.name_base_datatype}):\n""") + # ### Layout subplot properties ### + if datatype_class == 'Layout': + subplot_nodes = [node for node in node.child_compound_datatypes + if node.node_data.get('_isSubplotObj', False)] + subplot_names = [n.name_property for n in subplot_nodes] + buffer.write(f""" + _subplotid_prop_names = {repr(subplot_names)} + + import re + _subplotid_prop_re = re.compile( + '^(' + '|'.join(_subplotid_prop_names) + ')(\d+)$') +""") + + subplot_validator_names = [n.name_validator_class + for n in subplot_nodes] + + validator_csv = ', '.join(subplot_validator_names) + subplot_dict_str = ( + '{' + + ', '.join(f"'{subname}': {valname}" for subname, valname in + zip(subplot_names, subplot_validator_names)) + + '}' + ) + + buffer.write(f""" + @property + def _subplotid_validators(self): + \"\"\" + dict of validator classes for each subplot type + + Returns + ------- + dict + \"\"\" + from plotly.validators.layout import ({validator_csv}) + + return {subplot_dict_str} + + def _subplot_re_match(self, prop): + return self._subplotid_prop_re.match(prop) +""") + # ### Property definitions ### child_datatype_nodes = node.child_datatypes diff --git a/codegen/figure.py b/codegen/figure.py index 494f11980e9..7e5d32af1f3 100644 --- a/codegen/figure.py +++ b/codegen/figure.py @@ -8,9 +8,12 @@ add_constructor_params, add_docstring) from codegen.utils import PlotlyNode, format_and_write_source_py +import inflect + def build_figure_py(trace_node, base_package, base_classname, fig_classname, - data_validator, layout_validator, frame_validator): + data_validator, layout_validator, frame_validator, + subplot_nodes): """ Parameters @@ -30,7 +33,8 @@ def build_figure_py(trace_node, base_package, base_classname, fig_classname, LayoutValidator instance frame_validator : CompoundArrayValidator FrameValidator instance - + subplot_nodes: list of str + List of names of all of the layout subplot properties Returns ------- str @@ -53,6 +57,7 @@ def build_figure_py(trace_node, base_package, base_classname, fig_classname, # ### Import trace graph_obj classes ### trace_types_csv = ', '.join([n.name_datatype_class for n in trace_nodes]) buffer.write(f'from plotly.graph_objs import ({trace_types_csv})\n') + buffer.write("from plotly.subplots import _validate_v4_subplots\n") # Write class definition # ---------------------- @@ -141,6 +146,111 @@ def add_{trace_node.plotly_name}(self""") buffer.write(f""" return self.add_trace(new_trace, row=row, col=col)""") + # update layout subplots + # ---------------------- + inflect_eng = inflect.engine() + for subplot_node in subplot_nodes: + singular_name = subplot_node.name_property + plural_name = inflect_eng.plural_noun(singular_name) + buffer.write(f""" + + def select_{plural_name}(self, selector=None, row=None, col=None): + \"\"\" + Select {singular_name} subplot objects from a particular subplot cell + and/or {singular_name} subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + {singular_name} objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all {singular_name} objects are selected. + row, col: int or None (default None) + Subplot row and column index of {singular_name} objects to select. + To select {singular_name} objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all {singular_name} objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the {singular_name} + objects that satisfy all of the specified selection criteria + \"\"\" + if row is not None or col is not None: + _validate_v4_subplots('select_{plural_name}') + + return self._select_layout_subplots_by_prefix( + '{singular_name}', selector, row, col) + + def for_each_{singular_name}(self, fn, selector=None, row=None, col=None): + \"\"\" + Apply a function to all {singular_name} objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single {singular_name} object. + selector: dict or None (default None) + Dict to use as selection criteria. + {singular_name} objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all {singular_name} objects are selected. + row, col: int or None (default None) + Subplot row and column index of {singular_name} objects to select. + To select {singular_name} objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all {singular_name} objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + \"\"\" + for obj in self.select_{plural_name}( + selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_{plural_name}(self, patch, selector=None, row=None, col=None): + \"\"\" + Perform a property update operation on all {singular_name} objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + {singular_name} objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + {singular_name} objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all {singular_name} objects are selected. + row, col: int or None (default None) + Subplot row and column index of {singular_name} objects to select. + To select {singular_name} objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all {singular_name} objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + \"\"\" + for obj in self.select_{plural_name}( + selector=selector, row=row, col=col): + obj.update(patch) + + return self""") + # Return source string # -------------------- buffer.write('\n') @@ -150,7 +260,8 @@ def add_{trace_node.plotly_name}(self""") def write_figure_classes(outdir, trace_node, data_validator, layout_validator, - frame_validator): + frame_validator, + subplot_nodes): """ Construct source code for the Figure and FigureWidget classes and write to graph_objs/_figure.py and graph_objs/_figurewidget.py @@ -169,6 +280,8 @@ def write_figure_classes(outdir, trace_node, LayoutValidator instance frame_validator : CompoundArrayValidator FrameValidator instance + subplot_nodes: list of str + List of names of all of the layout subplot properties Returns ------- @@ -195,7 +308,8 @@ def write_figure_classes(outdir, trace_node, fig_classname, data_validator, layout_validator, - frame_validator) + frame_validator, + subplot_nodes) # ### Format and write to file### filepath = opath.join(outdir, 'graph_objs', diff --git a/optional-requirements.txt b/optional-requirements.txt index f037d594dc6..697b38934a2 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -23,6 +23,7 @@ psutil ## codegen dependencies ## yapf +inflect ## template generation ## colorcet diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py index 8ed2943057f..6bab9c79a38 100644 --- a/plotly/basedatatypes.py +++ b/plotly/basedatatypes.py @@ -9,6 +9,7 @@ from contextlib import contextmanager from copy import deepcopy, copy +from _plotly_utils.utils import _natural_sort_strings from plotly.subplots import ( _set_trace_grid_reference, _get_grid_subplot, @@ -666,48 +667,67 @@ def select_traces(self, selector=None, row=None, col=None): if not selector: selector = {} - if row is not None and col is not None: + if row is not None or col is not None: _validate_v4_subplots('select_traces') grid_ref = self._validate_get_grid_ref() - grid_subplot_ref = grid_ref[row-1][col-1] filter_by_subplot = True + + if row is None: + # All rows for column + grid_subplot_refs = [ref_row[col-1] for ref_row in grid_ref] + elif col is None: + # All columns for row + grid_subplot_refs = grid_ref[row-1] + else: + # Single grid cell + grid_subplot_refs = [grid_ref[row-1][col-1]] + else: filter_by_subplot = False - grid_subplot_ref = None + grid_subplot_refs = None return self._perform_select_traces( - filter_by_subplot, grid_subplot_ref, selector) + filter_by_subplot, grid_subplot_refs, selector) def _perform_select_traces( - self, filter_by_subplot, grid_subplot_ref, selector): - - def select_eq(obj1, obj2): - try: - obj1 = obj1.to_plotly_json() - except Exception: - pass - try: - obj2 = obj2.to_plotly_json() - except Exception: - pass - - return BasePlotlyType._vals_equal(obj1, obj2) + self, filter_by_subplot, grid_subplot_refs, selector): for trace in self.data: # Filter by subplot if filter_by_subplot: trace_subplot_ref = _get_subplot_ref_for_trace(trace) - if grid_subplot_ref != trace_subplot_ref: + if trace_subplot_ref not in grid_subplot_refs: continue # Filter by selector - if not all( - k in trace and select_eq(trace[k], selector[k]) - for k in selector): + if not self._selector_matches(trace, selector): continue yield trace + @staticmethod + def _selector_matches(obj, selector): + if selector is None: + return True + + for k in selector: + if k not in obj: + return False + + obj_val = obj[k] + selector_val = selector[k] + + if isinstance(obj_val, BasePlotlyType): + obj_val = obj_val.to_plotly_json() + + if isinstance(selector_val, BasePlotlyType): + selector_val = selector_val.to_plotly_json() + + if obj_val != selector_val: + return False + + return True + def for_each_trace(self, fn, selector=None, row=None, col=None): """ Apply a function to all traces that satisfy the specified selection @@ -749,8 +769,6 @@ def update_traces(self, patch, selector=None, row=None, col=None): patch: dict Dictionary of property updates to be applied to all traces that satisfy the selection criteria. - fn: - Function that inputs a single trace object. selector: dict or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding @@ -772,6 +790,49 @@ def update_traces(self, patch, selector=None, row=None, col=None): trace.update(patch) return self + def _select_layout_subplots_by_prefix( + self, prefix, selector=None, row=None, col=None): + """ + Helper called by code generated select_* methods + """ + + if row is not None or col is not None: + # Build mapping from container keys ('xaxis2', 'scene4', etc.) + # to row/col pairs + grid_ref = self._validate_get_grid_ref() + container_to_row_col = {} + for r, subplot_row in enumerate(grid_ref): + for c, ref in enumerate(subplot_row): + if ref is None: + continue + for layout_key in ref['layout_keys']: + if layout_key.startswith(prefix): + container_to_row_col[layout_key] = r + 1, c + 1 + else: + container_to_row_col = None + + # Natural sort keys so that xaxis20 is after xaxis3 + layout_keys = _natural_sort_strings(list(self.layout)) + + for k in layout_keys: + if k.startswith(prefix) and self.layout[k] is not None: + + # Filter by row/col + if (row is not None and + container_to_row_col.get(k, (None, None))[0] != row): + # row specified and this is not a match + continue + elif (col is not None and + container_to_row_col.get(k, (None, None))[1] != col): + # col specified and this is not a match + continue + + # Filter by selector + if not self._selector_matches(self.layout[k], selector): + continue + + yield self.layout[k] + # Restyle # ------- def plotly_restyle(self, restyle_data, trace_indexes=None, **kwargs): @@ -2473,7 +2534,7 @@ def _perform_update(plotly_obj, update_obj): if isinstance(plotly_obj, BaseLayoutType): for key in update_obj: if key not in plotly_obj: - match = plotly_obj._subplotid_prop_re.match(key) + match = plotly_obj._subplot_re_match(key) if match: # We need to create a subplotid object plotly_obj[key] = {} @@ -3031,7 +3092,7 @@ def __contains__(self, prop): else: return False else: - if p in obj._validators: + if obj is not None and p in obj._validators: obj = obj[p] else: return False @@ -3798,18 +3859,6 @@ class BaseLayoutType(BaseLayoutHierarchyType): # for xaxis, yaxis, geo, ternary, and scene. But, we need to dynamically # generated properties/validators as needed for xaxis2, yaxis3, etc. - # # ### Create subplot property regular expression ### - _subplotid_prop_names = ['xaxis', - 'yaxis', - 'geo', - 'ternary', - 'scene', - 'mapbox', - 'polar'] - - _subplotid_prop_re = re.compile( - '^(' + '|'.join(_subplotid_prop_names) + ')(\d+)$') - @property def _subplotid_validators(self): """ @@ -3819,20 +3868,10 @@ def _subplotid_validators(self): ------- dict """ - from .validators.layout import (XAxisValidator, YAxisValidator, - GeoValidator, TernaryValidator, - SceneValidator, MapboxValidator, - PolarValidator) - - return { - 'xaxis': XAxisValidator, - 'yaxis': YAxisValidator, - 'geo': GeoValidator, - 'ternary': TernaryValidator, - 'scene': SceneValidator, - 'mapbox': MapboxValidator, - 'polar': PolarValidator - } + raise NotImplementedError() + + def _subplot_re_match(self, prop): + raise NotImplementedError() def __init__(self, plotly_name, **kwargs): """ @@ -3872,14 +3911,14 @@ def _process_kwargs(self, **kwargs): unknown_kwargs = { k: v for k, v in kwargs.items() - if not self._subplotid_prop_re.match(k) + if not self._subplot_re_match(k) } super(BaseLayoutHierarchyType, self)._process_kwargs(**unknown_kwargs) subplot_kwargs = { k: v for k, v in kwargs.items() - if self._subplotid_prop_re.match(k) + if self._subplot_re_match(k) } for prop, value in subplot_kwargs.items(): @@ -3899,7 +3938,7 @@ def _set_subplotid_prop(self, prop, value): # Get regular expression match # ---------------------------- # Note: we already tested that match exists in the constructor - match = self._subplotid_prop_re.match(prop) + match = self._subplot_re_match(prop) subplot_prop = match.group(1) suffix_digit = int(match.group(2)) @@ -3960,7 +3999,7 @@ def _strip_subplot_suffix_of_1(self, prop): # Handle subplot suffix digit of 1 # -------------------------------- # Remove digit of 1 from subplot id (e.g.. xaxis1 -> xaxis) - match = self._subplotid_prop_re.match(prop) + match = self._subplot_re_match(prop) if match: subplot_prop = match.group(1) @@ -4020,7 +4059,7 @@ def __setitem__(self, prop, value): # Check for subplot assignment # ---------------------------- - match = self._subplotid_prop_re.match(prop) + match = self._subplot_re_match(prop) if match is None: # Set as ordinary property super(BaseLayoutHierarchyType, self).__setitem__(prop, value) @@ -4034,7 +4073,7 @@ def __setattr__(self, prop, value): """ # Check for subplot assignment # ---------------------------- - match = self._subplotid_prop_re.match(prop) + match = self._subplot_re_match(prop) if match is None: # Set as ordinary property super(BaseLayoutHierarchyType, self).__setattr__(prop, value) diff --git a/plotly/basewidget.py b/plotly/basewidget.py index 1bcdaa4e0d1..1f6806dbfb9 100644 --- a/plotly/basewidget.py +++ b/plotly/basewidget.py @@ -558,7 +558,7 @@ def _handler_js2py_layoutDelta(self, change): # may include axes that weren't explicitly defined by the user. for proppath in delta_transform: prop = proppath[0] - match = self.layout._subplotid_prop_re.match(prop) + match = self.layout._subplot_re_match(prop) if match and prop not in self.layout: # We need to create a subplotid object self.layout[prop] = {} diff --git a/plotly/graph_objs/__init__.py b/plotly/graph_objs/__init__.py index 182c1349873..a5b7409c0da 100644 --- a/plotly/graph_objs/__init__.py +++ b/plotly/graph_objs/__init__.py @@ -6,6 +6,42 @@ class Layout(_BaseLayoutType): + _subplotid_prop_names = [ + 'geo', 'mapbox', 'polar', 'scene', 'ternary', 'xaxis', 'yaxis' + ] + + import re + _subplotid_prop_re = re.compile( + '^(' + '|'.join(_subplotid_prop_names) + ')(\d+)$' + ) + + @property + def _subplotid_validators(self): + """ + dict of validator classes for each subplot type + + Returns + ------- + dict + """ + from plotly.validators.layout import ( + GeoValidator, MapboxValidator, PolarValidator, SceneValidator, + TernaryValidator, XAxisValidator, YAxisValidator + ) + + return { + 'geo': GeoValidator, + 'mapbox': MapboxValidator, + 'polar': PolarValidator, + 'scene': SceneValidator, + 'ternary': TernaryValidator, + 'xaxis': XAxisValidator, + 'yaxis': YAxisValidator + } + + def _subplot_re_match(self, prop): + return self._subplotid_prop_re.match(prop) + # angularaxis # ----------- @property diff --git a/plotly/graph_objs/_figure.py b/plotly/graph_objs/_figure.py index 0e2c9559a23..3ac55bc02f3 100644 --- a/plotly/graph_objs/_figure.py +++ b/plotly/graph_objs/_figure.py @@ -7,6 +7,7 @@ Scattergl, Scattermapbox, Scatterpolar, Scatterpolargl, Scatterternary, Splom, Streamtube, Sunburst, Surface, Table, Violin, Volume, Waterfall ) +from plotly.subplots import _validate_v4_subplots class Figure(BaseFigure): @@ -12300,3 +12301,675 @@ def add_waterfall( **kwargs ) return self.add_trace(new_trace, row=row, col=col) + + def select_geos(self, selector=None, row=None, col=None): + """ + Select geo subplot objects from a particular subplot cell + and/or geo subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + geo objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all geo objects are selected. + row, col: int or None (default None) + Subplot row and column index of geo objects to select. + To select geo objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all geo objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the geo + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_geos') + + return self._select_layout_subplots_by_prefix( + 'geo', selector, row, col + ) + + def for_each_geo(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all geo objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single geo object. + selector: dict or None (default None) + Dict to use as selection criteria. + geo objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all geo objects are selected. + row, col: int or None (default None) + Subplot row and column index of geo objects to select. + To select geo objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all geo objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_geos(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_geos(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all geo objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + geo objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + geo objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all geo objects are selected. + row, col: int or None (default None) + Subplot row and column index of geo objects to select. + To select geo objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all geo objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_geos(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_mapboxes(self, selector=None, row=None, col=None): + """ + Select mapbox subplot objects from a particular subplot cell + and/or mapbox subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + mapbox objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all mapbox objects are selected. + row, col: int or None (default None) + Subplot row and column index of mapbox objects to select. + To select mapbox objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all mapbox objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the mapbox + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_mapboxes') + + return self._select_layout_subplots_by_prefix( + 'mapbox', selector, row, col + ) + + def for_each_mapbox(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all mapbox objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single mapbox object. + selector: dict or None (default None) + Dict to use as selection criteria. + mapbox objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all mapbox objects are selected. + row, col: int or None (default None) + Subplot row and column index of mapbox objects to select. + To select mapbox objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all mapbox objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_mapboxes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_mapboxes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all mapbox objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + mapbox objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + mapbox objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all mapbox objects are selected. + row, col: int or None (default None) + Subplot row and column index of mapbox objects to select. + To select mapbox objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all mapbox objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_mapboxes(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_polars(self, selector=None, row=None, col=None): + """ + Select polar subplot objects from a particular subplot cell + and/or polar subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + polar objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all polar objects are selected. + row, col: int or None (default None) + Subplot row and column index of polar objects to select. + To select polar objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all polar objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the polar + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_polars') + + return self._select_layout_subplots_by_prefix( + 'polar', selector, row, col + ) + + def for_each_polar(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all polar objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single polar object. + selector: dict or None (default None) + Dict to use as selection criteria. + polar objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all polar objects are selected. + row, col: int or None (default None) + Subplot row and column index of polar objects to select. + To select polar objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all polar objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_polars(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_polars(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all polar objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + polar objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + polar objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all polar objects are selected. + row, col: int or None (default None) + Subplot row and column index of polar objects to select. + To select polar objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all polar objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_polars(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_scenes(self, selector=None, row=None, col=None): + """ + Select scene subplot objects from a particular subplot cell + and/or scene subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + scene objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all scene objects are selected. + row, col: int or None (default None) + Subplot row and column index of scene objects to select. + To select scene objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all scene objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the scene + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_scenes') + + return self._select_layout_subplots_by_prefix( + 'scene', selector, row, col + ) + + def for_each_scene(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all scene objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single scene object. + selector: dict or None (default None) + Dict to use as selection criteria. + scene objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all scene objects are selected. + row, col: int or None (default None) + Subplot row and column index of scene objects to select. + To select scene objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all scene objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_scenes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_scenes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all scene objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + scene objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + scene objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all scene objects are selected. + row, col: int or None (default None) + Subplot row and column index of scene objects to select. + To select scene objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all scene objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_scenes(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_ternaries(self, selector=None, row=None, col=None): + """ + Select ternary subplot objects from a particular subplot cell + and/or ternary subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + ternary objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all ternary objects are selected. + row, col: int or None (default None) + Subplot row and column index of ternary objects to select. + To select ternary objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all ternary objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the ternary + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_ternaries') + + return self._select_layout_subplots_by_prefix( + 'ternary', selector, row, col + ) + + def for_each_ternary(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all ternary objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single ternary object. + selector: dict or None (default None) + Dict to use as selection criteria. + ternary objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all ternary objects are selected. + row, col: int or None (default None) + Subplot row and column index of ternary objects to select. + To select ternary objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all ternary objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_ternaries(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_ternaries(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all ternary objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + ternary objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + ternary objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all ternary objects are selected. + row, col: int or None (default None) + Subplot row and column index of ternary objects to select. + To select ternary objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all ternary objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_ternaries(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_xaxes(self, selector=None, row=None, col=None): + """ + Select xaxis subplot objects from a particular subplot cell + and/or xaxis subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + xaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all xaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of xaxis objects to select. + To select xaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all xaxis objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the xaxis + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_xaxes') + + return self._select_layout_subplots_by_prefix( + 'xaxis', selector, row, col + ) + + def for_each_xaxis(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all xaxis objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single xaxis object. + selector: dict or None (default None) + Dict to use as selection criteria. + xaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all xaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of xaxis objects to select. + To select xaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all xaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_xaxes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_xaxes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all xaxis objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + xaxis objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + xaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all xaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of xaxis objects to select. + To select xaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all xaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_xaxes(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_yaxes(self, selector=None, row=None, col=None): + """ + Select yaxis subplot objects from a particular subplot cell + and/or yaxis subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + yaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all yaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of yaxis objects to select. + To select yaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all yaxis objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the yaxis + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_yaxes') + + return self._select_layout_subplots_by_prefix( + 'yaxis', selector, row, col + ) + + def for_each_yaxis(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all yaxis objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single yaxis object. + selector: dict or None (default None) + Dict to use as selection criteria. + yaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all yaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of yaxis objects to select. + To select yaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all yaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_yaxes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_yaxes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all yaxis objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + yaxis objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + yaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all yaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of yaxis objects to select. + To select yaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all yaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_yaxes(selector=selector, row=row, col=col): + obj.update(patch) + + return self diff --git a/plotly/graph_objs/_figurewidget.py b/plotly/graph_objs/_figurewidget.py index 4cecab636bf..1b6831c416e 100644 --- a/plotly/graph_objs/_figurewidget.py +++ b/plotly/graph_objs/_figurewidget.py @@ -7,6 +7,7 @@ Scattergl, Scattermapbox, Scatterpolar, Scatterpolargl, Scatterternary, Splom, Streamtube, Sunburst, Surface, Table, Violin, Volume, Waterfall ) +from plotly.subplots import _validate_v4_subplots class FigureWidget(BaseFigureWidget): @@ -12300,3 +12301,675 @@ def add_waterfall( **kwargs ) return self.add_trace(new_trace, row=row, col=col) + + def select_geos(self, selector=None, row=None, col=None): + """ + Select geo subplot objects from a particular subplot cell + and/or geo subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + geo objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all geo objects are selected. + row, col: int or None (default None) + Subplot row and column index of geo objects to select. + To select geo objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all geo objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the geo + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_geos') + + return self._select_layout_subplots_by_prefix( + 'geo', selector, row, col + ) + + def for_each_geo(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all geo objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single geo object. + selector: dict or None (default None) + Dict to use as selection criteria. + geo objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all geo objects are selected. + row, col: int or None (default None) + Subplot row and column index of geo objects to select. + To select geo objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all geo objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_geos(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_geos(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all geo objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + geo objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + geo objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all geo objects are selected. + row, col: int or None (default None) + Subplot row and column index of geo objects to select. + To select geo objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all geo objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_geos(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_mapboxes(self, selector=None, row=None, col=None): + """ + Select mapbox subplot objects from a particular subplot cell + and/or mapbox subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + mapbox objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all mapbox objects are selected. + row, col: int or None (default None) + Subplot row and column index of mapbox objects to select. + To select mapbox objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all mapbox objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the mapbox + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_mapboxes') + + return self._select_layout_subplots_by_prefix( + 'mapbox', selector, row, col + ) + + def for_each_mapbox(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all mapbox objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single mapbox object. + selector: dict or None (default None) + Dict to use as selection criteria. + mapbox objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all mapbox objects are selected. + row, col: int or None (default None) + Subplot row and column index of mapbox objects to select. + To select mapbox objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all mapbox objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_mapboxes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_mapboxes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all mapbox objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + mapbox objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + mapbox objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all mapbox objects are selected. + row, col: int or None (default None) + Subplot row and column index of mapbox objects to select. + To select mapbox objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all mapbox objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_mapboxes(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_polars(self, selector=None, row=None, col=None): + """ + Select polar subplot objects from a particular subplot cell + and/or polar subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + polar objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all polar objects are selected. + row, col: int or None (default None) + Subplot row and column index of polar objects to select. + To select polar objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all polar objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the polar + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_polars') + + return self._select_layout_subplots_by_prefix( + 'polar', selector, row, col + ) + + def for_each_polar(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all polar objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single polar object. + selector: dict or None (default None) + Dict to use as selection criteria. + polar objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all polar objects are selected. + row, col: int or None (default None) + Subplot row and column index of polar objects to select. + To select polar objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all polar objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_polars(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_polars(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all polar objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + polar objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + polar objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all polar objects are selected. + row, col: int or None (default None) + Subplot row and column index of polar objects to select. + To select polar objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all polar objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_polars(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_scenes(self, selector=None, row=None, col=None): + """ + Select scene subplot objects from a particular subplot cell + and/or scene subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + scene objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all scene objects are selected. + row, col: int or None (default None) + Subplot row and column index of scene objects to select. + To select scene objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all scene objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the scene + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_scenes') + + return self._select_layout_subplots_by_prefix( + 'scene', selector, row, col + ) + + def for_each_scene(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all scene objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single scene object. + selector: dict or None (default None) + Dict to use as selection criteria. + scene objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all scene objects are selected. + row, col: int or None (default None) + Subplot row and column index of scene objects to select. + To select scene objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all scene objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_scenes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_scenes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all scene objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + scene objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + scene objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all scene objects are selected. + row, col: int or None (default None) + Subplot row and column index of scene objects to select. + To select scene objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all scene objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_scenes(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_ternaries(self, selector=None, row=None, col=None): + """ + Select ternary subplot objects from a particular subplot cell + and/or ternary subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + ternary objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all ternary objects are selected. + row, col: int or None (default None) + Subplot row and column index of ternary objects to select. + To select ternary objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all ternary objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the ternary + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_ternaries') + + return self._select_layout_subplots_by_prefix( + 'ternary', selector, row, col + ) + + def for_each_ternary(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all ternary objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single ternary object. + selector: dict or None (default None) + Dict to use as selection criteria. + ternary objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all ternary objects are selected. + row, col: int or None (default None) + Subplot row and column index of ternary objects to select. + To select ternary objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all ternary objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_ternaries(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_ternaries(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all ternary objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + ternary objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + ternary objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all ternary objects are selected. + row, col: int or None (default None) + Subplot row and column index of ternary objects to select. + To select ternary objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all ternary objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_ternaries(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_xaxes(self, selector=None, row=None, col=None): + """ + Select xaxis subplot objects from a particular subplot cell + and/or xaxis subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + xaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all xaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of xaxis objects to select. + To select xaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all xaxis objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the xaxis + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_xaxes') + + return self._select_layout_subplots_by_prefix( + 'xaxis', selector, row, col + ) + + def for_each_xaxis(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all xaxis objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single xaxis object. + selector: dict or None (default None) + Dict to use as selection criteria. + xaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all xaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of xaxis objects to select. + To select xaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all xaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_xaxes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_xaxes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all xaxis objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + xaxis objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + xaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all xaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of xaxis objects to select. + To select xaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all xaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_xaxes(selector=selector, row=row, col=col): + obj.update(patch) + + return self + + def select_yaxes(self, selector=None, row=None, col=None): + """ + Select yaxis subplot objects from a particular subplot cell + and/or yaxis subplot objects that satisfy custom selection + criteria. + + Parameters + ---------- + selector: dict or None (default None) + Dict to use as selection criteria. + yaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all yaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of yaxis objects to select. + To select yaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all yaxis objects are selected. + + Returns + ------- + generator + Generator that iterates through all of the yaxis + objects that satisfy all of the specified selection criteria + """ + if row is not None or col is not None: + _validate_v4_subplots('select_yaxes') + + return self._select_layout_subplots_by_prefix( + 'yaxis', selector, row, col + ) + + def for_each_yaxis(self, fn, selector=None, row=None, col=None): + """ + Apply a function to all yaxis objects that satisfy the + specified selection criteria + + Parameters + ---------- + fn: + Function that inputs a single yaxis object. + selector: dict or None (default None) + Dict to use as selection criteria. + yaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all yaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of yaxis objects to select. + To select yaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all yaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_yaxes(selector=selector, row=row, col=col): + fn(obj) + + return self + + def update_yaxes(self, patch, selector=None, row=None, col=None): + """ + Perform a property update operation on all yaxis objects + that satisfy the specified selection criteria + + Parameters + ---------- + patch: dict + Dictionary of property updates to be applied to all + yaxis objects that satisfy the selection criteria. + selector: dict or None (default None) + Dict to use as selection criteria. + yaxis objects will be selected if they contain + properties corresponding to all of the dictionary's keys, with + values that exactly match the supplied values. If None + (the default), all yaxis objects are selected. + row, col: int or None (default None) + Subplot row and column index of yaxis objects to select. + To select yaxis objects by row and column, the Figure + must have been created using plotly.subplots.make_subplots. + If None (the default), all yaxis objects are selected. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self.select_yaxes(selector=selector, row=row, col=col): + obj.update(patch) + + return self diff --git a/plotly/subplots.py b/plotly/subplots.py index ba489422789..eb0718619b5 100644 --- a/plotly/subplots.py +++ b/plotly/subplots.py @@ -904,7 +904,7 @@ def _init_subplot_single( 'trace_kwargs': {trace_key: label}} # increment max_subplot_id - max_subplot_ids['scene'] = cnt + max_subplot_ids[subplot_type] = cnt return ref_element diff --git a/plotly/tests/test_core/test_graph_objs/test_frames.py b/plotly/tests/test_core/test_graph_objs/test_frames.py index 1e2ec3ddcd1..66239207a09 100644 --- a/plotly/tests/test_core/test_graph_objs/test_frames.py +++ b/plotly/tests/test_core/test_graph_objs/test_frames.py @@ -4,6 +4,8 @@ from plotly.graph_objs import Bar, Frames, Frame, Layout +from nose.plugins.attrib import attr + import re @@ -44,6 +46,7 @@ def test_non_string_frame(self): # with self.assertRaises(exceptions.PlotlyListEntryError): # frames.append(0) + @attr('nodev') def test_deeply_nested_layout_attributes(self): frames = Frame frames.layout = [Layout()] @@ -58,6 +61,7 @@ def test_deeply_nested_layout_attributes(self): {'color', 'family', 'size'} ) + @attr('nodev') def test_deeply_nested_data_attributes(self): frames = Frame frames.data = [Bar()] diff --git a/plotly/tests/test_core/test_update_traces/__init__.py b/plotly/tests/test_core/test_update_objects/__init__.py similarity index 100% rename from plotly/tests/test_core/test_update_traces/__init__.py rename to plotly/tests/test_core/test_update_objects/__init__.py diff --git a/plotly/tests/test_core/test_update_objects/test_update_subplots.py b/plotly/tests/test_core/test_update_objects/test_update_subplots.py new file mode 100644 index 00000000000..d435cd1ca9f --- /dev/null +++ b/plotly/tests/test_core/test_update_objects/test_update_subplots.py @@ -0,0 +1,472 @@ +from __future__ import absolute_import + +import copy +from unittest import TestCase + +import plotly.graph_objs as go +from plotly.graph_objs import Figure +from plotly.subplots import make_subplots +from _plotly_future_ import _future_flags + + +class TestSelectForEachUpdateSubplots(TestCase): + + def setUp(self): + _future_flags.add('v4_subplots') + + fig = make_subplots( + rows=3, + cols=3, + specs=[[{}, {'type': 'scene'}, {}], + [{}, {'type': 'polar'}, {'type': 'polar'}], + [{'type': 'xy', 'colspan': 2}, None, {'type': 'ternary'}]] + ).update(layout={'height': 800}) + + fig.layout.xaxis.title.text = 'A' + fig.layout.xaxis2.title.text = 'A' + fig.layout.xaxis3.title.text = 'B' + fig.layout.xaxis4.title.text = 'B' + + fig.layout.yaxis.title.text = 'A' + fig.layout.yaxis2.title.text = 'B' + fig.layout.yaxis3.title.text = 'A' + fig.layout.yaxis4.title.text = 'B' + + fig.layout.polar.angularaxis.rotation = 45 + fig.layout.polar2.angularaxis.rotation = 45 + fig.layout.polar.radialaxis.title.text = 'A' + fig.layout.polar2.radialaxis.title.text = 'B' + + fig.layout.scene.xaxis.title.text = 'A' + fig.layout.scene.yaxis.title.text = 'B' + + fig.layout.ternary.aaxis.title.text = 'A' + + self.fig = fig + self.fig_no_grid = go.Figure(self.fig.to_dict()) + + def tearDown(self): + _future_flags.remove('v4_subplots') + + def assert_select_subplots( + self, subplot_type, subplots_name, expected_nums, + selector=None, row=None, col=None, test_no_grid=False): + + select_fn = getattr(Figure, 'select_' + subplots_name) + for_each_fn = getattr(Figure, 'for_each_' + subplot_type) + + def check_select(fig): + # Check select_* + subplots = list( + select_fn(fig, selector=selector, row=row, col=col)) + expected_keys = [ + subplot_type + (str(cnt) if cnt > 1 else '') + for cnt in expected_nums + ] + + self.assertEqual(len(subplots), len(expected_keys)) + self.assertTrue(all( + v1 is fig.layout[k] + for v1, k in zip(subplots, expected_keys))) + + # Check for_each_* + subplots = [] + res = for_each_fn( + fig, lambda obj: subplots.append(obj), + selector=selector, row=row, col=col + ) + + self.assertIs(res, fig) + + self.assertEqual(len(subplots), len(expected_keys)) + self.assertTrue(all( + v1 is fig.layout[k] + for v1, k in zip(subplots, expected_keys))) + + check_select(self.fig) + if test_no_grid: + check_select(self.fig_no_grid) + + def test_select_by_type(self): + self.assert_select_subplots( + 'xaxis', 'xaxes', [1, 2, 3, 4], test_no_grid=True) + + self.assert_select_subplots( + 'yaxis', 'yaxes', [1, 2, 3, 4], test_no_grid=True) + + self.assert_select_subplots( + 'scene', 'scenes', [1], test_no_grid=True) + + self.assert_select_subplots( + 'polar', 'polars', [1, 2], test_no_grid=True) + + self.assert_select_subplots( + 'ternary', 'ternaries', [1], test_no_grid=True) + + # No 'geo' or 'mapbox' subplots initialized, but the first subplot + # object is always present + self.assert_select_subplots( + 'geo', 'geos', [1], test_no_grid=True) + + self.assert_select_subplots( + 'mapbox', 'mapboxes', [1], test_no_grid=True) + + def test_select_by_type_and_grid(self): + self.assert_select_subplots( + 'xaxis', 'xaxes', [1, 2], row=1) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [1, 3, 4], col=1) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [2], col=3) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [4], row=3, col=1) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [], row=2, col=2) + + def test_select_by_type_and_selector(self): + # xaxis + self.assert_select_subplots( + 'xaxis', 'xaxes', [1, 2], + selector={'title.text': 'A'}, test_no_grid=True) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [3, 4], + selector={'title.text': 'B'}, test_no_grid=True) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [], + selector={'title.text': 'C'}, test_no_grid=True) + + # yaxis + self.assert_select_subplots( + 'yaxis', 'yaxes', [1, 3], + selector={'title.text': 'A'}, test_no_grid=True) + + self.assert_select_subplots( + 'yaxis', 'yaxes', [2, 4], + selector={'title.text': 'B'}, test_no_grid=True) + + self.assert_select_subplots( + 'yaxis', 'yaxes', [], + selector={'title.text': 'C'}, test_no_grid=True) + + # scene + self.assert_select_subplots( + 'scene', 'scenes', [1], + selector={'xaxis.title.text': 'A'}, test_no_grid=True) + + self.assert_select_subplots( + 'scene', 'scenes', [1], + selector={'xaxis.title.text': 'A', + 'yaxis.title.text': 'B'}, + test_no_grid=True) + + self.assert_select_subplots( + 'scene', 'scenes', [], + selector={'xaxis.title.text': 'A', + 'yaxis.title.text': 'C'}, + test_no_grid=True) + + # polar + self.assert_select_subplots( + 'polar', 'polars', [1, 2], + selector={'angularaxis.rotation': 45}, + test_no_grid=True) + + self.assert_select_subplots( + 'polar', 'polars', [2], + selector={'angularaxis.rotation': 45, + 'radialaxis_title_text': 'B'}, + test_no_grid=True) + + self.assert_select_subplots( + 'polar', 'polars', [], + selector={'angularaxis.rotation': 45, + 'radialaxis_title_text': 'C'}, + test_no_grid=True) + + # ternary + self.assert_select_subplots( + 'ternary', 'ternaries', [1], + selector={'aaxis.title.text': 'A'}, + test_no_grid=True) + + self.assert_select_subplots( + 'ternary', 'ternaries', [], + selector={'aaxis.title.text': 'C'}, + test_no_grid=True) + + self.assert_select_subplots( + 'ternary', 'ternaries', [], + selector={'aaxis.bogus.text': 'A'}, + test_no_grid=True) + + # No 'geo' or 'mapbox' subplots initialized, but the first subplot + # object is always present + self.assert_select_subplots( + 'geo', 'geos', [], + selector={'bgcolor': 'blue'}, + test_no_grid=True) + + self.assert_select_subplots( + 'geo', 'geos', [], + selector={'bogus': 'blue'}, + test_no_grid=True) + + self.assert_select_subplots( + 'mapbox', 'mapboxes', [], + selector={'pitch': 45}, + test_no_grid=True) + + def test_select_by_type_and_grid_and_selector(self): + # xaxis + self.assert_select_subplots( + 'xaxis', 'xaxes', [1, 2], + row=1, + selector={'title.text': 'A'}) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [1], + col=1, + selector={'title.text': 'A'}) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [], + col=2, + selector={'title.text': 'A'}) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [3, 4], + col=1, + selector={'title.text': 'B'}) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [3], + row=2, + selector={'title.text': 'B'}) + + self.assert_select_subplots( + 'xaxis', 'xaxes', [4], + row=3, col=1, + selector={'title.text': 'B'}) + + # yaxis + self.assert_select_subplots( + 'yaxis', 'yaxes', [1, 3], + col=1, + selector={'title.text': 'A'}) + + self.assert_select_subplots( + 'yaxis', 'yaxes', [4], + col=1, + selector={'title.text': 'B'}) + + # polar + self.assert_select_subplots( + 'polar', 'polars', [1, 2], + row=2, + selector={'angularaxis.rotation': 45}) + + self.assert_select_subplots( + 'polar', 'polars', [1], + col=2, + selector={'angularaxis.rotation': 45}) + + self.assert_select_subplots( + 'polar', 'polars', [2], + row=2, col=3, + selector={'angularaxis.rotation': 45}) + + self.assert_select_subplots( + 'polar', 'polars', [], + row=2, col=3, + selector={'angularaxis.rotation': 0}) + + def assert_update_subplots( + self, subplot_type, subplots_name, expected_nums, patch, + selector=None, row=None, col=None, test_no_grid=False): + + update_fn = getattr(Figure, 'update_' + subplots_name) + + def check_update(fig): + + # Copy input figure so that we don't modify it + fig_orig = fig + fig = copy.deepcopy(fig) + + # perform update_* + update_res = update_fn( + fig, patch, selector=selector, row=row, col=col) + self.assertIs(update_res, fig) + + # Build expected layout keys + expected_keys = [ + subplot_type + (str(cnt) if cnt > 1 else '') + for cnt in expected_nums + ] + + # Iterate over all layout keys + for k in fig.layout: + orig_obj = copy.deepcopy(fig_orig.layout[k]) + new_obj = fig.layout[k] + if k in expected_keys: + # Make sure sure there is an initial difference + self.assertNotEqual(orig_obj, new_obj) + orig_obj.update(patch) + + self.assertEqual(new_obj, orig_obj) + + check_update(self.fig) + if test_no_grid: + check_update(self.fig_no_grid) + + def test_update_by_type(self): + self.assert_update_subplots( + 'xaxis', 'xaxes', [1, 2, 3, 4], + {'title.font.family': 'Rockwell'}, + test_no_grid=True) + + self.assert_update_subplots( + 'yaxis', 'yaxes', [1, 2, 3, 4], + {'range': [5, 10]}, + test_no_grid=True) + + self.assert_update_subplots( + 'scene', 'scenes', [1], + {'zaxis.title.text': 'Z-AXIS'}, + test_no_grid=True) + + self.assert_update_subplots( + 'polar', 'polars', [1, 2], + {'angularaxis.rotation': 15}, + test_no_grid=True) + + self.assert_update_subplots( + 'ternary', 'ternaries', [1], + {'aaxis.title.font.family': 'Rockwell'}, + test_no_grid=True) + + # No 'geo' or 'mapbox' subplots initialized, but the first subplot + # object is always present + self.assert_update_subplots( + 'geo', 'geos', [1], + {'bgcolor': 'purple'}, + test_no_grid=True) + + self.assert_update_subplots( + 'mapbox', 'mapboxes', [1], + {'pitch': 99}, + test_no_grid=True) + + def test_update_by_type_and_grid(self): + self.assert_update_subplots( + 'xaxis', 'xaxes', [1, 3, 4], + {'title.font.family': 'Rockwell'}, + col=1) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [1, 2], + {'title.font.family': 'Rockwell'}, + row=1) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [1], + {'title.font.family': 'Rockwell'}, + row=1, + col=1) + + self.assert_update_subplots( + 'polar', 'polars', [1, 2], + {'angularaxis.rotation': 15}, + row=2) + + self.assert_update_subplots( + 'polar', 'polars', [1], + {'angularaxis.rotation': 15}, + col=2) + + self.assert_update_subplots( + 'polar', 'polars', [2], + {'angularaxis.rotation': 15}, + row=2, + col=3) + + def test_update_by_type_and_grid_and_selector(self): + # xaxis + self.assert_update_subplots( + 'xaxis', 'xaxes', [1, 2], + {'title.font.family': 'Rockwell'}, + row=1, + selector={'title.text': 'A'}) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [1], + {'title.font.family': 'Rockwell'}, + col=1, + selector={'title.text': 'A'}) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [], + {'title.font.family': 'Rockwell'}, + col=2, + selector={'title.text': 'A'}) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [3, 4], + {'title.font.family': 'Rockwell'}, + col=1, + selector={'title.text': 'B'}) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [3], + {'title.font.family': 'Rockwell'}, + row=2, + selector={'title.text': 'B'}) + + self.assert_update_subplots( + 'xaxis', 'xaxes', [4], + {'title.font.family': 'Rockwell'}, + row=3, col=1, + selector={'title.text': 'B'}) + + # yaxis + self.assert_update_subplots( + 'yaxis', 'yaxes', [1, 3], + {'title.font.family': 'Rockwell'}, + col=1, + selector={'title.text': 'A'}) + + self.assert_update_subplots( + 'yaxis', 'yaxes', [4], + {'title.font.family': 'Rockwell'}, + col=1, + selector={'title.text': 'B'}) + + # polar + self.assert_update_subplots( + 'polar', 'polars', [1, 2], + {'radialaxis.title.font.family': 'Rockwell'}, + row=2, + selector={'angularaxis.rotation': 45}) + + self.assert_update_subplots( + 'polar', 'polars', [1], + {'radialaxis.title.font.family': 'Rockwell'}, + col=2, + selector={'angularaxis.rotation': 45}) + + self.assert_update_subplots( + 'polar', 'polars', [2], + {'radialaxis.title.font.family': 'Rockwell'}, + row=2, col=3, + selector={'angularaxis.rotation': 45}) + + self.assert_update_subplots( + 'polar', 'polars', [], + {'radialaxis.title.font.family': 'Rockwell'}, + row=2, col=3, + selector={'angularaxis.rotation': 0}) diff --git a/plotly/tests/test_core/test_update_traces/test_update_traces.py b/plotly/tests/test_core/test_update_objects/test_update_traces.py similarity index 91% rename from plotly/tests/test_core/test_update_traces/test_update_traces.py rename to plotly/tests/test_core/test_update_objects/test_update_traces.py index 875002bd425..36d598d2aa8 100644 --- a/plotly/tests/test_core/test_update_traces/test_update_traces.py +++ b/plotly/tests/test_core/test_update_objects/test_update_traces.py @@ -158,12 +158,22 @@ def test_select_by_type(self): [], selector={'type': 'pie'}, test_no_grid=True) def test_select_by_grid(self): + # Row and column self.assert_select_traces([0, 1], row=1, col=1) self.assert_select_traces([2, 3], row=2, col=1) self.assert_select_traces([4, 5], row=1, col=2) self.assert_select_traces([6, 7], row=2, col=2) self.assert_select_traces([8], row=3, col=1) + # Row only + self.assert_select_traces([0, 1, 4, 5], row=1) + self.assert_select_traces([2, 3, 6, 7], row=2) + self.assert_select_traces([8], row=3) + + # Col only + self.assert_select_traces([0, 1, 2, 3, 8], col=1) + self.assert_select_traces([4, 5, 6, 7], col=2) + def test_select_by_property_across_trace_types(self): self.assert_select_traces( [0, 4, 6], selector={'mode': 'markers'}, test_no_grid=True) @@ -322,3 +332,25 @@ def test_update_traces_by_type(self): {'hoverinfo': 'label+percent'}, [], selector={'type': 'pie'} ) + + def test_update_traces_by_grid_and_selector(self): + self.assert_update_traces( + {'marker.size': 5}, + [4, 6], + selector={'marker.color': 'green'}, + col=2 + ) + + self.assert_update_traces( + {'marker.size': 6}, + [0, 4], + selector={'marker.color': 'green'}, + row=1 + ) + + self.assert_update_traces( + {'marker.size': 6}, + [6], + selector={'marker.color': 'green'}, + row=2, col=2 + )