diff --git a/doc/api/api_changes_3.3/deprecations.rst b/doc/api/api_changes_3.3/deprecations.rst index dc4b4a4390db..8acb855753fc 100644 --- a/doc/api/api_changes_3.3/deprecations.rst +++ b/doc/api/api_changes_3.3/deprecations.rst @@ -475,3 +475,15 @@ is deprecated; use the explicit defaults of 1 and 0, respectively, instead. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... is deprecated. Scale ``Figure.dpi_scale_trans`` by 1/72 to achieve the same effect. + +``offset_position`` property of `.Collection` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The ``offset_position`` property of `.Collection` is deprecated. In the +future, `.Collection`\s will always behave as if ``offset_position`` is set to +"screen" (the default). + +Support for passing ``offset_position="data"`` to the ``draw_path_collection`` +of all renderer classes is deprecated. + +`.transforms.AffineDeltaTransform` can be used as a replacement. This API is +experimental and may change in the future. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 63b16c311810..0e71263d61b2 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4816,8 +4816,7 @@ def reduce_C_function(C: array) -> float edgecolors=edgecolors, linewidths=linewidths, offsets=offsets, - transOffset=mtransforms.IdentityTransform(), - offset_position="data" + transOffset=mtransforms.AffineDeltaTransform(self.transData), ) # Set normalizer if bins is 'log' diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index cf9453959853..ac93942cf5b7 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -205,8 +205,10 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, *linestyles* and *antialiaseds*. *offsets* is a list of offsets to apply to each of the paths. The offsets in *offsets* are first transformed by *offsetTrans* before being - applied. *offset_position* may be either "screen" or "data" - depending on the space that the offsets are in. + applied. + + *offset_position* may be either "screen" or "data" depending on the + space that the offsets are in; "data" is deprecated. This provides a fallback implementation of :meth:`draw_path_collection` that makes multiple calls to @@ -380,6 +382,11 @@ def _iter_collection(self, gc, master_transform, all_transforms, Naa = len(antialiaseds) Nurls = len(urls) + if offset_position == "data": + cbook.warn_deprecated( + "3.3", message="Support for offset_position='data' is " + "deprecated since %(since)s and will be removed %(removal)s.") + if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0: return if Noffsets: diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 16e07daf4e4b..79607784523a 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -110,7 +110,9 @@ def _update_methods(self): self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles self.draw_image = self._renderer.draw_image self.draw_markers = self._renderer.draw_markers - self.draw_path_collection = self._renderer.draw_path_collection + # This is its own method for the duration of the deprecation of + # offset_position = "data". + # self.draw_path_collection = self._renderer.draw_path_collection self.draw_quad_mesh = self._renderer.draw_quad_mesh self.copy_from_bbox = self._renderer.copy_from_bbox self.get_content_extents = self._renderer.get_content_extents @@ -155,6 +157,19 @@ def draw_path(self, gc, path, transform, rgbFace=None): raise OverflowError("Exceeded cell block limit (set " "'agg.path.chunksize' rcparam)") from err + def draw_path_collection(self, gc, master_transform, paths, all_transforms, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds, urls, + offset_position): + if offset_position == "data": + cbook.warn_deprecated( + "3.3", message="Support for offset_position='data' is " + "deprecated since %(since)s and will be removed %(removal)s.") + return self._renderer.draw_path_collection( + gc, master_transform, paths, all_transforms, offsets, offsetTrans, + facecolors, edgecolors, linewidths, linestyles, antialiaseds, urls, + offset_position) + def draw_mathtext(self, gc, x, y, s, prop, angle): """Draw mathtext using :mod:`matplotlib.mathtext`.""" ox, oy, width, height, descent, font_image, used_characters = \ diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 88ef027b64bb..5ba2d08d15df 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -49,7 +49,7 @@ class Collection(artist.Artist, cm.ScalarMappable): - *antialiaseds*: None - *offsets*: None - *transOffset*: transforms.IdentityTransform() - - *offset_position*: 'screen' (default) or 'data' + - *offset_position* (deprecated): 'screen' (default) or 'data' (deprecated) - *norm*: None (optional for `matplotlib.cm.ScalarMappable`) - *cmap*: None (optional for `matplotlib.cm.ScalarMappable`) - *hatch*: None @@ -59,8 +59,8 @@ class Collection(artist.Artist, cm.ScalarMappable): rendering (default no offsets). If offset_position is 'screen' (default) the offset is applied after the master transform has been applied, that is, the offsets are in screen coordinates. If - offset_position is 'data', the offset is applied before the master - transform, i.e., the offsets are in data coordinates. + offset_position is 'data' (deprecated), the offset is applied before the + master transform, i.e., the offsets are in data coordinates. If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* are None, they default to their `.rcParams` patch setting, in sequence form. @@ -85,6 +85,7 @@ class Collection(artist.Artist, cm.ScalarMappable): # subclass-by-subclass basis. _edge_default = False + @cbook._delete_parameter("3.3", "offset_position") def __init__(self, edgecolors=None, facecolors=None, @@ -130,7 +131,9 @@ def __init__(self, self.set_pickradius(pickradius) self.set_urls(urls) self.set_hatch(hatch) - self.set_offset_position(offset_position) + self._offset_position = "screen" + if offset_position != "screen": + self.set_offset_position(offset_position) # emit deprecation. self.set_zorder(zorder) if capstyle: @@ -404,7 +407,7 @@ def contains(self, mouseevent): self._picker is not True # the bool, not just nonzero or 1 else self._pickradius) - if self.axes and self.get_offset_position() == "data": + if self.axes: self.axes._unstale_viewLim() transform, transOffset, offsets, paths = self._prepare_points() @@ -413,7 +416,7 @@ def contains(self, mouseevent): mouseevent.x, mouseevent.y, pickradius, transform.frozen(), paths, self.get_transforms(), offsets, transOffset, pickradius <= 0, - self.get_offset_position()) + self._offset_position) return len(ind) > 0, dict(ind=ind) @@ -494,6 +497,7 @@ def get_offsets(self): else: return self._uniform_offsets + @cbook.deprecated("3.3") def set_offset_position(self, offset_position): """ Set how offsets are applied. If *offset_position* is 'screen' @@ -511,6 +515,7 @@ def set_offset_position(self, offset_position): self._offset_position = offset_position self.stale = True + @cbook.deprecated("3.3") def get_offset_position(self): """ Return how offsets are applied for the collection. If diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 469b601ce0a7..5ef21fe11277 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -26,11 +26,12 @@ def check(master_transform, paths, all_transforms, master_transform, paths, all_transforms)) gc = rb.new_gc() ids = [path_id for xo, yo, path_id, gc0, rgbFace in - rb._iter_collection(gc, master_transform, all_transforms, - range(len(raw_paths)), offsets, - transforms.IdentityTransform(), - facecolors, edgecolors, [], [], [False], - [], 'data')] + rb._iter_collection( + gc, master_transform, all_transforms, + range(len(raw_paths)), offsets, + transforms.AffineDeltaTransform(master_transform), + facecolors, edgecolors, [], [], [False], + [], 'screen')] uses = rb._iter_collection_uses_per_path( paths, all_transforms, offsets, facecolors, edgecolors) if raw_paths: diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 82c65046acf3..72341c9f0c72 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -2617,6 +2617,36 @@ def get_matrix(self): return self._mtx +class AffineDeltaTransform(Affine2DBase): + r""" + A transform wrapper for transforming displacements between pairs of points. + + This class is intended to be used to transform displacements ("position + deltas") between pairs of points (e.g., as the ``offset_transform`` + of `.Collection`\s): given a transform ``t`` such that ``t = + AffineDeltaTransform(t) + offset``, ``AffineDeltaTransform`` + satisfies ``AffineDeltaTransform(a - b) == AffineDeltaTransform(a) - + AffineDeltaTransform(b)``. + + This is implemented by forcing the offset components of the transform + matrix to zero. + + This class is experimental as of 3.3, and the API may change. + """ + + def __init__(self, transform, **kwargs): + super().__init__(**kwargs) + self._base_transform = transform + + __str__ = _make_str_method("_base_transform") + + def get_matrix(self): + if self._invalid: + self._mtx = self._base_transform.get_matrix().copy() + self._mtx[:2, -1] = 0 + return self._mtx + + class TransformedPath(TransformNode): """ A `TransformedPath` caches a non-affine transformed copy of the