diff --git a/doc/api/next_api_changes/deprecations/19026-AL.rst b/doc/api/next_api_changes/deprecations/19026-AL.rst new file mode 100644 index 000000000000..37396a3f3db8 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/19026-AL.rst @@ -0,0 +1,5 @@ +Setting pickradius on Collections and Patches via ``set_picker`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Setting the pickradius (i.e. the tolerance for pick events and containment +checks)of a `.Collection` or a `.Patch` via `.Artist.set_picker` is deprecated; +use ``set_pickradius`` instead. diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index b3251e60f191..73b1efbc0097 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -558,10 +558,10 @@ def set_picker(self, picker): artist, return *hit=True* and props is a dictionary of properties you want added to the PickEvent attributes. - - *deprecated*: For `.Line2D` only, *picker* can also be a float - that sets the tolerance for checking whether an event occurred - "on" the line; this is deprecated. Use `.Line2D.set_pickradius` - instead. + - *deprecated*: For `.Line2D` and `.Collection`, *picker* can also + be a float that sets the tolerance for checking whether an event + occurred "on" the artist; this is deprecated. Use + `.Line2D.set_pickradius`/`.Collection.set_pickradius` instead. """ self._picker = picker diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 8aed4182b00f..10d9a333702d 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -415,6 +415,17 @@ def draw(self, renderer): renderer.close_group(self.__class__.__name__) self.stale = False + def set_picker(self, p): + # docstring inherited + # After deprecation, the whole method can be deleted and inherited. + if isinstance(p, Number) and not isinstance(p, bool): + cbook.warn_deprecated( + "3.4", message="Setting the collections's pick radius via " + "set_picker is deprecated since %(since)s and will be removed " + "%(removal)s; use set_pickradius instead.") + self.set_pickradius(p) + self._picker = p + def set_pickradius(self, pr): """ Set the pick radius used for containment tests. @@ -439,32 +450,21 @@ def contains(self, mouseevent): inside, info = self._default_contains(mouseevent) if inside is not None: return inside, info - if not self.get_visible(): return False, {} - - pickradius = ( - float(self._picker) - if isinstance(self._picker, Number) and - self._picker is not True # the bool, not just nonzero or 1 - else self._pickradius) - if self.axes: self.axes._unstale_viewLim() - transform, transOffset, offsets, paths = self._prepare_points() - # Tests if the point is contained on one of the polygons formed # by the control points of each of the paths. A point is considered # "on" a path if it would lie within a stroke of width 2*pickradius # following the path. If pickradius <= 0, then we instead simply check # if the point is *inside* of the path instead. ind = _path.point_in_path_collection( - mouseevent.x, mouseevent.y, pickradius, + mouseevent.x, mouseevent.y, self._pickradius, transform.frozen(), paths, self.get_transforms(), - offsets, transOffset, pickradius <= 0, + offsets, transOffset, self._pickradius <= 0, self._offset_position) - return len(ind) > 0, dict(ind=ind) def set_urls(self, urls): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 103adee18366..08f9ff9d3019 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -94,6 +94,7 @@ def __init__(self, # unscaled dashes. Needed to scale dash patterns by lw self._us_dashes = None self._linewidth = 0 + self._pickradius = None self.set_fill(fill) self.set_linestyle(linestyle) @@ -102,9 +103,7 @@ def __init__(self, self.set_hatch(hatch) self.set_capstyle(capstyle) self.set_joinstyle(joinstyle) - - if len(kwargs): - self.update(kwargs) + self.update(kwargs) def get_verts(self): """ @@ -120,17 +119,52 @@ def get_verts(self): return polygons[0] return [] + def set_picker(self, p): + # docstring inherited + # After deprecation, the whole method can be deleted and inherited. + if isinstance(p, Number) and not isinstance(p, bool): + cbook.warn_deprecated( + "3.4", message="Setting the collections's pick radius via " + "set_picker is deprecated since %(since)s and will be removed " + "%(removal)s; use set_pickradius instead.") + self.set_pickradius(p) + self._picker = p + + def set_pickradius(self, d): + """ + Set the pick radius used for containment tests. + + Parameters + ---------- + d : float or None + The pick radius, in points. If None, the patch linewidth is used + as pickradius if the patch edge is not transparent. + """ + if not isinstance(d, Number) or d < 0: + raise ValueError("pick radius should be a distance") + self._pickradius = d + + def get_pickradius(self): + """ + Return the pick radius used for containment tests. + + Returns + ------- + float or None + The pick radius, in points. If None, the patch linewidth is used + as pickradius if the patch edge is not transparent. + """ + return self._pickradius + def _process_radius(self, radius): - if radius is not None: - return radius - if isinstance(self._picker, Number): - _radius = self._picker - else: - if self.get_edgecolor()[3] == 0: - _radius = 0 - else: - _radius = self.get_linewidth() - return _radius + return ( + radius if radius is not None + else self._pickradius if self._pickradius is not None + # Deprecated case. + else self._picker if (isinstance(self._picker, Number) + and not isinstance(self._picker, bool)) + else 0 if self.get_edgecolor()[3] == 0 + else self.get_linewidth()) def contains(self, mouseevent, radius=None): """ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 49627e9ce433..a4f5f6c81c94 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -757,7 +757,7 @@ def test_hexbin_pickable(): fig, ax = plt.subplots() data = (np.arange(200) / 200).reshape((2, 100)) x, y = data - hb = ax.hexbin(x, y, extent=[.1, .3, .6, .7], picker=-1) + hb = ax.hexbin(x, y, extent=[.1, .3, .6, .7], picker=True, pickradius=-1) mouse_event = SimpleNamespace(x=400, y=300) assert hb.contains(mouse_event)[0] diff --git a/lib/mpl_toolkits/tests/test_axes_grid1.py b/lib/mpl_toolkits/tests/test_axes_grid1.py index 5121b7e970a6..fa72b0a66f3d 100644 --- a/lib/mpl_toolkits/tests/test_axes_grid1.py +++ b/lib/mpl_toolkits/tests/test_axes_grid1.py @@ -3,7 +3,7 @@ import matplotlib import matplotlib.pyplot as plt -from matplotlib import cbook +from matplotlib import _api, cbook from matplotlib.cbook import MatplotlibDeprecationWarning from matplotlib.backend_bases import MouseEvent from matplotlib.colors import LogNorm @@ -409,8 +409,9 @@ def test_picking_callbacks_overlap(big_on_axes, small_on_axes, click_on): # In each case we expect that both rectangles are picked if we click on the # small one and only the big one is picked if we click on the big one. # Also tests picking on normal axes ("gca") as a control. - big = plt.Rectangle((0.25, 0.25), 0.5, 0.5, picker=5) - small = plt.Rectangle((0.4, 0.4), 0.2, 0.2, facecolor="r", picker=5) + big = plt.Rectangle((0.25, 0.25), 0.5, 0.5, picker=True, pickradius=5) + with _api.suppress_matplotlib_deprecation_warning(): + small = plt.Rectangle((0.4, 0.4), 0.2, 0.2, facecolor="r", picker=5) # Machinery for "receiving" events received_events = [] def on_pick(event):