Skip to content

Commit 459461c

Browse files
committed
Deprecate offset_position="data".
The `offset_position` property of collections is currently set to "data" only for hexbin(), but one can do away with it by just setting the correct offsetTrans instead -- which this PR does. The advantage of doing so is that the correct offsetTrans only needs to be implemented once, whereas support for `offset_position` needs to be implemented for each renderer class separately (including at the C-level for the Agg backend). Note that the *offset_position* argument to `draw_path_collection` is not pending removal because its removal would require coordination with third-party backends; the plan is to just always set it to "screen" after the deprecation period. VectorTransform can be used as a replacement for `offset_position="data"`. This API is experimental.
1 parent e9a2918 commit 459461c

File tree

7 files changed

+95
-30
lines changed

7 files changed

+95
-30
lines changed

doc/api/next_api_changes/deprecations.rst

+12
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,15 @@ Axes navigation can still be toggled programmatically using
232232
The following related APIs are also deprecated:
233233
``backend_tools.ToolEnableAllNavigation``,
234234
``backend_tools.ToolEnableNavigation``, and ``rcParams["keymap.all_axes"]``.
235+
236+
``offset_position`` property of `.Collection`
237+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238+
The ``offset_position`` property of `.Collection` is deprecated. In the future,
239+
`.Collection`\s will always behave as if ``offset_position`` is set to "screen"
240+
(the default).
241+
242+
Support for passing ``offset_position="data"`` to the ``draw_path_collection``
243+
of all renderer classes is deprecated.
244+
245+
`.transforms.VectorTransform` can be used as a replacement. This API is
246+
experimental and may change in the future.

lib/matplotlib/axes/_axes.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -4759,8 +4759,7 @@ def reduce_C_function(C: array) -> float
47594759
edgecolors=edgecolors,
47604760
linewidths=linewidths,
47614761
offsets=offsets,
4762-
transOffset=mtransforms.IdentityTransform(),
4763-
offset_position="data"
4762+
transOffset=mtransforms.VectorTransform(self.transData),
47644763
)
47654764

47664765
# Set normalizer if bins is 'log'

lib/matplotlib/backend_bases.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,10 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
202202
*linestyles* and *antialiaseds*. *offsets* is a list of
203203
offsets to apply to each of the paths. The offsets in
204204
*offsets* are first transformed by *offsetTrans* before being
205-
applied. *offset_position* may be either "screen" or "data"
206-
depending on the space that the offsets are in.
205+
applied.
206+
207+
*offset_position* may be either "screen" or "data" depending on the
208+
space that the offsets are in; "data" is deprecated.
207209
208210
This provides a fallback implementation of
209211
:meth:`draw_path_collection` that makes multiple calls to
@@ -381,6 +383,11 @@ def _iter_collection(self, gc, master_transform, all_transforms,
381383
Naa = len(antialiaseds)
382384
Nurls = len(urls)
383385

386+
if offset_position == "data":
387+
cbook.warn_deprecated(
388+
"3.3", message="Support for offset_position='data' is "
389+
"deprecated since %(since)s and will be removed %(removal)s.")
390+
384391
if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0:
385392
return
386393
if Noffsets:

lib/matplotlib/backends/backend_agg.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ def _update_methods(self):
109109
self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles
110110
self.draw_image = self._renderer.draw_image
111111
self.draw_markers = self._renderer.draw_markers
112-
self.draw_path_collection = self._renderer.draw_path_collection
112+
# This is its own method for the duration of the deprecation of
113+
# offset_position = "data".
114+
# self.draw_path_collection = self._renderer.draw_path_collection
113115
self.draw_quad_mesh = self._renderer.draw_quad_mesh
114116
self.copy_from_bbox = self._renderer.copy_from_bbox
115117
self.get_content_extents = self._renderer.get_content_extents
@@ -153,6 +155,19 @@ def draw_path(self, gc, path, transform, rgbFace=None):
153155
raise OverflowError("Exceeded cell block limit (set "
154156
"'agg.path.chunksize' rcparam)")
155157

158+
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
159+
offsets, offsetTrans, facecolors, edgecolors,
160+
linewidths, linestyles, antialiaseds, urls,
161+
offset_position):
162+
if offset_position == "data":
163+
cbook.warn_deprecated(
164+
"3.3", message="Support for offset_position='data' is "
165+
"deprecated since %(since)s and will be removed %(removal)s.")
166+
return self._renderer.draw_path_collection(
167+
gc, master_transform, paths, all_transforms, offsets, offsetTrans,
168+
facecolors, edgecolors, linewidths, linestyles, antialiaseds, urls,
169+
offset_position)
170+
156171
def draw_mathtext(self, gc, x, y, s, prop, angle):
157172
"""Draw mathtext using :mod:`matplotlib.mathtext`."""
158173
ox, oy, width, height, descent, font_image, used_characters = \

lib/matplotlib/collections.py

