@@ -1947,9 +1947,8 @@ def press(self, event):
19471947 key = event .key or ''
19481948 key = key .replace ('ctrl' , 'control' )
19491949 # move state is locked in on a button press
1950- for state in ['move' ]:
1951- if key == self ._state_modifier_keys [state ]:
1952- self ._state .add (state )
1950+ if key == self ._state_modifier_keys ['move' ]:
1951+ self ._state .add ('move' )
19531952 self ._press (event )
19541953 return True
19551954 return False
@@ -2000,14 +1999,10 @@ def on_key_press(self, event):
20001999 self .clear ()
20012000 return
20022001 for (state , modifier ) in self ._state_modifier_keys .items ():
2003- if modifier in key .split ('+' ):
2004- # rotate and data_coordinates are enable/disable
2005- # on key press
2006- if (state in ['rotate' , 'data_coordinates' ] and
2007- state in self ._state ):
2008- self ._state .discard (state )
2009- else :
2010- self ._state .add (state )
2002+ # 'rotate' and 'data_coordinates' are added in _default_state
2003+ if (modifier in key .split ('+' ) and
2004+ state not in ['rotate' , 'data_coordinates' ]):
2005+ self ._state .add (state )
20112006 self ._on_key_press (event )
20122007
20132008 def _on_key_press (self , event ):
@@ -2017,9 +2012,9 @@ def on_key_release(self, event):
20172012 """Key release event handler and validator."""
20182013 if self .active :
20192014 key = event .key or ''
2015+ key = key .replace ('ctrl' , 'control' )
20202016 for (state , modifier ) in self ._state_modifier_keys .items ():
2021- if (modifier in key .split ('+' ) and
2022- state not in ['rotate' , 'data_coordinates' ]):
2017+ if modifier in key .split ('+' ):
20232018 self ._state .discard (state )
20242019 self ._on_key_release (event )
20252020
@@ -2858,7 +2853,8 @@ def __init__(self, ax, onselect, drawtype='box',
28582853 self ._interactive = interactive
28592854 self .drag_from_anywhere = drag_from_anywhere
28602855 self .ignore_event_outside = ignore_event_outside
2861- self ._rotation = 0
2856+ self ._rotation = 0.0
2857+ self ._aspect_ratio_correction = 1.0
28622858
28632859 if drawtype == 'none' : # draw a line but make it invisible
28642860 _api .warn_deprecated (
@@ -2892,6 +2888,7 @@ def __init__(self, ax, onselect, drawtype='box',
28922888 self .ax .add_line (to_draw )
28932889
28942890 self ._selection_artist = to_draw
2891+ self ._set_aspect_ratio_correction ()
28952892
28962893 self .minspanx = minspanx
28972894 self .minspany = minspany
@@ -2975,6 +2972,8 @@ def _press(self, event):
29752972 self .set_visible (True )
29762973
29772974 self ._extents_on_press = self .extents
2975+ self ._rotation_on_press = self ._rotation
2976+ self ._set_aspect_ratio_correction ()
29782977
29792978 return False
29802979
@@ -3050,13 +3049,8 @@ def _onmove(self, event):
30503049 dy = event .ydata - eventpress .ydata
30513050 refmax = None
30523051 if 'data_coordinates' in state :
3053- aspect_ratio = 1
30543052 refx , refy = dx , dy
30553053 else :
3056- figure_size = self .ax .get_figure ().get_size_inches ()
3057- ll , ur = self .ax .get_position () * figure_size
3058- width , height = ur - ll
3059- aspect_ratio = height / width * self .ax .get_data_ratio ()
30603054 refx = event .xdata / (eventpress .xdata + 1e-6 )
30613055 refy = event .ydata / (eventpress .ydata + 1e-6 )
30623056
@@ -3067,8 +3061,9 @@ def _onmove(self, event):
30673061 a = np .array ([eventpress .xdata , eventpress .ydata ])
30683062 b = np .array (self .center )
30693063 c = np .array ([event .xdata , event .ydata ])
3070- self ._rotation = (np .arctan2 (c [1 ]- b [1 ], c [0 ]- b [0 ]) -
3071- np .arctan2 (a [1 ]- b [1 ], a [0 ]- b [0 ]))
3064+ angle = (np .arctan2 (c [1 ]- b [1 ], c [0 ]- b [0 ]) -
3065+ np .arctan2 (a [1 ]- b [1 ], a [0 ]- b [0 ]))
3066+ self .rotation = np .rad2deg (self ._rotation_on_press + angle )
30723067
30733068 # resize an existing shape
30743069 elif self ._active_handle and self ._active_handle != 'C' :
@@ -3083,10 +3078,10 @@ def _onmove(self, event):
30833078 refmax = max (refx , refy , key = abs )
30843079 if self ._active_handle in ['E' , 'W' ] or refmax == refx :
30853080 hw = event .xdata - center [0 ]
3086- hh = hw / aspect_ratio
3081+ hh = hw / self . _aspect_ratio_correction
30873082 else :
30883083 hh = event .ydata - center [1 ]
3089- hw = hh * aspect_ratio
3084+ hw = hh * self . _aspect_ratio_correction
30903085 else :
30913086 hw = size_on_press [0 ] / 2
30923087 hh = size_on_press [1 ] / 2
@@ -3119,10 +3114,12 @@ def _onmove(self, event):
31193114 refmax = max (refx , refy , key = abs )
31203115 if self ._active_handle in ['E' , 'W' ] or refmax == refx :
31213116 sign = np .sign (event .ydata - y0 )
3122- y1 = y0 + sign * abs (x1 - x0 ) / aspect_ratio
3117+ y1 = y0 + sign * abs (x1 - x0 ) / \
3118+ self ._aspect_ratio_correction
31233119 else :
31243120 sign = np .sign (event .xdata - x0 )
3125- x1 = x0 + sign * abs (y1 - y0 ) * aspect_ratio
3121+ x1 = x0 + sign * abs (y1 - y0 ) * \
3122+ self ._aspect_ratio_correction
31263123
31273124 # move existing shape
31283125 elif self ._active_handle == 'C' :
@@ -3149,9 +3146,9 @@ def _onmove(self, event):
31493146 if 'square' in state :
31503147 refmax = max (refx , refy , key = abs )
31513148 if refmax == refx :
3152- dy = dx / aspect_ratio
3149+ dy = np . sign ( dy ) * abs ( dx ) / self . _aspect_ratio_correction
31533150 else :
3154- dx = dy * aspect_ratio
3151+ dx = np . sign ( dx ) * abs ( dy ) * self . _aspect_ratio_correction
31553152
31563153 # from center
31573154 if 'center' in state :
@@ -3168,6 +3165,18 @@ def _onmove(self, event):
31683165
31693166 self .extents = x0 , x1 , y0 , y1
31703167
3168+ def _on_key_press (self , event ):
3169+ key = event .key or ''
3170+ key = key .replace ('ctrl' , 'control' )
3171+ for (state , modifier ) in self ._state_modifier_keys .items ():
3172+ if modifier in key .split ('+' ):
3173+ if state in ['rotate' , 'data_coordinates' ]:
3174+ if state in self ._default_state :
3175+ self ._default_state .discard (state )
3176+ else :
3177+ self ._default_state .add (state )
3178+ self ._set_aspect_ratio_correction ()
3179+
31713180 @property
31723181 def _rect_bbox (self ):
31733182 if self ._drawtype == 'box' :
@@ -3178,8 +3187,27 @@ def _rect_bbox(self):
31783187 y0 , y1 = min (y ), max (y )
31793188 return x0 , y0 , x1 - x0 , y1 - y0
31803189
3190+ def _set_aspect_ratio_correction (self ):
3191+ aspect_ratio = self .ax ._get_aspect_ratio ()
3192+ if not hasattr (self ._selection_artist , '_aspect_ratio_correction' ):
3193+ # Aspect ratio correction is not supported with deprecated
3194+ # drawtype='line'. Remove this block in matplotlib 3.7
3195+ self ._aspect_ratio_correction = 1
3196+ return
3197+
3198+ self ._selection_artist ._aspect_ratio_correction = aspect_ratio
3199+ if 'data_coordinates' in self ._state | self ._default_state :
3200+ self ._aspect_ratio_correction = 1
3201+ else :
3202+ self ._aspect_ratio_correction = aspect_ratio
3203+
31813204 def _get_rotation_transform (self ):
3182- return Affine2D ().rotate_around (* self .center , self ._rotation )
3205+ aspect_ratio = self .ax ._get_aspect_ratio ()
3206+ return Affine2D ().translate (- self .center [0 ], - self .center [1 ]) \
3207+ .scale (1 , aspect_ratio ) \
3208+ .rotate (self ._rotation ) \
3209+ .scale (1 , 1 / aspect_ratio ) \
3210+ .translate (* self .center )
31833211
31843212 @property
31853213 def corners (self ):
@@ -3234,14 +3262,17 @@ def extents(self, extents):
32343262
32353263 @property
32363264 def rotation (self ):
3237- """Rotation in degree."""
3265+ """Rotation in degree in interval [0, 45] ."""
32383266 return np .rad2deg (self ._rotation )
32393267
32403268 @rotation .setter
32413269 def rotation (self , value ):
3242- self ._rotation = np .deg2rad (value )
3243- # call extents setter to draw shape and update handles positions
3244- self .extents = self .extents
3270+ # Restrict to a limited range of rotation [0, 45] to avoid changing
3271+ # order of handles
3272+ if 0 <= value and value <= 45 :
3273+ self ._rotation = np .deg2rad (value )
3274+ # call extents setter to draw shape and update handles positions
3275+ self .extents = self .extents
32453276
32463277 draw_shape = _api .deprecate_privatize_attribute ('3.5' )
32473278
@@ -3352,7 +3383,7 @@ def _draw_shape(self, extents):
33523383 self ._selection_artist .center = center
33533384 self ._selection_artist .width = 2 * a
33543385 self ._selection_artist .height = 2 * b
3355- self ._selection_artist .set_angle ( self .rotation )
3386+ self ._selection_artist .angle = self .rotation
33563387 else :
33573388 rad = np .deg2rad (np .arange (31 ) * 12 )
33583389 x = a * np .cos (rad ) + center [0 ]
0 commit comments