diff --git a/packages/python/plotly/codegen/figure.py b/packages/python/plotly/codegen/figure.py index 42a964d2ec9..e94ac554449 100644 --- a/packages/python/plotly/codegen/figure.py +++ b/packages/python/plotly/codegen/figure.py @@ -89,18 +89,18 @@ def __init__(self, data=None, layout=None, frames=None, skip_invalid=False, **kwargs): \"\"\" Create a new :class:{fig_classname} instance - + Parameters ---------- data {data_description} - + layout {layout_description} - + frames {frames_description} - + skip_invalid: bool If True, invalid properties in the figure specification will be skipped silently. If False (default) invalid properties in the @@ -233,8 +233,8 @@ def add_{trace_node.plotly_name}(self""" * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -287,7 +287,7 @@ def for_each_{singular_name}( \"\"\" Apply a function to all {singular_name} objects that satisfy the specified selection criteria - + Parameters ---------- fn: @@ -329,7 +329,7 @@ def update_{plural_name}( \"\"\" Perform a property update operation on all {singular_name} objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -395,7 +395,7 @@ def select_{method_prefix}{plural_name}( Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -403,7 +403,9 @@ def select_{method_prefix}{plural_name}( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each {singular_name} and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth {singular_name} matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of {plural_name} to select. To select {plural_name} by row and column, the Figure must have been @@ -443,7 +445,7 @@ def for_each_{method_prefix}{singular_name}( ---------- fn: Function that inputs a single {singular_name} object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -451,7 +453,9 @@ def for_each_{method_prefix}{singular_name}( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each {singular_name} and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth {singular_name} matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of {plural_name} to select. To select {plural_name} by row and column, the Figure must have been @@ -504,7 +508,7 @@ def update_{method_prefix}{plural_name}( patch: dict or None (default None) Dictionary of property updates to be applied to all {plural_name} that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -512,7 +516,9 @@ def update_{method_prefix}{plural_name}( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each {singular_name} and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth {singular_name} matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of {plural_name} to select. To select {plural_name} by row and column, the Figure must have been diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 7e089eeb3c1..5de2e4c23e5 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -177,7 +177,7 @@ def _check_path_in_prop_tree(obj, path, error_cast=None): an Exception object or None. The caller can raise this exception to see where the lookup error occurred. """ - if type(path) == type(tuple()): + if isinstance(path, tuple): path = _remake_path_from_tuple(path) prop, prop_idcs = _str_to_dict_path_full(path) prev_objs = [] @@ -242,7 +242,7 @@ def _check_path_in_prop_tree(obj, path, error_cast=None): # Make KeyError more pretty by changing it to a PlotlyKeyError, # because the Python interpreter has a special way of printing # KeyError - if type(e) == type(KeyError()): + if isinstance(e, KeyError): e = PlotlyKeyError() if error_cast is not None: e = error_cast() @@ -282,7 +282,7 @@ def _indexing_combinations(dims, alls, product=False): for d, a in zip(dims, alls): if d == "all": d = a - elif type(d) != type(list()): + elif not isinstance(d, list): d = [d] r.append(d) if product: @@ -293,7 +293,7 @@ def _indexing_combinations(dims, alls, product=False): def _is_select_subplot_coordinates_arg(*args): """ Returns true if any args are lists or the string 'all' """ - return any((a == "all") or (type(a) == type(list())) for a in args) + return any((a == "all") or isinstance(a, list) for a in args) def _axis_spanning_shapes_docstr(shape_type): @@ -382,6 +382,12 @@ def _axis_spanning_shapes_docstr(shape_type): return docstr +def _generator(i): + """ "cast" an iterator to a generator """ + for x in i: + yield x + + class BaseFigure(object): """ Base class for all figure types (both widget and non-widget) @@ -1101,7 +1107,7 @@ def select_traces(self, selector=None, row=None, col=None, secondary_y=None): Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -1109,7 +1115,9 @@ def select_traces(self, selector=None, row=None, col=None, secondary_y=None): selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each trace and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth trace matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of traces to select. To select traces by row and column, the Figure must have been @@ -1177,25 +1185,27 @@ def select_traces(self, selector=None, row=None, col=None, secondary_y=None): def _perform_select_traces(self, filter_by_subplot, grid_subplot_refs, selector): from plotly.subplots import _get_subplot_ref_for_trace - for trace in self.data: - # Filter by subplot - if filter_by_subplot: - trace_subplot_ref = _get_subplot_ref_for_trace(trace) - if trace_subplot_ref not in grid_subplot_refs: - continue + # functions for filtering + def _filter_by_subplot_ref(trace): + trace_subplot_ref = _get_subplot_ref_for_trace(trace) + return trace_subplot_ref in grid_subplot_refs - # Filter by selector - if not self._selector_matches(trace, selector): - continue + funcs = [] + if filter_by_subplot: + funcs.append(_filter_by_subplot_ref) - yield trace + return _generator(self._filter_by_selector(self.data, funcs, selector)) @staticmethod def _selector_matches(obj, selector): if selector is None: return True + # If selector is a string then put it at the 'type' key of a dictionary + # to select objects where "type":selector + if isinstance(selector, six.string_types): + selector = dict(type=selector) # If selector is a dict, compare the fields - if (type(selector) == type(dict())) or isinstance(selector, BasePlotlyType): + if isinstance(selector, dict) or isinstance(selector, BasePlotlyType): # This returns True if selector is an empty dict for k in selector: if k not in obj: @@ -1214,7 +1224,7 @@ def _selector_matches(obj, selector): return False return True # If selector is a function, call it with the obj as the argument - elif type(selector) == type(lambda x: True): + elif six.callable(selector): return selector(obj) else: raise TypeError( @@ -1222,6 +1232,34 @@ def _selector_matches(obj, selector): "accepting a graph object returning a boolean." ) + def _filter_by_selector(self, objects, funcs, selector): + """ + objects is a sequence of objects, funcs a list of functions that + return True if the object should be included in the selection and False + otherwise and selector is an argument to the self._selector_matches + function. + If selector is an integer, the resulting sequence obtained after + sucessively filtering by each function in funcs is indexed by this + integer. + Otherwise selector is used as the selector argument to + self._selector_matches which is used to filter down the sequence. + The function returns the sequence (an iterator). + """ + + # if selector is not an int, we call it on each trace to test it for selection + if not isinstance(selector, int): + funcs.append(lambda obj: self._selector_matches(obj, selector)) + + def _filt(last, f): + return filter(f, last) + + filtered_objects = reduce(_filt, funcs, objects) + + if isinstance(selector, int): + return iter([list(filtered_objects)[selector]]) + + return filtered_objects + def for_each_trace(self, fn, selector=None, row=None, col=None, secondary_y=None): """ Apply a function to all traces that satisfy the specified selection @@ -1231,7 +1269,7 @@ def for_each_trace(self, fn, selector=None, row=None, col=None, secondary_y=None ---------- fn: Function that inputs a single trace object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -1239,7 +1277,9 @@ def for_each_trace(self, fn, selector=None, row=None, col=None, secondary_y=None selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each trace and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth trace matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of traces to select. To select traces by row and column, the Figure must have been @@ -1288,7 +1328,7 @@ def update_traces( patch: dict or None (default None) Dictionary of property updates to be applied to all traces that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -1296,7 +1336,9 @@ def update_traces( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each trace and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth trace matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of traces to select. To select traces by row and column, the Figure must have been @@ -1391,37 +1433,25 @@ def _select_layout_subplots_by_prefix( 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, 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, None))[1] != col - ): - # col specified and this is not a match - continue - elif ( - secondary_y is not None - and container_to_row_col.get(k, (None, None, None))[2] - != secondary_y - ): - continue - - # Filter by selector - if not self._selector_matches(self.layout[k], selector): - continue - - yield self.layout[k] + layout_keys_filters = [ + lambda k: k.startswith(prefix) and self.layout[k] is not None, + lambda k: row is None + or container_to_row_col.get(k, (None, None, None))[0] == row, + lambda k: col is None + or container_to_row_col.get(k, (None, None, None))[1] == col, + lambda k: ( + secondary_y is None + or container_to_row_col.get(k, (None, None, None))[2] == secondary_y + ), + ] + layout_keys = reduce( + lambda last, f: filter(f, last), + layout_keys_filters, + # Natural sort keys so that xaxis20 is after xaxis3 + _natural_sort_strings(list(self.layout)), + ) + layout_objs = [self.layout[k] for k in layout_keys] + return _generator(self._filter_by_selector(layout_objs, [], selector)) def _select_annotations_like( self, prop, selector=None, row=None, col=None, secondary_y=None @@ -1450,27 +1480,25 @@ def _select_annotations_like( yref_to_row[yref] = r + 1 yref_to_secondary_y[yref] = is_secondary_y - for obj in self.layout[prop]: - # Filter by row - if col is not None and xref_to_col.get(obj.xref, None) != col: - continue + # filter down (select) which graph objects, by applying the filters + # successively + def _filter_row(obj): + """ Filter objects in rows by column """ + return (col is None) or (xref_to_col.get(obj.xref, None) == col) - # Filter by col - if row is not None and yref_to_row.get(obj.yref, None) != row: - continue + def _filter_col(obj): + """ Filter objects in columns by row """ + return (row is None) or (yref_to_row.get(obj.yref, None) == row) - # Filter by secondary y - if ( - secondary_y is not None - and yref_to_secondary_y.get(obj.yref, None) != secondary_y - ): - continue + def _filter_sec_y(obj): + """ Filter objects on secondary y axes """ + return (secondary_y is None) or ( + yref_to_secondary_y.get(obj.yref, None) == secondary_y + ) - # Filter by selector - if not self._selector_matches(obj, selector): - continue + funcs = [_filter_row, _filter_col, _filter_sec_y] - yield obj + return _generator(self._filter_by_selector(self.layout[prop], funcs, selector)) def _add_annotation_like( self, @@ -3913,14 +3941,10 @@ def _make_axis_spanning_layout_object(self, direction, shape): """ if direction == "vertical": # fix y points to top and bottom of subplot - axis = "y" ref = "yref" - axis_layout_key_template = "yaxis%s" elif direction == "horizontal": # fix x points to left and right of subplot - axis = "x" ref = "xref" - axis_layout_key_template = "xaxis%s" else: raise ValueError( "Bad direction: %s. Permissible values are 'vertical' and 'horizontal'." @@ -3991,7 +4015,7 @@ def _process_multiple_axis_spanning_shapes( ): n_layout_objs_after = len(self.layout[layout_obj]) if (n_layout_objs_after > n_layout_objs_before) and ( - row == None and col == None + row is None and col is None ): # this was called intending to add to a single plot (and # self.add_{layout_obj} succeeded) @@ -4104,7 +4128,7 @@ def _subplot_not_empty(self, xref, yref, selector="all"): if not selector: # If nothing to select was specified then a subplot is always deemed non-empty return True - if selector == True: + if selector is True: selector = "all" if selector == "all": selector = ["traces", "shapes", "annotations", "images"] @@ -4274,7 +4298,6 @@ def _process_kwargs(self, **kwargs): """ Process any extra kwargs that are not predefined as constructor params """ - invalid_kwargs = {} for k, v in kwargs.items(): err = _check_path_in_prop_tree(self, k, error_cast=ValueError) if err is None: @@ -6249,7 +6272,7 @@ def _get_child_props(self, child): # ------------------------------------- try: trace_index = BaseFigure._index_is(self.data, child) - except ValueError as _: + except ValueError: trace_index = None # Child is a trace diff --git a/packages/python/plotly/plotly/graph_objs/_figure.py b/packages/python/plotly/plotly/graph_objs/_figure.py index d9cf48be670..eda51ca99ed 100644 --- a/packages/python/plotly/plotly/graph_objs/_figure.py +++ b/packages/python/plotly/plotly/graph_objs/_figure.py @@ -7,7 +7,7 @@ def __init__( ): """ Create a new :class:Figure instance - + Parameters ---------- data @@ -39,7 +39,7 @@ def __init__( the specified trace type (e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}]) - + layout The 'layout' property is an instance of Layout that may be specified as: @@ -540,7 +540,7 @@ def __init__( yaxis :class:`plotly.graph_objects.layout.YAxis` instance or dict with compatible properties - + frames The 'frames' property is a tuple of instances of Frame that may be specified as: @@ -573,7 +573,7 @@ def __init__( traces A list of trace indices that identify the respective traces in the data attribute - + skip_invalid: bool If True, invalid properties in the figure specification will be skipped silently. If False (default) invalid properties in the @@ -17506,7 +17506,7 @@ def for_each_coloraxis(self, fn, selector=None, row=None, col=None): """ Apply a function to all coloraxis objects that satisfy the specified selection criteria - + Parameters ---------- fn: @@ -17542,7 +17542,7 @@ def update_coloraxes( """ Perform a property update operation on all coloraxis objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17618,7 +17618,7 @@ 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: @@ -17654,7 +17654,7 @@ def update_geos( """ Perform a property update operation on all geo objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17730,7 +17730,7 @@ 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: @@ -17766,7 +17766,7 @@ def update_mapboxes( """ Perform a property update operation on all mapbox objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17842,7 +17842,7 @@ 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: @@ -17878,7 +17878,7 @@ def update_polars( """ Perform a property update operation on all polar objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17954,7 +17954,7 @@ 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: @@ -17990,7 +17990,7 @@ def update_scenes( """ Perform a property update operation on all scene objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18066,7 +18066,7 @@ 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: @@ -18102,7 +18102,7 @@ def update_ternaries( """ Perform a property update operation on all ternary objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18178,7 +18178,7 @@ 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: @@ -18214,7 +18214,7 @@ def update_xaxes( """ Perform a property update operation on all xaxis objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18283,8 +18283,8 @@ def select_yaxes(self, selector=None, row=None, col=None, secondary_y=None): * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -18304,7 +18304,7 @@ def for_each_yaxis(self, fn, selector=None, row=None, col=None, secondary_y=None """ Apply a function to all yaxis objects that satisfy the specified selection criteria - + Parameters ---------- fn: @@ -18330,8 +18330,8 @@ def for_each_yaxis(self, fn, selector=None, row=None, col=None, secondary_y=None * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -18361,7 +18361,7 @@ def update_yaxes( """ Perform a property update operation on all yaxis objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18392,8 +18392,8 @@ def update_yaxes( * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -18422,7 +18422,7 @@ def select_annotations(self, selector=None, row=None, col=None, secondary_y=None Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -18430,7 +18430,9 @@ def select_annotations(self, selector=None, row=None, col=None, secondary_y=None selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each annotation and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth annotation matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of annotations to select. To select annotations by row and column, the Figure must have been @@ -18470,7 +18472,7 @@ def for_each_annotation( ---------- fn: Function that inputs a single annotation object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -18478,7 +18480,9 @@ def for_each_annotation( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each annotation and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth annotation matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of annotations to select. To select annotations by row and column, the Figure must have been @@ -18525,7 +18529,7 @@ def update_annotations( patch: dict or None (default None) Dictionary of property updates to be applied to all annotations that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -18533,7 +18537,9 @@ def update_annotations( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each annotation and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth annotation matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of annotations to select. To select annotations by row and column, the Figure must have been @@ -18993,7 +18999,7 @@ def select_layout_images(self, selector=None, row=None, col=None, secondary_y=No Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19001,7 +19007,9 @@ def select_layout_images(self, selector=None, row=None, col=None, secondary_y=No selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each image and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth image matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of images to select. To select images by row and column, the Figure must have been @@ -19041,7 +19049,7 @@ def for_each_layout_image( ---------- fn: Function that inputs a single image object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19049,7 +19057,9 @@ def for_each_layout_image( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each image and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth image matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of images to select. To select images by row and column, the Figure must have been @@ -19092,7 +19102,7 @@ def update_layout_images( patch: dict or None (default None) Dictionary of property updates to be applied to all images that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19100,7 +19110,9 @@ def update_layout_images( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each image and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth image matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of images to select. To select images by row and column, the Figure must have been @@ -19306,7 +19318,7 @@ def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19314,7 +19326,9 @@ def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each shape and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth shape matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of shapes to select. To select shapes by row and column, the Figure must have been @@ -19352,7 +19366,7 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None ---------- fn: Function that inputs a single shape object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19360,7 +19374,9 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each shape and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth shape matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of shapes to select. To select shapes by row and column, the Figure must have been @@ -19403,7 +19419,7 @@ def update_shapes( patch: dict or None (default None) Dictionary of property updates to be applied to all shapes that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19411,7 +19427,9 @@ def update_shapes( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each shape and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth shape matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of shapes to select. To select shapes by row and column, the Figure must have been diff --git a/packages/python/plotly/plotly/graph_objs/_figurewidget.py b/packages/python/plotly/plotly/graph_objs/_figurewidget.py index d32e8b36e38..0004e72598d 100644 --- a/packages/python/plotly/plotly/graph_objs/_figurewidget.py +++ b/packages/python/plotly/plotly/graph_objs/_figurewidget.py @@ -7,7 +7,7 @@ def __init__( ): """ Create a new :class:FigureWidget instance - + Parameters ---------- data @@ -39,7 +39,7 @@ def __init__( the specified trace type (e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}]) - + layout The 'layout' property is an instance of Layout that may be specified as: @@ -540,7 +540,7 @@ def __init__( yaxis :class:`plotly.graph_objects.layout.YAxis` instance or dict with compatible properties - + frames The 'frames' property is a tuple of instances of Frame that may be specified as: @@ -573,7 +573,7 @@ def __init__( traces A list of trace indices that identify the respective traces in the data attribute - + skip_invalid: bool If True, invalid properties in the figure specification will be skipped silently. If False (default) invalid properties in the @@ -17506,7 +17506,7 @@ def for_each_coloraxis(self, fn, selector=None, row=None, col=None): """ Apply a function to all coloraxis objects that satisfy the specified selection criteria - + Parameters ---------- fn: @@ -17542,7 +17542,7 @@ def update_coloraxes( """ Perform a property update operation on all coloraxis objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17618,7 +17618,7 @@ 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: @@ -17654,7 +17654,7 @@ def update_geos( """ Perform a property update operation on all geo objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17730,7 +17730,7 @@ 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: @@ -17766,7 +17766,7 @@ def update_mapboxes( """ Perform a property update operation on all mapbox objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17842,7 +17842,7 @@ 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: @@ -17878,7 +17878,7 @@ def update_polars( """ Perform a property update operation on all polar objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -17954,7 +17954,7 @@ 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: @@ -17990,7 +17990,7 @@ def update_scenes( """ Perform a property update operation on all scene objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18066,7 +18066,7 @@ 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: @@ -18102,7 +18102,7 @@ def update_ternaries( """ Perform a property update operation on all ternary objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18178,7 +18178,7 @@ 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: @@ -18214,7 +18214,7 @@ def update_xaxes( """ Perform a property update operation on all xaxis objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18283,8 +18283,8 @@ def select_yaxes(self, selector=None, row=None, col=None, secondary_y=None): * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -18304,7 +18304,7 @@ def for_each_yaxis(self, fn, selector=None, row=None, col=None, secondary_y=None """ Apply a function to all yaxis objects that satisfy the specified selection criteria - + Parameters ---------- fn: @@ -18330,8 +18330,8 @@ def for_each_yaxis(self, fn, selector=None, row=None, col=None, secondary_y=None * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -18361,7 +18361,7 @@ def update_yaxes( """ Perform a property update operation on all yaxis objects that satisfy the specified selection criteria - + Parameters ---------- patch: dict @@ -18392,8 +18392,8 @@ def update_yaxes( * If False, only select yaxis objects associated with the primary y-axis of the subplot. * If None (the default), do not filter yaxis objects based on - a secondary y-axis condition. - + a secondary y-axis condition. + To select yaxis objects by secondary y-axis, the Figure must have been created using plotly.subplots.make_subplots. See the docstring for the specs argument to make_subplots for more @@ -18422,7 +18422,7 @@ def select_annotations(self, selector=None, row=None, col=None, secondary_y=None Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -18430,7 +18430,9 @@ def select_annotations(self, selector=None, row=None, col=None, secondary_y=None selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each annotation and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth annotation matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of annotations to select. To select annotations by row and column, the Figure must have been @@ -18470,7 +18472,7 @@ def for_each_annotation( ---------- fn: Function that inputs a single annotation object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -18478,7 +18480,9 @@ def for_each_annotation( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each annotation and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth annotation matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of annotations to select. To select annotations by row and column, the Figure must have been @@ -18525,7 +18529,7 @@ def update_annotations( patch: dict or None (default None) Dictionary of property updates to be applied to all annotations that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -18533,7 +18537,9 @@ def update_annotations( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each annotation and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth annotation matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of annotations to select. To select annotations by row and column, the Figure must have been @@ -18993,7 +18999,7 @@ def select_layout_images(self, selector=None, row=None, col=None, secondary_y=No Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19001,7 +19007,9 @@ def select_layout_images(self, selector=None, row=None, col=None, secondary_y=No selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each image and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth image matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of images to select. To select images by row and column, the Figure must have been @@ -19041,7 +19049,7 @@ def for_each_layout_image( ---------- fn: Function that inputs a single image object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19049,7 +19057,9 @@ def for_each_layout_image( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each image and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth image matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of images to select. To select images by row and column, the Figure must have been @@ -19092,7 +19102,7 @@ def update_layout_images( patch: dict or None (default None) Dictionary of property updates to be applied to all images that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19100,7 +19110,9 @@ def update_layout_images( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each image and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth image matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of images to select. To select images by row and column, the Figure must have been @@ -19306,7 +19318,7 @@ def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): Parameters ---------- - selector: dict, function, or None (default None) + selector: dict, function, int, str, or None (default None) Dict to use as selection criteria. Annotations will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19314,7 +19326,9 @@ def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each shape and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth shape matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of shapes to select. To select shapes by row and column, the Figure must have been @@ -19352,7 +19366,7 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None ---------- fn: Function that inputs a single shape object. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19360,7 +19374,9 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each shape and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth shape matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of shapes to select. To select shapes by row and column, the Figure must have been @@ -19403,7 +19419,7 @@ def update_shapes( patch: dict or None (default None) Dictionary of property updates to be applied to all shapes that satisfy the selection criteria. - selector: dict, function, or None (default None) + selector: dict, function, int, str or None (default None) Dict to use as selection criteria. Traces will be selected if they contain properties corresponding to all of the dictionary's keys, with values that exactly match @@ -19411,7 +19427,9 @@ def update_shapes( selected. If a function, it must be a function accepting a single argument and returning a boolean. The function will be called on each shape and those for which the function returned True - will be in the selection. + will be in the selection. If an int N, the Nth shape matching row + and col will be selected (N can be negative). If a string S, the selector + is equivalent to dict(type=S). row, col: int or None (default None) Subplot row and column index of shapes to select. To select shapes by row and column, the Figure must have been diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_selector_matches.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_selector_matches.py index 138d0b48921..d04d13cf987 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_selector_matches.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_selector_matches.py @@ -89,3 +89,8 @@ def _sel(d): return d["x"] == 1 and d["y"] == 3 and d["text"] == "pat metheny" assert BaseFigure._selector_matches(obj, _sel) == False + + +def test_string_selector_matches_type_key(): + assert BaseFigure._selector_matches(dict(type="bar"), "bar") + assert BaseFigure._selector_matches(dict(type="scatter"), "bar") == False diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py index e7ac974d68a..bd3cd59878d 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -5,6 +5,7 @@ import plotly.graph_objs as go from plotly.subplots import make_subplots +import pytest class TestSelectForEachUpdateAnnotations(TestCase): @@ -348,3 +349,26 @@ def test_no_exclude_empty_subplots(): assert fig.layout[k][1]["xref"] == "x2" and fig.layout[k][1]["yref"] == "y2" assert fig.layout[k][2]["xref"] == "x3" and fig.layout[k][2]["yref"] == "y3" assert fig.layout[k][3]["xref"] == "x4" and fig.layout[k][3]["yref"] == "y4" + + +@pytest.fixture +def select_annotations_integer(): + fig = make_subplots(2, 3) + fig.add_annotation(row=1, col=2, text="B") + fig.add_annotation(row=2, col=2, text="A") + fig.add_annotation(row=2, col=2, text="B") + fig.add_annotation(row=2, col=2, text="AB") + fig.add_annotation(text="hello") + return fig + + +def test_select_annotations_integer(select_annotations_integer): + fig = select_annotations_integer + anns = list(fig.select_annotations(selector=-1)) + assert (len(anns) == 1) and (anns[0]["text"] == "hello") + anns = list(fig.select_annotations(row=2, col=2, selector=-1)) + assert (len(anns) == 1) and anns[0]["text"] == "AB" + anns = list(fig.select_annotations(row=1, col=2, selector=-1)) + assert (len(anns) == 1) and anns[0]["text"] == "B" + with pytest.raises(IndexError): + fig.select_annotations(row=2, col=2, selector=3) diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_subplots.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_subplots.py index 14a4758b102..8fd76120f6f 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_subplots.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_subplots.py @@ -152,6 +152,10 @@ def test_select_by_type_and_selector(self): "xaxis", "xaxes", [], selector={"title.text": "C"}, test_no_grid=True ) + self.assert_select_subplots( + "xaxis", "xaxes", [4], selector=-1, test_no_grid=True + ) + # yaxis self.assert_select_subplots( "yaxis", "yaxes", [1, 3], selector={"title.text": "A"}, test_no_grid=True @@ -165,6 +169,14 @@ def test_select_by_type_and_selector(self): "yaxis", "yaxes", [], selector={"title.text": "C"}, test_no_grid=True ) + self.assert_select_subplots( + "yaxis", "yaxes", [5], selector=-1, test_no_grid=True + ) + + self.assert_select_subplots( + "yaxis", "yaxes", [2], selector=1, test_no_grid=True + ) + # scene self.assert_select_subplots( "scene", @@ -190,6 +202,10 @@ def test_select_by_type_and_selector(self): test_no_grid=True, ) + self.assert_select_subplots( + "scene", "scenes", [1], selector=0, test_no_grid=True + ) + # polar self.assert_select_subplots( "polar", @@ -215,6 +231,10 @@ def test_select_by_type_and_selector(self): test_no_grid=True, ) + self.assert_select_subplots( + "polar", "polars", [2], selector=-1, test_no_grid=True + ) + # ternary self.assert_select_subplots( "ternary", @@ -240,6 +260,10 @@ def test_select_by_type_and_selector(self): test_no_grid=True, ) + self.assert_select_subplots( + "ternary", "ternaries", [1], selector=-1, test_no_grid=True + ) + # No 'geo' or 'mapbox' subplots initialized, but the first subplot # object is always present self.assert_select_subplots( @@ -272,6 +296,8 @@ def test_select_by_type_and_grid_and_selector(self): "xaxis", "xaxes", [3, 4], col=1, selector={"title.text": "B"} ) + self.assert_select_subplots("xaxis", "xaxes", [4], col=1, selector=-1) + self.assert_select_subplots( "xaxis", "xaxes", [3], row=2, selector={"title.text": "B"} ) @@ -285,6 +311,10 @@ def test_select_by_type_and_grid_and_selector(self): "yaxis", "yaxes", [1, 3], col=1, selector={"title.text": "A"} ) + self.assert_select_subplots("yaxis", "yaxes", [5], col=1, selector=-1) + + self.assert_select_subplots("yaxis", "yaxes", [1], col=1, selector=0) + self.assert_select_subplots( "yaxis", "yaxes", [4], col=1, selector={"title.text": "B"} ) @@ -294,6 +324,8 @@ def test_select_by_type_and_grid_and_selector(self): "polar", "polars", [1, 2], row=2, selector={"angularaxis.rotation": 45} ) + self.assert_select_subplots("polar", "polars", [2], row=2, selector=-1) + self.assert_select_subplots( "polar", "polars", [1], col=2, selector={"angularaxis.rotation": 45} ) diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_traces.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_traces.py index ccfe50382e0..1e6c64df44f 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_traces.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_traces.py @@ -2,9 +2,11 @@ from unittest import TestCase import inspect import copy +import pytest import plotly.graph_objs as go from plotly.subplots import make_subplots +from functools import reduce class TestSelectForEachUpdateTraces(TestCase): @@ -260,7 +262,7 @@ def f(t): def test_select_traces_type_error(self): with self.assertRaises(TypeError): - self.assert_select_traces([0], selector=123, row=1, col=1) + self.assert_select_traces([0], selector=123.456, row=1, col=1) def test_for_each_trace_lowercase_names(self): # Names are all uppercase to start @@ -414,3 +416,60 @@ def test_update_traces_overwrite(self): {"type": "bar", "marker": {"line": {"width": 10}}}, ], ) + + +@pytest.fixture +def select_traces_fixture(): + fig = make_subplots(2, 3) + for n in range(3): + fig.add_trace(go.Scatter(x=[1, 2], y=[3, n]), row=2, col=3) + for n, ty in zip(range(3), [go.Scatter, go.Bar, go.Bar]): + fig.add_trace(ty(x=[1, 2], y=[3, 10 * n]), row=1, col=3) + return fig + + +def test_select_traces_integer(select_traces_fixture): + fig = select_traces_fixture + # check we can index last trace selected + tr = list(fig.select_traces(selector=-1))[0] + assert tr.y[1] == 20 + # check we can index last trace selected in a row and column + tr = list(fig.select_traces(selector=-1, row=2, col=3))[0] + assert tr.y[1] == 2 + # check that indexing out of bounds raises IndexError + with pytest.raises(IndexError): + tr = list(fig.select_traces(selector=6))[0] + + +def test_select_traces_string(select_traces_fixture): + fig = select_traces_fixture + # check we can select traces by type simply by passing a string to selector + trs = list(fig.select_traces(selector="bar")) + assert len(trs) == 2 and reduce( + lambda last, cur: last + and (cur[0]["type"] == "bar") + and (cur[0]["y"][1] == cur[1]), + zip(trs, [10, 20]), + True, + ) + # check we can select traces by type regardless of the subplots they are on + trs = list(fig.select_traces(selector="scatter")) + assert len(trs) == 4 and reduce( + lambda last, cur: last + and (cur[0]["type"] == "scatter") + and (cur[0]["y"][1] == cur[1]), + zip(trs, [0, 1, 2, 0]), + True, + ) + # check that we can select traces by type but only on a specific subplot + trs = list(fig.select_traces(row=2, col=3, selector="scatter")) + assert len(trs) == 3 and reduce( + lambda last, cur: last + and (cur[0]["type"] == "scatter") + and (cur[0]["y"][1] == cur[1]), + zip(trs, [0, 1, 2]), + True, + ) + # check that if selector matches no trace types then no traces are returned + trs = list(fig.select_traces(selector="bogus")) + assert len(trs) == 0