Skip to content

Commit a7b7260

Browse files
authored
Merge pull request #21374 from ericpre/snap_selectors
Snap selectors
2 parents 036f103 + 99f11d5 commit a7b7260

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SpanSelector widget can now be snapped to specified values
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
The SpanSelector widget can now be snapped to values specified by the *snap_values*
4+
argument.

lib/matplotlib/tests/test_widgets.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ def test_rectangle_selector():
6767
@pytest.mark.parametrize('spancoords', ['data', 'pixels'])
6868
@pytest.mark.parametrize('minspanx, x1', [[0, 10], [1, 10.5], [1, 11]])
6969
@pytest.mark.parametrize('minspany, y1', [[0, 10], [1, 10.5], [1, 11]])
70-
def test_rectangle_minspan(spancoords, minspanx, x1, minspany, y1):
71-
ax = get_ax()
70+
def test_rectangle_minspan(ax, spancoords, minspanx, x1, minspany, y1):
7271
# attribute to track number of onselect calls
7372
ax._n_onselect = 0
7473

@@ -924,6 +923,37 @@ def mean(vmin, vmax):
924923
assert ln2.stale is False
925924

926925

926+
def test_snapping_values_span_selector(ax):
927+
def onselect(*args):
928+
pass
929+
930+
tool = widgets.SpanSelector(ax, onselect, direction='horizontal',)
931+
snap_function = tool._snap
932+
933+
snap_values = np.linspace(0, 5, 11)
934+
values = np.array([-0.1, 0.1, 0.2, 0.5, 0.6, 0.7, 0.9, 4.76, 5.0, 5.5])
935+
expect = np.array([00.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 5.00, 5.0, 5.0])
936+
values = snap_function(values, snap_values)
937+
assert_allclose(values, expect)
938+
939+
940+
def test_span_selector_snap(ax):
941+
def onselect(vmin, vmax):
942+
ax._got_onselect = True
943+
944+
snap_values = np.arange(50) * 4
945+
946+
tool = widgets.SpanSelector(ax, onselect, direction='horizontal',
947+
snap_values=snap_values)
948+
tool.extents = (17, 35)
949+
assert tool.extents == (16, 36)
950+
951+
tool.snap_values = None
952+
assert tool.snap_values is None
953+
tool.extents = (17, 35)
954+
assert tool.extents == (17, 35)
955+
956+
927957
def check_lasso_selector(**kwargs):
928958
ax = get_ax()
929959

lib/matplotlib/widgets.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -2238,6 +2238,9 @@ def on_select(min: float, max: float) -> Any
22382238
If `True`, the event triggered outside the span selector will be
22392239
ignored.
22402240
2241+
snap_values : 1D array-like, optional
2242+
Snap the selector edges to the given values.
2243+
22412244
Examples
22422245
--------
22432246
>>> import matplotlib.pyplot as plt
@@ -2259,7 +2262,7 @@ def __init__(self, ax, onselect, direction, minspan=0, useblit=False,
22592262
props=None, onmove_callback=None, interactive=False,
22602263
button=None, handle_props=None, grab_range=10,
22612264
state_modifier_keys=None, drag_from_anywhere=False,
2262-
ignore_event_outside=False):
2265+
ignore_event_outside=False, snap_values=None):
22632266

22642267
if state_modifier_keys is None:
22652268
state_modifier_keys = dict(clear='escape',
@@ -2278,6 +2281,7 @@ def __init__(self, ax, onselect, direction, minspan=0, useblit=False,
22782281

22792282
self.visible = True
22802283
self._extents_on_press = None
2284+
self.snap_values = snap_values
22812285

22822286
# self._pressv is deprecated and we don't use it internally anymore
22832287
# but we maintain it until it is removed
@@ -2577,6 +2581,15 @@ def _contains(self, event):
25772581
"""Return True if event is within the patch."""
25782582
return self._selection_artist.contains(event, radius=0)[0]
25792583

2584+
@staticmethod
2585+
def _snap(values, snap_values):
2586+
"""Snap values to a given array values (snap_values)."""
2587+
# take into account machine precision
2588+
eps = np.min(np.abs(np.diff(snap_values))) * 1e-12
2589+
return tuple(
2590+
snap_values[np.abs(snap_values - v + np.sign(v) * eps).argmin()]
2591+
for v in values)
2592+
25802593
@property
25812594
def extents(self):
25822595
"""Return extents of the span selector."""
@@ -2591,6 +2604,8 @@ def extents(self):
25912604
@extents.setter
25922605
def extents(self, extents):
25932606
# Update displayed shape
2607+
if self.snap_values is not None:
2608+
extents = tuple(self._snap(extents, self.snap_values))
25942609
self._draw_shape(*extents)
25952610
if self._interactive:
25962611
# Update displayed handles

0 commit comments

Comments
 (0)