8000 Allow Selectors to be dragged from anywhere within their patch · matplotlib/matplotlib@17c6f8c · GitHub
[go: up one dir, main page]

Skip to content

Commit 17c6f8c

Browse files
committed
Allow Selectors to be dragged from anywhere within their patch
1 parent 906cae4 commit 17c6f8c

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Dragging selectors
2+
------------------
3+
4+
The `~matplotlib.widgets.RectangleSelector` and
5+
`~matplotlib.widgets.EllipseSelector` have a new keyword argument,
6+
*drag_from_anywhere*, which when set to `True` allows you to click and drag
7+
from anywhere inside the selector to move it. Previously it was only possible
8+
to move it by either activating the move modifier button, or clicking on the
9+
central handle.

lib/matplotlib/tests/test_widgets.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,41 @@ def test_rectangle_selector():
4444
check_rectangle(rectprops=dict(fill=True))
4545

4646

47+
@pytest.mark.parametrize('drag_from_anywhere, new_center',
48+
[[True, (60, 75)],
49+
[False, (30, 20)]])
50+
def test_rectangle_drag(drag_from_anywhere, new_center):
51+
ax = get_ax()
52+
53+
def onselect(epress, erelease):
54+
pass
55+
56+
tool = widgets.RectangleSelector(ax, onselect, interactive=True,
57+
drag_from_anywhere=drag_from_anywhere)
58+
# Create rectangle
59+
do_event(tool, 'press', xdata=0, ydata=10, button=1)
60+
do_event(tool, 'onmove', xdata=100, ydata=120, button=1)
61+
do_event(tool, 'release', xdata=100, ydata=120, button=1)
62+
assert tool.center == (50, 65)
63+
# Drag inside rectangle, but away from centre handle
64+
#
65+
# If drag_from_anywhere == True, this will move the rectangle by (10, 10),
66+
# giving it a new center of (60, 75)
67+
#
68+
# If drag_from_anywhere == False, this will create a new rectangle with
69+
# center (30, 20)
70+
do_event(tool, 'press', xdata=25, ydata=15, button=1)
71+
do_event(tool, 'onmove', xdata=35, ydata=25, button=1)
72+
do_event(tool, 'release', xdata=35, ydata=25, button=1)
73+
assert tool.center == new_center
74+
# Check that in both cases, dragging outside the rectangle draws a new
75+
# rectangle
76+
do_event(tool, 'press', xdata=175, ydata=185, button=1)
77+
do_event(tool, 'onmove', xdata=185, ydata=195, button=1)
78+
do_event(tool, 'release', xdata=185, ydata=195, button=1)
79+
assert tool.center == (180, 190)
80+
81+
4782
def test_ellipse():
4883
"""For ellipse, test out the key modifiers"""
4984
ax = get_ax()

lib/matplotlib/widgets.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,7 +2189,8 @@ def __init__(self, ax, onselect, drawtype='box',
21892189
minspanx=0, minspany=0, useblit=False,
21902190
lineprops=None, rectprops=None, spancoords='data',
21912191
button=None, maxdist=10, marker_props=None,
2192-
interactive=False, state_modifier_keys=None):
2192+
interactive=False, state_modifier_keys=None,
2193+
drag_from_anywhere=False):
21932194
r"""
21942195
Parameters
21952196
----------
@@ -2261,13 +2262,18 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent)
22612262
default: "ctrl".
22622263
22632264
"square" and "center" can be combined.
2265+
2266+
drag_from_anywhere : bool, optional
2267+
If `True`, the widget can be moved by clicking anywhere within
2268+
its bounds.
22642269
"""
22652270
super().__init__(ax, onselect, useblit=useblit, button=button,
22662271
state_modifier_keys=state_modifier_keys)
22672272

22682273
self.to_draw = None
22692274
self.visible = True
22702275
self.interactive = interactive
2276+
self.drag_from_anywhere = drag_from_anywhere
22712277

22722278
if drawtype == 'none': # draw a line but make it invisible
22732279
drawtype = 'line'
@@ -2407,8 +2413,9 @@ def _onmove(self, event):
24072413
y1 = event.ydata
24082414

24092415
# move existing shape
2410-
elif (('move' in self.state or self.active_handle == 'C')
2411-
and self._extents_on_press is not None):
2416+
elif (('move' in self.state or self.active_handle == 'C' or
2417+
(self.drag_from_anywhere and self._contains(event))) and
2418+
self._extents_on_press is not None):
24122419
x0, x1, y0, y1 = self._extents_on_press
24132420
dx = event.xdata - self.eventpress.xdata
24142421
dy = event.ydata - self.eventpress.ydata
@@ -2539,16 +2546,24 @@ def _set_active_handle(self, event):
25392546
if 'move' in self.state:
25402547
self.active_handle = 'C'
25412548
self._extents_on_press = self.extents
2542-
25432549
# Set active handle as closest handle, if mouse click is close enough.
25442550
elif m_dist < self.maxdist * 2:
2551+
# Prioritise center handle over other handles
25452552
self.active_handle = 'C'
25462553
elif c_dist > self.maxdist and e_dist > self.maxdist:
2547-
self.active_handle = None
2548-
return
2554+
# Not close to any handles
2555+
if self.drag_from_anywhere and self._contains(event):
2556+
# Check if we've clicked inside the region
2557+
self.active_handle = 'C'
2558+
self._extents_on_press = self.extents
2559+
else:
2560+
self.active_handle = None
2561+
return
25492562
elif c_dist < e_dist:
2563+
# Closest to a corner handle
25502564
self.active_handle = self._corner_order[c_idx]
25512565
else:
2566+
# Closest to an edge handle
25522567
self.active_handle = self._edge_order[e_idx]
25532568

25542569
# Save coordinates of rectangle at the start of handle movement.
@@ -2560,6 +2575,10 @@ def _set_active_handle(self, event):
25602575
y0, y1 = y1, event.ydata
25612576
self._extents_on_press = x0, x1, y0, y1
25622577

2578+
def _contains(self, event):
2579+
"""Return True if event is within the patch."""
2580+
return self.to_draw.contains(event, radius=0)[0]
2581+
25632582
@property
25642583
def geometry(self):
25652584
"""

0 commit comments

Comments
 (0)
0