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

Skip to content

Commit 2036ee3

Browse files
committed
Apply inverse transformation to event so that the onmove calculation are correct
1 parent 9350a02 commit 2036ee3

File tree

2 files changed

+73
-23
lines changed

2 files changed

+73
-23
lines changed

lib/matplotlib/tests/test_widgets.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from matplotlib.testing.decorators import check_figures_equal, image_comparison
66
from matplotlib.testing.widgets import do_event, get_ax, mock_event
77

8+
import numpy as np
89
from numpy.testing import assert_allclose
910

1011
import pytest
@@ -406,6 +407,42 @@ def onselect(epress, erelease):
406407
ydata_new, extents[3] - ydiff)
407408

408409

410+
@pytest.mark.parametrize('selector_class',
411+
[widgets.RectangleSelector, widgets.EllipseSelector])
412+
def test_rectangle_rotate(selector_class):
413+
ax = get_ax()
414+
415+
def onselect(epress, erelease):
416+
pass
417+
418+
tool = selector_class(ax, onselect=onselect, interactive=True)
419+
# Draw rectangle
420+
do_event(tool, 'press', xdata=100, ydata=100)
421+
do_event(tool, 'onmove', xdata=130, ydata=140)
422+
do_event(tool, 'release', xdata=130, ydata=140)
423+
assert tool.extents == (100, 130, 100, 140)
424+
425+
# Rotate anticlockwise using top-right corner
426+
do_event(tool, 'on_key_press', key='r')
427+
do_event(tool, 'press', xdata=130, ydata=140)
428+
do_event(tool, 'onmove', xdata=110, ydata=145)
429+
do_event(tool, 'release', xdata=110, ydata=145)
430+
do_event(tool, 'on_key_press', key='r')
431+
# Extents shouldn't change (as shape of rectangle hasn't changed)
432+
assert tool.extents == (100, 130, 100, 140)
433+
# Corners should move
434+
# The third corner is at (100, 145)
435+
assert_allclose(tool.corners,
436+
np.array([[119.9, 139.9, 110.1, 90.1],
437+
[95.4, 117.8, 144.5, 122.2]]), atol=0.1)
438+
439+
# Scale using top-right corner
440+
do_event(tool, 'press', xdata=110, ydata=145)
441+
do_event(tool, 'onmove', xdata=110, ydata=160)
442+
do_event(tool, 'release', xdata=110, ydata=160)
443+
assert_allclose(tool.extents, (100, 141.5, 100, 150.4), atol=0.1)
444+
445+
409446
def test_rectangle_resize_square_center_aspect():
410447
ax = get_ax()
411448
ax.set_aspect(0.8)

lib/matplotlib/widgets.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,9 +1934,9 @@ def press(self, event):
19341934
key = event.key or ''
19351935
key = key.replace('ctrl', 'control')
19361936
# move state is locked in on a button press
1937-
for action in ['move']:
1938-
if key == self._state_modifier_keys[action]:
1939-
self._state.add(action)
1937+
for state in ['move']:
1938+
if key == self._state_modifier_keys[state]:
1939+
self._state.add(state)
19401940
self._press(event)
19411941
return True
19421942
return False
@@ -1988,16 +1988,15 @@ def on_key_press(self, event):
19881988
artist.set_visible(False)
19891989
self.update()
19901990
return
1991-
for state in ['rotate', 'data_coordinates']:
1992-
if key == self._state_modifier_keys[state]:
1993-
if state in self._default_state:
1994-
self._default_state.remove(state)
1995-
else:
1996-
self.add_default_state(state)
19971991
for (state, modifier) in self._state_modifier_keys.items():
1998-
# Multiple keys are string concatenated using '+'
19991992
if modifier in key.split('+'):
2000-
self._state.add(state)
1993+
# rotate and data_coordinates are enable/disable
1994+
# on key press
1995+
if (state in ['rotate', 'data_coordinates'] and
1996+
state in self._state):
1997+
self._state.discard(state)
1998+
else:
1999+
self._state.add(state)
20012000
self._on_key_press(event)
20022001

