10000 Simplify state selectors and validate `rotation_point` attribute Rect… · matplotlib/matplotlib@efce8b1 · GitHub
[go: up one dir, main page]

Skip to content

Commit efce8b1

Browse files
committed
Simplify state selectors and validate rotation_point attribute Rectangle patch
1 parent 6434911 commit efce8b1

File tree

4 files changed

+152
-79
lines changed

4 files changed

+152
-79
lines changed
Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
1-
Rotating selectors and aspect ratio correction
2-
----------------------------------------------
1+
Selectors improvement: rotation, aspect ratio correction and add/remove state
2+
-----------------------------------------------------------------------------
33

44
The `~matplotlib.widgets.RectangleSelector` and
55
`~matplotlib.widgets.EllipseSelector` can now be rotated interactively.
6-
7-
The rotation is enabled when *rotate* is added to
8-
:py:attr:`~matplotlib.widgets._SelectorWidget.state`, which can be done using
9-
:py:meth:`~matplotlib.widgets._SelectorWidget.add_state` or by striking
10-
the *state_modifier_keys* for *rotate* (default *r*).
6+
The rotation is enabled or disable by striking the *r* key
7+
(default value of 'rotate' in *state_modifier_keys*) or by calling
8+
*selector.add_state('rotate')*.
119

1210
The aspect ratio of the axes can now be taken into account when using the
13-
"square" state. When *data_coordinates* is added to
14-
:py:attr:`~matplotlib.widgets._SelectorWidget.state`, which can be done using
15-
:py:meth:`~matplotlib.widgets._SelectorWidget.add_state` or by striking
16-
the *state_modifier_keys* for *data_coordinates* (default *d*).
11+
"square" state. This can be enable or disable by striking the *d* key
12+
(default value of 'data_coordinates' in *state_modifier_keys*)
13+
or by calling *selector.add_state('rotate')*.
14+
15+
In addition to changing selector state interactively using the modifier keys
16+
defined in *state_modifier_keys*, the selector state can now be changed
17+
programmatically using the *add_state* and *remove_state* method.
18+
19+
20+
.. code-block:: python
21+
22+
import matplotlib.pyplot as plt
23+
from matplotlib.widgets import RectangleSelector
24+
import numpy as np
25+
26+
values = np.arange(0, 100)
27+
28+
fig = plt.figure()
29+
ax = fig.add_subplot()
30+
ax.plot(values, values)
31+
32+
selector = RectangleSelector(ax, print, interactive=True, drag_from_anywhere=True)
33+
selector.add_state('rotate') # alternatively press 'r' key
34+
# rotate the selector interactively
35+
36+
selector.remove_state('rotate') # alternatively press 'r' key
37+
38+
selector.add_state('square')
39+
# to keep the aspect ratio in data coordinates
40+
selector.add_state('data_coordinates')

lib/matplotlib/patches.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -768,8 +768,7 @@ def get_patch_transform(self):
768768
rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2.
769769
elif self.rotation_point == 'xy':
770770
rotation_point = bbox.x0, bbox.y0
771-
elif (isinstance(self.rotation_point[0], Number) and
772-
isinstance(self.rotation_point[1], Number)):
771+
else:
773772
rotation_point = self.rotation_point
774773
return transforms.BboxTransformTo(bbox) \
775774
+ transforms.Affine2D() \
@@ -779,6 +778,21 @@ def get_patch_transform(self):
779778
.scale(1, 1 / self._aspect_ratio_correction) \
780779
.translate(*rotation_point)
781780

781+
@property
782+
def rotation_point(self):
783+
return self._rotation_point
784+
785+
@rotation_point.setter
786+
def rotation_point(self, value):
787+
if value in ['center', 'xy'] or (
788+
isinstance(value, tuple) and len(value) == 2 and
789+
isinstance(value[0], Number) and isinstance(value[1], Number)
790+
):
791+
self._rotation_point = value
792+
else:
793+
raise ValueError("`rotation_point` must be one of "
794+
"{'xy', 'center', (number, number)}.")
795+
782796
def get_x(self):
783797
"""Return the left coordinate of the rectangle."""
784798
return self._x0

lib/matplotlib/tests/test_widgets.py

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def onselect(epress, erelease):
172172
assert tool.extents == (xdata_new, extents[1], ydata_new, extents[3])
173173

174174

175-
def test_rectangle_add_default_state():
175+
def test_rectangle_add_state():
176176
ax = get_ax()
177177

178178
def onselect(epress, erelease):
@@ -183,18 +183,18 @@ def onselect(epress, erelease):
183183
_resize_rectangle(tool, 70, 65, 125, 130)
184184

185185
with pytest.raises(ValueError):
186-
tool.add_default_state('unsupported_state')
186+
tool.add_state('unsupported_state')
187187

188188
with pytest.raises(ValueError):
189-
tool.add_default_state('clear')
190-
tool.add_default_state('move')
191-
tool.add_default_state('square')
192-
tool.add_default_state('center')
193-
tool.add_default_state('data_coordinates')
189+
tool.add_state('clear')
190+
tool.add_state('move')
191+
tool.add_state('square')
192+
tool.add_state('center')
193+
tool.add_state('data_coordinates')
194194

195195

196-
@pytest.mark.parametrize('use_default_state', [True, False])
197-
def test_rectangle_resize_center(use_default_state):
196+
@pytest.mark.parametrize('add_state', [True, False])
197+
def test_rectangle_resize_center(add_state):
198198
ax = get_ax()
199199

200200
def onselect(epress, erelease):
@@ -205,8 +205,8 @@ def onselect(epress, erelease):
205205
_resize_rectangle(tool, 70, 65, 125, 130)
206206
assert tool.extents == (70.0, 125.0, 65.0, 130.0)
207207

