Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c89cf88

Browse filesBrowse 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
Copy full SHA for c89cf88

File tree

Expand file treeCollapse file tree

3 files changed

+69
-6
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+69
-6
lines changed
+9Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_widgets.py
+35Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/widgets.py
+25-6Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.