+25-20
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,28 @@ class Collection(artist.Artist, cm.ScalarMappable):
4141
4242
Keyword arguments and default values:
4343
44-
* *edgecolors*: None
45-
* *facecolors*: None
46-
* *linewidths*: None
47-
* *capstyle*: None
48-
* *joinstyle*: None
49-
* *antialiaseds*: None
50-
* *offsets*: None
51-
* *transOffset*: transforms.IdentityTransform()
52-
* *offset_position*: 'screen' (default) or 'data'
53-
* *norm*: None (optional for
54-
:class:`matplotlib.cm.ScalarMappable`)
55-
* *cmap*: None (optional for
56-
:class:`matplotlib.cm.ScalarMappable`)
57-
* *hatch*: None
58-
* *zorder*: 1
44+
* *edgecolors*: None
45+
* *facecolors*: None
46+
* *linewidths*: None
47+
* *capstyle*: None
48+
* *joinstyle*: None
49+
* *antialiaseds*: None
50+
* *offsets*: None
51+
* *transOffset*: transforms.IdentityTransform()
52+
* *offset_position* (deprecated): 'screen' (default) or 'data' (deprecated)
53+
* *norm*: None (optional for
54+
:class:`matplotlib.cm.ScalarMappable`)
55+
* *cmap*: None (optional for
56+
:class:`matplotlib.cm.ScalarMappable`)
57+
* *hatch*: None
58+
* *zorder*: 1
5959
6060
*offsets* and *transOffset* are used to translate the patch after
6161
rendering (default no offsets). If offset_position is 'screen'
6262
(default) the offset is applied after the master transform has
6363
been applied, that is, the offsets are in screen coordinates. If
64-
offset_position is 'data', the offset is applied before the master
65-
transform, i.e., the offsets are in data coordinates.
64+
offset_position is 'data' (deprecated), the offset is applied before the
65+
master transform, i.e., the offsets are in data coordinates.
6666
6767
If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds*
6868
are None, they default to their :data:`matplotlib.rcParams` patch
@@ -87,6 +87,7 @@ class Collection(artist.Artist, cm.ScalarMappable):
8787
# subclass-by-subclass basis.
8888
_edge_default = False
8989

90+
@cbook._delete_parameter("3.1", "offset_position")
9091
def __init__(self,
9192
edgecolors=None,
9293
facecolors=None,
@@ -132,7 +133,9 @@ def __init__(self,
132133
self.set_pickradius(pickradius)
133134
self.set_urls(urls)
134135
self.set_hatch(hatch)
135-
self.set_offset_position(offset_position)
136+
self._offset_position = "screen"
137+
if offset_position != "screen":
138+
self.set_offset_position(offset_position) # emit deprecation.
136139
self.set_zorder(zorder)
137140

138141
if capstyle:
@@ -407,7 +410,7 @@ def contains(self, mouseevent):
407410
self._picker is not True # the bool, not just nonzero or 1
408411
else self._pickradius)
409412

410-
if self.axes and self.get_offset_position() == "data":
413+
if self.axes:
411414
self.axes._unstale_viewLim()
412415

413416
transform, transOffset, offsets, paths = self._prepare_points()
@@ -416,7 +419,7 @@ def contains(self, mouseevent):
416419
mouseevent.x, mouseevent.y, pickradius,
417420
transform.frozen(), paths, self.get_transforms(),
418421
offsets, transOffset, pickradius <= 0,
419-
self.get_offset_position())
422+
self._offset_position)
420423

421424
return len(ind) > 0, dict(ind=ind)
422425

@@ -497,6 +500,7 @@ def get_offsets(self):
497500
else:
498501
return self._uniform_offsets
499502

503+
@cbook.deprecated("3.3")
500504
def set_offset_position(self, offset_position):
501505
"""
502506
Set how offsets are applied. If *offset_position* is 'screen'
@@ -514,6 +518,7 @@ def set_offset_position(self, offset_position):
514518
self._offset_position = offset_position
515519
self.stale = True
516520

521+
@cbook.deprecated("3.3")
517522
def get_offset_position(self):
518523
"""
519524
Returns how offsets are applied for the collection. If

lib/matplotlib/tests/test_backend_bases.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ def check(master_transform, paths, all_transforms,
2525
master_transform, paths, all_transforms))
2626
gc = rb.new_gc()
2727
ids = [path_id for xo, yo, path_id, gc0, rgbFace in
28-
rb._iter_collection(gc, master_transform, all_transforms,
29-
range(len(raw_paths)), offsets,
30-
transforms.IdentityTransform(),
31-
facecolors, edgecolors, [], [], [False],
32-
[], 'data')]
28+
rb._iter_collection(
29+
gc, master_transform, all_transforms,
30+
range(len(raw_paths)), offsets,
31+
transforms.VectorTransform(master_transform),
32+
facecolors, edgecolors, [], [], [False],
33+
[], 'screen')]
3334
uses = rb._iter_collection_uses_per_path(
3435
paths, all_transforms, offsets, facecolors, edgecolors)
3536
if raw_paths:

lib/matplotlib/transforms.py

+26
Original file line numberDiff line numberDiff line change
@@ -2612,6 +2612,32 @@ def get_matrix(self):
26122612
return self._mtx
26132613

26142614

2615+
class VectorTransform(Affine2DBase):
2616+
r"""
2617+
A transform wrapper that ignores the offset component of a base transform.
2618+
2619+
This class is intended to be used to transform offsets between pairs
2620+
of points (e.g., as the ``offset_transform`` of `.Collection`\s): given
2621+
a transform ``t`` such that ``t = VectorTransform(t) + offset``,
2622+
``VectorTransform`` satisfies
2623+
``VectorTransform(a - b) == VectorTransform(a) - VectorTransform(b)``.
2624+
2625+
This class is experimental as of 3.3, and the API may change.
2626+
"""
2627+
2628+
def __init__(self, transform, **kwargs):
2629+
super().__init__(**kwargs)
2630+
self._base_transform = transform
2631+
2632+
__str__ = _make_str_method("_base_transform")
2633+
2634+
def get_matrix(self):
2635+
if self._invalid:
2636+
self._mtx = self._base_transform.get_matrix().copy()
2637+
self._mtx[:2, -1] = 0
2638+
return self._mtx
2639+
2640+
26152641
class TransformedPath(TransformNode):
26162642
"""
26172643
A `TransformedPath` caches a non-affine transformed copy of the

0 commit comments

Comments
 (0)