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

Skip to content

Fix centre and square state and add rotation for rectangle selector #20839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/api/next_api_changes/deprecations/20839-EP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Selector widget state internals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The *state_modifier_keys* attribute have been privatized and the modifier keys
needs to be set when creating the widget.
5 changes: 5 additions & 0 deletions doc/users/next_whats_new/rectangle_patch_rotation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Rectangle patch rotation point
------------------------------

The rotation point of the `~matplotlib.patches.Rectangle` can now be set to 'xy',
'center' or a 2-tuple of numbers.
40 changes: 40 additions & 0 deletions doc/users/next_whats_new/selector_improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Selectors improvement: rotation, aspect ratio correction and add/remove state
-----------------------------------------------------------------------------

The `~matplotlib.widgets.RectangleSelector` and
`~matplotlib.widgets.EllipseSelector` can now be rotated interactively between
-45° and 45°. The range limits are currently dictated by the implementation.
The rotation is enabled or disabled by striking the *r* key
('r' is the default key mapped to 'rotate' in *state_modifier_keys*) or by calling
``selector.add_state('rotate')``.

The aspect ratio of the axes can now be taken into account when using the
"square" state. This is enabled by specifying ``use_data_coordinates='True'`` when
the selector is initialized.

In addition to changing selector state interactively using the modifier keys
defined in *state_modifier_keys*, the selector state can now be changed
programmatically using the *add_state* and *remove_state* methods.


.. code-block:: python

import matplotlib.pyplot as plt
from matplotlib.widgets import RectangleSelector
import numpy as np

values = np.arange(0, 100)

fig = plt.figure()
ax = fig.add_subplot()
ax.plot(values, values)

selector = RectangleSelector(ax, print, interactive=True,
drag_from_anywhere=True,
use_data_coordinates=True)
selector.add_state('rotate') # alternatively press 'r' key
# rotate the selector interactively

selector.remove_state('rotate') # alternatively press 'r' key

selector.add_state('square')
10 changes: 10 additions & 0 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8094,3 +8094,13 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
tricontourf = mtri.tricontourf
tripcolor = mtri.tripcolor
triplot = mtri.triplot

def _get_aspect_ratio(self):
"""
Convenience method to calculate the aspect ratio of the axes in
the display coordinate system.
"""
figure_size = self.get_figure().get_size_inches()
ll, ur = self.get_position() * figure_size
width, height = ur - ll
return height / (width * self.get_data_ratio())
59 changes: 53 additions & 6 deletions lib/matplotlib/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,8 @@ def __str__(self):
return fmt % pars

@docstring.dedent_interpd
def __init__(self, xy, width, height, angle=0.0, **kwargs):
def __init__(self, xy, width, height, angle=0.0, *,
rotation_point='xy', **kwargs):
"""
Parameters
----------
Expand All @@ -717,7 +718,11 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs):
height : float
Rectangle height.
angle : float, default: 0
Rotation in degrees anti-clockwise about *xy*.
Rotation in degrees anti-clockwise about the rotation point.
rotation_point : {'xy', 'center', (number, number)}, default: 'xy'
If ``'xy'``, rotate around the anchor point. If ``'center'`` rotate
around the center. If 2-tuple of number, rotate around this
coordinate.

Other Parameters
----------------
Expand All @@ -730,6 +735,14 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs):
self._width = width
self._height = height
self.angle = float(angle)
self.rotation_point = rotation_point
# Required for RectangleSelector with axes aspect ratio != 1
# The patch is defined in data coordinates and when changing the
# selector with square modifier and not in data coordinates, we need
# to correct for the aspect ratio difference between the data and
# display coordinate systems. Its value is typically provide by
# Axes._get_aspect_ratio()
self._aspect_ratio_correction = 1.0
self._convert_units() # Validate the inputs.

def get_path(self):
Expand All @@ -750,9 +763,36 @@ def get_patch_transform(self):
# important to call the accessor method and not directly access the
# transformation member variable.
bbox = self.get_bbox()
return (transforms.BboxTransformTo(bbox)
+ transforms.Affine2D().rotate_deg_around(
bbox.x0, bbox.y0, self.angle))
if self.rotation_point == 'center':
width, height = bbox.x1 - bbox.x0, bbox.y1 - bbox.y0
rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2.
elif self.rotation_point == 'xy':
rotation_point = bbox.x0, bbox.y0
else:
rotation_point = self.rotation_point
return transforms.BboxTransformTo(bbox) \
+ transforms.Affine2D() \
.translate(-rotation_point[0], -rotation_point[1]) \
.scale(1, self._aspect_ratio_correction) \
.rotate_deg(self.angle) \
.scale(1, 1 / self._aspect_ratio_correction) \
.translate(*rotation_point)

@property
def rotation_point(self):
"""The rotation point of the patch."""
return self._rotation_point

@rotation_point.setter
def rotation_point(self, value):
if value in ['center', 'xy'] or (
isinstance(value, tuple) and len(value) == 2 and
isinstance(value[0], Number) and isinstance(value[1], Number)
):
self._rotation_point = value
else:
raise ValueError("`rotation_point` must be one of "
"{'xy', 'center', (number, number)}.")

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

Expand All @@ -1528,8 +1574,9 @@ def _recompute_transform(self):
width = self.convert_xunits(self._width)
height = self.convert_yunits(self._height)
self._patch_transform = transforms.Affine2D() \
.scale(width * 0.5, height * 0.5) \
.scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \
.rotate_deg(self.angle) \
.scale(1, 1 / self._aspect_ratio_correction) \
.translate(*center)

def get_path(self):
Expand Down
Loading