Skip to content

Commit 0aedc3a

Browse files
committed
Use display coordinates for RectangleSelector
1 parent 66a94a2 commit 0aedc3a

File tree

3 files changed

+318
-267
lines changed

3 files changed

+318
-267
lines changed

lib/matplotlib/patches.py

+6-22
Original file line numberDiff line numberDiff line change
@@ -736,13 +736,6 @@ def __init__(self, xy, width, height, angle=0.0, *,
736736
self._height = height
737737
self.angle = float(angle)
738738
self.rotation_point = rotation_point
739-
# Required for RectangleSelector with axes aspect ratio != 1
740-
# The patch is defined in data coordinates and when changing the
741-
# selector with square modifier and not in data coordinates, we need
742-
# to correct for the aspect ratio difference between the data and
743-
# display coordinate systems. Its value is typically provide by
744-
# Axes._get_aspect_ratio()
745-
self._aspect_ratio_correction = 1.0
746739
self._convert_units() # Validate the inputs.
747740

748741
def get_path(self):
@@ -770,13 +763,11 @@ def get_patch_transform(self):
770763
rotation_point = bbox.x0, bbox.y0
771764
else:
772765
rotation_point = self.rotation_point
773-
return transforms.BboxTransformTo(bbox) \
774-
+ transforms.Affine2D() \
775-
.translate(-rotation_point[0], -rotation_point[1]) \
776-
.scale(1, self._aspect_ratio_correction) \
777-
.rotate_deg(self.angle) \
778-
.scale(1, 1 / self._aspect_ratio_correction) \
779-
.translate(*rotation_point)
766+
return (transforms.BboxTransformTo(bbox) +
767+
transforms.Affine2D()
768+
.translate(-rotation_point[0], -rotation_point[1])
769+
.rotate_deg(self.angle)
770+
.translate(*rotation_point))
780771

781772
@property
782773
def rotation_point(self):
@@ -1569,12 +1560,6 @@ def __init__(self, xy, width, height, angle=0, **kwargs):
15691560
self._width, self._height = width, height
15701561
self._angle = angle
15711562
self._path = Path.unit_circle()
1572-
# Required for EllipseSelector with axes aspect ratio != 1
1573-
# The patch is defined in data coordinates and when changing the
1574-
# selector with square modifier and not in data coordinates, we need
1575-
# to correct for the aspect ratio difference between the data and
1576-
# display coordinate systems.
1577-
self._aspect_ratio_correction = 1.0
15781563
# Note: This cannot be calculated until this is added to an Axes
15791564
self._patch_transform = transforms.IdentityTransform()
15801565

@@ -1592,9 +1577,8 @@ def _recompute_transform(self):
15921577
width = self.convert_xunits(self._width)
15931578
height = self.convert_yunits(self._height)
15941579
self._patch_transform = transforms.Affine2D() \
1595-
.scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \
1580+
.scale(width * 0.5, height * 0.5) \
15961581
.rotate_deg(self.angle) \
1597-
.scale(1, 1 / self._aspect_ratio_correction) \
15981582
.translate(*center)
15991583

16001584
def get_path(self):

lib/matplotlib/tests/test_widgets.py

+40-38
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,19 @@ def onselect(epress, erelease):
2727
ax._got_onselect = True
2828
assert epress.xdata == 100
2929
assert epress.ydata == 100
30-
assert erelease.xdata == 199
31-
assert erelease.ydata == 199
30+
assert erelease.xdata == 200
31+
assert erelease.ydata == 200
3232

3333
tool = widgets.RectangleSelector(ax, onselect, **kwargs)
3434
do_event(tool, 'press', xdata=100, ydata=100, button=1)
35-
do_event(tool, 'onmove', xdata=199, ydata=199, button=1)
36-
35+
do_event(tool, 'onmove', xdata=250, ydata=250, button=1)
3736
# purposely drag outside of axis for release
3837
do_event(tool, 'release', xdata=250, ydata=250, button=1)
3938

4039
if kwargs.get('drawtype', None) not in ['line', 'none']:
4140
assert_allclose(tool.geometry,
42-
[[100., 100, 199, 199, 100],
43-
[100, 199, 199, 100, 100]],
41+
[[100., 100, 200, 200, 100],
42+
[100, 200, 200, 100, 100]],
4443
err_msg=tool.geometry)
4544

4645
assert ax._got_onselect
@@ -259,8 +258,8 @@ def onselect(epress, erelease):
259258
xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
260259
click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
261260
key=use_key)
262-
assert tool.extents == (xdata_new, extents[1] - xdiff,
263-
ydata_new, extents[3] - ydiff)
261+
assert_allclose(tool.extents, (xdata_new, extents[1] - xdiff,
262+
ydata_new, extents[3] - ydiff))
264263

265264

266265
@pytest.mark.parametrize('add_state', [True, False])
@@ -273,7 +272,7 @@ def onselect(epress, erelease):
273272
tool = widgets.RectangleSelector(ax, onselect, interactive=True)
274273
# Create rectangle
275274
click_and_drag(tool, start=(70, 65), end=(120, 115))
276-
assert tool.extents == (70.0, 120.0, 65.0, 115.0)
275+
assert_allclose(tool.extents, (70.0, 120.0, 65.0, 115.0))
277276

