From 485e1cf79163fedcaa1ce2da193f94917dda4b94 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sun, 6 Oct 2019 06:21:09 -0400 Subject: [PATCH 1/8] Initial _select_annotations_like helper method --- .../python/plotly/plotly/basedatatypes.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index a423ae11978..aa84a155625 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -978,6 +978,49 @@ def _select_layout_subplots_by_prefix( yield self.layout[k] + def _select_annotations_like( + self, property, selector=None, row=None, col=None, secondary_y=None + ): + xref_to_col = {} + yref_to_row = {} + yref_to_secondary_y = {} + if row is not None or col is not None or secondary_y is not None: + grid_ref = self._validate_get_grid_ref() + for r, subplot_row in enumerate(grid_ref): + for c, subplot_refs in enumerate(subplot_row): + if not subplot_refs: + continue + + for i, subplot_ref in enumerate(subplot_refs): + if subplot_ref.subplot_type == 'xy': + is_secondary_y = i == 1 + xaxis, yaxis = subplot_ref.layout_keys[0] + xref = xaxis.replace('axis', '') + yref = yaxis.replace('axis', '') + xref_to_col[xref] = c + 1 + yref_to_row[yref] = r + 1 + yref_to_secondary_y[yref] = is_secondary_y + + for obj in self.layout[property]: + # Filter by row + if col is not None and xref_to_col.get(obj.xref, None) != col: + continue + + # Filter by col + if row is not None and yref_to_row.get(obj.yref, None) != row: + continue + + # Filter by secondary y + if (secondary_y is not None and + yref_to_secondary_y.get(obj.yref, None) != secondary_y): + continue + + # Filter by selector + if not self._selector_matches(self.layout[k], selector): + continue + + yield obj + # Restyle # ------- def plotly_restyle(self, restyle_data, trace_indexes=None, **kwargs): From 06f9d680238962d394c92dc2a4074ca00dbdee56 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Mon, 7 Oct 2019 07:33:24 -0400 Subject: [PATCH 2/8] select/for_each/update annotations methods --- .../python/plotly/plotly/basedatatypes.py | 234 +++++++++++++++++- 1 file changed, 222 insertions(+), 12 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index aa84a155625..33be972d9db 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -979,8 +979,12 @@ def _select_layout_subplots_by_prefix( yield self.layout[k] def _select_annotations_like( - self, property, selector=None, row=None, col=None, secondary_y=None + self, property, selector=None, row=None, col=None, secondary_y=None ): + """ + Helper to select annotation-like elements from a layout object array. + Compatible with layout.annotations, layout.shapes, and layout.images + """ xref_to_col = {} yref_to_row = {} yref_to_secondary_y = {} @@ -992,35 +996,241 @@ def _select_annotations_like( continue for i, subplot_ref in enumerate(subplot_refs): - if subplot_ref.subplot_type == 'xy': + if subplot_ref.subplot_type == "xy": is_secondary_y = i == 1 - xaxis, yaxis = subplot_ref.layout_keys[0] - xref = xaxis.replace('axis', '') - yref = yaxis.replace('axis', '') + xaxis, yaxis = subplot_ref.layout_keys + xref = xaxis.replace("axis", "") + yref = yaxis.replace("axis", "") xref_to_col[xref] = c + 1 yref_to_row[yref] = r + 1 yref_to_secondary_y[yref] = is_secondary_y for obj in self.layout[property]: # Filter by row - if col is not None and xref_to_col.get(obj.xref, None) != col: - continue + if col is not None: + if col == "paper" and obj.xref != "paper": + continue + elif col != "paper" and xref_to_col.get(obj.xref, None) != col: + continue # Filter by col - if row is not None and yref_to_row.get(obj.yref, None) != row: - continue + if row is not None: + if row == "paper" and obj.yref != "paper": + continue + elif row != "paper" and yref_to_row.get(obj.yref, None) != row: + continue # Filter by secondary y - if (secondary_y is not None and - yref_to_secondary_y.get(obj.yref, None) != secondary_y): + if ( + secondary_y is not None + and yref_to_secondary_y.get(obj.yref, None) != secondary_y + ): continue # Filter by selector - if not self._selector_matches(self.layout[k], selector): + if not self._selector_matches(obj, selector): continue yield obj + def select_annotations(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select annotations from a particular subplot cell and/or annotations + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotation that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the annotations that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "annotations", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def _for_each_annotation_like( + self, property, fn, selector=None, row=None, col=None, secondary_y=None + ): + """ + Helper to perform for-each on selected annotation-like elements from a + layout object array. + Compatible with layout.annotations, layout.shapes, and layout.images + """ + for obj in self._select_annotations_like( + property=property, + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def for_each_annotation(self, fn, selector=None, row=None, col=None, secondary_y=None): + """ + Apply a function to all annotations that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single annotation object. + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotations that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property='annotations', + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def _update_annotations_like( + self, + property, + patch, + selector=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): + """ + Helper to update selected annotation-like elements from a layout object + array. + Compatible with layout.annotations, layout.shapes, and layout.images + """ + for obj in self._select_annotations_like( + property=property, + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self + + def update_annotations( + self, + patch, + selector=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): + """ + Perform a property update operation on all annotations that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all annotations that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotation that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected annotation. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property='annotations', + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self + # Restyle # ------- def plotly_restyle(self, restyle_data, trace_indexes=None, **kwargs): From 49f21534a6d63b3116d573105b83b1b0dd954c2c Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Sat, 12 Oct 2019 13:15:01 -0400 Subject: [PATCH 3/8] code generation for select/for_each/update annotations/shapes/images --- packages/python/plotly/codegen/figure.py | 166 +++++++ .../python/plotly/plotly/basedatatypes.py | 198 -------- .../plotly/plotly/graph_objs/_figure.py | 443 ++++++++++++++++++ .../plotly/plotly/graph_objs/_figurewidget.py | 443 ++++++++++++++++++ 4 files changed, 1052 insertions(+), 198 deletions(-) diff --git a/packages/python/plotly/codegen/figure.py b/packages/python/plotly/codegen/figure.py index ab1e1c9b748..649b897c454 100644 --- a/packages/python/plotly/codegen/figure.py +++ b/packages/python/plotly/codegen/figure.py @@ -353,6 +353,172 @@ def update_{plural_name}( return self""" ) + # update annotations/shapes/images + # -------------------------------- + for singular_name in ["annotation", "shape", "image"]: + plural_name = plural_name = inflect_eng.plural_noun(singular_name) + + buffer.write( + f""" + def select_{plural_name}( + self, selector=None, row=None, col=None, secondary_y=None + ): + \"\"\" + Select {plural_name} from a particular subplot cell and/or {plural_name} + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all {plural_name} are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + {singular_name} that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all {plural_name} are selected. + secondary_y: boolean or None (default None) + * If True, only select {plural_name} associated with the secondary + y-axis of the subplot. + * If False, only select {plural_name} associated with the primary + y-axis of the subplot. + * If None (the default), do not filter {plural_name} based on secondary + y-axis. + + To select {plural_name} 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the {plural_name} that satisfy + all of the specified selection criteria + \"\"\" + return self._select_annotations_like( + "{plural_name}", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_{singular_name}( + self, fn, selector=None, row=None, col=None, secondary_y=None + ): + \"\"\" + Apply a function to all {plural_name} 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. + Traces 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 {plural_name} are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + {plural_name} that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all {plural_name} are selected. + secondary_y: boolean or None (default None) + * If True, only select {plural_name} associated with the secondary + y-axis of the subplot. + * If False, only select {plural_name} associated with the primary + y-axis of the subplot. + * If None (the default), do not filter {plural_name} based on secondary + y-axis. + + To select {plural_name} 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + \"\"\" + for obj in self._select_annotations_like( + property='{plural_name}', + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_{plural_name}( + self, + patch, + selector=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): + \"\"\" + Perform a property update operation on all {plural_name} that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all {plural_name} that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all {plural_name} are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + {singular_name} that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all {plural_name} are selected. + secondary_y: boolean or None (default None) + * If True, only select {plural_name} associated with the secondary + y-axis of the subplot. + * If False, only select {plural_name} associated with the primary + y-axis of the subplot. + * If None (the default), do not filter {plural_name} based on secondary + y-axis. + + To select {plural_name} 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected {singular_name}. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + \"\"\" + for obj in self._select_annotations_like( + property='{plural_name}', + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self +""" + ) + # Return source string # -------------------- buffer.write("\n") diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 33be972d9db..04788471df5 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -1033,204 +1033,6 @@ def _select_annotations_like( yield obj - def select_annotations(self, selector=None, row=None, col=None, secondary_y=None): - """ - Select annotations from a particular subplot cell and/or annotations - that satisfy custom selection criteria. - - Parameters - ---------- - selector: dict 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 - the supplied values. If None (the default), all annotations are - selected. - 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 - created using plotly.subplots.make_subplots. To select only those - annotation that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all annotations are selected. - secondary_y: boolean or None (default None) - * If True, only select annotations associated with the secondary - y-axis of the subplot. - * If False, only select annotations associated with the primary - y-axis of the subplot. - * If None (the default), do not filter annotations based on secondary - y-axis. - - To select annotations 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 info on - creating subplots with secondary y-axes. - Returns - ------- - generator - Generator that iterates through all of the annotations that satisfy - all of the specified selection criteria - """ - return self._select_annotations_like( - "annotations", selector=selector, row=row, col=col, secondary_y=secondary_y - ) - - def _for_each_annotation_like( - self, property, fn, selector=None, row=None, col=None, secondary_y=None - ): - """ - Helper to perform for-each on selected annotation-like elements from a - layout object array. - Compatible with layout.annotations, layout.shapes, and layout.images - """ - for obj in self._select_annotations_like( - property=property, - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, - ): - fn(obj) - - return self - - def for_each_annotation(self, fn, selector=None, row=None, col=None, secondary_y=None): - """ - Apply a function to all annotations that satisfy the specified selection - criteria - - Parameters - ---------- - fn: - Function that inputs a single annotation object. - selector: dict 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 - the supplied values. If None (the default), all annotations are - selected. - 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 - created using plotly.subplots.make_subplots. To select only those - annotations that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all annotations are selected. - secondary_y: boolean or None (default None) - * If True, only select annotations associated with the secondary - y-axis of the subplot. - * If False, only select annotations associated with the primary - y-axis of the subplot. - * If None (the default), do not filter annotations based on secondary - y-axis. - - To select annotations 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 info on - creating subplots with secondary y-axes. - Returns - ------- - self - Returns the Figure object that the method was called on - """ - for obj in self._select_annotations_like( - property='annotations', - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, - ): - fn(obj) - - return self - - def _update_annotations_like( - self, - property, - patch, - selector=None, - row=None, - col=None, - secondary_y=None, - **kwargs - ): - """ - Helper to update selected annotation-like elements from a layout object - array. - Compatible with layout.annotations, layout.shapes, and layout.images - """ - for obj in self._select_annotations_like( - property=property, - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, - ): - obj.update(patch, **kwargs) - - return self - - def update_annotations( - self, - patch, - selector=None, - row=None, - col=None, - secondary_y=None, - **kwargs - ): - """ - Perform a property update operation on all annotations that satisfy the - specified selection criteria - - Parameters - ---------- - patch: dict or None (default None) - Dictionary of property updates to be applied to all annotations that - satisfy the selection criteria. - selector: dict 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 - the supplied values. If None (the default), all annotations are - selected. - 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 - created using plotly.subplots.make_subplots. To select only those - annotation that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all annotations are selected. - secondary_y: boolean or None (default None) - * If True, only select annotations associated with the secondary - y-axis of the subplot. - * If False, only select annotations associated with the primary - y-axis of the subplot. - * If None (the default), do not filter annotations based on secondary - y-axis. - - To select annotations 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 info on - creating subplots with secondary y-axes. - **kwargs - Additional property updates to apply to each selected annotation. If - a property is specified in both patch and in **kwargs then the - one in **kwargs takes precedence. - - Returns - ------- - self - Returns the Figure object that the method was called on - """ - for obj in self._select_annotations_like( - property='annotations', - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, - ): - obj.update(patch, **kwargs) - - return self - # Restyle # ------- def plotly_restyle(self, restyle_data, trace_indexes=None, **kwargs): diff --git a/packages/python/plotly/plotly/graph_objs/_figure.py b/packages/python/plotly/plotly/graph_objs/_figure.py index a3890db0188..754b6bb75c0 100644 --- a/packages/python/plotly/plotly/graph_objs/_figure.py +++ b/packages/python/plotly/plotly/graph_objs/_figure.py @@ -15818,3 +15818,446 @@ def update_yaxes( obj.update(patch, **kwargs) return self + + def select_annotations(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select annotations from a particular subplot cell and/or annotations + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotation that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the annotations that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "annotations", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_annotation( + self, fn, selector=None, row=None, col=None, secondary_y=None + ): + """ + Apply a function to all annotations that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single annotation object. + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotations that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="annotations", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_annotations( + self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs + ): + """ + Perform a property update operation on all annotations that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all annotations that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotation that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected annotation. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="annotations", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self + + def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select shapes from a particular subplot cell and/or shapes + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all shapes are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. + secondary_y: boolean or None (default None) + * If True, only select shapes associated with the secondary + y-axis of the subplot. + * If False, only select shapes associated with the primary + y-axis of the subplot. + * If None (the default), do not filter shapes based on secondary + y-axis. + + To select shapes 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the shapes that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "shapes", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None): + """ + Apply a function to all shapes that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single shape object. + selector: dict 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 + the supplied values. If None (the default), all shapes are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + shapes that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. + secondary_y: boolean or None (default None) + * If True, only select shapes associated with the secondary + y-axis of the subplot. + * If False, only select shapes associated with the primary + y-axis of the subplot. + * If None (the default), do not filter shapes based on secondary + y-axis. + + To select shapes 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="shapes", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_shapes( + self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs + ): + """ + Perform a property update operation on all shapes that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all shapes that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all shapes are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. + secondary_y: boolean or None (default None) + * If True, only select shapes associated with the secondary + y-axis of the subplot. + * If False, only select shapes associated with the primary + y-axis of the subplot. + * If None (the default), do not filter shapes based on secondary + y-axis. + + To select shapes 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected shape. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="shapes", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self + + def select_images(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select images from a particular subplot cell and/or images + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all images are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. + secondary_y: boolean or None (default None) + * If True, only select images associated with the secondary + y-axis of the subplot. + * If False, only select images associated with the primary + y-axis of the subplot. + * If None (the default), do not filter images based on secondary + y-axis. + + To select images 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the images that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "images", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): + """ + Apply a function to all images that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single image object. + selector: dict 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 + the supplied values. If None (the default), all images are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + images that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. + secondary_y: boolean or None (default None) + * If True, only select images associated with the secondary + y-axis of the subplot. + * If False, only select images associated with the primary + y-axis of the subplot. + * If None (the default), do not filter images based on secondary + y-axis. + + To select images 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="images", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_images( + self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs + ): + """ + Perform a property update operation on all images that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all images that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all images are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. + secondary_y: boolean or None (default None) + * If True, only select images associated with the secondary + y-axis of the subplot. + * If False, only select images associated with the primary + y-axis of the subplot. + * If None (the default), do not filter images based on secondary + y-axis. + + To select images 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected image. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="images", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self diff --git a/packages/python/plotly/plotly/graph_objs/_figurewidget.py b/packages/python/plotly/plotly/graph_objs/_figurewidget.py index af6758944b3..30ebf14249f 100644 --- a/packages/python/plotly/plotly/graph_objs/_figurewidget.py +++ b/packages/python/plotly/plotly/graph_objs/_figurewidget.py @@ -15818,3 +15818,446 @@ def update_yaxes( obj.update(patch, **kwargs) return self + + def select_annotations(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select annotations from a particular subplot cell and/or annotations + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotation that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the annotations that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "annotations", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_annotation( + self, fn, selector=None, row=None, col=None, secondary_y=None + ): + """ + Apply a function to all annotations that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single annotation object. + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotations that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="annotations", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_annotations( + self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs + ): + """ + Perform a property update operation on all annotations that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all annotations that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all annotations are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + annotation that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all annotations are selected. + secondary_y: boolean or None (default None) + * If True, only select annotations associated with the secondary + y-axis of the subplot. + * If False, only select annotations associated with the primary + y-axis of the subplot. + * If None (the default), do not filter annotations based on secondary + y-axis. + + To select annotations 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected annotation. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="annotations", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self + + def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select shapes from a particular subplot cell and/or shapes + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all shapes are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. + secondary_y: boolean or None (default None) + * If True, only select shapes associated with the secondary + y-axis of the subplot. + * If False, only select shapes associated with the primary + y-axis of the subplot. + * If None (the default), do not filter shapes based on secondary + y-axis. + + To select shapes 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the shapes that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "shapes", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None): + """ + Apply a function to all shapes that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single shape object. + selector: dict 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 + the supplied values. If None (the default), all shapes are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + shapes that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. + secondary_y: boolean or None (default None) + * If True, only select shapes associated with the secondary + y-axis of the subplot. + * If False, only select shapes associated with the primary + y-axis of the subplot. + * If None (the default), do not filter shapes based on secondary + y-axis. + + To select shapes 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="shapes", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_shapes( + self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs + ): + """ + Perform a property update operation on all shapes that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all shapes that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all shapes are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. + secondary_y: boolean or None (default None) + * If True, only select shapes associated with the secondary + y-axis of the subplot. + * If False, only select shapes associated with the primary + y-axis of the subplot. + * If None (the default), do not filter shapes based on secondary + y-axis. + + To select shapes 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected shape. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="shapes", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self + + def select_images(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select images from a particular subplot cell and/or images + that satisfy custom selection criteria. + + Parameters + ---------- + selector: dict 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 + the supplied values. If None (the default), all images are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. + secondary_y: boolean or None (default None) + * If True, only select images associated with the secondary + y-axis of the subplot. + * If False, only select images associated with the primary + y-axis of the subplot. + * If None (the default), do not filter images based on secondary + y-axis. + + To select images 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 info on + creating subplots with secondary y-axes. + Returns + ------- + generator + Generator that iterates through all of the images that satisfy + all of the specified selection criteria + """ + return self._select_annotations_like( + "images", selector=selector, row=row, col=col, secondary_y=secondary_y + ) + + def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): + """ + Apply a function to all images that satisfy the specified selection + criteria + + Parameters + ---------- + fn: + Function that inputs a single image object. + selector: dict 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 + the supplied values. If None (the default), all images are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + images that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. + secondary_y: boolean or None (default None) + * If True, only select images associated with the secondary + y-axis of the subplot. + * If False, only select images associated with the primary + y-axis of the subplot. + * If None (the default), do not filter images based on secondary + y-axis. + + To select images 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 info on + creating subplots with secondary y-axes. + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="images", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + fn(obj) + + return self + + def update_images( + self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs + ): + """ + Perform a property update operation on all images that satisfy the + specified selection criteria + + Parameters + ---------- + patch: dict or None (default None) + Dictionary of property updates to be applied to all images that + satisfy the selection criteria. + selector: dict 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 + the supplied values. If None (the default), all images are + selected. + 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 + created using plotly.subplots.make_subplots. To select only those + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. + secondary_y: boolean or None (default None) + * If True, only select images associated with the secondary + y-axis of the subplot. + * If False, only select images associated with the primary + y-axis of the subplot. + * If None (the default), do not filter images based on secondary + y-axis. + + To select images 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 info on + creating subplots with secondary y-axes. + **kwargs + Additional property updates to apply to each selected image. If + a property is specified in both patch and in **kwargs then the + one in **kwargs takes precedence. + + Returns + ------- + self + Returns the Figure object that the method was called on + """ + for obj in self._select_annotations_like( + property="images", + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ): + obj.update(patch, **kwargs) + + return self From 87714886a6b11bb975a255833f027120fe0b6e65 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Mon, 14 Oct 2019 10:03:11 -0400 Subject: [PATCH 4/8] Add add_* methods for annotations/shapes/images --- packages/python/plotly/codegen/__init__.py | 7 + packages/python/plotly/codegen/figure.py | 88 +- packages/python/plotly/codegen/utils.py | 6 + .../python/plotly/plotly/basedatatypes.py | 71 +- .../plotly/plotly/graph_objs/_figure.py | 850 ++++++++++++++++-- .../plotly/plotly/graph_objs/_figurewidget.py | 850 ++++++++++++++++-- 6 files changed, 1695 insertions(+), 177 deletions(-) diff --git a/packages/python/plotly/codegen/__init__.py b/packages/python/plotly/codegen/__init__.py index d33b785bf8f..5b739f4c7c9 100644 --- a/packages/python/plotly/codegen/__init__.py +++ b/packages/python/plotly/codegen/__init__.py @@ -143,6 +143,12 @@ def perform_codegen(): if node.node_data.get("_isSubplotObj", False) ] + layout_array_nodes = [ + node + for node in layout_node.child_compound_datatypes + if node.is_array_element and node.has_child("xref") and node.has_child("yref") + ] + # ### FrameNode ### compound_frame_nodes = PlotlyNode.get_all_compound_datatype_nodes( plotly_schema, FrameNode @@ -207,6 +213,7 @@ def perform_codegen(): layout_validator, frame_validator, subplot_nodes, + layout_array_nodes, ) # Write datatype __init__.py files diff --git a/packages/python/plotly/codegen/figure.py b/packages/python/plotly/codegen/figure.py index 649b897c454..4bd8bb4907a 100644 --- a/packages/python/plotly/codegen/figure.py +++ b/packages/python/plotly/codegen/figure.py @@ -25,6 +25,7 @@ def build_figure_py( layout_validator, frame_validator, subplot_nodes, + layout_array_nodes, ): """ @@ -47,6 +48,8 @@ def build_figure_py( FrameValidator instance subplot_nodes: list of str List of names of all of the layout subplot properties + layout_array_nodes: list of PlotlyNode + List of array nodes under layout that can be positioned using xref/yref Returns ------- str @@ -66,8 +69,10 @@ def build_figure_py( # ### Import base class ### buffer.write(f"from plotly.{base_package} import {base_classname}\n") - # ### Import trace graph_obj classes ### - trace_types_csv = ", ".join([n.name_datatype_class for n in trace_nodes]) + # ### Import trace graph_obj classes / layout ### + trace_types_csv = ", ".join( + [n.name_datatype_class for n in trace_nodes] + ["layout as _layout"] + ) buffer.write(f"from plotly.graph_objs import ({trace_types_csv})\n") # Write class definition @@ -355,8 +360,9 @@ def update_{plural_name}( # update annotations/shapes/images # -------------------------------- - for singular_name in ["annotation", "shape", "image"]: - plural_name = plural_name = inflect_eng.plural_noun(singular_name) + for node in layout_array_nodes: + singular_name = node.plotly_name + plural_name = node.name_property buffer.write( f""" @@ -518,6 +524,66 @@ def update_{plural_name}( return self """ ) + # Add layout array items + buffer.write( + f""" + def add_{singular_name}(self""" + ) + add_constructor_params( + buffer, + node.child_datatypes, + prepend_extras=["arg"], + append_extras=["row", "col", "secondary_y"], + ) + + prepend_extras = [ + ( + "arg", + f"instance of {node.name_datatype_class} or dict with " + "compatible properties", + ) + ] + append_extras = [ + ("row", f"Subplot row for {singular_name}"), + ("col", f"Subplot column for {singular_name}"), + ("secondary_y", f"Whether to add {singular_name} to secondary y-axis"), + ] + add_docstring( + buffer, + node, + header=f"Create and add a new {singular_name} to the figure's layout", + prepend_extras=prepend_extras, + append_extras=append_extras, + return_type=fig_classname, + ) + + # #### Function body #### + buffer.write( + f""" + new_obj = _layout.{node.name_datatype_class}(arg, + """ + ) + + for i, subtype_node in enumerate(node.child_datatypes): + subtype_prop_name = subtype_node.name_property + buffer.write( + f""" + {subtype_prop_name}={subtype_prop_name},""" + ) + + buffer.write("""**kwargs)""") + + buffer.write( + f""" + return self._add_annotation_like( + '{singular_name}', + '{plural_name}', + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + )""" + ) # Return source string # -------------------- @@ -526,7 +592,13 @@ def update_{plural_name}( def write_figure_classes( - outdir, trace_node, data_validator, layout_validator, frame_validator, subplot_nodes + outdir, + trace_node, + data_validator, + layout_validator, + frame_validator, + subplot_nodes, + layout_array_nodes, ): """ Construct source code for the Figure and FigureWidget classes and @@ -546,9 +618,10 @@ def write_figure_classes( LayoutValidator instance frame_validator : CompoundArrayValidator FrameValidator instance - subplot_nodes: list of str + subplot_nodes: list of PlotlyNode List of names of all of the layout subplot properties - + layout_array_nodes: list of PlotlyNode + List of array nodes under layout that can be positioned using xref/yref Returns ------- None @@ -581,6 +654,7 @@ def write_figure_classes( layout_validator, frame_validator, subplot_nodes, + layout_array_nodes, ) # ### Format and write to file### diff --git a/packages/python/plotly/codegen/utils.py b/packages/python/plotly/codegen/utils.py index f60be8391ee..1a689972575 100644 --- a/packages/python/plotly/codegen/utils.py +++ b/packages/python/plotly/codegen/utils.py @@ -893,6 +893,12 @@ def child_literals(self) -> List["PlotlyNode"]: """ return [n for n in self.children if n.is_literal] + def has_child(self, name) -> bool: + """ + Check whether node has child of the specified name + """ + return bool([n for n in self.children if n.plotly_name == name]) + def get_constructor_params_docstring(self, indent=12): """ Return a docstring-style string containing the names and diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 04788471df5..fcbd7f924f2 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -979,7 +979,7 @@ def _select_layout_subplots_by_prefix( yield self.layout[k] def _select_annotations_like( - self, property, selector=None, row=None, col=None, secondary_y=None + self, prop, selector=None, row=None, col=None, secondary_y=None ): """ Helper to select annotation-like elements from a layout object array. @@ -988,7 +988,7 @@ def _select_annotations_like( xref_to_col = {} yref_to_row = {} yref_to_secondary_y = {} - if row is not None or col is not None or secondary_y is not None: + if isinstance(row, int) or isinstance(col, int) or secondary_y is not None: grid_ref = self._validate_get_grid_ref() for r, subplot_row in enumerate(grid_ref): for c, subplot_refs in enumerate(subplot_row): @@ -1005,7 +1005,7 @@ def _select_annotations_like( yref_to_row[yref] = r + 1 yref_to_secondary_y[yref] = is_secondary_y - for obj in self.layout[property]: + for obj in self.layout[prop]: # Filter by row if col is not None: if col == "paper" and obj.xref != "paper": @@ -1033,6 +1033,64 @@ def _select_annotations_like( yield obj + def _add_annotation_like( + self, prop_singular, prop_plural, new_obj, row=None, col=None, secondary_y=None + ): + # Make sure we have both row and col or neither + if row is not None and col is None: + raise ValueError( + "Received row parameter but not col.\n" + "row and col must be specified together" + ) + elif col is not None and row is None: + raise ValueError( + "Received col parameter but not row.\n" + "row and col must be specified together" + ) + + # Get grid_ref if specific row or column requested + if row is not None: + grid_ref = self._validate_get_grid_ref() + refs = grid_ref[row - 1][col - 1] + + if not refs: + raise ValueError( + "No subplot found at position ({r}, {c})".format(r=row, c=col) + ) + + if refs[0].subplot_type != "xy": + raise ValueError( + """ +Cannot add {prop_singular} to subplot at position ({r}, {c}) because subplot +is of type {subplot_type}.""".format( + prop_singular=prop_singular, + r=row, + c=col, + subplot_type=refs[0].subplot_type, + ) + ) + if len(refs) == 1 and secondary_y: + raise ValueError( + """ +Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c}) +because subplot does not have a secondary y-axis""" + ) + if secondary_y: + xaxis, yaxis = refs[1].layout_keys + else: + xaxis, yaxis = refs[0].layout_keys + xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") + new_obj.update(xref=xref, yref=yref) + + if new_obj.xref is None: + new_obj.xref = "paper" + if new_obj.yref is None: + new_obj.yref = "paper" + + self.layout[prop_plural] += (new_obj,) + + return self + # Restyle # ------- def plotly_restyle(self, restyle_data, trace_indexes=None, **kwargs): @@ -1526,13 +1584,6 @@ def add_trace(self, trace, row=None, col=None, secondary_y=None): >>> fig.add_trace(go.Scatter(x=[1,2,3], y=[2,1,2]), row=1, col=1) >>> fig.add_trace(go.Scatter(x=[1,2,3], y=[2,1,2]), row=2, col=1) """ - # Validate row/col - if row is not None and not isinstance(row, int): - pass - - if col is not None and not isinstance(col, int): - pass - # Make sure we have both row and col or neither if row is not None and col is None: raise ValueError( diff --git a/packages/python/plotly/plotly/graph_objs/_figure.py b/packages/python/plotly/plotly/graph_objs/_figure.py index 754b6bb75c0..2a97b85456f 100644 --- a/packages/python/plotly/plotly/graph_objs/_figure.py +++ b/packages/python/plotly/plotly/graph_objs/_figure.py @@ -45,6 +45,7 @@ Violin, Volume, Waterfall, + layout as _layout, ) @@ -15968,9 +15969,363 @@ def update_annotations( return self - def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): + def add_annotation( + self, + arg=None, + align=None, + arrowcolor=None, + arrowhead=None, + arrowside=None, + arrowsize=None, + arrowwidth=None, + ax=None, + axref=None, + ay=None, + ayref=None, + bgcolor=None, + bordercolor=None, + borderpad=None, + borderwidth=None, + captureevents=None, + clicktoshow=None, + font=None, + height=None, + hoverlabel=None, + hovertext=None, + name=None, + opacity=None, + showarrow=None, + standoff=None, + startarrowhead=None, + startarrowsize=None, + startstandoff=None, + templateitemname=None, + text=None, + textangle=None, + valign=None, + visible=None, + width=None, + x=None, + xanchor=None, + xclick=None, + xref=None, + xshift=None, + y=None, + yanchor=None, + yclick=None, + yref=None, + yshift=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): """ - Select shapes from a particular subplot cell and/or shapes + Create and add a new annotation to the figure's layout + + Parameters + ---------- + arg + instance of Annotation or dict with compatible + properties + align + Sets the horizontal alignment of the `text` within the + box. Has an effect only if `text` spans more two or + more lines (i.e. `text` contains one or more
HTML + tags) or if an explicit width is set to override the + text width. + arrowcolor + Sets the color of the annotation arrow. + arrowhead + Sets the end annotation arrow head style. + arrowside + Sets the annotation arrow head position. + arrowsize + Sets the size of the end annotation arrow head, + relative to `arrowwidth`. A value of 1 (default) gives + a head about 3x as wide as the line. + arrowwidth + Sets the width (in px) of annotation arrow line. + ax + Sets the x component of the arrow tail about the arrow + head. If `axref` is `pixel`, a positive (negative) + component corresponds to an arrow pointing from right + to left (left to right). If `axref` is an axis, this is + an absolute value on that axis, like `x`, NOT a + relative value. + axref + Indicates in what terms the tail of the annotation + (ax,ay) is specified. If `pixel`, `ax` is a relative + offset in pixels from `x`. If set to an x axis id + (e.g. "x" or "x2"), `ax` is specified in the same + terms as that axis. This is useful for trendline + annotations which should continue to indicate the + correct trend when zoomed. + ay + Sets the y component of the arrow tail about the arrow + head. If `ayref` is `pixel`, a positive (negative) + component corresponds to an arrow pointing from bottom + to top (top to bottom). If `ayref` is an axis, this is + an absolute value on that axis, like `y`, NOT a + relative value. + ayref + Indicates in what terms the tail of the annotation + (ax,ay) is specified. If `pixel`, `ay` is a relative + offset in pixels from `y`. If set to a y axis id (e.g. + "y" or "y2"), `ay` is specified in the same terms as + that axis. This is useful for trendline annotations + which should continue to indicate the correct trend + when zoomed. + bgcolor + Sets the background color of the annotation. + bordercolor + Sets the color of the border enclosing the annotation + `text`. + borderpad + Sets the padding (in px) between the `text` and the + enclosing border. + borderwidth + Sets the width (in px) of the border enclosing the + annotation `text`. + captureevents + Determines whether the annotation text box captures + mouse move and click events, or allows those events to + pass through to data points in the plot that may be + behind the annotation. By default `captureevents` is + False unless `hovertext` is provided. If you use the + event `plotly_clickannotation` without `hovertext` you + must explicitly enable `captureevents`. + clicktoshow + Makes this annotation respond to clicks on the plot. If + you click a data point that exactly matches the `x` and + `y` values of this annotation, and it is hidden + (visible: false), it will appear. In "onoff" mode, you + must click the same point again to make it disappear, + so if you click multiple points, you can show multiple + annotations. In "onout" mode, a click anywhere else in + the plot (on another data point or not) will hide this + annotation. If you need to show/hide this annotation in + response to different `x` or `y` values, you can set + `xclick` and/or `yclick`. This is useful for example to + label the side of a bar. To label markers though, + `standoff` is preferred over `xclick` and `yclick`. + font + Sets the annotation text font. + height + Sets an explicit height for the text box. null + (default) lets the text set the box height. Taller text + will be clipped. + hoverlabel + plotly.graph_objects.layout.annotation.Hoverlabel + instance or dict with compatible properties + hovertext + Sets text to appear when hovering over this annotation. + If omitted or blank, no hover label will appear. + name + When used in a template, named items are created in the + output figure in addition to any items the figure + already has in this array. You can modify these items + in the output figure by making your own item with + `templateitemname` matching this `name` alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). Has no effect outside of a + template. + opacity + Sets the opacity of the annotation (text + arrow). + showarrow + Determines whether or not the annotation is drawn with + an arrow. If True, `text` is placed near the arrow's + tail. If False, `text` lines up with the `x` and `y` + provided. + standoff + Sets a distance, in pixels, to move the end arrowhead + away from the position it is pointing at, for example + to point at the edge of a marker independent of zoom. + Note that this shortens the arrow from the `ax` / `ay` + vector, in contrast to `xshift` / `yshift` which moves + everything by this amount. + startarrowhead + Sets the start annotation arrow head style. + startarrowsize + Sets the size of the start annotation arrow head, + relative to `arrowwidth`. A value of 1 (default) gives + a head about 3x as wide as the line. + startstandoff + Sets a distance, in pixels, to move the start arrowhead + away from the position it is pointing at, for example + to point at the edge of a marker independent of zoom. + Note that this shortens the arrow from the `ax` / `ay` + vector, in contrast to `xshift` / `yshift` which moves + everything by this amount. + templateitemname + Used to refer to a named item in this array in the + template. Named items from the template will be created + even without a matching item in the input figure, but + you can modify one by making an item with + `templateitemname` matching its `name`, alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). If there is no template or no + matching item, this item will be hidden unless you + explicitly show it with `visible: true`. + text + Sets the text associated with this annotation. Plotly + uses a subset of HTML tags to do things like newline + (
), bold (), italics (), hyperlinks + (). Tags , , + are also supported. + textangle + Sets the angle at which the `text` is drawn with + respect to the horizontal. + valign + Sets the vertical alignment of the `text` within the + box. Has an effect only if an explicit height is set to + override the text height. + visible + Determines whether or not this annotation is visible. + width + Sets an explicit width for the text box. null (default) + lets the text set the box width. Wider text will be + clipped. There is no automatic wrapping; use
to + start a new line. + x + Sets the annotation's x position. If the axis `type` is + "log", then you must take the log of your desired + range. If the axis `type` is "date", it should be date + strings, like date data, though Date objects and unix + milliseconds will be accepted and converted to strings. + If the axis `type` is "category", it should be numbers, + using the scale where each category is assigned a + serial number from zero in the order it appears. + xanchor + Sets the text box's horizontal position anchor This + anchor binds the `x` position to the "left", "center" + or "right" of the annotation. For example, if `x` is + set to 1, `xref` to "paper" and `xanchor` to "right" + then the right-most portion of the annotation lines up + with the right-most edge of the plotting area. If + "auto", the anchor is equivalent to "center" for data- + referenced annotations or if there is an arrow, whereas + for paper-referenced with no arrow, the anchor picked + corresponds to the closest side. + xclick + Toggle this annotation when clicking a data point whose + `x` value is `xclick` rather than the annotation's `x` + value. + xref + Sets the annotation's x coordinate axis. If set to an x + axis id (e.g. "x" or "x2"), the `x` position refers to + an x coordinate If set to "paper", the `x` position + refers to the distance from the left side of the + plotting area in normalized coordinates where 0 (1) + corresponds to the left (right) side. + xshift + Shifts the position of the whole annotation and arrow + to the right (positive) or left (negative) by this many + pixels. + y + Sets the annotation's y position. If the axis `type` is + "log", then you must take the log of your desired + range. If the axis `type` is "date", it should be date + strings, like date data, though Date objects and unix + milliseconds will be accepted and converted to strings. + If the axis `type` is "category", it should be numbers, + using the scale where each category is assigned a + serial number from zero in the order it appears. + yanchor + Sets the text box's vertical position anchor This + anchor binds the `y` position to the "top", "middle" or + "bottom" of the annotation. For example, if `y` is set + to 1, `yref` to "paper" and `yanchor` to "top" then the + top-most portion of the annotation lines up with the + top-most edge of the plotting area. If "auto", the + anchor is equivalent to "middle" for data-referenced + annotations or if there is an arrow, whereas for paper- + referenced with no arrow, the anchor picked corresponds + to the closest side. + yclick + Toggle this annotation when clicking a data point whose + `y` value is `yclick` rather than the annotation's `y` + value. + yref + Sets the annotation's y coordinate axis. If set to an y + axis id (e.g. "y" or "y2"), the `y` position refers to + an y coordinate If set to "paper", the `y` position + refers to the distance from the bottom of the plotting + area in normalized coordinates where 0 (1) corresponds + to the bottom (top). + yshift + Shifts the position of the whole annotation and arrow + up (positive) or down (negative) by this many pixels. + row + Subplot row for annotation + col + Subplot column for annotation + secondary_y + Whether to add annotation to secondary y-axis + + Returns + ------- + Figure + """ + new_obj = _layout.Annotation( + arg, + align=align, + arrowcolor=arrowcolor, + arrowhead=arrowhead, + arrowside=arrowside, + arrowsize=arrowsize, + arrowwidth=arrowwidth, + ax=ax, + axref=axref, + ay=ay, + ayref=ayref, + bgcolor=bgcolor, + bordercolor=bordercolor, + borderpad=borderpad, + borderwidth=borderwidth, + captureevents=captureevents, + clicktoshow=clicktoshow, + font=font, + height=height, + hoverlabel=hoverlabel, + hovertext=hovertext, + name=name, + opacity=opacity, + showarrow=showarrow, + standoff=standoff, + startarrowhead=startarrowhead, + startarrowsize=startarrowsize, + startstandoff=startstandoff, + templateitemname=templateitemname, + text=text, + textangle=textangle, + valign=valign, + visible=visible, + width=width, + x=x, + xanchor=xanchor, + xclick=xclick, + xref=xref, + xshift=xshift, + y=y, + yanchor=yanchor, + yclick=yclick, + yref=yref, + yshift=yshift, + **kwargs + ) + return self._add_annotation_like( + "annotation", + "annotations", + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + ) + + def select_images(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select images from a particular subplot cell and/or images that satisfy custom selection criteria. Parameters @@ -15979,66 +16334,66 @@ def select_shapes(self, selector=None, row=None, col=None, secondary_y=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 - the supplied values. If None (the default), all shapes are + the supplied values. If None (the default), all images are selected. 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 + Subplot row and column index of images to select. + To select images by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - shape that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all shapes are selected. + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. secondary_y: boolean or None (default None) - * If True, only select shapes associated with the secondary + * If True, only select images associated with the secondary y-axis of the subplot. - * If False, only select shapes associated with the primary + * If False, only select images associated with the primary y-axis of the subplot. - * If None (the default), do not filter shapes based on secondary + * If None (the default), do not filter images based on secondary y-axis. - To select shapes by secondary y-axis, the Figure must have been + To select images 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 info on creating subplots with secondary y-axes. Returns ------- generator - Generator that iterates through all of the shapes that satisfy + Generator that iterates through all of the images that satisfy all of the specified selection criteria """ return self._select_annotations_like( - "shapes", selector=selector, row=row, col=col, secondary_y=secondary_y + "images", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None): + def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): """ - Apply a function to all shapes that satisfy the specified selection + Apply a function to all images that satisfy the specified selection criteria Parameters ---------- fn: - Function that inputs a single shape object. + Function that inputs a single image object. selector: dict 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 - the supplied values. If None (the default), all shapes are + the supplied values. If None (the default), all images are selected. 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 + Subplot row and column index of images to select. + To select images by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - shapes that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all shapes are selected. + images that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. secondary_y: boolean or None (default None) - * If True, only select shapes associated with the secondary + * If True, only select images associated with the secondary y-axis of the subplot. - * If False, only select shapes associated with the primary + * If False, only select images associated with the primary y-axis of the subplot. - * If None (the default), do not filter shapes based on secondary + * If None (the default), do not filter images based on secondary y-axis. - To select shapes by secondary y-axis, the Figure must have been + To select images 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 info on creating subplots with secondary y-axes. @@ -16048,7 +16403,7 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", + property="images", selector=selector, row=row, col=col, @@ -16058,44 +16413,44 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None return self - def update_shapes( + def update_images( self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs ): """ - Perform a property update operation on all shapes that satisfy the + Perform a property update operation on all images that satisfy the specified selection criteria Parameters ---------- patch: dict or None (default None) - Dictionary of property updates to be applied to all shapes that + Dictionary of property updates to be applied to all images that satisfy the selection criteria. selector: dict 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 - the supplied values. If None (the default), all shapes are + the supplied values. If None (the default), all images are selected. 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 + Subplot row and column index of images to select. + To select images by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - shape that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all shapes are selected. + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. secondary_y: boolean or None (default None) - * If True, only select shapes associated with the secondary + * If True, only select images associated with the secondary y-axis of the subplot. - * If False, only select shapes associated with the primary + * If False, only select images associated with the primary y-axis of the subplot. - * If None (the default), do not filter shapes based on secondary + * If None (the default), do not filter images based on secondary y-axis. - To select shapes by secondary y-axis, the Figure must have been + To select images 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 info on creating subplots with secondary y-axes. **kwargs - Additional property updates to apply to each selected shape. If + Additional property updates to apply to each selected image. If a property is specified in both patch and in **kwargs then the one in **kwargs takes precedence. @@ -16105,7 +16460,7 @@ def update_shapes( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", + property="images", selector=selector, row=row, col=col, @@ -16115,9 +16470,142 @@ def update_shapes( return self - def select_images(self, selector=None, row=None, col=None, secondary_y=None): + def add_image( + self, + arg=None, + layer=None, + name=None, + opacity=None, + sizex=None, + sizey=None, + sizing=None, + source=None, + templateitemname=None, + visible=None, + x=None, + xanchor=None, + xref=None, + y=None, + yanchor=None, + yref=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): """ - Select images from a particular subplot cell and/or images + Create and add a new image to the figure's layout + + Parameters + ---------- + arg + instance of Image or dict with compatible properties + layer + Specifies whether images are drawn below or above + traces. When `xref` and `yref` are both set to `paper`, + image is drawn below the entire plot area. + name + When used in a template, named items are created in the + output figure in addition to any items the figure + already has in this array. You can modify these items + in the output figure by making your own item with + `templateitemname` matching this `name` alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). Has no effect outside of a + template. + opacity + Sets the opacity of the image. + sizex + Sets the image container size horizontally. The image + will be sized based on the `position` value. When + `xref` is set to `paper`, units are sized relative to + the plot width. + sizey + Sets the image container size vertically. The image + will be sized based on the `position` value. When + `yref` is set to `paper`, units are sized relative to + the plot height. + sizing + Specifies which dimension of the image to constrain. + source + Specifies the URL of the image to be used. The URL must + be accessible from the domain where the plot code is + run, and can be either relative or absolute. + templateitemname + Used to refer to a named item in this array in the + template. Named items from the template will be created + even without a matching item in the input figure, but + you can modify one by making an item with + `templateitemname` matching its `name`, alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). If there is no template or no + matching item, this item will be hidden unless you + explicitly show it with `visible: true`. + visible + Determines whether or not this image is visible. + x + Sets the image's x position. When `xref` is set to + `paper`, units are sized relative to the plot height. + See `xref` for more info + xanchor + Sets the anchor for the x position + xref + Sets the images's x coordinate axis. If set to a x axis + id (e.g. "x" or "x2"), the `x` position refers to an x + data coordinate If set to "paper", the `x` position + refers to the distance from the left of plot in + normalized coordinates where 0 (1) corresponds to the + left (right). + y + Sets the image's y position. When `yref` is set to + `paper`, units are sized relative to the plot height. + See `yref` for more info + yanchor + Sets the anchor for the y position. + yref + Sets the images's y coordinate axis. If set to a y axis + id (e.g. "y" or "y2"), the `y` position refers to a y + data coordinate. If set to "paper", the `y` position + refers to the distance from the bottom of the plot in + normalized coordinates where 0 (1) corresponds to the + bottom (top). + row + Subplot row for image + col + Subplot column for image + secondary_y + Whether to add image to secondary y-axis + + Returns + ------- + Figure + """ + new_obj = _layout.Image( + arg, + layer=layer, + name=name, + opacity=opacity, + sizex=sizex, + sizey=sizey, + sizing=sizing, + source=source, + templateitemname=templateitemname, + visible=visible, + x=x, + xanchor=xanchor, + xref=xref, + y=y, + yanchor=yanchor, + yref=yref, + **kwargs + ) + return self._add_annotation_like( + "image", "images", new_obj, row=row, col=col, secondary_y=secondary_y + ) + + def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select shapes from a particular subplot cell and/or shapes that satisfy custom selection criteria. Parameters @@ -16126,66 +16614,66 @@ def select_images(self, selector=None, row=None, col=None, secondary_y=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 - the supplied values. If None (the default), all images are + the supplied values. If None (the default), all shapes are selected. 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 + Subplot row and column index of shapes to select. + To select shapes by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - image that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all images are selected. + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. secondary_y: boolean or None (default None) - * If True, only select images associated with the secondary + * If True, only select shapes associated with the secondary y-axis of the subplot. - * If False, only select images associated with the primary + * If False, only select shapes associated with the primary y-axis of the subplot. - * If None (the default), do not filter images based on secondary + * If None (the default), do not filter shapes based on secondary y-axis. - To select images by secondary y-axis, the Figure must have been + To select shapes 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 info on creating subplots with secondary y-axes. Returns ------- generator - Generator that iterates through all of the images that satisfy + Generator that iterates through all of the shapes that satisfy all of the specified selection criteria """ return self._select_annotations_like( - "images", selector=selector, row=row, col=col, secondary_y=secondary_y + "shapes", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): + def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None): """ - Apply a function to all images that satisfy the specified selection + Apply a function to all shapes that satisfy the specified selection criteria Parameters ---------- fn: - Function that inputs a single image object. + Function that inputs a single shape object. selector: dict 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 - the supplied values. If None (the default), all images are + the supplied values. If None (the default), all shapes are selected. 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 + Subplot row and column index of shapes to select. + To select shapes by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - images that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all images are selected. + shapes that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. secondary_y: boolean or None (default None) - * If True, only select images associated with the secondary + * If True, only select shapes associated with the secondary y-axis of the subplot. - * If False, only select images associated with the primary + * If False, only select shapes associated with the primary y-axis of the subplot. - * If None (the default), do not filter images based on secondary + * If None (the default), do not filter shapes based on secondary y-axis. - To select images by secondary y-axis, the Figure must have been + To select shapes 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 info on creating subplots with secondary y-axes. @@ -16195,7 +16683,7 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", + property="shapes", selector=selector, row=row, col=col, @@ -16205,44 +16693,44 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None return self - def update_images( + def update_shapes( self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs ): """ - Perform a property update operation on all images that satisfy the + Perform a property update operation on all shapes that satisfy the specified selection criteria Parameters ---------- patch: dict or None (default None) - Dictionary of property updates to be applied to all images that + Dictionary of property updates to be applied to all shapes that satisfy the selection criteria. selector: dict 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 - the supplied values. If None (the default), all images are + the supplied values. If None (the default), all shapes are selected. 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 + Subplot row and column index of shapes to select. + To select shapes by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - image that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all images are selected. + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. secondary_y: boolean or None (default None) - * If True, only select images associated with the secondary + * If True, only select shapes associated with the secondary y-axis of the subplot. - * If False, only select images associated with the primary + * If False, only select shapes associated with the primary y-axis of the subplot. - * If None (the default), do not filter images based on secondary + * If None (the default), do not filter shapes based on secondary y-axis. - To select images by secondary y-axis, the Figure must have been + To select shapes 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 info on creating subplots with secondary y-axes. **kwargs - Additional property updates to apply to each selected image. If + Additional property updates to apply to each selected shape. If a property is specified in both patch and in **kwargs then the one in **kwargs takes precedence. @@ -16252,7 +16740,7 @@ def update_images( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", + property="shapes", selector=selector, row=row, col=col, @@ -16261,3 +16749,205 @@ def update_images( obj.update(patch, **kwargs) return self + + def add_shape( + self, + arg=None, + fillcolor=None, + layer=None, + line=None, + name=None, + opacity=None, + path=None, + templateitemname=None, + type=None, + visible=None, + x0=None, + x1=None, + xanchor=None, + xref=None, + xsizemode=None, + y0=None, + y1=None, + yanchor=None, + yref=None, + ysizemode=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): + """ + Create and add a new shape to the figure's layout + + Parameters + ---------- + arg + instance of Shape or dict with compatible properties + fillcolor + Sets the color filling the shape's interior. + layer + Specifies whether shapes are drawn below or above + traces. + line + plotly.graph_objects.layout.shape.Line instance or dict + with compatible properties + name + When used in a template, named items are created in the + output figure in addition to any items the figure + already has in this array. You can modify these items + in the output figure by making your own item with + `templateitemname` matching this `name` alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). Has no effect outside of a + template. + opacity + Sets the opacity of the shape. + path + For `type` "path" - a valid SVG path with the pixel + values replaced by data values in + `xsizemode`/`ysizemode` being "scaled" and taken + unmodified as pixels relative to `xanchor` and + `yanchor` in case of "pixel" size mode. There are a few + restrictions / quirks only absolute instructions, not + relative. So the allowed segments are: M, L, H, V, Q, + C, T, S, and Z arcs (A) are not allowed because radius + rx and ry are relative. In the future we could consider + supporting relative commands, but we would have to + decide on how to handle date and log axes. Note that + even as is, Q and C Bezier paths that are smooth on + linear axes may not be smooth on log, and vice versa. + no chained "polybezier" commands - specify the segment + type for each one. On category axes, values are numbers + scaled to the serial numbers of categories because + using the categories themselves there would be no way + to describe fractional positions On data axes: because + space and T are both normal components of path strings, + we can't use either to separate date from time parts. + Therefore we'll use underscore for this purpose: + 2015-02-21_13:45:56.789 + templateitemname + Used to refer to a named item in this array in the + template. Named items from the template will be created + even without a matching item in the input figure, but + you can modify one by making an item with + `templateitemname` matching its `name`, alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). If there is no template or no + matching item, this item will be hidden unless you + explicitly show it with `visible: true`. + type + Specifies the shape type to be drawn. If "line", a line + is drawn from (`x0`,`y0`) to (`x1`,`y1`) with respect + to the axes' sizing mode. If "circle", a circle is + drawn from ((`x0`+`x1`)/2, (`y0`+`y1`)/2)) with radius + (|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|) with + respect to the axes' sizing mode. If "rect", a + rectangle is drawn linking (`x0`,`y0`), (`x1`,`y0`), + (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`) with respect to + the axes' sizing mode. If "path", draw a custom SVG + path using `path`. with respect to the axes' sizing + mode. + visible + Determines whether or not this shape is visible. + x0 + Sets the shape's starting x position. See `type` and + `xsizemode` for more info. + x1 + Sets the shape's end x position. See `type` and + `xsizemode` for more info. + xanchor + Only relevant in conjunction with `xsizemode` set to + "pixel". Specifies the anchor point on the x axis to + which `x0`, `x1` and x coordinates within `path` are + relative to. E.g. useful to attach a pixel sized shape + to a certain data value. No effect when `xsizemode` not + set to "pixel". + xref + Sets the shape's x coordinate axis. If set to an x axis + id (e.g. "x" or "x2"), the `x` position refers to an x + coordinate. If set to "paper", the `x` position refers + to the distance from the left side of the plotting area + in normalized coordinates where 0 (1) corresponds to + the left (right) side. If the axis `type` is "log", + then you must take the log of your desired range. If + the axis `type` is "date", then you must convert the + date to unix time in milliseconds. + xsizemode + Sets the shapes's sizing mode along the x axis. If set + to "scaled", `x0`, `x1` and x coordinates within `path` + refer to data values on the x axis or a fraction of the + plot area's width (`xref` set to "paper"). If set to + "pixel", `xanchor` specifies the x position in terms of + data or plot fraction but `x0`, `x1` and x coordinates + within `path` are pixels relative to `xanchor`. This + way, the shape can have a fixed width while maintaining + a position relative to data or plot fraction. + y0 + Sets the shape's starting y position. See `type` and + `ysizemode` for more info. + y1 + Sets the shape's end y position. See `type` and + `ysizemode` for more info. + yanchor + Only relevant in conjunction with `ysizemode` set to + "pixel". Specifies the anchor point on the y axis to + which `y0`, `y1` and y coordinates within `path` are + relative to. E.g. useful to attach a pixel sized shape + to a certain data value. No effect when `ysizemode` not + set to "pixel". + yref + Sets the annotation's y coordinate axis. If set to an y + axis id (e.g. "y" or "y2"), the `y` position refers to + an y coordinate If set to "paper", the `y` position + refers to the distance from the bottom of the plotting + area in normalized coordinates where 0 (1) corresponds + to the bottom (top). + ysizemode + Sets the shapes's sizing mode along the y axis. If set + to "scaled", `y0`, `y1` and y coordinates within `path` + refer to data values on the y axis or a fraction of the + plot area's height (`yref` set to "paper"). If set to + "pixel", `yanchor` specifies the y position in terms of + data or plot fraction but `y0`, `y1` and y coordinates + within `path` are pixels relative to `yanchor`. This + way, the shape can have a fixed height while + maintaining a position relative to data or plot + fraction. + row + Subplot row for shape + col + Subplot column for shape + secondary_y + Whether to add shape to secondary y-axis + + Returns + ------- + Figure + """ + new_obj = _layout.Shape( + arg, + fillcolor=fillcolor, + layer=layer, + line=line, + name=name, + opacity=opacity, + path=path, + templateitemname=templateitemname, + type=type, + visible=visible, + x0=x0, + x1=x1, + xanchor=xanchor, + xref=xref, + xsizemode=xsizemode, + y0=y0, + y1=y1, + yanchor=yanchor, + yref=yref, + ysizemode=ysizemode, + **kwargs + ) + return self._add_annotation_like( + "shape", "shapes", new_obj, row=row, col=col, secondary_y=secondary_y + ) diff --git a/packages/python/plotly/plotly/graph_objs/_figurewidget.py b/packages/python/plotly/plotly/graph_objs/_figurewidget.py index 30ebf14249f..232c1956c1b 100644 --- a/packages/python/plotly/plotly/graph_objs/_figurewidget.py +++ b/packages/python/plotly/plotly/graph_objs/_figurewidget.py @@ -45,6 +45,7 @@ Violin, Volume, Waterfall, + layout as _layout, ) @@ -15968,9 +15969,363 @@ def update_annotations( return self - def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): + def add_annotation( + self, + arg=None, + align=None, + arrowcolor=None, + arrowhead=None, + arrowside=None, + arrowsize=None, + arrowwidth=None, + ax=None, + axref=None, + ay=None, + ayref=None, + bgcolor=None, + bordercolor=None, + borderpad=None, + borderwidth=None, + captureevents=None, + clicktoshow=None, + font=None, + height=None, + hoverlabel=None, + hovertext=None, + name=None, + opacity=None, + showarrow=None, + standoff=None, + startarrowhead=None, + startarrowsize=None, + startstandoff=None, + templateitemname=None, + text=None, + textangle=None, + valign=None, + visible=None, + width=None, + x=None, + xanchor=None, + xclick=None, + xref=None, + xshift=None, + y=None, + yanchor=None, + yclick=None, + yref=None, + yshift=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): """ - Select shapes from a particular subplot cell and/or shapes + Create and add a new annotation to the figure's layout + + Parameters + ---------- + arg + instance of Annotation or dict with compatible + properties + align + Sets the horizontal alignment of the `text` within the + box. Has an effect only if `text` spans more two or + more lines (i.e. `text` contains one or more
HTML + tags) or if an explicit width is set to override the + text width. + arrowcolor + Sets the color of the annotation arrow. + arrowhead + Sets the end annotation arrow head style. + arrowside + Sets the annotation arrow head position. + arrowsize + Sets the size of the end annotation arrow head, + relative to `arrowwidth`. A value of 1 (default) gives + a head about 3x as wide as the line. + arrowwidth + Sets the width (in px) of annotation arrow line. + ax + Sets the x component of the arrow tail about the arrow + head. If `axref` is `pixel`, a positive (negative) + component corresponds to an arrow pointing from right + to left (left to right). If `axref` is an axis, this is + an absolute value on that axis, like `x`, NOT a + relative value. + axref + Indicates in what terms the tail of the annotation + (ax,ay) is specified. If `pixel`, `ax` is a relative + offset in pixels from `x`. If set to an x axis id + (e.g. "x" or "x2"), `ax` is specified in the same + terms as that axis. This is useful for trendline + annotations which should continue to indicate the + correct trend when zoomed. + ay + Sets the y component of the arrow tail about the arrow + head. If `ayref` is `pixel`, a positive (negative) + component corresponds to an arrow pointing from bottom + to top (top to bottom). If `ayref` is an axis, this is + an absolute value on that axis, like `y`, NOT a + relative value. + ayref + Indicates in what terms the tail of the annotation + (ax,ay) is specified. If `pixel`, `ay` is a relative + offset in pixels from `y`. If set to a y axis id (e.g. + "y" or "y2"), `ay` is specified in the same terms as + that axis. This is useful for trendline annotations + which should continue to indicate the correct trend + when zoomed. + bgcolor + Sets the background color of the annotation. + bordercolor + Sets the color of the border enclosing the annotation + `text`. + borderpad + Sets the padding (in px) between the `text` and the + enclosing border. + borderwidth + Sets the width (in px) of the border enclosing the + annotation `text`. + captureevents + Determines whether the annotation text box captures + mouse move and click events, or allows those events to + pass through to data points in the plot that may be + behind the annotation. By default `captureevents` is + False unless `hovertext` is provided. If you use the + event `plotly_clickannotation` without `hovertext` you + must explicitly enable `captureevents`. + clicktoshow + Makes this annotation respond to clicks on the plot. If + you click a data point that exactly matches the `x` and + `y` values of this annotation, and it is hidden + (visible: false), it will appear. In "onoff" mode, you + must click the same point again to make it disappear, + so if you click multiple points, you can show multiple + annotations. In "onout" mode, a click anywhere else in + the plot (on another data point or not) will hide this + annotation. If you need to show/hide this annotation in + response to different `x` or `y` values, you can set + `xclick` and/or `yclick`. This is useful for example to + label the side of a bar. To label markers though, + `standoff` is preferred over `xclick` and `yclick`. + font + Sets the annotation text font. + height + Sets an explicit height for the text box. null + (default) lets the text set the box height. Taller text + will be clipped. + hoverlabel + plotly.graph_objects.layout.annotation.Hoverlabel + instance or dict with compatible properties + hovertext + Sets text to appear when hovering over this annotation. + If omitted or blank, no hover label will appear. + name + When used in a template, named items are created in the + output figure in addition to any items the figure + already has in this array. You can modify these items + in the output figure by making your own item with + `templateitemname` matching this `name` alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). Has no effect outside of a + template. + opacity + Sets the opacity of the annotation (text + arrow). + showarrow + Determines whether or not the annotation is drawn with + an arrow. If True, `text` is placed near the arrow's + tail. If False, `text` lines up with the `x` and `y` + provided. + standoff + Sets a distance, in pixels, to move the end arrowhead + away from the position it is pointing at, for example + to point at the edge of a marker independent of zoom. + Note that this shortens the arrow from the `ax` / `ay` + vector, in contrast to `xshift` / `yshift` which moves + everything by this amount. + startarrowhead + Sets the start annotation arrow head style. + startarrowsize + Sets the size of the start annotation arrow head, + relative to `arrowwidth`. A value of 1 (default) gives + a head about 3x as wide as the line. + startstandoff + Sets a distance, in pixels, to move the start arrowhead + away from the position it is pointing at, for example + to point at the edge of a marker independent of zoom. + Note that this shortens the arrow from the `ax` / `ay` + vector, in contrast to `xshift` / `yshift` which moves + everything by this amount. + templateitemname + Used to refer to a named item in this array in the + template. Named items from the template will be created + even without a matching item in the input figure, but + you can modify one by making an item with + `templateitemname` matching its `name`, alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). If there is no template or no + matching item, this item will be hidden unless you + explicitly show it with `visible: true`. + text + Sets the text associated with this annotation. Plotly + uses a subset of HTML tags to do things like newline + (
), bold (), italics (), hyperlinks + (). Tags , , + are also supported. + textangle + Sets the angle at which the `text` is drawn with + respect to the horizontal. + valign + Sets the vertical alignment of the `text` within the + box. Has an effect only if an explicit height is set to + override the text height. + visible + Determines whether or not this annotation is visible. + width + Sets an explicit width for the text box. null (default) + lets the text set the box width. Wider text will be + clipped. There is no automatic wrapping; use
to + start a new line. + x + Sets the annotation's x position. If the axis `type` is + "log", then you must take the log of your desired + range. If the axis `type` is "date", it should be date + strings, like date data, though Date objects and unix + milliseconds will be accepted and converted to strings. + If the axis `type` is "category", it should be numbers, + using the scale where each category is assigned a + serial number from zero in the order it appears. + xanchor + Sets the text box's horizontal position anchor This + anchor binds the `x` position to the "left", "center" + or "right" of the annotation. For example, if `x` is + set to 1, `xref` to "paper" and `xanchor` to "right" + then the right-most portion of the annotation lines up + with the right-most edge of the plotting area. If + "auto", the anchor is equivalent to "center" for data- + referenced annotations or if there is an arrow, whereas + for paper-referenced with no arrow, the anchor picked + corresponds to the closest side. + xclick + Toggle this annotation when clicking a data point whose + `x` value is `xclick` rather than the annotation's `x` + value. + xref + Sets the annotation's x coordinate axis. If set to an x + axis id (e.g. "x" or "x2"), the `x` position refers to + an x coordinate If set to "paper", the `x` position + refers to the distance from the left side of the + plotting area in normalized coordinates where 0 (1) + corresponds to the left (right) side. + xshift + Shifts the position of the whole annotation and arrow + to the right (positive) or left (negative) by this many + pixels. + y + Sets the annotation's y position. If the axis `type` is + "log", then you must take the log of your desired + range. If the axis `type` is "date", it should be date + strings, like date data, though Date objects and unix + milliseconds will be accepted and converted to strings. + If the axis `type` is "category", it should be numbers, + using the scale where each category is assigned a + serial number from zero in the order it appears. + yanchor + Sets the text box's vertical position anchor This + anchor binds the `y` position to the "top", "middle" or + "bottom" of the annotation. For example, if `y` is set + to 1, `yref` to "paper" and `yanchor` to "top" then the + top-most portion of the annotation lines up with the + top-most edge of the plotting area. If "auto", the + anchor is equivalent to "middle" for data-referenced + annotations or if there is an arrow, whereas for paper- + referenced with no arrow, the anchor picked corresponds + to the closest side. + yclick + Toggle this annotation when clicking a data point whose + `y` value is `yclick` rather than the annotation's `y` + value. + yref + Sets the annotation's y coordinate axis. If set to an y + axis id (e.g. "y" or "y2"), the `y` position refers to + an y coordinate If set to "paper", the `y` position + refers to the distance from the bottom of the plotting + area in normalized coordinates where 0 (1) corresponds + to the bottom (top). + yshift + Shifts the position of the whole annotation and arrow + up (positive) or down (negative) by this many pixels. + row + Subplot row for annotation + col + Subplot column for annotation + secondary_y + Whether to add annotation to secondary y-axis + + Returns + ------- + FigureWidget + """ + new_obj = _layout.Annotation( + arg, + align=align, + arrowcolor=arrowcolor, + arrowhead=arrowhead, + arrowside=arrowside, + arrowsize=arrowsize, + arrowwidth=arrowwidth, + ax=ax, + axref=axref, + ay=ay, + ayref=ayref, + bgcolor=bgcolor, + bordercolor=bordercolor, + borderpad=borderpad, + borderwidth=borderwidth, + captureevents=captureevents, + clicktoshow=clicktoshow, + font=font, + height=height, + hoverlabel=hoverlabel, + hovertext=hovertext, + name=name, + opacity=opacity, + showarrow=showarrow, + standoff=standoff, + startarrowhead=startarrowhead, + startarrowsize=startarrowsize, + startstandoff=startstandoff, + templateitemname=templateitemname, + text=text, + textangle=textangle, + valign=valign, + visible=visible, + width=width, + x=x, + xanchor=xanchor, + xclick=xclick, + xref=xref, + xshift=xshift, + y=y, + yanchor=yanchor, + yclick=yclick, + yref=yref, + yshift=yshift, + **kwargs + ) + return self._add_annotation_like( + "annotation", + "annotations", + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + ) + + def select_images(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select images from a particular subplot cell and/or images that satisfy custom selection criteria. Parameters @@ -15979,66 +16334,66 @@ def select_shapes(self, selector=None, row=None, col=None, secondary_y=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 - the supplied values. If None (the default), all shapes are + the supplied values. If None (the default), all images are selected. 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 + Subplot row and column index of images to select. + To select images by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - shape that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all shapes are selected. + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. secondary_y: boolean or None (default None) - * If True, only select shapes associated with the secondary + * If True, only select images associated with the secondary y-axis of the subplot. - * If False, only select shapes associated with the primary + * If False, only select images associated with the primary y-axis of the subplot. - * If None (the default), do not filter shapes based on secondary + * If None (the default), do not filter images based on secondary y-axis. - To select shapes by secondary y-axis, the Figure must have been + To select images 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 info on creating subplots with secondary y-axes. Returns ------- generator - Generator that iterates through all of the shapes that satisfy + Generator that iterates through all of the images that satisfy all of the specified selection criteria """ return self._select_annotations_like( - "shapes", selector=selector, row=row, col=col, secondary_y=secondary_y + "images", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None): + def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): """ - Apply a function to all shapes that satisfy the specified selection + Apply a function to all images that satisfy the specified selection criteria Parameters ---------- fn: - Function that inputs a single shape object. + Function that inputs a single image object. selector: dict 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 - the supplied values. If None (the default), all shapes are + the supplied values. If None (the default), all images are selected. 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 + Subplot row and column index of images to select. + To select images by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - shapes that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all shapes are selected. + images that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. secondary_y: boolean or None (default None) - * If True, only select shapes associated with the secondary + * If True, only select images associated with the secondary y-axis of the subplot. - * If False, only select shapes associated with the primary + * If False, only select images associated with the primary y-axis of the subplot. - * If None (the default), do not filter shapes based on secondary + * If None (the default), do not filter images based on secondary y-axis. - To select shapes by secondary y-axis, the Figure must have been + To select images 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 info on creating subplots with secondary y-axes. @@ -16048,7 +16403,7 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", + property="images", selector=selector, row=row, col=col, @@ -16058,44 +16413,44 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None return self - def update_shapes( + def update_images( self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs ): """ - Perform a property update operation on all shapes that satisfy the + Perform a property update operation on all images that satisfy the specified selection criteria Parameters ---------- patch: dict or None (default None) - Dictionary of property updates to be applied to all shapes that + Dictionary of property updates to be applied to all images that satisfy the selection criteria. selector: dict 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 - the supplied values. If None (the default), all shapes are + the supplied values. If None (the default), all images are selected. 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 + Subplot row and column index of images to select. + To select images by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - shape that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all shapes are selected. + image that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all images are selected. secondary_y: boolean or None (default None) - * If True, only select shapes associated with the secondary + * If True, only select images associated with the secondary y-axis of the subplot. - * If False, only select shapes associated with the primary + * If False, only select images associated with the primary y-axis of the subplot. - * If None (the default), do not filter shapes based on secondary + * If None (the default), do not filter images based on secondary y-axis. - To select shapes by secondary y-axis, the Figure must have been + To select images 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 info on creating subplots with secondary y-axes. **kwargs - Additional property updates to apply to each selected shape. If + Additional property updates to apply to each selected image. If a property is specified in both patch and in **kwargs then the one in **kwargs takes precedence. @@ -16105,7 +16460,7 @@ def update_shapes( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", + property="images", selector=selector, row=row, col=col, @@ -16115,9 +16470,142 @@ def update_shapes( return self - def select_images(self, selector=None, row=None, col=None, secondary_y=None): + def add_image( + self, + arg=None, + layer=None, + name=None, + opacity=None, + sizex=None, + sizey=None, + sizing=None, + source=None, + templateitemname=None, + visible=None, + x=None, + xanchor=None, + xref=None, + y=None, + yanchor=None, + yref=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): """ - Select images from a particular subplot cell and/or images + Create and add a new image to the figure's layout + + Parameters + ---------- + arg + instance of Image or dict with compatible properties + layer + Specifies whether images are drawn below or above + traces. When `xref` and `yref` are both set to `paper`, + image is drawn below the entire plot area. + name + When used in a template, named items are created in the + output figure in addition to any items the figure + already has in this array. You can modify these items + in the output figure by making your own item with + `templateitemname` matching this `name` alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). Has no effect outside of a + template. + opacity + Sets the opacity of the image. + sizex + Sets the image container size horizontally. The image + will be sized based on the `position` value. When + `xref` is set to `paper`, units are sized relative to + the plot width. + sizey + Sets the image container size vertically. The image + will be sized based on the `position` value. When + `yref` is set to `paper`, units are sized relative to + the plot height. + sizing + Specifies which dimension of the image to constrain. + source + Specifies the URL of the image to be used. The URL must + be accessible from the domain where the plot code is + run, and can be either relative or absolute. + templateitemname + Used to refer to a named item in this array in the + template. Named items from the template will be created + even without a matching item in the input figure, but + you can modify one by making an item with + `templateitemname` matching its `name`, alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). If there is no template or no + matching item, this item will be hidden unless you + explicitly show it with `visible: true`. + visible + Determines whether or not this image is visible. + x + Sets the image's x position. When `xref` is set to + `paper`, units are sized relative to the plot height. + See `xref` for more info + xanchor + Sets the anchor for the x position + xref + Sets the images's x coordinate axis. If set to a x axis + id (e.g. "x" or "x2"), the `x` position refers to an x + data coordinate If set to "paper", the `x` position + refers to the distance from the left of plot in + normalized coordinates where 0 (1) corresponds to the + left (right). + y + Sets the image's y position. When `yref` is set to + `paper`, units are sized relative to the plot height. + See `yref` for more info + yanchor + Sets the anchor for the y position. + yref + Sets the images's y coordinate axis. If set to a y axis + id (e.g. "y" or "y2"), the `y` position refers to a y + data coordinate. If set to "paper", the `y` position + refers to the distance from the bottom of the plot in + normalized coordinates where 0 (1) corresponds to the + bottom (top). + row + Subplot row for image + col + Subplot column for image + secondary_y + Whether to add image to secondary y-axis + + Returns + ------- + FigureWidget + """ + new_obj = _layout.Image( + arg, + layer=layer, + name=name, + opacity=opacity, + sizex=sizex, + sizey=sizey, + sizing=sizing, + source=source, + templateitemname=templateitemname, + visible=visible, + x=x, + xanchor=xanchor, + xref=xref, + y=y, + yanchor=yanchor, + yref=yref, + **kwargs + ) + return self._add_annotation_like( + "image", "images", new_obj, row=row, col=col, secondary_y=secondary_y + ) + + def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): + """ + Select shapes from a particular subplot cell and/or shapes that satisfy custom selection criteria. Parameters @@ -16126,66 +16614,66 @@ def select_images(self, selector=None, row=None, col=None, secondary_y=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 - the supplied values. If None (the default), all images are + the supplied values. If None (the default), all shapes are selected. 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 + Subplot row and column index of shapes to select. + To select shapes by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - image that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all images are selected. + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. secondary_y: boolean or None (default None) - * If True, only select images associated with the secondary + * If True, only select shapes associated with the secondary y-axis of the subplot. - * If False, only select images associated with the primary + * If False, only select shapes associated with the primary y-axis of the subplot. - * If None (the default), do not filter images based on secondary + * If None (the default), do not filter shapes based on secondary y-axis. - To select images by secondary y-axis, the Figure must have been + To select shapes 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 info on creating subplots with secondary y-axes. Returns ------- generator - Generator that iterates through all of the images that satisfy + Generator that iterates through all of the shapes that satisfy all of the specified selection criteria """ return self._select_annotations_like( - "images", selector=selector, row=row, col=col, secondary_y=secondary_y + "shapes", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): + def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None): """ - Apply a function to all images that satisfy the specified selection + Apply a function to all shapes that satisfy the specified selection criteria Parameters ---------- fn: - Function that inputs a single image object. + Function that inputs a single shape object. selector: dict 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 - the supplied values. If None (the default), all images are + the supplied values. If None (the default), all shapes are selected. 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 + Subplot row and column index of shapes to select. + To select shapes by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - images that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all images are selected. + shapes that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. secondary_y: boolean or None (default None) - * If True, only select images associated with the secondary + * If True, only select shapes associated with the secondary y-axis of the subplot. - * If False, only select images associated with the primary + * If False, only select shapes associated with the primary y-axis of the subplot. - * If None (the default), do not filter images based on secondary + * If None (the default), do not filter shapes based on secondary y-axis. - To select images by secondary y-axis, the Figure must have been + To select shapes 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 info on creating subplots with secondary y-axes. @@ -16195,7 +16683,7 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", + property="shapes", selector=selector, row=row, col=col, @@ -16205,44 +16693,44 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None return self - def update_images( + def update_shapes( self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs ): """ - Perform a property update operation on all images that satisfy the + Perform a property update operation on all shapes that satisfy the specified selection criteria Parameters ---------- patch: dict or None (default None) - Dictionary of property updates to be applied to all images that + Dictionary of property updates to be applied to all shapes that satisfy the selection criteria. selector: dict 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 - the supplied values. If None (the default), all images are + the supplied values. If None (the default), all shapes are selected. 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 + Subplot row and column index of shapes to select. + To select shapes by row and column, the Figure must have been created using plotly.subplots.make_subplots. To select only those - image that are in paper coordinates, set row and col to the - string 'paper'. If None (the default), all images are selected. + shape that are in paper coordinates, set row and col to the + string 'paper'. If None (the default), all shapes are selected. secondary_y: boolean or None (default None) - * If True, only select images associated with the secondary + * If True, only select shapes associated with the secondary y-axis of the subplot. - * If False, only select images associated with the primary + * If False, only select shapes associated with the primary y-axis of the subplot. - * If None (the default), do not filter images based on secondary + * If None (the default), do not filter shapes based on secondary y-axis. - To select images by secondary y-axis, the Figure must have been + To select shapes 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 info on creating subplots with secondary y-axes. **kwargs - Additional property updates to apply to each selected image. If + Additional property updates to apply to each selected shape. If a property is specified in both patch and in **kwargs then the one in **kwargs takes precedence. @@ -16252,7 +16740,7 @@ def update_images( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", + property="shapes", selector=selector, row=row, col=col, @@ -16261,3 +16749,205 @@ def update_images( obj.update(patch, **kwargs) return self + + def add_shape( + self, + arg=None, + fillcolor=None, + layer=None, + line=None, + name=None, + opacity=None, + path=None, + templateitemname=None, + type=None, + visible=None, + x0=None, + x1=None, + xanchor=None, + xref=None, + xsizemode=None, + y0=None, + y1=None, + yanchor=None, + yref=None, + ysizemode=None, + row=None, + col=None, + secondary_y=None, + **kwargs + ): + """ + Create and add a new shape to the figure's layout + + Parameters + ---------- + arg + instance of Shape or dict with compatible properties + fillcolor + Sets the color filling the shape's interior. + layer + Specifies whether shapes are drawn below or above + traces. + line + plotly.graph_objects.layout.shape.Line instance or dict + with compatible properties + name + When used in a template, named items are created in the + output figure in addition to any items the figure + already has in this array. You can modify these items + in the output figure by making your own item with + `templateitemname` matching this `name` alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). Has no effect outside of a + template. + opacity + Sets the opacity of the shape. + path + For `type` "path" - a valid SVG path with the pixel + values replaced by data values in + `xsizemode`/`ysizemode` being "scaled" and taken + unmodified as pixels relative to `xanchor` and + `yanchor` in case of "pixel" size mode. There are a few + restrictions / quirks only absolute instructions, not + relative. So the allowed segments are: M, L, H, V, Q, + C, T, S, and Z arcs (A) are not allowed because radius + rx and ry are relative. In the future we could consider + supporting relative commands, but we would have to + decide on how to handle date and log axes. Note that + even as is, Q and C Bezier paths that are smooth on + linear axes may not be smooth on log, and vice versa. + no chained "polybezier" commands - specify the segment + type for each one. On category axes, values are numbers + scaled to the serial numbers of categories because + using the categories themselves there would be no way + to describe fractional positions On data axes: because + space and T are both normal components of path strings, + we can't use either to separate date from time parts. + Therefore we'll use underscore for this purpose: + 2015-02-21_13:45:56.789 + templateitemname + Used to refer to a named item in this array in the + template. Named items from the template will be created + even without a matching item in the input figure, but + you can modify one by making an item with + `templateitemname` matching its `name`, alongside your + modifications (including `visible: false` or `enabled: + false` to hide it). If there is no template or no + matching item, this item will be hidden unless you + explicitly show it with `visible: true`. + type + Specifies the shape type to be drawn. If "line", a line + is drawn from (`x0`,`y0`) to (`x1`,`y1`) with respect + to the axes' sizing mode. If "circle", a circle is + drawn from ((`x0`+`x1`)/2, (`y0`+`y1`)/2)) with radius + (|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|) with + respect to the axes' sizing mode. If "rect", a + rectangle is drawn linking (`x0`,`y0`), (`x1`,`y0`), + (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`) with respect to + the axes' sizing mode. If "path", draw a custom SVG + path using `path`. with respect to the axes' sizing + mode. + visible + Determines whether or not this shape is visible. + x0 + Sets the shape's starting x position. See `type` and + `xsizemode` for more info. + x1 + Sets the shape's end x position. See `type` and + `xsizemode` for more info. + xanchor + Only relevant in conjunction with `xsizemode` set to + "pixel". Specifies the anchor point on the x axis to + which `x0`, `x1` and x coordinates within `path` are + relative to. E.g. useful to attach a pixel sized shape + to a certain data value. No effect when `xsizemode` not + set to "pixel". + xref + Sets the shape's x coordinate axis. If set to an x axis + id (e.g. "x" or "x2"), the `x` position refers to an x + coordinate. If set to "paper", the `x` position refers + to the distance from the left side of the plotting area + in normalized coordinates where 0 (1) corresponds to + the left (right) side. If the axis `type` is "log", + then you must take the log of your desired range. If + the axis `type` is "date", then you must convert the + date to unix time in milliseconds. + xsizemode + Sets the shapes's sizing mode along the x axis. If set + to "scaled", `x0`, `x1` and x coordinates within `path` + refer to data values on the x axis or a fraction of the + plot area's width (`xref` set to "paper"). If set to + "pixel", `xanchor` specifies the x position in terms of + data or plot fraction but `x0`, `x1` and x coordinates + within `path` are pixels relative to `xanchor`. This + way, the shape can have a fixed width while maintaining + a position relative to data or plot fraction. + y0 + Sets the shape's starting y position. See `type` and + `ysizemode` for more info. + y1 + Sets the shape's end y position. See `type` and + `ysizemode` for more info. + yanchor + Only relevant in conjunction with `ysizemode` set to + "pixel". Specifies the anchor point on the y axis to + which `y0`, `y1` and y coordinates within `path` are + relative to. E.g. useful to attach a pixel sized shape + to a certain data value. No effect when `ysizemode` not + set to "pixel". + yref + Sets the annotation's y coordinate axis. If set to an y + axis id (e.g. "y" or "y2"), the `y` position refers to + an y coordinate If set to "paper", the `y` position + refers to the distance from the bottom of the plotting + area in normalized coordinates where 0 (1) corresponds + to the bottom (top). + ysizemode + Sets the shapes's sizing mode along the y axis. If set + to "scaled", `y0`, `y1` and y coordinates within `path` + refer to data values on the y axis or a fraction of the + plot area's height (`yref` set to "paper"). If set to + "pixel", `yanchor` specifies the y position in terms of + data or plot fraction but `y0`, `y1` and y coordinates + within `path` are pixels relative to `yanchor`. This + way, the shape can have a fixed height while + maintaining a position relative to data or plot + fraction. + row + Subplot row for shape + col + Subplot column for shape + secondary_y + Whether to add shape to secondary y-axis + + Returns + ------- + FigureWidget + """ + new_obj = _layout.Shape( + arg, + fillcolor=fillcolor, + layer=layer, + line=line, + name=name, + opacity=opacity, + path=path, + templateitemname=templateitemname, + type=type, + visible=visible, + x0=x0, + x1=x1, + xanchor=xanchor, + xref=xref, + xsizemode=xsizemode, + y0=y0, + y1=y1, + yanchor=yanchor, + yref=yref, + ysizemode=ysizemode, + **kwargs + ) + return self._add_annotation_like( + "shape", "shapes", new_obj, row=row, col=col, secondary_y=secondary_y + ) From bea85bfc04654d746a44909f5e247e1e586ced02 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Mon, 14 Oct 2019 12:57:01 -0400 Subject: [PATCH 5/8] add add/select/update tests for annotations --- packages/python/plotly/codegen/figure.py | 4 +- .../plotly/plotly/graph_objs/_figure.py | 28 +--- .../plotly/plotly/graph_objs/_figurewidget.py | 28 +--- .../test_update_annotations.py | 155 ++++++++++++++++++ 4 files changed, 169 insertions(+), 46 deletions(-) create mode 100644 packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py diff --git a/packages/python/plotly/codegen/figure.py b/packages/python/plotly/codegen/figure.py index 4bd8bb4907a..5b1d9bbb043 100644 --- a/packages/python/plotly/codegen/figure.py +++ b/packages/python/plotly/codegen/figure.py @@ -450,7 +450,7 @@ def for_each_{singular_name}( Returns the Figure object that the method was called on \"\"\" for obj in self._select_annotations_like( - property='{plural_name}', + prop='{plural_name}', selector=selector, row=row, col=col, @@ -513,7 +513,7 @@ def update_{plural_name}( Returns the Figure object that the method was called on \"\"\" for obj in self._select_annotations_like( - property='{plural_name}', + prop='{plural_name}', selector=selector, row=row, col=col, diff --git a/packages/python/plotly/plotly/graph_objs/_figure.py b/packages/python/plotly/plotly/graph_objs/_figure.py index 2a97b85456f..418c022dde2 100644 --- a/packages/python/plotly/plotly/graph_objs/_figure.py +++ b/packages/python/plotly/plotly/graph_objs/_figure.py @@ -15902,7 +15902,7 @@ def for_each_annotation( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="annotations", + prop="annotations", selector=selector, row=row, col=col, @@ -15959,7 +15959,7 @@ def update_annotations( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="annotations", + prop="annotations", selector=selector, row=row, col=col, @@ -16403,11 +16403,7 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="images", selector=selector, row=row, col=col, secondary_y=secondary_y ): fn(obj) @@ -16460,11 +16456,7 @@ def update_images( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="images", selector=selector, row=row, col=col, secondary_y=secondary_y ): obj.update(patch, **kwargs) @@ -16683,11 +16675,7 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="shapes", selector=selector, row=row, col=col, secondary_y=secondary_y ): fn(obj) @@ -16740,11 +16728,7 @@ def update_shapes( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="shapes", selector=selector, row=row, col=col, secondary_y=secondary_y ): obj.update(patch, **kwargs) diff --git a/packages/python/plotly/plotly/graph_objs/_figurewidget.py b/packages/python/plotly/plotly/graph_objs/_figurewidget.py index 232c1956c1b..0774628c4b2 100644 --- a/packages/python/plotly/plotly/graph_objs/_figurewidget.py +++ b/packages/python/plotly/plotly/graph_objs/_figurewidget.py @@ -15902,7 +15902,7 @@ def for_each_annotation( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="annotations", + prop="annotations", selector=selector, row=row, col=col, @@ -15959,7 +15959,7 @@ def update_annotations( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="annotations", + prop="annotations", selector=selector, row=row, col=col, @@ -16403,11 +16403,7 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="images", selector=selector, row=row, col=col, secondary_y=secondary_y ): fn(obj) @@ -16460,11 +16456,7 @@ def update_images( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="images", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="images", selector=selector, row=row, col=col, secondary_y=secondary_y ): obj.update(patch, **kwargs) @@ -16683,11 +16675,7 @@ def for_each_shape(self, fn, selector=None, row=None, col=None, secondary_y=None Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="shapes", selector=selector, row=row, col=col, secondary_y=secondary_y ): fn(obj) @@ -16740,11 +16728,7 @@ def update_shapes( Returns the Figure object that the method was called on """ for obj in self._select_annotations_like( - property="shapes", - selector=selector, - row=row, - col=col, - secondary_y=secondary_y, + prop="shapes", selector=selector, row=row, col=col, secondary_y=secondary_y ): obj.update(patch, **kwargs) 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 new file mode 100644 index 00000000000..526c68da8e9 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -0,0 +1,155 @@ +from __future__ import absolute_import + +import types +from unittest import TestCase + +import plotly.graph_objs as go +from plotly.subplots import make_subplots + + +class TestSelectForEachUpdateAnnotations(TestCase): + def setUp(self): + self.fig = make_subplots( + rows=2, cols=2, specs=[[{}, {"secondary_y": True}], [{}, {"type": "polar"}]] + ) + + def assert_selected( + self, prop, inds, selector=None, row=None, col=None, secondary_y=None + ): + # ## Test select_* + # Get select_ method + fn = getattr(self.fig, "select_" + prop) + + # Perform selection + res = fn(selector=selector, row=row, col=col, secondary_y=secondary_y) + self.assertIsInstance(res, types.GeneratorType) + objs = list(res) + + # Check length of selected objects + self.assertEqual(len(objs), len(inds)) + + # Check individual annotations + for i, obj in zip(inds, objs): + self.assertEqual(self.fig.layout[prop][i], obj) + + # ## Test for_each_* + objs = [] + fn = getattr(self.fig, "for_each_" + prop[:-1]) + fn( + lambda v: objs.append(v), + selector=selector, + row=row, + col=col, + secondary_y=secondary_y, + ) + self.assertEqual(len(objs), len(inds)) + for i, obj in zip(inds, objs): + self.assertEqual(self.fig.layout[prop][i], obj) + + def assert_update( + self, prop, inds, patch, selector=None, row=None, col=None, secondary_y=None + ): + # Copy figure and perform update + fig_orig = go.Figure(self.fig) + fig = go.Figure(self.fig) + fn = getattr(fig, "update_" + prop) + fn(patch, selector=selector, row=row, col=col, secondary_y=secondary_y) + + # Get original up updated object lis + objs_orig = fig_orig.layout[prop] + objs = fig.layout[prop] + + for i, (obj, obj_orig) in enumerate(zip(objs, objs_orig)): + if i in inds: + # Check that object changed from original + self.assertNotEqual(obj, obj_orig) + + # Apply update to original and check that they match now + obj_orig.update(patch) + self.assertEqual(obj, obj_orig) + else: + # Check object unchanged + self.assertEqual(obj, obj_orig) + + def test_add_annotations(self): + # Paper annotation + self.fig.add_annotation(text="A") + annot = self.fig.layout.annotations[-1] + self.assertEqual(annot.text, "A") + self.assertEqual(annot.xref, "paper") + self.assertEqual(annot.yref, "paper") + + # (1, 1) annotation + self.fig.add_annotation(text="B", row=1, col=1) + annot = self.fig.layout.annotations[-1] + self.assertEqual(annot.text, "B") + self.assertEqual(annot.xref, "x") + self.assertEqual(annot.yref, "y") + + # (1, 2) annotation, primary y-axis + self.fig.add_annotation(text="C1", row=1, col=2) + annot = self.fig.layout.annotations[-1] + self.assertEqual(annot.text, "C1") + self.assertEqual(annot.xref, "x2") + self.assertEqual(annot.yref, "y2") + + # (1, 2) annotation, secondary y-axis + self.fig.add_annotation(text="C2", row=1, col=2, secondary_y=True) + annot = self.fig.layout.annotations[-1] + self.assertEqual(annot.text, "C2") + self.assertEqual(annot.xref, "x2") + self.assertEqual(annot.yref, "y3") + + # (2, 1) annotation + self.fig.add_annotation(text="D", row=2, col=1) + annot = self.fig.layout.annotations[-1] + self.assertEqual(annot.text, "D") + self.assertEqual(annot.xref, "x3") + self.assertEqual(annot.yref, "y4") + + # Try to add to (2, 2), which not a valid + with self.assertRaisesRegexp(ValueError, "of type polar"): + self.fig.add_annotation(text="D", row=2, col=2) + + def test_select_annotations(self): + ( + self.fig.add_annotation(text="A1", arrowcolor="red") + .add_annotation(text="A2", arrowcolor="blue") + .add_annotation(text="B", arrowcolor="red", row=1, col=1) + .add_annotation(text="C1", row=1, col=2) + .add_annotation(text="C2", row=1, col=2, secondary_y=True) + .add_annotation(text="D", arrowcolor="blue", row=2, col=1) + ) + + # Test selections + self.assert_selected("annotations", [0, 1, 2, 3, 4, 5]) + self.assert_selected("annotations", [0, 2], selector=dict(arrowcolor="red")) + self.assert_selected("annotations", [2, 3, 4], row=1) + self.assert_selected("annotations", [2], selector=dict(arrowcolor="red"), row=1) + self.assert_selected("annotations", [0, 1], row="paper", col="paper") + self.assert_selected("annotations", [4], secondary_y=True) + + def test_update_annotations(self): + ( + self.fig.add_annotation(text="A1", arrowcolor="red") + .add_annotation(text="A2", arrowcolor="blue") + .add_annotation(text="B", arrowcolor="red", row=1, col=1) + .add_annotation(text="C1", row=1, col=2) + .add_annotation(text="C2", row=1, col=2, secondary_y=True) + .add_annotation(text="D", arrowcolor="blue", row=2, col=1) + ) + + self.assert_update( + "annotations", [0, 1, 2, 3, 4, 5], patch=dict(showarrow=False) + ) + self.assert_update( + "annotations", + [1, 5], + patch=dict(showarrow=False), + selector=dict(arrowcolor="blue"), + ) + self.assert_update("annotations", [2, 3, 4], patch=dict(showarrow=False), row=1) + self.assert_update("annotations", [2, 5], patch=dict(showarrow=False), col=1) + self.assert_update( + "annotations", [4], patch=dict(showarrow=False), secondary_y=True + ) From 40ea7fe33b19e062efa656603a2814087e69f6fd Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Mon, 14 Oct 2019 15:52:26 -0400 Subject: [PATCH 6/8] Add shape/image tests --- .../test_update_annotations.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) 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 526c68da8e9..823503020ac 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 @@ -129,6 +129,42 @@ def test_select_annotations(self): self.assert_selected("annotations", [0, 1], row="paper", col="paper") self.assert_selected("annotations", [4], secondary_y=True) + def test_select_shapes(self): + ( + self.fig.add_shape(opacity=0.1, fillcolor="red") + .add_shape(opacity=0.2, fillcolor="blue") + .add_shape(opacity=0.3, fillcolor="red", row=1, col=1) + .add_shape(opacity=0.4, row=1, col=2) + .add_shape(opacity=0.5, row=1, col=2, secondary_y=True) + .add_shape(opacity=0.6, fillcolor="blue", row=2, col=1) + ) + + # Test selections + self.assert_selected("shapes", [0, 1, 2, 3, 4, 5]) + self.assert_selected("shapes", [0, 2], selector=dict(fillcolor="red")) + self.assert_selected("shapes", [2, 3, 4], row=1) + self.assert_selected("shapes", [2], selector=dict(fillcolor="red"), row=1) + self.assert_selected("shapes", [0, 1], row="paper", col="paper") + self.assert_selected("shapes", [4], secondary_y=True) + + def test_select_images(self): + ( + self.fig.add_image(opacity=0.1, source="red") + .add_image(opacity=0.2, source="blue") + .add_image(opacity=0.3, source="red", row=1, col=1) + .add_image(opacity=0.4, row=1, col=2) + .add_image(opacity=0.5, row=1, col=2, secondary_y=True) + .add_image(opacity=0.6, source="blue", row=2, col=1) + ) + + # Test selections + self.assert_selected("images", [0, 1, 2, 3, 4, 5]) + self.assert_selected("images", [0, 2], selector=dict(source="red")) + self.assert_selected("images", [2, 3, 4], row=1) + self.assert_selected("images", [2], selector=dict(source="red"), row=1) + self.assert_selected("images", [0, 1], row="paper", col="paper") + self.assert_selected("images", [4], secondary_y=True) + def test_update_annotations(self): ( self.fig.add_annotation(text="A1", arrowcolor="red") @@ -153,3 +189,39 @@ def test_update_annotations(self): self.assert_update( "annotations", [4], patch=dict(showarrow=False), secondary_y=True ) + + def test_update_shapes(self): + ( + self.fig.add_shape(opacity=0.1, fillcolor="red") + .add_shape(opacity=0.2, fillcolor="blue") + .add_shape(opacity=0.3, fillcolor="red", row=1, col=1) + .add_shape(opacity=0.4, row=1, col=2) + .add_shape(opacity=0.5, row=1, col=2, secondary_y=True) + .add_shape(opacity=0.6, fillcolor="blue", row=2, col=1) + ) + + self.assert_update("shapes", [0, 1, 2, 3, 4, 5], patch=dict(opacity=0)) + self.assert_update( + "shapes", [1, 5], patch=dict(opacity=0), selector=dict(fillcolor="blue") + ) + self.assert_update("shapes", [2, 3, 4], patch=dict(opacity=0), row=1) + self.assert_update("shapes", [2, 5], patch=dict(opacity=0), col=1) + self.assert_update("shapes", [4], patch=dict(opacity=0), secondary_y=True) + + def test_update_images(self): + ( + self.fig.add_image(opacity=0.1, source="red") + .add_image(opacity=0.2, source="blue") + .add_image(opacity=0.3, source="red", row=1, col=1) + .add_image(opacity=0.4, row=1, col=2) + .add_image(opacity=0.5, row=1, col=2, secondary_y=True) + .add_image(opacity=0.6, source="blue", row=2, col=1) + ) + + self.assert_update("images", [0, 1, 2, 3, 4, 5], patch=dict(opacity=0)) + self.assert_update( + "images", [1, 5], patch=dict(opacity=0), selector=dict(source="blue") + ) + self.assert_update("images", [2, 3, 4], patch=dict(opacity=0), row=1) + self.assert_update("images", [2, 5], patch=dict(opacity=0), col=1) + self.assert_update("images", [4], patch=dict(opacity=0), secondary_y=True) From 52ae979a7cba8769f2fcc0ada981198517ff6bfe Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Tue, 15 Oct 2019 05:51:19 -0400 Subject: [PATCH 7/8] Add add/select tests without row/col on figure not created with make_subplots --- .../test_update_annotations.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 823503020ac..3e81debd1ad 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 @@ -71,6 +71,19 @@ def assert_update( # Check object unchanged self.assertEqual(obj, obj_orig) + def test_add_annotation_no_grid(self): + # Paper annotation + fig = go.Figure() + fig.add_annotation(text="A") + annot = fig.layout.annotations[-1] + self.assertEqual(annot.text, "A") + self.assertEqual(annot.xref, "paper") + self.assertEqual(annot.yref, "paper") + + # Not valid to add annotation by row/col + with self.assertRaisesRegexp(Exception, "make_subplots"): + fig.add_annotation(text="B", row=1, col=1) + def test_add_annotations(self): # Paper annotation self.fig.add_annotation(text="A") @@ -111,6 +124,16 @@ def test_add_annotations(self): with self.assertRaisesRegexp(ValueError, "of type polar"): self.fig.add_annotation(text="D", row=2, col=2) + def test_select_annotations_no_grid(self): + ( + self.fig.add_annotation(text="A1", arrowcolor="red") + .add_annotation(text="A2", arrowcolor="blue") + .add_annotation(text="A3", arrowcolor="blue") + ) + self.assert_selected("annotations", [0, 1, 2]) + self.assert_selected("annotations", [0], selector=dict(arrowcolor="red")) + self.assert_selected("annotations", [1, 2], selector=dict(arrowcolor="blue")) + def test_select_annotations(self): ( self.fig.add_annotation(text="A1", arrowcolor="red") From fd0ae43aefb6939c9d8181f09ed4e82c38994076 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Tue, 15 Oct 2019 08:52:49 -0400 Subject: [PATCH 8/8] Rename add_image to add_layout_image to avoid conflict with future image trace Same for select/for_each/update. --- packages/python/plotly/codegen/figure.py | 14 +++++--- .../plotly/plotly/graph_objs/_figure.py | 10 +++--- .../plotly/plotly/graph_objs/_figurewidget.py | 10 +++--- .../test_update_annotations.py | 32 ++++++++++--------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/packages/python/plotly/codegen/figure.py b/packages/python/plotly/codegen/figure.py index 5b1d9bbb043..034145c01f2 100644 --- a/packages/python/plotly/codegen/figure.py +++ b/packages/python/plotly/codegen/figure.py @@ -364,9 +364,15 @@ def update_{plural_name}( singular_name = node.plotly_name plural_name = node.name_property + if singular_name == "image": + # Rename image to layout_image to avoid conflict with an image trace + method_prefix = "layout_" + else: + method_prefix = "" + buffer.write( f""" - def select_{plural_name}( + def select_{method_prefix}{plural_name}( self, selector=None, row=None, col=None, secondary_y=None ): \"\"\" @@ -409,7 +415,7 @@ def select_{plural_name}( "{plural_name}", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_{singular_name}( + def for_each_{method_prefix}{singular_name}( self, fn, selector=None, row=None, col=None, secondary_y=None ): \"\"\" @@ -460,7 +466,7 @@ def for_each_{singular_name}( return self - def update_{plural_name}( + def update_{method_prefix}{plural_name}( self, patch, selector=None, @@ -527,7 +533,7 @@ def update_{plural_name}( # Add layout array items buffer.write( f""" - def add_{singular_name}(self""" + def add_{method_prefix}{singular_name}(self""" ) add_constructor_params( buffer, diff --git a/packages/python/plotly/plotly/graph_objs/_figure.py b/packages/python/plotly/plotly/graph_objs/_figure.py index 418c022dde2..e037b75363d 100644 --- a/packages/python/plotly/plotly/graph_objs/_figure.py +++ b/packages/python/plotly/plotly/graph_objs/_figure.py @@ -16323,7 +16323,7 @@ def add_annotation( secondary_y=secondary_y, ) - def select_images(self, selector=None, row=None, col=None, secondary_y=None): + def select_layout_images(self, selector=None, row=None, col=None, secondary_y=None): """ Select images from a particular subplot cell and/or images that satisfy custom selection criteria. @@ -16364,7 +16364,9 @@ def select_images(self, selector=None, row=None, col=None, secondary_y=None): "images", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): + def for_each_layout_image( + self, fn, selector=None, row=None, col=None, secondary_y=None + ): """ Apply a function to all images that satisfy the specified selection criteria @@ -16409,7 +16411,7 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None return self - def update_images( + def update_layout_images( self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs ): """ @@ -16462,7 +16464,7 @@ def update_images( return self - def add_image( + def add_layout_image( self, arg=None, layer=None, diff --git a/packages/python/plotly/plotly/graph_objs/_figurewidget.py b/packages/python/plotly/plotly/graph_objs/_figurewidget.py index 0774628c4b2..9e878b5c10f 100644 --- a/packages/python/plotly/plotly/graph_objs/_figurewidget.py +++ b/packages/python/plotly/plotly/graph_objs/_figurewidget.py @@ -16323,7 +16323,7 @@ def add_annotation( secondary_y=secondary_y, ) - def select_images(self, selector=None, row=None, col=None, secondary_y=None): + def select_layout_images(self, selector=None, row=None, col=None, secondary_y=None): """ Select images from a particular subplot cell and/or images that satisfy custom selection criteria. @@ -16364,7 +16364,9 @@ def select_images(self, selector=None, row=None, col=None, secondary_y=None): "images", selector=selector, row=row, col=col, secondary_y=secondary_y ) - def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None): + def for_each_layout_image( + self, fn, selector=None, row=None, col=None, secondary_y=None + ): """ Apply a function to all images that satisfy the specified selection criteria @@ -16409,7 +16411,7 @@ def for_each_image(self, fn, selector=None, row=None, col=None, secondary_y=None return self - def update_images( + def update_layout_images( self, patch, selector=None, row=None, col=None, secondary_y=None, **kwargs ): """ @@ -16462,7 +16464,7 @@ def update_images( return self - def add_image( + def add_layout_image( self, arg=None, layer=None, 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 3e81debd1ad..beb02e48006 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 @@ -18,7 +18,8 @@ def assert_selected( ): # ## Test select_* # Get select_ method - fn = getattr(self.fig, "select_" + prop) + prefix = "layout_" if prop == "images" else "" + fn = getattr(self.fig, "select_" + prefix + prop) # Perform selection res = fn(selector=selector, row=row, col=col, secondary_y=secondary_y) @@ -34,7 +35,7 @@ def assert_selected( # ## Test for_each_* objs = [] - fn = getattr(self.fig, "for_each_" + prop[:-1]) + fn = getattr(self.fig, "for_each_" + prefix + prop[:-1]) fn( lambda v: objs.append(v), selector=selector, @@ -50,9 +51,10 @@ def assert_update( self, prop, inds, patch, selector=None, row=None, col=None, secondary_y=None ): # Copy figure and perform update + prefix = "layout_" if prop == "images" else "" fig_orig = go.Figure(self.fig) fig = go.Figure(self.fig) - fn = getattr(fig, "update_" + prop) + fn = getattr(fig, "update_" + prefix + prop) fn(patch, selector=selector, row=row, col=col, secondary_y=secondary_y) # Get original up updated object lis @@ -172,12 +174,12 @@ def test_select_shapes(self): def test_select_images(self): ( - self.fig.add_image(opacity=0.1, source="red") - .add_image(opacity=0.2, source="blue") - .add_image(opacity=0.3, source="red", row=1, col=1) - .add_image(opacity=0.4, row=1, col=2) - .add_image(opacity=0.5, row=1, col=2, secondary_y=True) - .add_image(opacity=0.6, source="blue", row=2, col=1) + self.fig.add_layout_image(opacity=0.1, source="red") + .add_layout_image(opacity=0.2, source="blue") + .add_layout_image(opacity=0.3, source="red", row=1, col=1) + .add_layout_image(opacity=0.4, row=1, col=2) + .add_layout_image(opacity=0.5, row=1, col=2, secondary_y=True) + .add_layout_image(opacity=0.6, source="blue", row=2, col=1) ) # Test selections @@ -233,12 +235,12 @@ def test_update_shapes(self): def test_update_images(self): ( - self.fig.add_image(opacity=0.1, source="red") - .add_image(opacity=0.2, source="blue") - .add_image(opacity=0.3, source="red", row=1, col=1) - .add_image(opacity=0.4, row=1, col=2) - .add_image(opacity=0.5, row=1, col=2, secondary_y=True) - .add_image(opacity=0.6, source="blue", row=2, col=1) + self.fig.add_layout_image(opacity=0.1, source="red") + .add_layout_image(opacity=0.2, source="blue") + .add_layout_image(opacity=0.3, source="red", row=1, col=1) + .add_layout_image(opacity=0.4, row=1, col=2) + .add_layout_image(opacity=0.5, row=1, col=2, secondary_y=True) + .add_layout_image(opacity=0.6, source="blue", row=2, col=1) ) self.assert_update("images", [0, 1, 2, 3, 4, 5], patch=dict(opacity=0))