@@ -1947,9 +1947,8 @@ def press(self, event):
1947
1947
key = event .key or ''
1948
1948
key = key .replace ('ctrl' , 'control' )
1949
1949
# 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' )
1953
1952
self ._press (event )
1954
1953
return True
1955
1954
return False
@@ -2000,14 +1999,10 @@ def on_key_press(self, event):
2000
1999
self .clear ()
2001
2000
return
2002
2001
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 )
2011
2006
self ._on_key_press (event )
2012
2007
2013
2008
def _on_key_press (self , event ):
@@ -2017,9 +2012,9 @@ def on_key_release(self, event):
2017
2012
"""Key release event handler and validator."""
2018
2013
if self .active :
2019
2014
key = event .key or ''
2015
+ key = key .replace ('ctrl' , 'control' )
2020
2016
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 ('+' ):
2023
2018
self ._state .discard (state )
2024
2019
self ._on_key_release (event )
2025
2020
@@ -2858,7 +2853,8 @@ def __init__(self, ax, onselect, drawtype='box',
2858
2853
self ._interactive = interactive
2859
2854
self .drag_from_anywhere = drag_from_anywhere
2860
2855
self .ignore_event_outside = ignore_event_outside
2861
- self ._rotation = 0
2856
+ self ._rotation = 0.0
2857
+ self ._aspect_ratio_correction = 1.0
2862
2858
2863
2859
if drawtype == 'none' : # draw a line but make it invisible
2864
2860
_api .warn_deprecated (
@@ -2892,6 +2888,7 @@ def __init__(self, ax, onselect, drawtype='box',
2892
2888
self .ax .add_line (to_draw )
2893
2889
2894
2890
self ._selection_artist = to_draw
2891
+ self ._set_aspect_ratio_correction ()
2895
2892
2896
2893
self .minspanx = minspanx
2897
2894
self .minspany = minspany
@@ -2975,6 +2972,8 @@ def _press(self, event):
2975
2972
self .set_visible (True )
2976
2973
2977
2974
self ._extents_on_press = self .extents
2975
+ self ._rotation_on_press = self ._rotation
2976
+ self ._set_aspect_ratio_correction ()
2978
2977
2979
2978
return False
2980
2979
@@ -3050,13 +3049,8 @@ def _onmove(self, event):
3050
3049
dy = event .ydata - eventpress .ydata
3051
3050
refmax = None
3052
3051
if 'data_coordinates' in state :
3053
- aspect_ratio = 1
3054
3052
refx , refy = dx , dy
3055
3053
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 ()
3060
3054
refx = event .xdata / (eventpress .xdata + 1e-6 )
3061
3055
refy = event .ydata / (eventpress .ydata + 1e-6 )
3062
3056
@@ -3067,8 +3061,9 @@ def _onmove(self, event):
3067
3061
a = np .array ([eventpress .xdata , eventpress .ydata ])
3068
3062
b = np .array (self .center )
3069
3063
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 )
3072
3067
3073
3068
# resize an existing shape
3074
3069
elif self ._active_handle and self ._active_handle != 'C' :
@@ -3083,10 +3078,10 @@ def _onmove(self, event):
3083
3078
refmax = max (refx , refy , key = abs )
3084
3079
if self ._active_handle in ['E' , 'W' ] or refmax == refx :
3085
3080
hw = event .xdata - center [0 ]
3086
- hh = hw / aspect_ratio
3081
+ hh = hw / self . _aspect_ratio_correction
3087
3082
else :
3088
3083
hh = event .ydata - center [1 ]
3089
- hw = hh * aspect_ratio
3084
+ hw = hh * self . _aspect_ratio_correction
3090
3085
else :
3091
3086
hw = size_on_press [0 ] / 2
3092
3087
hh = size_on_press [1 ] / 2
@@ -3119,10 +3114,12 @@ def _onmove(self, event):
3119
3114
refmax = max (refx , refy , key = abs )
3120
3115
if self ._active_handle in ['E' , 'W' ] or refmax == refx :
3121
3116
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
3123
3119
else :
3124
3120
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
3126
3123
3127
3124
# move existing shape
3128
3125
elif self ._active_handle == 'C' :
@@ -3149,9 +3146,9 @@ def _onmove(self, event):
3149
3146
if 'square' in state :
3150
3147
refmax = max (refx , refy , key = abs )
3151
3148
if refmax == refx :
3152
- dy = dx / aspect_ratio
3149
+ dy = np . sign ( dy ) * abs ( dx ) / self . _aspect_ratio_correction
3153
3150
else :
3154
- dx = dy * aspect_ratio
3151
+ dx = np . sign ( dx ) * abs ( dy ) * self . _aspect_ratio_correction
3155
3152
3156
3153
# from center
3157
3154
if 'center' in state :
@@ -3168,6 +3165,18 @@ def _onmove(self, event):
3168
3165
3169
3166
self .extents = x0 , x1 , y0 , y1
3170
3167
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
+
3171
3180
@property
3172
3181
def _rect_bbox (self ):
3173
3182
if self ._drawtype == 'box' :
@@ -3178,8 +3187,27 @@ def _rect_bbox(self):
3178
3187
y0 , y1 = min (y
10000
), max (y )
3179
3188
return x0 , y0 , x1 - x0 , y1 - y0
3180
3189
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
+
3181
3204
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 )
3183
3211
3184
3212
@property
3185
3213
def corners (self ):
@@ -3234,14 +3262,17 @@ def extents(self, extents):
3234
3262
3235
3263
@property
3236
3264
def rotation (self ):
3237
- """Rotation in degree."""
3265
+ """Rotation in degree in interval [0, 45] ."""
3238
3266
return np .rad2deg (self ._rotation )
3239
3267
3240
3268
@rotation .setter
3241
3269
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
3245
3276
3246
3277
draw_shape = _api .deprecate_privatize_attribute ('3.5' )
3247
3278
@@ -3352,7 +3383,7 @@ def _draw_shape(self, extents):
3352
3383
self ._selection_artist .center = center
3353
3384
self ._selection_artist .width = 2 * a
3354
3385
self ._selection_artist .height = 2 * b
3355
- self ._selection_artist .set_angle ( self .rotation )
3386
+ self ._selection_artist .angle = self .rotation
3356
3387
else :
3357
3388
rad = np .deg2rad (np .arange (31 ) * 12 )
3358
3389
x = a * np .cos (rad ) + center [0 ]
0 commit comments