@@ -1934,9 +1934,8 @@ def press(self, event):
19341934 key = event .key or ''
19351935 key = key .replace ('ctrl' , 'control' )
19361936 # move state is locked in on a button press
1937- for state in ['move' ]:
1938- if key == self ._state_modifier_keys [state ]:
1939- self ._state .add (state )
1937+ if key == self ._state_modifier_keys ['move' ]:
1938+ self ._state .add ('move' )
19401939 self ._press (event )
19411940 return True
19421941 return False
@@ -1989,14 +1988,10 @@ def on_key_press(self, event):
19891988 self .update ()
19901989 return
19911990 for (state , modifier ) in self ._state_modifier_keys .items ():
1992- if modifier in key .split ('+' ):
1993- # rotate and data_coordinates are enable/disable
1994- # on key press
1995- if (state in ['rotate' , 'data_coordinates' ] and
1996- state in self ._state ):
1997- self ._state .discard (state )
1998- else :
1999- self ._state .add (state )
1991+ # 'rotate' and 'data_coordinates' are added in _default_state
1992+ if (modifier in key .split ('+' ) and
1993+ state not in ['rotate' , 'data_coordinates' ]):
1994+ self ._state .add (state )
20001995 self ._on_key_press (event )
20011996
20021997 def _on_key_press (self , event ):
@@ -2006,9 +2001,9 @@ def on_key_release(self, event):
20062001 """Key release event handler and validator."""
20072002 if self .active :
20082003 key = event .key or ''
2004+ key = key .replace ('ctrl' , 'control' )
20092005 for (state , modifier ) in self ._state_modifier_keys .items ():
2010- if (modifier in key .split ('+' ) and
2011- state not in ['rotate' , 'data_coordinates' ]):
2006+ if modifier in key .split ('+' ):
20122007 self ._state .discard (state )
20132008 self ._on_key_release (event )
20142009
@@ -2835,7 +2830,8 @@ def __init__(self, ax, onselect, drawtype='box',
28352830 self ._interactive = interactive
28362831 self .drag_from_anywhere = drag_from_anywhere
28372832 self .ignore_event_outside = ignore_event_outside
2838- self ._rotation = 0
2833+ self ._rotation = 0.0
2834+ self ._aspect_ratio_correction = 1.0
28392835
28402836 if drawtype == 'none' : # draw a line but make it invisible
28412837 _api .warn_deprecated (
@@ -2869,6 +2865,7 @@ def __init__(self, ax, onselect, drawtype='box',
28692865 self .ax .add_line (to_draw )
28702866
28712867 self ._selection_artist = to_draw
2868+ self ._set_aspect_ratio_correction ()
28722869
28732870 self .minspanx = minspanx
28742871 self .minspany = minspany
@@ -2952,6 +2949,8 @@ def _press(self, event):
29522949 self .set_visible (True )
29532950
29542951 self ._extents_on_press = self .extents
2952+ self ._rotation_on_press = self ._rotation
2953+ self ._set_aspect_ratio_correction ()
29552954
29562955 return False
29572956
@@ -3027,13 +3026,8 @@ def _onmove(self, event):
30273026 dy = event .ydata - eventpress .ydata
30283027 refmax = None
30293028 if 'data_coordinates' in state :
3030- aspect_ratio = 1
30313029 refx , refy = dx , dy
30323030 else :
3033- figure_size = self .ax .get_figure ().get_size_inches ()
3034- ll , ur = self .ax .get_position () * figure_size
3035- width , height = ur - ll
3036- aspect_ratio = height / width * self .ax .get_data_ratio ()
30373031 refx = event .xdata / (eventpress .xdata + 1e-6 )
30383032 refy = event .ydata / (eventpress .ydata + 1e-6 )
30393033
@@ -3044,8 +3038,9 @@ def _onmove(self, event):
30443038 a = np .array ([eventpress .xdata , eventpress .ydata ])
30453039 b = np .array (self .center )
30463040 c = np .array ([event .xdata , event .ydata ])
3047- self ._rotation = (np .arctan2 (c [1 ]- b [1 ], c [0 ]- b [0 ]) -
3048- np .arctan2 (a [1 ]- b [1 ], a [0 ]- b [0 ]))
3041+ angle = (np .arctan2 (c [1 ]- b [1 ], c [0 ]- b [0 ]) -
3042+ np .arctan2 (a [1 ]- b [1 ], a [0 ]- b [0 ]))
3043+ self .rotation = np .rad2deg (self ._rotation_on_press + angle )
30493044
30503045 # resize an existing shape
30513046 elif self ._active_handle and self ._active_handle != 'C' :
@@ -3060,10 +3055,10 @@ def _onmove(self, event):
30603055 refmax = max (refx , refy , key = abs )
30613056 if self ._active_handle in ['E' , 'W' ] or refmax == refx :
30623057 dw = event .xdata - center [0 ]
3063- dh = dw / aspect_ratio
3058+ dh = dw / self . _aspect_ratio_correction
30643059 else :
30653060 dh = event .ydata - center [1 ]
3066- dw = dh * aspect_ratio
3061+ dw = dh * self . _aspect_ratio_correction
30673062 else :
30683063 dw = sizepress [0 ] / 2
30693064 dh = sizepress [1 ] / 2
@@ -3096,10 +3091,12 @@ def _onmove(self, event):
30963091 refmax = max (refx , refy , key = abs )
30973092 if self ._active_handle in ['E' , 'W' ] or refmax == refx :
30983093 sign = np .sign (event .ydata - y0 )
3099- y1 = y0 + sign * abs (x1 - x0 ) / aspect_ratio
3094+ y1 = y0 + sign * abs (x1 - x0 ) / \
3095+ self ._aspect_ratio_correction
31003096 else :
31013097 sign = np .sign (event .xdata - x0 )
3102- x1 = x0 + sign * abs (y1 - y0 ) * aspect_ratio
3098+ x1 = x0 + sign * abs (y1 - y0 ) * \
3099+ self ._aspect_ratio_correction
31033100
31043101 # move existing shape
31053102 elif self ._active_handle == 'C' :
@@ -3126,9 +3123,9 @@ def _onmove(self, event):
31263123 if 'square' in state :
31273124 refmax = max (refx , refy , key = abs )
31283125 if refmax == refx :
3129- dy = dx / aspect_ratio
3126+ dy = np . sign ( dy ) * abs ( dx ) / self . _aspect_ratio_correction
31303127 else :
3131- dx = dy * aspect_ratio
3128+ dx = np . sign ( dx ) * abs ( dy ) * self . _aspect_ratio_correction
31323129
31333130 # from center
31343131 if 'center' in state :
@@ -3145,6 +3142,18 @@ def _onmove(self, event):
31453142
31463143 self .extents = x0 , x1 , y0 , y1
31473144
3145+ def _on_key_press (self , event ):
3146+ key = event .key or ''
3147+ key = key .replace ('ctrl' , 'control' )
3148+ for (state , modifier ) in self ._state_modifier_keys .items ():
3149+ if modifier in key .split ('+' ):
3150+ if state in ['rotate' , 'data_coordinates' ]:
3151+ if state in self ._default_state :
3152+ self ._default_state .discard (state )
3153+ else :
3154+ self ._default_state .add (state )
3155+ self ._set_aspect_ratio_correction ()
3156+
31483157 @property
31493158 def _rect_bbox (self ):
31503159 if self ._drawtype == 'box' :
@@ -3155,8 +3164,27 @@ def _rect_bbox(self):
31553164 y0 , y1 = min (y ), max (y )
31563165 return x0 , y0 , x1 - x0 , y1 - y0
31573166
3167+ def _set_aspect_ratio_correction (self ):
3168+ aspect_ratio = self .ax ._get_aspect_ratio ()
3169+ if not hasattr (self ._selection_artist , '_aspect_ratio_correction' ):
3170+ # Aspect ratio correction is not supported with deprecated
3171+ # drawtype='line'. Remove this block in matplotlib 3.7
3172+ self ._aspect_ratio_correction = 1
3173+ return
3174+
3175+ self ._selection_artist ._aspect_ratio_correction = aspect_ratio
3176+ if 'data_coordinates' in self ._state | self ._default_state :
3177+ self ._aspect_ratio_correction = 1
3178+ else :
3179+ self ._aspect_ratio_correction = aspect_ratio
3180+
31583181 def _get_rotation_transform (self ):
3159- return Affine2D ().rotate_around (* self .center , self ._rotation )
3182+ aspect_ratio = self .ax ._get_aspect_ratio ()
3183+ return Affine2D ().translate (- self .center [0 ], - self .center [1 ]) \
3184+ .scale (1 , aspect_ratio ) \
3185+ .rotate (self ._rotation ) \
3186+ .scale (1 , 1 / aspect_ratio ) \
3187+ .translate (* self .center )
31603188
31613189 @property
31623190 def corners (self ):
@@ -3211,14 +3239,17 @@ def extents(self, extents):
32113239
32123240 @property
32133241 def rotation (self ):
3214- """Rotation in degree."""
3242+ """Rotation in degree in interval [0, 45] ."""
32153243 return np .rad2deg (self ._rotation )
32163244
32173245 @rotation .setter
32183246 def rotation (self , value ):
3219- self ._rotation = np .deg2rad (value )
3220- # call extents setter to draw shape and update handles positions
3221- self .extents = self .extents
3247+ # Restrict to a limited range of rotation [0, 45] to avoid changing
3248+ # order of handles
3249+ if 0 <= value and value <= 45 :
3250+ self ._rotation = np .deg2rad (value )
3251+ # call extents setter to draw shape and update handles positions
3252+ self .extents = self .extents
32223253
32233254 draw_shape = _api .deprecate_privatize_attribute ('3.5' )
32243255
@@ -3329,7 +3360,7 @@ def _draw_shape(self, extents):
33293360 self ._selection_artist .center = center
33303361 self ._selection_artist .width = 2 * a
33313362 self ._selection_artist .height = 2 * b
3332- self ._selection_artist .set_angle ( self .rotation )
3363+ self ._selection_artist .angle = self .rotation
33333364 else :
33343365 rad = np .deg2rad (np .arange (31 ) * 12 )
33353366 x = a * np .cos (rad ) + center [0 ]
0 commit comments