8000 Apply inverse transformation to event so that the onmove calculation … · matplotlib/matplotlib@db55921 · GitHub
[go: up one dir, main page]

Skip to content

Commit db55921

Browse files
committed
Apply inverse transformation to event so that the onmove calculation are correct
1 parent e64c6ab commit db55921

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
@@ -378,6 +379,42 @@ def onselect(epress, erelease):
378379
ydata_new, extents[3] - ydiff)
379380

380381

382+
@pytest.mark.parametrize('selector_class',
383+
[widgets.RectangleSelector, widgets.EllipseSelector])
384+
def test_rectangle_rotate(selector_class):
385+
ax = get_ax()
386+
387+
def onselect(epress, erelease):
388+
pass
389+
390+
tool = selector_class(ax, onselect=onselect, interactive=True)
391+
# Draw rectangle
392+
do_event(tool, 'press', xdata=100, ydata=100)
393+
do_event(tool, 'onmove', xdata=130, ydata=140)
394+
do_event(tool, 'release', xdata=130, ydata=140)
395+
assert tool.extents == (100, 130, 100, 140)
396+
397+
# Rotate anticlockwise using top-right corner
398+
do_event(tool, 'on_key_press', key='r')
399+
do_event(tool, 'press', xdata=130, ydata=140)
400+
do_event(tool, 'onmove', xdata=110, ydata=145)
401+
do_event(tool, 'release', xdata=110, ydata=145)
402+
do_event(tool, 'on_key_press', key='r')
403+
# Extents shouldn't change (as shape of rectangle hasn't changed)
404+
assert tool.extents == (100, 130, 100, 140)
405+
# Corners should move
406+
# The third corner is at (100, 145)
407+
assert_allclose(tool.corners,
408+
np.array([[119.9, 139.9, 110.1, 90.1],
409+
[95.4, 117.8, 144.5, 122.2]]), atol=0.1)
410+
411+
# Scale using top-right corner
412+
do_event(tool, 'press', xdata=110, ydata=145)
413+
do_event(tool, 'onmove', xdata=110, ydata=160)
414+
do_event(tool, 'release', xdata=110, ydata=160)
415+
assert_allclose(tool.extents, (100, 141.5, 100, 150.4), atol=0.1)
416+
417+
381418
def test_rectangle_resize_square_center_aspect():
382419
ax = get_ax()
383420
ax.set_aspect(0.8)

lib/matplotlib/widgets.py

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

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

@@ -2967,9 +2967,21 @@ def _onmove(self, event):
29672967
"""Motion notify event handler."""
29682968

29692969
state = self._state | self._default_state
2970+
rotate = ('rotate' in state and
2971+
self._active_handle in self._corner_order)
2972+
eventpress = self._eventpress
2973+
# The calculations are done for rotation at zero: we apply inverse
2974+
# transformation to events except when we rotate and move
2975+
if not (self._active_handle == 'C' or rotate):
2976+
inv_tr = self._get_rotation_transform().inverted()
2977+
event.xdata, event.ydata = inv_tr.transform(
2978+
[event.xdata, event.ydata])
2979+
eventpress.xdata, eventpress.ydata = inv_tr.transform(
2980+
[eventpress.xdata, eventpress.ydata]
2981+
)
29702982

2971-
dx = event.xdata - self._eventpress.xdata
2972-
dy = event.ydata - self._eventpress.ydata
2983+
dx = event.xdata - eventpress.xdata
2984+
dy = event.ydata - eventpress.ydata
29732985
refmax = None
29742986
if 'data_coordinates' in state:
29752987
aspect_ratio = 1
@@ -2979,19 +2991,20 @@ def _onmove(self, event):
29792991
ll, ur = self.ax.get_position() * figure_size
29802992
width, height = ur - ll
29812993
aspect_ratio = height / width * self.ax.get_data_ratio()
2982-
refx = event.xdata / (self._eventpress.xdata + 1e-6)
2983-
refy = event.ydata / (self._eventpress.ydata + 1e-6)
2984-
2994+
refx = event.xdata / (eventpress.xdata + 1e-6)
2995+
refy = event.ydata / (eventpress.ydata + 1e-6)
29852996

29862997
x0, x1, y0, y1 = self._extents_on_press
2987-
# resize an existing shape
2988-
if 'rotate' in state and self._active_handle in self._corner_order:
2998+
# rotate an existing shape
2999+
if rotate:
29893000
# calculate angle abc
2990-
a = np.array([self._eventpress.xdata, self._eventpress.ydata])
3001+
a = np.array([eventpress.xdata, eventpress.ydata])
29913002
b = np.array(self.center)
29923003
c = np.array([event.xdata, event.ydata])
29933004
self._rotation = (np.arctan2(c[1]-b[1], c[0]-b[0]) -
29943005
np.arctan2(a[1]-b[1], a[0]-b[0]))
3006+
3007+
# resize an existing shape
29953008
elif self._active_handle and self._active_handle != 'C':
29963009
sizepress = [x1 - x0, y1 - y0]
29973010
center = [x0 + sizepress[0] / 2, y0 + sizepress[1] / 2]
@@ -3048,8 +3061,8 @@ def _onmove(self, event):
30483061
# move existing shape
30493062
elif self._active_handle == 'C':
30503063
x0, x1, y0, y1 = self._extents_on_press
3051-
dx = event.xdata - self._eventpress.xdata
3052-
dy = event.ydata - self._eventpress.ydata
3064+
dx = event.xdata - eventpress.xdata
3065+
dy = event.ydata - eventpress.ydata
30533066
x0 += dx
30543067
x1 += dx
30553068
y0 += dy
@@ -3062,7 +3075,7 @@ def _onmove(self, event):
30623075
# ignore_event_outside=True
30633076
if self.ignore_event_outside and self._selection_completed:
30643077
return
3065-
center = [self._eventpress.xdata, self._eventpress.ydata]
3078+
center = [eventpress.xdata, eventpress.ydata]
30663079
dx = (event.xdata - center[0]) / 2.
30673080
dy = (event.ydata - center[1]) / 2.
30683081

0 commit comments

Comments
 (0)
0