278277
if add_state:
279278
tool.add_state('square')
@@ -288,8 +287,8 @@ def onselect(epress, erelease):
288287
xdata_new, ydata_new = xdata + xdiff, ydata + ydiff
289288
click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new),
290289
key=use_key)
291-
assert tool.extents == (extents[0], xdata_new,
292-
extents[2], extents[3] + xdiff)
290+
assert_allclose(tool.extents, (extents[0], xdata_new,
291+
extents[2], extents[3] + xdiff))
293292

294293
# resize E handle
295294
extents = tool.extents
@@ -344,6 +343,7 @@ def onselect(epress, erelease):
344343

345344
def test_rectangle_resize_square_center():
346345
ax = get_ax()
346+
ax.set_aspect(1)
347347

348348
def onselect(epress, erelease):
349349
pass
@@ -414,6 +414,7 @@ def onselect(epress, erelease):
414414
[widgets.RectangleSelector, widgets.EllipseSelector])
415415
def test_rectangle_rotate(selector_class):
416416
ax = get_ax()
417+
ax.set_aspect(1)
417418

418419
def onselect(epress, erelease):
419420
pass
@@ -431,19 +432,19 @@ def onselect(epress, erelease):
431432
click_and_drag(tool, start=(130, 140), end=(120, 145))
432433
do_event(tool, 'on_key_press', key='r')
433434
assert len(tool._state) == 0
434-
# Extents shouldn't change (as shape of rectangle hasn't changed)
435-
assert tool.extents == (100, 130, 100, 140)
435+
# Extents change as the selector remains rigid in display coordinates
436+
assert_allclose(tool.extents, (110.10, 119.90, 95.49, 144.51), atol=0.01)
436437
assert_allclose(tool.rotation, 25.56, atol=0.01)
437438
tool.rotation = 45
438439
assert tool.rotation == 45
439440
# Corners should move
440441
assert_allclose(tool.corners,
441-
np.array([[118.53, 139.75, 111.46, 90.25],
442-
[95.25, 116.46, 144.75, 123.54]]), atol=0.01)
442+
np.array([[110.10, 131.31, 103.03, 81.81],
443+
[95.49, 116.70, 144.98, 123.77]]), atol=0.01)
443444

444445
# Scale using top-right corner
445446
click_and_drag(tool, start=(110, 145), end=(110, 160))
446-
assert_allclose(tool.extents, (100, 139.75, 100, 151.82), atol=0.01)
447+
assert_allclose(tool.extents, (110, 110, 145, 160), atol=0.01)
447448

448449
if selector_class == widgets.RectangleSelector:
449450
with pytest.raises(ValueError):
@@ -471,39 +472,40 @@ def onselect(epress, erelease):
471472
@pytest.mark.parametrize('use_data_coordinates', [False, True])
472473
def test_rectangle_resize_square_center_aspect(use_data_coordinates):
473474
ax = get_ax()
474-
ax.set_aspect(0.8)
475+
ax.set_aspect(0.5)
476+
# Need to call a draw to update ax.transData
477+
plt.gcf().canvas.draw()
475478

476479
def onselect(epress, erelease):
477480
pass
478481

479482
tool = widgets.RectangleSelector(ax, onselect, interactive=True,
480483
use_data_coordinates=use_data_coordinates)
481-
# Create rectangle
482-
click_and_drag(tool, start=(70, 65), end=(120, 115))
483-
assert_allclose(tool.extents, (70.0, 120.0, 65.0, 115.0))
484484
tool.add_state('square')
485485
tool.add_state('center')
486+
# Create rectangle, width 50 in data coordinates
487+
click_and_drag(tool, start=(70, 65), end=(120, 65))
486488

487489
if use_data_coordinates:
488-
# resize E handle
489-
extents = tool.extents
490-
xdata, ydata, width = extents[1], extents[3], extents[1] - extents[0]
491-
xdiff, ycenter = 10, extents[2] + (extents[3] - extents[2]) / 2
492-
xdata_new, ydata_new = xdata + xdiff, ydata
493-
ychange = width / 2 + xdiff
494-
click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
495-
assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new,
496-
ycenter - ychange, ycenter + ychange])
490+
assert_allclose(tool.extents, (20, 120, 15, 115))
497491
else:
498-
# resize E handle
499-
extents = tool.extents
500-
xdata, ydata = extents[1], extents[3]
501-
xdiff = 10
502-
xdata_new, ydata_new = xdata + xdiff, ydata
503-
ychange = xdiff * 1 / tool._aspect_ratio_correction
504-
click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
505-
assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new,
506-
46.25, 133.75])
492+
assert_allclose(tool.extents, (20, 120, -35, 165))
493+
494+
# resize E handle
495+
extents = tool.extents
496+
xdata, ydata = extents[1], extents[3]
497+
xdiff = 10
498+
xdata_new, ydata_new = xdata + xdiff, ydata
499+
if use_data_coordinates:
500+
# In data coordinates the difference should be equal in both directions
501+
ydiff = xdiff
502+
else:
503+
# In display coordiantes, the change in data coordinates should be
504+
# different in each direction
505+
ydiff = xdiff / tool.ax._get_aspect_ratio()
506+
click_and_drag(tool, start=(xdata, ydata), end=(xdata_new, ydata_new))
507+
assert_allclose(tool.extents, [extents[0] - xdiff, xdata_new,
508+
extents[2] - ydiff, extents[3] + ydiff])
507509

508510

509511
def test_ellipse():

0 commit comments

Comments
 (0)