@@ -1810,7 +1810,8 @@ def __init__(self, ax, onselect, useblit=False, button=None,
18101810 self .connect_default_events ()
18111811
18121812 self ._state_modifier_keys = dict (move = ' ' , clear = 'escape' ,
1813- square = 'shift' , center = 'control' )
1813+ square = 'shift' , center = 'control' ,
1814+ data_coordinates = 'd' )
18141815 self ._state_modifier_keys .update (state_modifier_keys or {})
18151816
18161817 self .background = None
@@ -1996,6 +1997,12 @@ def on_key_press(self, event):
19961997 if key == self ._state_modifier_keys ['clear' ]:
19971998 self .clear ()
19981999 return
2000+ if key == 'd' and key in self ._state_modifier_keys .values ():
2001+ modifier = 'data_coordinates'
2002+ if modifier in self ._default_state :
2003+ self ._default_state .remove (modifier )
2004+ else :
2005+ self .add_default_state (modifier )
19992006 for (state , modifier ) in self ._state_modifier_keys .items ():
20002007 if modifier in key :
20012008 self ._state .add (state )
@@ -2204,7 +2211,8 @@ def __init__(self, ax, onselect, direction, minspan=0, useblit=False,
22042211 if state_modifier_keys is None :
22052212 state_modifier_keys = dict (clear = 'escape' ,
22062213 square = 'not-applicable' ,
2207- center = 'not-applicable' )
2214+ center = 'not-applicable' ,
2215+ data_coordinates = 'not-applicable' )
22082216 super ().__init__ (ax , onselect , useblit = useblit , button = button ,
22092217 state_modifier_keys = state_modifier_keys )
22102218
@@ -2778,8 +2786,12 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent)
27782786 - "clear": Clear the current shape, default: "escape".
27792787 - "square": Make the shape square, default: "shift".
27802788 - "center": change the shape around its center, default: "ctrl".
2789+ - "data_coordinates": define if data or figure coordinates should be
2790+ used to define the square shape, default: "d"
27812791
2782- "square" and "center" can be combined.
2792+ "square" and "center" can be combined. The square shape can be defined
2793+ in data or figure coordinates as determined by the ``data_coordinates``
2794+ modifier, which can be enable and disable by pressing the 'd' key.
27832795
27842796 drag_from_anywhere : bool, default: False
27852797 If `True`, the widget can be moved by clicking anywhere within
@@ -3014,64 +3026,75 @@ def _onmove(self, event):
30143026 """Motion notify event handler."""
30153027
30163028 state = self ._state | self ._default_state
3029+
3030+ dx = event .xdata - self ._eventpress .xdata
3031+ dy = event .ydata - self ._eventpress .ydata
3032+ refmax = None
3033+ if 'data_coordinates' in state :
3034+ aspect_ratio = 1
3035+ refx , refy = dx , dy
3036+ else :
3037+ figure_size = self .ax .get_figure ().get_size_inches ()
3038+ ll , ur = self .ax .get_position () * figure_size
3039+ width , height = ur - ll
3040+ aspect_ratio = height / width * self .ax .get_data_ratio ()
3041+ refx = event .xdata / (self ._eventpress .xdata + 1e-6 )
3042+ refy = event .ydata / (self ._eventpress .ydata + 1e-6 )
3043+
30173044 # resize an existing shape
30183045 if self ._active_handle and self ._active_handle != 'C' :
30193046 x0 , x1 , y0 , y1 = self ._extents_on_press
30203047 size_on_press = [x1 - x0 , y1 - y0 ]
30213048 center = [x0 + size_on_press [0 ] / 2 , y0 + size_on_press [1 ] / 2 ]
3022- dx = event .xdata - self ._eventpress .xdata
3023- dy = event .ydata - self ._eventpress .ydata
3024-
3025- # change sign of relative changes to simplify calculation
3026- # Switch variables so that only x1 and/or y1 are updated on move
3027- x_factor = y_factor = 1
3028- if 'W' in self ._active_handle :
3029- x_factor *= - 1
3030- dx *= x_factor
3031- x0 = x1
3032- if 'S' in self ._active_handle :
3033- y_factor *= - 1
3034- dy *= y_factor
3035- y0 = y1
30363049
30373050 # Keeping the center fixed
30383051 if 'center' in state :
30393052 if 'square' in state :
3040- # Force the same change in dx and dy
3041- if self ._active_handle in ['E' , 'W' ]:
3042- # using E, W handle we need to update dy accordingly
3043- dy = dx
3044- elif self ._active_handle in ['S' , 'N' ]:
3045- # using S, N handle, we need to update dx accordingly
3046- dx = dy
3053+ # when using a corner, find which reference to use
3054+ if self ._active_handle in self ._corner_order :
3055+ refmax = max (refx , refy , key = abs )
3056+ if self ._active_handle in ['E' , 'W' ] or refmax == refx :
3057+ hw = event .xdata - center [0 ]
3058+ hh = hw / aspect_ratio
30473059 else :
3048- dx = dy = max (dx , dy , key = abs )
3049-
3050- # new half-width and half-height
3051- hw = size_on_press [0 ] / 2 + dx
3052- hh = size_on_press [1 ] / 2 + dy
3053-
3054- if 'square' not in state :
3060+ hh = event .ydata - center [1 ]
3061+ hw = hh * aspect_ratio
3062+ else :
3063+ hw = size_on_press [0 ] / 2
3064+ hh = size_on_press [1 ] / 2
30553065 # cancel changes in perpendicular direction
3056- if self ._active_handle in ['E' , 'W' ]:
3057- hh = size_on_press [ 1 ] / 2
3058- if self ._active_handle in ['N' , 'S' ]:
3059- hw = size_on_press [ 0 ] / 2
3066+ if self ._active_handle in ['E' , 'W' ] + self . _corner_order :
3067+ hw = abs ( event . xdata - center [ 0 ])
3068+ if self ._active_handle in ['N' , 'S' ] + self . _corner_order :
3069+ hh = abs ( event . ydata - center [ 1 ])
30603070
30613071 x0 , x1 , y0 , y1 = (center [0 ] - hw , center [0 ] + hw ,
30623072 center [1 ] - hh , center [1 ] + hh )
30633073
30643074 else :
3065- # Keeping the opposite corner/edge fixed
3075+ # change sign of relative changes to simplify calculation
3076+ # Switch variables so that x1 and/or y1 are updated on move
3077+ x_factor = y_factor = 1
3078+ if 'W' in self ._active_handle :
3079+ x0 = x1
3080+ x_factor *= - 1
3081+ if 'S' in self ._active_handle :
3082+ y0 = y1
3083+ y_factor *= - 1
3084+ if self ._active_handle in ['E' , 'W' ] + self ._corner_order :
3085+ x1 = event .xdata
3086+ if self ._active_handle in ['N' , 'S' ] + self ._corner_order :
3087+ y1 = event .ydata
30663088 if 'square' in state :
3067- dx = dy = max (dx , dy , key = abs )
3068- x1 = x0 + x_factor * (dx + size_on_press [0 ])
3069- y1 = y0 + y_factor * (dy + size_on_press [1 ])
3070- else :
3071- if self ._active_handle in ['E' , 'W' ] + self ._corner_order :
3072- x1 = event .xdata
3073- if self ._active_handle in ['N' , 'S' ] + self ._corner_order :
3074- y1 = event .ydata
3089+ # when using a corner, find which reference to use
3090+ if self ._active_handle in self ._corner_order :
3091+ refmax = max (refx , refy , key = abs )
3092+ if self ._active_handle in ['E' , 'W' ] or refmax == refx :
3093+ sign = np .sign (event .ydata - y0 )
3094+ y1 = y0 + sign * abs (x1 - x0 ) / aspect_ratio
3095+ else :
3096+ sign = np .sign (event .xdata - x0 )
3097+ x1 = x0 + sign * abs (y1 - y0 ) * aspect_ratio
30753098
30763099 # move existing shape
30773100 elif self ._active_handle == 'C' :
@@ -3090,21 +3113,16 @@ def _onmove(self, event):
30903113 if self .ignore_event_outside and self ._selection_completed :
30913114 return
30923115 center = [self ._eventpress .xdata , self ._eventpress .ydata ]
3093- center_pix = [self ._eventpress .x , self ._eventpress .y ]
30943116 dx = (event .xdata - center [0 ]) / 2.
30953117 dy = (event .ydata - center [1 ]) / 2.
30963118
30973119 # square shape
30983120 if 'square' in state :
3099- dx_pix = abs (event .x - center_pix [0 ])
3100- dy_pix = abs (event .y - center_pix [1 ])
3101- if not dx_pix :
3102- return
3103- maxd = max (abs (dx_pix ), abs (dy_pix ))
3104- if abs (dx_pix ) < maxd :
3105- dx *= maxd / (abs (dx_pix ) + 1e-6 )
3106- if abs (dy_pix ) < maxd :
3107- dy *= maxd / (abs (dy_pix ) + 1e-6 )
3121+ refmax = max (refx , refy , key = abs )
3122+ if refmax == refx :
3123+ dy = dx / aspect_ratio
3124+ else :
3125+ dx = dy * aspect_ratio
31083126
31093127 # from center
31103128 if 'center' in state :
@@ -3464,7 +3482,8 @@ def __init__(self, ax, onselect, useblit=False,
34643482 state_modifier_keys = dict (clear = 'escape' , move_vertex = 'control' ,
34653483 move_all = 'shift' , move = 'not-applicable' ,
34663484 square = 'not-applicable' ,
3467- center = 'not-applicable' )
3485+ center = 'not-applicable' ,
3486+ data_coordinates = 'not-applicable' )
34683487 super ().__init__ (ax , onselect , useblit = useblit ,
34693488 state_modifier_keys = state_modifier_keys )
34703489
0 commit comments