@@ -1934,9 +1934,8 @@ def press(self, event):
1934
1934
key = event .key or ''
1935
1935
key = key .replace ('ctrl' , 'control' )
1936
1936
# 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' )
1940
1939
self ._press (event )
1941
1940
return True
1942
1941
return False
@@ -1989,14 +1988,10 @@ def on_key_press(self, event):
1989
1988
self .update ()
1990
1989
return
1991
1990
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
+ sel
10000
f ._state .add (state )
2000
1995
self ._on_key_press (event )
2001
1996
2002
1997
def _on_key_press (self , event ):
@@ -2006,9 +2001,9 @@ def on_key_release(self, event):
2006
2001
"""Key release event handler and validator."""
2007
2002
if self .active :
2008
2003
key = event .key or ''
2004
+ key = key .replace ('ctrl' , 'control' )
2009
2005
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 ('+' ):
2012
2007
self ._state .discard (state )
2013
2008
self ._on_key_release (event )
2014
2009
@@ -2835,7 +2830,8 @@ def __init__(self, ax, onselect, drawtype='box',
2835
2830
self ._interactive = interactive
2836
2831
self .drag_from_anywhere = drag_from_anywhere
2837
2832
self .ignore_event_outside = ignore_event_outside
2838
- self ._rotation = 0
2833
+ self ._rotation = 0.0
2834
+ self ._aspect_ratio_correction = 1.0
2839
2835
2840
2836
if drawtype == 'none' : # draw a line but make it invisible
2841
2837
_api .warn_deprecated (
@@ -2869,6 +2865,7 @@ def __init__(self, ax, onselect, drawtype='box',
2869
2865
self .ax .add_line (to_draw )
2870
2866
2871
2867
self ._selection_artist = to_draw
2868
+ self ._set_aspect_ratio_correction ()
2872
2869
2873
2870
self .minspanx = minspanx
2874
2871
self .minspany = minspany
@@ -2952,6 +2949,8 @@ def _press(self, event):
2952
2949
self .set_visible (True )
2953
2950
2954
2951
self ._extents_on_press = self .extents
2952
+ self ._rotation_on_press = self ._rotation
2953
+ self ._set_aspect_ratio_correction ()
2955
2954
2956
2955
return False
2957
2956
@@ -3027,13 +3026,8 @@ def _onmove(self, event):
3027
3026
dy = event .ydata - eventpress .ydata
3028
3027
refmax = None
3029
3028
if 'data_coordinates' in state :
3030
- aspect_ratio = 1
3031
3029
refx , refy = dx , dy
3032
3030
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 ()
3037
3031
refx = event .xdata / (eventpress .xdata + 1e-6 )
3038
3032
refy = event .ydata / (eventpress .ydata + 1e-6 )
3039
3033
@@ -3044,8 +3038,9 @@ def _onmove(self, event):
3044
3038
a = np .array ([eventpress .xdata , eventpress .ydata ])
3045
3039
b = np .array (self .center )
3046
3040
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 )
3049
3044
3050
3045
# resize an existing shape
3051
3046
elif self ._active_handle and self ._active_handle != 'C' :
@@ -3060,10 +3055,10 @@ def _onmove(self, event):
3060
3055
refmax = max (refx , refy , key = abs )
3061
3056
if self ._active_handle in ['E' , 'W' ] or refmax == refx :
3062
3057
dw = event .xdata - center [0 ]
3063
- dh = dw / aspect_ratio
3058
+ dh = dw / self . _aspect_ratio_correction
3064
3059
else :
3065
3060
dh = event .ydata - center [1 ]
3066
- dw = dh * aspect_ratio
3061
+ dw = dh * self . _aspect_ratio_correction
3067
3062
else :
3068
3063
dw = sizepress [0 ] / 2
3069
3064
dh = sizepress [1 ] / 2
@@ -3096,10 +3091,12 @@ def _onmove(self, event):
3096
3091
refmax = max (refx , refy , key = abs )
3097
3092
if self ._active_handle in ['E' , 'W' ] or refmax == refx :
3098
3093
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
3100
3096
else :
3101
3097
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
3103
3100
3104
3101
# move existing shape
3105
3102
elif self ._active_handle == 'C' :
@@ -3126,9 +3123,9 @@ def _onmove(self, event):
3126
3123
if 'square' in state :
3127
3124
refmax = max (refx , refy , key = abs )
3128
3125
if refmax == refx :
3129
- dy = dx / aspect_ratio
3126
+ dy = np . sign ( dy ) * abs ( dx ) / self . _aspect_ratio_correction
3130
3127
else :
3131
- dx = dy * aspect_ratio
3128
+ dx = np . sign ( dx ) * abs ( dy ) * self . _aspect_ratio_correction
3132
3129
3133
3130
# from center
3134
3131
if 'center' in state :
@@ -3145,6 +3142,18 @@ def _onmove(self, event):
3145
3142
3146
3143
self .extents = x0 , x1 , y0 , y1
3147
3144
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
+
3148
3157
@property
3149
3158
def _rect_bbox (self ):
3150
3159
if self ._drawtype == 'box' :
@@ -3155,8 +3164,27 @@ def _rect_bbox(self):
3155
3164
y0 , y1 = min (y ), max (y )
3156
3165
return x0 , y0 , x1 - x0 , y1 - y0
3157
3166
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
+
3158
3181
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 )
3160
3188
3161
3189
@property
3162
3190
def corners (self ):
@@ -3211,14 +3239,17 @@ def extents(self, extents):
3211
3239
3212
3240
@property
3213
3241
def rotation (self ):
3214
- """Rotation in degree."""
3242
+ """Rotation in degree in interval [0, 45] ."""
3215
3243
return np .rad2deg (self ._rotation )
3216
3244
3217
3245
@rotation .setter
3218
3246
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
3222
3253
3223
3254
draw_shape = _api .deprecate_privatize_attribute ('3.5' )
3224
3255
@@ -3329,7 +3360,7 @@ def _draw_shape(self, extents):
3329
3360
self ._selection_artist .center = center
3330
3361
self ._selection_artist .width = 2 * a
3331
3362
self ._selection_artist .height = 2 * b
3332
- self ._selection_artist .set_angle ( self .rotation )
3363
+ self ._selection_artist .angle = self .rotation
3333
3364
else :
3334
3365
rad = np .deg2rad (np .arange (31 ) * 12 )
3335
3366
x = a * np .cos (rad ) + center [0 ]
0 commit comments