20032002
def _on_key_press(self, event):
@@ -2008,7 +2007,8 @@ def on_key_release(self, event):
20082007
if self.active:
20092008
key = event.key or ''
20102009
for (state, modifier) in self._state_modifier_keys.items():
2011-
if modifier in key:
2010+
if (modifier in key.split('+') and
2011+
state not in ['rotate', 'data_coordinates']):
20122012
self._state.discard(state)
20132013
self._on_key_release(event)
20142014

@@ -3010,9 +3010,21 @@ def _onmove(self, event):
30103010
"""Motion notify event handler."""
30113011

30123012
state = self._state | self._default_state
3013+
rotate = ('rotate' in state and
3014+
self._active_handle in self._corner_order)
3015+
eventpress = self._eventpress
3016+
# The calculations are done for rotation at zero: we apply inverse
3017+
# transformation to events except when we rotate and move
3018+
if not (self._active_handle == 'C' or rotate):
3019+
inv_tr = self._get_rotation_transform().inverted()
3020+
event.xdata, event.ydata = inv_tr.transform(
3021+
[event.xdata, event.ydata])
3022+
eventpress.xdata, eventpress.ydata = inv_tr.transform(
3023+
[eventpress.xdata, eventpress.ydata]
3024+
)
30133025

3014-
dx = event.xdata - self._eventpress.xdata
3015-
dy = event.ydata - self._eventpress.ydata
3026+
dx = event.xdata - eventpress.xdata
3027+
dy = event.ydata - eventpress.ydata
30163028
refmax = None
30173029
if 'data_coordinates' in state:
30183030
aspect_ratio = 1
@@ -3022,19 +3034,20 @@ def _onmove(self, event):
30223034
ll, ur = self.ax.get_position() * figure_size
30233035
width, height = ur - ll
30243036
aspect_ratio = height / width * self.ax.get_data_ratio()
3025-
refx = event.xdata / (self._eventpress.xdata + 1e-6)
3026-
refy = event.ydata / (self._eventpress.ydata + 1e-6)
3027-
3037+
refx = event.xdata / (eventpress.xdata + 1e-6)
3038+
refy = event.ydata / (eventpress.ydata + 1e-6)
30283039

30293040
x0, x1, y0, y1 = self._extents_on_press
3030-
# resize an existing shape
3031-
if 'rotate' in state and self._active_handle in self._corner_order:
3041+
# rotate an existing shape
3042+
if rotate:
30323043
# calculate angle abc
3033-
a = np.array([self._eventpress.xdata, self._eventpress.ydata])
3044+
a = np.array([eventpress.xdata, eventpress.ydata])
30343045
b = np.array(self.center)
30353046
c = np.array([event.xdata, event.ydata])
30363047
self._rotation = (np.arctan2(c[1]-b[1], c[0]-b[0]) -
30373048
np.arctan2(a[1]-b[1], a[0]-b[0]))
3049+
3050+
# resize an existing shape
30383051
elif self._active_handle and self._active_handle != 'C':
30393052
sizepress = [x1 - x0, y1 - y0]
30403053
center = [x0 + sizepress[0] / 2, y0 + sizepress[1] / 2]
@@ -3091,8 +3104,8 @@ def _onmove(self, event):
30913104
# move existing shape
30923105
elif self._active_handle == 'C':
30933106
x0, x1, y0, y1 = self._extents_on_press
3094-
dx = event.xdata - self._eventpress.xdata
3095-
dy = event.ydata - self._eventpress.ydata
3107+
dx = event.xdata - eventpress.xdata
3108+
dy = event.ydata - eventpress.ydata
30963109
x0 += dx
30973110
x1 += dx
30983111
y0 += dy
@@ -3105,7 +3118,7 @@ def _onmove(self, event):
31053118
# ignore_event_outside=True
31063119
if self.ignore_event_outside and self._selection_completed:
31073120
return
3108-
center = [self._eventpress.xdata, self._eventpress.ydata]
3121+
center = [eventpress.xdata, eventpress.ydata]
31093122
dx = (event.xdata - center[0]) / 2.
31103123
dy = (event.ydata - center[1]) / 2.
31113124

0 commit comments

Comments
 (0)