@@ -32,34 +32,29 @@ class PolarTransform(mtransforms.Transform):
3232 output_dims = 2
3333 is_separable = False
3434
35- def __init__ (self , axis = None , use_rmin = True ):
35+ def __init__ (self , axis = None , use_rmin = True ,
36+ _apply_theta_transforms = True ):
3637 mtransforms .Transform .__init__ (self )
3738 self ._axis = axis
3839 self ._use_rmin = use_rmin
40+ self ._apply_theta_transforms = _apply_theta_transforms
3941
4042 def transform_non_affine (self , tr ):
4143 xy = np .empty (tr .shape , float )
42- if self ._axis is not None :
43- if self ._use_rmin :
44- rmin = self ._axis .viewLim .ymin
45- else :
46- rmin = 0
47- theta_offset = self ._axis .get_theta_offset ()
48- theta_direction = self ._axis .get_theta_direction ()
49- else :
50- rmin = 0
51- theta_offset = 0
52- theta_direction = 1
5344
5445 t = tr [:, 0 :1 ]
5546 r = tr [:, 1 :2 ]
5647 x = xy [:, 0 :1 ]
5748 y = xy [:, 1 :2 ]
5849
59- t *= theta_direction
60- t += theta_offset
50+ # PolarAxes does not use the theta transforms here, but apply them for
51+ # backwards-compatibility if not being used by it.
52+ if self ._apply_theta_transforms and self ._axis is not None :
53+ t *= self ._axis .get_theta_direction ()
54+ t += self ._axis .get_theta_offset ()
6155
62- r = r - rmin
56+ if self ._use_rmin and self ._axis is not None :
57+ r = r - self ._axis .viewLim .ymin
6358 mask = r < 0
6459 x [:] = np .where (mask , np .nan , r * np .cos (t ))
6560 y [:] = np .where (mask , np .nan , r * np .sin (t ))
@@ -78,7 +73,8 @@ def transform_path_non_affine(self, path):
7873 mtransforms .Transform .transform_path_non_affine .__doc__
7974
8075 def inverted (self ):
81- return PolarAxes .InvertedPolarTransform (self ._axis , self ._use_rmin )
76+ return PolarAxes .InvertedPolarTransform (self ._axis , self ._use_rmin ,
77+ self ._apply_theta_transforms )
8278 inverted .__doc__ = mtransforms .Transform .inverted .__doc__
8379
8480
@@ -122,24 +118,14 @@ class InvertedPolarTransform(mtransforms.Transform):
122118 output_dims = 2
123119 is_separable = False
124120
125- def __init__ (self , axis = None , use_rmin = True ):
121+ def __init__ (self , axis = None , use_rmin = True ,
122+ _apply_theta_transforms = True ):
126123 mtransforms .Transform .__init__ (self )
127124 self ._axis = axis
128125 self ._use_rmin = use_rmin
126+ self ._apply_theta_transforms = _apply_theta_transforms
129127
130128 def transform_non_affine (self , xy ):
131- if self ._axis is not None :
132- if self ._use_rmin :
133- rmin = self ._axis .viewLim .ymin
134- else :
135- rmin = 0
136- theta_offset = self ._axis .get_theta_offset ()
137- theta_direction = self ._axis .get_theta_direction ()
138- else :
139- rmin = 0
140- theta_offset = 0
141- theta_direction = 1
142-
143129 x = xy [:, 0 :1 ]
144130 y = xy [:, 1 :]
145131 r = np .sqrt (x * x + y * y )
@@ -152,18 +138,23 @@ def transform_non_affine(self, xy):
152138 theta = np .arccos (x / r )
153139 theta = np .where (y < 0 , 2 * np .pi - theta , theta )
154140
155- theta -= theta_offset
156- theta *= theta_direction
157- theta %= 2 * np .pi
141+ # PolarAxes does not use the theta transforms here, but apply them for
142+ # backwards-compatibility if not being used by it.
143+ if self ._apply_theta_transforms and self ._axis is not None :
144+ theta -= self ._axis .get_theta_offset ()
145+ theta *= self ._axis .get_theta_direction ()
146+ theta %= 2 * np .pi
158147
159- r += rmin
148+ if self ._use_rmin and self ._axis is not None :
149+ r += self ._axis .viewLim .ymin
160150
161151 return np .concatenate ((theta , r ), 1 )
162152 transform_non_affine .__doc__ = \
163153 mtransforms .Transform .transform_non_affine .__doc__
164154
165155 def inverted (self ):
166- return PolarAxes .PolarTransform (self ._axis , self ._use_rmin )
156+ return PolarAxes .PolarTransform (self ._axis , self ._use_rmin ,
157+ self ._apply_theta_transforms )
167158 inverted .__doc__ = mtransforms .Transform .inverted .__doc__
168159
169160
@@ -246,7 +237,8 @@ def __init__(self, *args, **kwargs):
246237 """
247238 self ._default_theta_offset = kwargs .pop ('theta_offset' , 0 )
248239 self ._default_theta_direction = kwargs .pop ('theta_direction' , 1 )
249- self ._default_rlabel_position = kwargs .pop ('rlabel_position' , 22.5 )
240+ self ._default_rlabel_position = np .deg2rad (
241+ kwargs .pop ('rlabel_position' , 22.5 ))
250242
251243 Axes .__init__ (self , * args , ** kwargs )
252244 self .set_aspect ('equal' , adjustable = 'box' , anchor = 'C' )
@@ -286,6 +278,23 @@ def _init_axis(self):
286278 self ._update_transScale ()
287279
288280 def _set_lim_and_transforms (self ):
281+ # A view limit where the minimum radius can be locked if the user
282+ # specifies an alternate origin.
283+ self ._originViewLim = mtransforms .LockableBbox (self .viewLim )
284+
285+ # Handle angular offset and direction.
286+ self ._direction = mtransforms .Affine2D () \
287+ .scale (self ._default_theta_direction , 1.0 )
288+ self ._theta_offset = mtransforms .Affine2D () \
289+ .translate (self ._default_theta_offset , 0.0 )
290+ self .transShift = mtransforms .composite_transform_factory (
291+ self ._direction ,
292+ self ._theta_offset )
293+ # A view limit shifted to the correct location after accounting for
294+ # orientation and offset.
295+ self ._shiftedViewLim = mtransforms .TransformedBbox (self .viewLim ,
296+ self .transShift )
297+
289298 self .transAxes = mtransforms .BboxTransformTo (self .bbox )
290299
291300 # Transforms the x and y axis separately by a scale factor
@@ -295,24 +304,31 @@ def _set_lim_and_transforms(self):
295304
296305 # A (possibly non-linear) projection on the (already scaled)
297306 # data. This one is aware of rmin
298- self .transProjection = self .PolarTransform (self )
307+ self .transProjection = self .PolarTransform (
308+ self ,
309+ _apply_theta_transforms = False )
299310
300311 # This one is not aware of rmin
301- self .transPureProjection = self .PolarTransform (self , use_rmin = False )
312+ self .transPureProjection = self .PolarTransform (
313+ self ,
314+ use_rmin = False ,
315+ _apply_theta_transforms = False )
302316
303317 # An affine transformation on the data, generally to limit the
304318 # range of the axes
305319 self .transProjectionAffine = self .PolarAffine (self .transScale , self .viewLim )
306320
307321 # The complete data transformation stack -- from data all the
308322 # way to display coordinates
309- self .transData = self .transScale + self .transProjection + \
310- (self .transProjectionAffine + self .transAxes )
323+ self .transData = (
324+ self .transScale + self .transShift + self .transProjection +
325+ (self .transProjectionAffine + self .transAxes ))
311326
312327 # This is the transform for theta-axis ticks. It is
313328 # equivalent to transData, except it always puts r == 1.0 at
314329 # the edge of the axis circle.
315330 self ._xaxis_transform = (
331+ self .transShift +
316332 self .transPureProjection +
317333 self .PolarAffine (mtransforms .IdentityTransform (),
318334 mtransforms .Bbox .unit ()) +
@@ -333,16 +349,13 @@ def _set_lim_and_transforms(self):
333349 # axis so the gridlines from 0.0 to 1.0, now go from 0.0 to
334350 # 2pi.
335351 self ._yaxis_transform = (
352+ self .transShift +
336353 mtransforms .Affine2D ().scale (np .pi * 2.0 , 1.0 ) +
337354 self .transData )
338355 # The r-axis labels are put at an angle and padded in the r-direction
339356 self ._r_label_position = mtransforms .ScaledTranslation (
340357 self ._default_rlabel_position , 0.0 , mtransforms .Affine2D ())
341- self ._yaxis_text_transform = (
342- self ._r_label_position +
343- mtransforms .Affine2D ().scale (1.0 / 360.0 , 1.0 ) +
344- self ._yaxis_transform
345- )
358+ self ._yaxis_text_transform = self ._r_label_position + self .transData
346359
347360 def get_xaxis_transform (self ,which = 'grid' ):
348361 if which not in ['tick1' ,'tick2' ,'grid' ]:
@@ -407,13 +420,15 @@ def set_theta_offset(self, offset):
407420 """
408421 Set the offset for the location of 0 in radians.
409422 """
410- self ._theta_offset = offset
423+ mtx = self ._theta_offset .get_matrix ()
424+ mtx [0 , 2 ] = offset
425+ self ._theta_offset .invalidate ()
411426
412427 def get_theta_offset (self ):
413428 """
414429 Get the offset for the location of 0 in radians.
415430 """
416- return self ._theta_offset
431+ return self ._theta_offset . get_matrix ()[ 0 , 2 ]
417432
418433 def set_theta_zero_location (self , loc ):
419434 """
@@ -443,14 +458,16 @@ def set_theta_direction(self, direction):
443458 counterclockwise, anticlockwise, 1:
444459 Theta increases in the counterclockwise direction
445460 """
461+ mtx = self ._direction .get_matrix ()
446462 if direction in ('clockwise' ,):
447- self . _direction = - 1
463+ mtx [ 0 , 0 ] = - 1
448464 elif direction in ('counterclockwise' , 'anticlockwise' ):
449- self . _direction = 1
465+ mtx [ 0 , 0 ] = 1
450466 elif direction in (1 , - 1 ):
451- self . _direction = direction
467+ mtx [ 0 , 0 ] = direction
452468 else :
453469 raise ValueError ("direction must be 1, -1, clockwise or counterclockwise" )
470+ self ._direction .invalidate ()
454471
455472 def get_theta_direction (self ):
456473 """
@@ -462,7 +479,7 @@ def get_theta_direction(self):
462479 1:
463480 Theta increases in the counterclockwise direction
464481 """
465- return self ._direction
482+ return self ._direction . get_matrix ()[ 0 , 0 ]
466483
467484 def set_rlim (self , * args , ** kwargs ):
468485 if 'rmin' in kwargs :
@@ -478,7 +495,7 @@ def get_rlabel_position(self):
478495 float
479496 The theta position of the radius labels in degrees.
480497 """
481- return self ._r_label_position .to_values ()[4 ]
498+ return np . rad2deg ( self ._r_label_position .to_values ()[4 ])
482499
483500 def set_rlabel_position (self , value ):
484501 """Updates the theta position of the radius labels.
@@ -488,7 +505,7 @@ def set_rlabel_position(self, value):
488505 value : number
489506 The angular position of the radius labels in degrees.
490507 """
491- self ._r_label_position ._t = (value , 0.0 )
508+ self ._r_label_position ._t = (np . deg2rad ( value ) , 0.0 )
492509 self ._r_label_position .invalidate ()
493510
494511 def set_yscale (self , * args , ** kwargs ):
@@ -596,6 +613,8 @@ def format_coord(self, theta, r):
596613 Return a format string formatting the coordinate using Unicode
597614 characters.
598615 """
616+ if theta < 0 :
617+ theta += 2 * math .pi
599618 theta /= math .pi
600619 return ('\N{GREEK SMALL LETTER THETA} =%0.3f\N{GREEK SMALL LETTER PI} '
601620 '(%0.3f\N{DEGREE SIGN} ), r=%0.3f' ) % (theta , theta * 180.0 , r )
0 commit comments