Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c89cf88

Browse files
authored
Merge pull request #19657 from dstansby/selector-dragging
Allow Selectors to be dragged from anywhere within their patch
2 parents 48b7c98 + 17c6f8c commit c89cf88

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
@@ -55,6 +55,41 @@ def test_rectangle_selector():
5555
check_rectangle(rectprops=dict(fill=True))
5656

5757

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

lib/matplotlib/widgets.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,7 +2191,8 @@ def __init__(self, ax, onselect, drawtype='box',
21912191
minspanx=0, minspany=0, useblit=False,
21922192
lineprops=None, rectprops=None, spancoords='data',
21932193
button=None, maxdist=10, marker_props=None,
2194-
interactive=False, state_modifier_keys=None):
2194+
interactive=False, state_modifier_keys=None,
2195+
drag_from_anywhere=False):
21952196
r"""
21962197
Parameters
21972198
----------
@@ -2263,13 +2264,18 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent)
22632264
default: "ctrl".
22642265
22652266
"square" and "center" can be combined.
2267+
2268+
drag_from_anywhere : bool, optional
2269+
If `True`, the widget can be moved by clicking anywhere within
2270+
its bounds.
22662271
"""
22672272
super().__init__(ax, onselect, useblit=useblit, button=button,
22682273
state_modifier_keys=state_modifier_keys)
22692274

22702275
self.to_draw = None
22712276
self.visible = True
22722277
self.interactive = interactive
2278+
self.drag_from_anywhere = drag_from_anywhere
22732279

22742280
if drawtype == 'none': # draw a line but make it invisible
22752281
_api.warn_deprecated(
@@ -2419,8 +2425,9 @@ def _onmove(self, event):
24192425
y1 = event.ydata
24202426

24212427
# move existing shape
2422-
elif (('move' in self.state or self.active_handle == 'C')
2423-
and self._extents_on_press is not None):
2428+
elif (('move' in self.state or self.active_handle == 'C' or
2429+
(self.drag_from_anywhere and self._contains(event))) and
2430+
self._extents_on_press is not None):
24242431
x0, x1, y0, y1 = self._extents_on_press
24252432
dx = event.xdata - self.eventpress.xdata
24262433
dy = event.ydata - self.eventpress.ydata
@@ -2551,16 +2558,24 @@ def _set_active_handle(self, event):
25512558
if 'move' in self.state:
25522559
self.active_handle = 'C'
25532560
self._extents_on_press = self.extents
2554-
25552561
# Set active handle as closest handle, if mouse click is close enough.
25562562
elif m_dist < self.maxdist * 2:
2563+
# Prioritise center handle over other handles
25572564
self.active_handle = 'C'
25582565
elif c_dist > self.maxdist and e_dist > self.maxdist:
2559-
self.active_handle = None
2560-
return
2566+
# Not close to any handles
2567+
if self.drag_from_anywhere and self._contains(event):
2568+
# Check if we've clicked inside the region
2569+
self.active_handle = 'C'
2570+
self._extents_on_press = self.extents
2571+
else:
2572+
self.active_handle = None
2573+
return
25612574
elif c_dist < e_dist:
2575+
# Closest to a corner handle
25622576
self.active_handle = self._corner_order[c_idx]
25632577
else:
2578+
# Closest to an edge handle
25642579
self.active_handle = self._edge_order[e_idx]
25652580

25662581
# Save coordinates of rectangle at the start of handle movement.
@@ -2572,6 +2587,10 @@ def _set_active_handle(self, event):
25722587
y0, y1 = y1, event.ydata
25732588
self._extents_on_press = x0, x1, y0, y1
25742589

2590+
def _contains(self, event):
2591+
"""Return True if event is within the patch."""
2592+
return self.to_draw.contains(event, radius=0)[0]
2593+
25752594
@property
25762595
def geometry(self):
25772596
"""

0 commit comments

Comments
 (0)