8000 Fix center modifier in interactive rectangle selector - in _onmove of… · ianhi/matplotlib@bad96e5 · GitHub
[go: up one dir, main page]

Skip to content

Commit bad96e5

Browse files
committed
Fix center modifier in interactive rectangle selector - in _onmove of existing shape
1 parent 4b50a8f commit bad96e5

File tree

2 files changed

+193
-12
lines changed

2 files changed

+193
-12
lines changed

lib/matplotlib/tests/test_widgets.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,158 @@ def onselect(epress, erelease):
118118
assert artist.get_alpha() == 0.3
119119

120120

121+
def test_rectangle_resize():
122+
ax = get_ax()
123+
124+
def onselect(epress, erelease):
125+
pass
126+
127+
tool = widgets.RectangleSelector(ax, onselect, interactive=True)
128+
# Create rectangle
129+
do_event(tool, 'press', xdata=0, ydata=10, button=1)
130+
do_event(tool, 'onmove', xdata=100, ydata=120, button=1)
131+
do_event(tool, 'release', xdata=100, ydata=120, button=1)
132+
assert tool.extents == (0.0, 100.0, 10.0, 120.0)
133+
134+
# resize NE handle
135+
extents = tool.extents
136+
xdata, ydata = extents[1], extents[3]
137+
xdata_new, ydata_new = xdata + 10, ydata + 5
138+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
139+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
140+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
141+
assert tool.extents == (0.0, xdata_new, 10.0, ydata_new)
142+
143+
# resize E handle
144+
extents = tool.extents
145+
xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
146+
xdata_new, ydata_new = xdata + 10, ydata
147+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
148+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
149+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
150+
assert tool.extents == (0.0, xdata_new, 10.0, 125.0)
151+
152+
# resize W handle
153+
extents = tool.extents
154+
xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
155+
xdata_new, ydata_new = xdata + 15, ydata
156+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
157+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
158+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
159+
assert tool.extents == (xdata_new, 120.0, 10.0, 125.0)
160+
161+
# resize SW handle
162+
extents = tool.extents
163+
xdata, ydata = extents[0], extents[2]
164+
xdata_new, ydata_new = xdata + 20, ydata + 25
165+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
166+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
167+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
168+
assert tool.extents == (xdata_new, 120.0, ydata_new, 125.0)
169+
170+
171+
@pytest.mark.parametrize('use_default_state', [True, False])
172+
def test_rectangle_resize_center(use_default_state):
173+
ax = get_ax()
174+
175+
def onselect(epress, erelease):
176+
pass
177+
178+
tool = widgets.RectangleSelector(ax, onselect, interactive=True)
179+
# Create rectangle
180+
do_event(tool, 'press', xdata=70, ydata=65, button=1)
181+
do_event(tool, 'onmove', xdata=125, ydata=130, button=1)
182+
do_event(tool, 'release', xdata=125, ydata=130, button=1)
183+
assert tool.extents == (70.0, 125.0, 65.0, 130.0)
184+
185+
if use_default_state:
186+
tool._default_state.add('center')
187+
188+
# resize NE handle
189+
extents = tool.extents
190+
xdata, ydata = extents[1], extents[3]
191+
xdiff, ydiff = 10, 5
192+
xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
193+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
194+
if not use_default_state:
195+
do_event(tool, 'on_key_press', key='control')
196+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
197+
if not use_default_state:
198+
do_event(tool, 'on_key_release', key='control')
199+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
200+
assert tool.extents == (70.0 - xdiff, xdata_new, 65.0 - ydiff, ydata_new)
201+
202+
# resize E handle
203+
extents = tool.extents
204+
xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
205+
xdiff = 10
206+
xdata_new, ydata_new = xdata + xdiff, ydata
207+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
208+
if not use_default_state:
209+
do_event(tool, 'on_key_press', key='control')
210+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
211+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
212+
if not use_default_state:
213+
do_event(tool, 'on_key_release', key='control')
214+
assert tool.extents == (60.0 - xdiff, xdata_new, 60.0, 135.0)
215+
216+
# resize E handle negative diff
217+
extents = tool.extents
218+
xdata, ydata = extents[1], extents[2] + (extents[3] - extents[2]) / 2
219+
xdiff = -20
220+
xdata_new, ydata_new = xdata + xdiff, ydata
221+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
222+
if not use_default_state:
223+
do_event(tool, 'on_key_press', key='control')
224+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
225+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
226+
if not use_default_state:
227+
do_event(tool, 'on_key_release', key='control')
228+
assert tool.extents == (50.0 - xdiff, xdata_new, 60.0, 135.0)
229+
230+
# resize W handle
231+
extents = tool.extents
232+
xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
233+
xdiff = 15
234+
xdata_new, ydata_new = xdata + xdiff, ydata
235+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
236+
if not use_default_state:
237+
do_event(tool, 'on_key_press', key='control')
238+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
239+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
240+
if not use_default_state:
241+
do_event(tool, 'on_key_release', key='control')
242+
assert tool.extents == (xdata_new, 125.0 - xdiff, 60.0, 135.0)
243+
244+
# resize W handle
245+
extents = tool.extents
246+
xdata, ydata = extents[0], extents[2] + (extents[3] - extents[2]) / 2
247+
xdiff = -25
248+
xdata_new, ydata_new = xdata + xdiff, ydata
249+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
250+
if not use_default_state:
251+
do_event(tool, 'on_key_press', key='control')
252+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
253+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
254+
if not use_default_state:
255+
do_event(tool, 'on_key_release', key='control')
256+
assert tool.extents == (xdata_new, 110.0 - xdiff, 60.0, 135.0)
257+
258+
# resize SW handle
259+
extents = tool.extents
260+
xdata, ydata = extents[0], extents[2]
261+
xdiff, ydiff = 20, 25
262+
xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
263+
do_event(tool, 'press', xdata=xdata, ydata=ydata, button=1)
264+
if not use_default_state:
265+
do_event(tool, 'on_key_press', key='control')
266+
do_event(tool, 'onmove', xdata=xdata_new, ydata=ydata_new, button=1)
267+
do_event(tool, 'release', xdata=xdata_new, ydata=ydata_new, button=1)
268+
if not use_default_state:
269+
do_event(tool, 'on_key_release', key='control')
270+
assert tool.extents == (xdata_new, 135.0 - xdiff, ydata_new, 135.0 - ydiff)
271+
272+
121273
def test_ellipse():
122274
"""For ellipse, test out the key modifiers"""
123275
ax = get_ax()

