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

Skip to content

Commit bbf32b8

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

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,36 @@ def test_rectangle_selector():
4444
check_rectangle(rectprops=dict(fill=True))
4545

4646

47+
@pytest.mark.parametrize('drag_from_anywhere, new_center',
48+
[[True, (100, 100)],
49+
[False, (50, 50)]])
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(too 8000 l, 'press', xdata=0, ydata=0, button=1)
60+
do_event(tool, 'onmove', xdata=100, ydata=100, button=1)
61+
do_event(tool, 'release', xdata=100, ydata=100, button=1)
62+
assert tool.center == (50, 50)
63+
# Drag inside rectangle, but away from centre handle to make sure rectangle
64+
# is still moved
65+
do_event(tool, 'press', xdata=25, ydata=25, button=1)
66+
do_event(tool, 'onmove', xdata=75, ydata=75, button=1)
67+
do_event(tool, 'release', xdata=75, ydata=75, button=1)
68+
assert tool.center == new_center
69+
# Check that in both cases, dragging outside the rectangle draws a new
70+
# rectangle
71+
do_event(tool, 'press', xdata=175, ydata=175, button=1)
72+
do_event(tool, 'onmove', xdata=185, ydata=185, button=1)
73+
do_event(tool, 'release', xdata=185, ydata=185, button=1)
74+
assert tool.center == (180, 180)
75+
76+
4777
def test_ellipse():
4878
"""For ellipse, test out the key modifiers"""
4979
ax = get_ax()

lib/matplotlib/widgets.py

Lines changed: 27 additions & 7 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
@@ -2536,19 +2543,28 @@ def _set_active_handle(self, event):
25362543
e_idx, e_dist = self._edge_handles.closest(event.x, event.y)
25372544
m_idx, m_dist = self._center_handle.closest(event.x, event.y)
25382545

2546+
contains = self._contains(event)
25392547
if 'move' in self.state:
25402548
self.active_handle = 'C'
25412549
self._extents_on_press = self.extents
2542-
25432550
# Set active handle as closest handle, if mouse click is close enough.
25442551
elif m_dist < self.maxdist * 2:
2552+
# Prioritise center handle over other handles
25452553
self.active_handle = 'C'
2546-
elif c_dist > self.maxdist and e_dist > self.maxdist:
2547-
self.active_handle = None
2548-
return
2554+
elif (c_dist > self.maxdist and e_dist > self.maxdist):
2555+
# Not close to any handles
2556+
if self.drag_from_anywhere and contains:
2557+
# Check if we've clicked inside the region
2558+
self.active_handle = 'C'
2559+
self._extents_on_press = self.extents
2560+
else:
2561+
self.active_handle = None
2562+
return
25492563
elif c_dist < e_dist:
2564+
# Closest to a corner handle
25502565
self.active_handle = self._corner_order[c_idx]
25512566
else:
2567+
# Closest to an edge handle
25522568
self.active_handle = self._edge_order[e_idx]
25532569

25542570
# Save coordinates of rectangle at the start of handle movement.
@@ -2560,6 +2576,10 @@ def _set_active_handle(self, event):
25602576
y0, y1 = y1, event.ydata
25612577
self._extents_on_press = x0, x1, y0, y1
25622578

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

0 commit comments

Comments
 (0)
0