208-
if use_default_state:
209-
tool.add_default_state('center')
208+
if add_state:
209+
tool.add_state('center')
210210
use_key = None
211211
else:
212212
use_key = 'control'
@@ -266,8 +266,8 @@ def onselect(epress, erelease):
266266
ydata_new, extents[3] - ydiff)
267267

268268

269-
@pytest.mark.parametrize('use_default_state', [True, False])
270-
def test_rectangle_resize_square(use_default_state):
269+
@pytest.mark.parametrize('add_state', [True, False])
270+
def test_rectangle_resize_square(add_state):
271271
ax = get_ax()
272272

273273
def onselect(epress, erelease):
@@ -278,8 +278,8 @@ def onselect(epress, erelease):
278278
_resize_rectangle(tool, 70, 65, 120, 115)
279279
assert tool.extents == (70.0, 120.0, 65.0, 115.0)
280280

281-
if use_default_state:
282-
tool.add_default_state('square')
281+
if add_state:
282+
tool.add_state('square')
283283
use_key = None
284284
else:
285285
use_key = 'shift'
@@ -348,8 +348,8 @@ def onselect(epress, erelease):
348348
tool = widgets.RectangleSelector(ax, onselect, interactive=True)
349349
# Create rectangle
350350
_resize_rectangle(tool, 70, 65, 120, 115)
351-
tool.add_default_state('square')
352-
tool.add_default_state('center')
351+
tool.add_state('square')
352+
tool.add_state('center')
353353
assert_allclose(tool.extents, (70.0, 120.0, 65.0, 115.0))
354354

355355
# resize NE handle
@@ -421,18 +421,16 @@ def onselect(epress, erelease):
421421
do_event(tool, 'onmove', xdata=130, ydata=140)
422422
do_event(tool, 'release', xdata=130, ydata=140)
423423
assert tool.extents == (100, 130, 100, 140)
424-
assert len(tool._default_state) == 0
425424
assert len(tool._state) == 0
426425

427426
# Rotate anticlockwise using top-right corner
428427
do_event(tool, 'on_key_press', key='r')
429-
assert tool._default_state == set(['rotate'])
430-
assert len(tool._state) == 0
428+
assert tool._state == set(['rotate'])
429+
assert len(tool._state) == 1
431430
do_event(tool, 'press', xdata=130, ydata=140)
432431
do_event(tool, 'onmove', xdata=120, ydata=145)
433432
do_event(tool, 'release', xdata=120, ydata=145)
434433
do_event(tool, 'on_key_press', key='r')
435-
assert len(tool._default_state) == 0
436434
assert len(tool._state) == 0
437435
# Extents shouldn't change (as shape of rectangle hasn't changed)
438436
assert tool.extents == (100, 130, 100, 140)
@@ -450,6 +448,30 @@ def onselect(epress, erelease):
450448
do_event(tool, 'release', xdata=110, ydata=160)
451449
assert_allclose(tool.extents, (100, 139.75, 100, 151.82), atol=0.01)
452450

451+
if selector_class == widgets.RectangleSelector:
452+
with pytest.raises(ValueError):
453+
tool._selection_artist.rotation_point = 'unvalid_value'
454+
455+
456+
def test_rectange_add_remove_set():
457+
ax = get_ax()
458+
459+
def onselect(epress, erelease):
460+
pass
461+
462+
tool = widgets.RectangleSelector(ax, onselect=onselect, interactive=True)
463+
# Draw rectangle
464+
do_event(tool, 'press', xdata=100, ydata=100)
465+
do_event(tool, 'onmove', xdata=130, ydata=140)
466+
do_event(tool, 'release', xdata=130, ydata=140)
467+
assert tool.extents == (100, 130, 100, 140)
468+
assert len(tool._state) == 0
469+
for state in ['rotate', 'data_coordinates', 'square', 'center']:
470+
tool.add_state(state)
471+
assert len(tool._state) == 1
472+
tool.remove_state(state)
473+
assert len(tool._state) == 0
474+
453475

454476
def test_rectangle_resize_square_center_aspect():
455477
ax = get_ax()
@@ -461,8 +483,8 @@ def onselect(epress, erelease):
461483
tool = widgets.RectangleSelector(ax, onselect, interactive=True)
462484
# Create rectangle
463485
_resize_rectangle(tool, 70, 65, 120, 115)
464-
tool.add_default_state('square')
465-
tool.add_default_state('center')
486+
tool.add_state('square')
487+
tool.add_state('center')
466488
assert tool.extents == (70.0, 120.0, 65.0, 115.0)
467489

468490
# resize E handle
@@ -905,7 +927,7 @@ def onselect(*args):
905927
assert tool.extents == (10, 50)
906928

907929

908-
def test_span_selector_add_default_state():
930+
def test_span_selector_add_state():
909931
ax = get_ax()
910932

911933
def onselect(*args):
@@ -914,13 +936,13 @@ def onselect(*args):
914936
tool = widgets.SpanSelector(ax, onselect, 'horizontal', interactive=True)
915937

916938
with pytest.raises(ValueError):
917-
tool.add_default_state('unsupported_state')
939+
tool.add_state('unsupported_state')
918940
with pytest.raises(ValueError):
919-
tool.add_default_state('center')
941+
tool.add_state('center')
920942
with pytest.raises(ValueError):
921-
tool.add_default_state('square')
943+
tool.add_state('square')
922944

923-
tool.add_default_state('move')
945+
tool.add_state('move')
924946

925947

926948
def test_tool_line_handle():

0 commit comments

Comments
 (0)
0