lib/matplotlib/widgets.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,7 @@ def __init__(self, ax, onselect, useblit=False, button=None,
18291829
self._eventrelease = None
18301830
self._prev_event = None
18311831
self._state = set()
1832+
self._default_state = set()
18321833

18331834
eventpress = _api.deprecate_privatize_attribute("3.5")
18341835
eventrelease = _api.deprecate_privatize_attribute("3.5")
@@ -2965,20 +2966,48 @@ def _release(self, event):
29652966
def _onmove(self, event):
29662967
"""Motion notify event handler."""
29672968

2968-
2969+
state = self._state | self._default_state
29692970
# resize an existing shape
29702971
if self._active_handle and self._active_handle != 'C':
29712972
x0, x1, y0, y1 = self._extents_on_press
2972-
# Switch variables so that only x1 and/or y1 are updated on move.
2973-
if self._active_handle in ['W', 'SW', 'NW']:
2974-
x0, x1 = x1, event.xdata
2975-
if self._active_handle in ['S', 'SW', 'SE']:
2976-
y0, y1 = y1, event.ydata
2973+
if len(state) == 0:
2974+
# Switch variables so that only x1 and/or y1 are updated on move.
2975+
if self._active_handle in ['W', 'SW', 'NW']:
2976+
x0, x1 = x1, event.xdata
2977+
if self._active_handle in ['S', 'SW', 'SE']:
2978+
y0, y1 = y1, event.ydata
2979+
2980+
if self._active_handle in ['E', 'W'] + self._corner_order:
2981+
x1 = event.xdata
2982+
if self._active_handle in ['N', 'S'] + self._corner_order:
2983+
y1 = event.ydata
2984+
2985+
else:
2986+
dx = event.xdata - self._eventpress.xdata
2987+
dy = event.ydata - self._eventpress.ydata
2988+
2989+
sizepress = [x1 - x0, y1 - y0]
2990+
centerpress = [x0 + sizepress[0] / 2, y0 + sizepress[1] / 2]
2991+
center = centerpress
2992+
2993+
# from center
2994+
if 'center' in state:
2995+
if 'W' in self._active_handle:
2996+
dx = -dx
2997+
if 'S' in self._active_handle:
2998+
dy = -dy
2999+
dw = sizepress[0] / 2 + dx
3000+
dh = sizepress[1] / 2 + dy
3001+
3002+
# cancel changes in perpendicular direction
3003+
if self._active_handle in ['E', 'W']:
3004+
dh = sizepress[1] / 2
3005+
if self._active_handle in ['N', 'S']:
3006+
dw = sizepress[0] / 2
3007+
3008+
x0, x1, y0, y1 = (center[0] - dw, center[0] + dw,
3009+
center[1] - dh, center[1] + dh)
29773010

2978-
if self._active_handle in ['E', 'W'] + self._corner_order:
2979-
x1 = event.xdata
2980-
if self._active_handle in ['N', 'S'] + self._corner_order:
2981-
y1 = event.ydata
29823011

29833012
# move existing shape
29843013
elif (self._active_handle == 'C' or
@@ -3004,7 +3033,7 @@ def _onmove(self, event):
30043033
dy = (event.ydata - center[1]) / 2.
30053034

30063035
# square shape
3007-
if 'square' in self._state:
3036+
if 'square' in state:
30083037
dx_pix = abs(event.x - center_pix[0])
30093038
dy_pix = abs(event.y - center_pix[1])
30103039
if not dx_pix:
@@ -3016,7 +3045,7 @@ def _onmove(self, event):
30163045
dy *= maxd / (abs(dy_pix) + 1e-6)
30173046

30183047
# from center
3019-
if 'center' in self._state:
3048+
if 'center' in state:
30203049
dx *= 2
30213050
dy *= 2
30223051

0 commit comments

Comments
 (0)
0