Repository: plotly/plotly.py

Issue:
xref/yref in annotation/shape/image without row/col
When row/col are not provided, we shouldn't write to those keys, to let the JS defaults shine through.


Discussion:
And we should document the JS defaults in the schema

Resolving patch:
diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py
--- a/packages/python/plotly/plotly/basedatatypes.py
+++ b/packages/python/plotly/plotly/basedatatypes.py
@@ -252,14 +252,11 @@ class is a subclass of both BaseFigure and widgets.DOMWidget.
         # Dict from trace indexes to trace edit dicts. These trace edit dicts
         # are suitable as `data` elements of Plotly.animate, but not
         # the Plotly.update (See `_build_update_params_from_batch`)
-        #
-        # type: OrderedDict[int, OrderedDict[str, typ.Any]]
         self._batch_trace_edits = OrderedDict()
 
         # ### Batch layout edits ###
         # Dict from layout properties to new layout values. This dict is
         # directly suitable for use in Plotly.animate and Plotly.update
-        # type: collections.OrderedDict[str, typ.Any]
         self._batch_layout_edits = OrderedDict()
 
         # Animation property validators
@@ -1030,18 +1027,12 @@ def _select_annotations_like(
 
         for obj in self.layout[prop]:
             # Filter by row
-            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
+            if col is not None and xref_to_col.get(obj.xref, None) != col:
+                continue
 
             # Filter by col
-            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
+            if row is not None and yref_to_row.get(obj.yref, None) != row:
+                continue
 
             # Filter by secondary y
             if (
@@ -1105,11 +1096,6 @@ def _add_annotation_like(
             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
@@ -2984,38 +2970,32 @@ def __init__(self, plotly_name, **kwargs):
         # ---------------------
         # ### _validators ###
         # A dict from property names to property validators
-        # type: Dict[str, BaseValidator]
         self._validators = {}
 
         # ### _compound_props ###
         # A dict from compound property names to compound objects
-        # type: Dict[str, BasePlotlyType]
         self._compound_props = {}
 
         # ### _compound_array_props ###
         # A dict from compound array property names to tuples of compound
         # objects
-        # type: Dict[str, Tuple[BasePlotlyType]]
         self._compound_array_props = {}
 
         # ### _orphan_props ###
         # A dict of properties for use while object has no parent. When
         # object has a parent, it requests its properties dict from its
         # parent and doesn't use this.
-        # type: Dict
         self._orphan_props = {}
 
         # ### _parent ###
         # The parent of the object. May be another BasePlotlyType or it may
         # be a BaseFigure (as is the case for the Layout and Trace objects)
-        # type: Union[BasePlotlyType, BaseFigure]
         self._parent = None
 
         # ### _change_callbacks ###
         # A dict from tuples of child property path tuples to lists
         # of callbacks that should be executed whenever any of these
         # properties is modified
-        # type: Dict[Tuple[Tuple[Union[str, int]]], List[Callable]]
         self._change_callbacks = {}
 
     def _process_kwargs(self, **kwargs):
@@ -3831,7 +3811,6 @@ def _set_compound_prop(self, prop, val):
         # Import value
         # ------------
         validator = self._validators.get(prop)
-        # type: BasePlotlyType
         val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
 
         # Save deep copies of current and new states
@@ -3906,7 +3885,6 @@ def _set_array_prop(self, prop, val):
         # Import value
         # ------------
         validator = self._validators.get(prop)
-        # type: Tuple[BasePlotlyType]
         val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
 
         # Save deep copies of current and new states


Test patch:
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
--- 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
@@ -76,10 +76,10 @@ def assert_update(
     def test_add_annotation_no_grid(self):
         # Paper annotation
         fig = go.Figure()
-        fig.add_annotation(text="A")
+        fig.add_annotation(text="A", yref="paper")
         annot = fig.layout.annotations[-1]
         self.assertEqual(annot.text, "A")
-        self.assertEqual(annot.xref, "paper")
+        self.assertEqual(annot.xref, None)
         self.assertEqual(annot.yref, "paper")
 
         # Not valid to add annotation by row/col
@@ -88,10 +88,10 @@ def test_add_annotation_no_grid(self):
 
     def test_add_annotations(self):
         # Paper annotation
-        self.fig.add_annotation(text="A")
+        self.fig.add_annotation(text="A", yref="paper")
         annot = self.fig.layout.annotations[-1]
         self.assertEqual(annot.text, "A")
-        self.assertEqual(annot.xref, "paper")
+        self.assertEqual(annot.xref, None)
         self.assertEqual(annot.yref, "paper")
 
         # (1, 1) annotation
@@ -138,8 +138,10 @@ def test_select_annotations_no_grid(self):
 
     def test_select_annotations(self):
         (
-            self.fig.add_annotation(text="A1", arrowcolor="red")
-            .add_annotation(text="A2", arrowcolor="blue")
+            self.fig.add_annotation(
+                text="A1", arrowcolor="red", xref="paper", yref="paper"
+            )
+            .add_annotation(text="A2", arrowcolor="blue", xref="paper", yref="paper")
             .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)
@@ -151,13 +153,13 @@ def test_select_annotations(self):
         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", [0, 1], dict(yref="paper", xref="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")
+            self.fig.add_shape(opacity=0.1, fillcolor="red", xref="paper", yref="paper")
+            .add_shape(opacity=0.2, fillcolor="blue", xref="paper", yref="paper")
             .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)
@@ -169,13 +171,15 @@ def test_select_shapes(self):
         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", [0, 1], dict(yref="paper", xref="paper"))
         self.assert_selected("shapes", [4], secondary_y=True)
 
     def test_select_images(self):
         (
-            self.fig.add_layout_image(opacity=0.1, source="red")
-            .add_layout_image(opacity=0.2, source="blue")
+            self.fig.add_layout_image(
+                opacity=0.1, source="red", xref="paper", yref="paper"
+            )
+            .add_layout_image(opacity=0.2, source="blue", xref="paper", yref="paper")
             .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)
@@ -187,7 +191,7 @@ def test_select_images(self):
         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", [0, 1], dict(yref="paper", xref="paper"))
         self.assert_selected("images", [4], secondary_y=True)
 
     def test_update_annotations(self):