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

Skip to content

Commit 21ea3fb

Browse files
authored
Merge pull request #20839 from ericpre/fix_centre_square_rectangle_selector
Fix centre and square state and add rotation for rectangle selector
2 parents fb5b91e + 56710f5 commit 21ea3fb

File tree

7 files changed

+550
-137
lines changed

7 files changed

+550
-137
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Selector widget state internals
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
The *state_modifier_keys* attribute have been privatized and the modifier keys
4+
needs to be set when creating the widget.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Rectangle patch rotation point
2+
------------------------------
3+
4+
The rotation point of the `~matplotlib.patches.Rectangle` can now be set to 'xy',
5+
'center' or a 2-tuple of numbers.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Selectors improvement: rotation, aspect ratio correction and add/remove state
2+
-----------------------------------------------------------------------------
3+
4+
The `~matplotlib.widgets.RectangleSelector` and
5+
`~matplotlib.widgets.EllipseSelector` can now be rotated interactively between
6+
-45° and 45°. The range limits are currently dictated by the implementation.
7+
The rotation is enabled or disabled by striking the *r* key
8+
('r' is the default key mapped to 'rotate' in *state_modifier_keys*) or by calling
9+
``selector.add_state('rotate')``.
10+
11+
The aspect ratio of the axes can now be taken into account when using the
12+
"square" state. This is enabled by specifying ``use_data_coordinates='True'`` when
13+
the selector is initialized.
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* methods.
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,
33+
drag_from_anywhere=True,
34+
use_data_coordinates=True)
35+
selector.add_state('rotate') # alternatively press 'r' key
36+
# rotate the selector interactively
37+
38+
selector.remove_state('rotate') # alternatively press 'r' key
39+
40+
selector.add_state('square')

lib/matplotlib/axes/_axes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8082,3 +8082,13 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
80828082
tricontourf = mtri.tricontourf
80838083
tripcolor = mtri.tripcolor
80848084
triplot = mtri.triplot
8085+
8086+
def _get_aspect_ratio(self):
8087+
"""
8088+
Convenience method to calculate the aspect ratio of the axes in
8089+
the display coordinate system.
8090+
"""
8091+
figure_size = self.get_figure().get_size_inches()
8092+
ll, ur = self.get_position() * figure_size
8093+
width, height = ur - ll
8094+
return height / (width * self.get_data_ratio())

lib/matplotlib/patches.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,8 @@ def __str__(self):
706706
return fmt % pars
707707

708708
@docstring.dedent_interpd
709-
def __init__(self, xy, width, height, angle=0.0, **kwargs):
709+
def __init__(self, xy, width, height, angle=0.0, *,
710+
rotation_point='xy', **kwargs):
710711
"""
711712
Parameters
712713
----------
@@ -717,7 +718,11 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs):
717718
height : float
718719
Rectangle height.
719720
angle : float, default: 0
720-
Rotation in degrees anti-clockwise about *xy*.
721+
Rotation in degrees anti-clockwise about the rotation point.
722+
rotation_point : {'xy', 'center', (number, number)}, default: 'xy'
723+
If ``'xy'``, rotate around the anchor point. If ``'center'`` rotate
724+
around the center. If 2-tuple of number, rotate around this
725+
coordinate.
721726
722727
Other Parameters
723728
----------------
@@ -730,6 +735,14 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs):
730735
self._width = width
731736
self._height = height
732737
self.angle = float(angle)
738+
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
733746
self._convert_units() # Validate the inputs.
734747

735748
def get_path(self):
@@ -750,9 +763,36 @@ def get_patch_transform(self):
750763
# important to call the accessor method and not directly access the
751764
# transformation member variable.
752765
bbox = self.get_bbox()
753-
return (transforms.BboxTransformTo(bbox)
754-
+ transforms.Affine2D().rotate_deg_around(
755-
bbox.x0, bbox.y0, self.angle))
766+
if self.rotation_point == 'center':
767+
width, height = bbox.x1 - bbox.x0, bbox.y1 - bbox.y0
768+
rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2.
769+
elif self.rotation_point == 'xy':
770+
rotation_point = bbox.x0, bbox.y0
771+
else:
772+
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)
780+
781+
@property
782+
def rotation_point(self):
783+
"""The rotation point of the patch."""
784+
return self._rotation_point
785+
786+
@rotation_point.setter
787+
def rotation_point(self, value):
788+
if value in ['center', 'xy'] or (
789+
isinstance(value, tuple) and len(value) == 2 and
790+
isinstance(value[0], Number) and isinstance(value[1], Number)
791+
):
792+
self._rotation_point = value
793+
else:
794+
raise ValueError("`rotation_point` must be one of "
795+
"{'xy', 'center', (number, number)}.")
756796

757797
def get_x(self):
758798
"""Return the left coordinate of the rectangle."""
@@ -1511,6 +1551,12 @@ def __init__(self, xy, width, height, angle=0, **kwargs):
15111551
self._width, self._height = width, height
15121552
self._angle = angle
15131553
self._path = Path.unit_circle()
1554+
# Required for EllipseSelector with axes aspect ratio != 1
1555+
# The patch is defined in data coordinates and when changing the
1556+
# selector with square modifier and not in data coordinates, we need
1557+
# to correct for the aspect ratio difference between the data and
1558+
# display coordinate systems.
1559+
self._aspect_ratio_correction = 1.0
15141560
# Note: This cannot be calculated until this is added to an Axes
15151561
self._patch_transform = transforms.IdentityTransform()
15161562

@@ -1528,8 +1574,9 @@ def _recompute_transform(self):
15281574
width = self.convert_xunits(self._width)
15291575
height = self.convert_yunits(self._height)
15301576
self._patch_transform = transforms.Affine2D() \
1531-
.scale(width * 0.5, height * 0.5) \
1577+
.scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \
15321578
.rotate_deg(self.angle) \
1579+
.scale(1, 1 / self._aspect_ratio_correction) \
15331580
.translate(*center)
15341581

15351582
def get_path(self):

0 commit comments

Comments
 (0)