@@ -32,34 +32,29 @@ class PolarTransform(mtransforms.Transform):
32
32
output_dims = 2
33
33
is_separable = False
34
34
35
- def __init__ (self , axis = None , use_rmin = True ):
35
+ def __init__ (self , axis = None , use_rmin = True ,
36
+ _apply_theta_transforms = True ):
36
37
mtransforms .Transform .__init__ (self )
37
38
self ._axis = axis
38
39
self ._use_rmin = use_rmin
40
+ self ._apply_theta_transforms = _apply_theta_transforms
39
41
40
42
def transform_non_affine (self , tr ):
41
43
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
53
44
54
45
t = tr [:, 0 :1 ]
55
46
r = tr [:, 1 :2 ]
56
47
x = xy [:, 0 :1 ]
57
48
y = xy [:, 1 :2 ]
58
49
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 ()
61
55
62
- r = r - rmin
56
+ if self ._use_rmin and self ._axis is not None :
57
+ r = r - self ._axis .viewLim .ymin
63
58
mask = r < 0
64
59
x [:] = np .where (mask , np .nan , r * np .cos (t ))
65
60
y [:] = np .where (mask , np .nan , r * np .sin (t ))
@@ -78,7 +73,8 @@ def transform_path_non_affine(self, path):
78
73
mtransforms .Transform .transform_path_non_affine .__doc__
79
74
80
75
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 )
82
78
inverted .__doc__ = mtransforms .Transform .inverted .__doc__
83
79
84
80
@@ -122,24 +118,14 @@ class InvertedPolarTransform(mtransforms.Transform):
122
118
output_dims = 2
123
119
is_separable = False
124
120
125
- def __init__ (self , axis = None , use_rmin = True ):
121
+ def __init__ (self , axis = None , use_rmin = True ,
122
+ _apply_theta_transforms = True ):
126
123
mtransforms .Transform .__init__ (self )
127
124
self ._axis = axis
128
125
self ._use_rmin = use_rmin
126
+ self ._apply_theta_transforms = _apply_theta_transforms
129
127
130
128
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
-
143
129
x = xy [:, 0 :1 ]
144
130
y = xy [:, 1 :]
145
131
r = np .sqrt (x * x + y * y )
@@ -152,18 +138,23 @@ def transform_non_affine(self, xy):
152
138
theta = np .arccos (x / r )
153
139
theta = np .where (y < 0 , 2 * np .pi - theta , theta )
154
140
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
158
147
159
- r += rmin
148
+ if self ._use_rmin and self ._axis is not None :
149
+ r += self ._axis .viewLim .ymin
160
150
161
151
return np .concatenate ((theta , r ), 1 )
162
152
transform_non_affine .__doc__ = \
163
153
mtransforms .Transform .transform_non_affine .__doc__
164
154
165
155
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 )
167
158
inverted .__doc__ = mtransforms .Transform .inverted .__doc__
168
159
169
160
@@ -246,7 +237,8 @@ def __init__(self, *args, **kwargs):
246
237
"""
247
238
self ._default_theta_offset = kwargs .pop ('theta_offset' , 0 )
248
239
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 ))
250
242
251
243
Axes .__init__ (self , * args , ** kwargs )
252
244
self .set_aspect ('equal' , adjustable = 'box' , anchor = 'C' )
@@ -286,6 +278,23 @@ def _init_axis(self):
286
278
self ._update_transScale ()
287
279
288
280
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
+
289
298
self .transAxes = mtransforms .BboxTransformTo (self .bbox )
290
299
291
300
# Transforms the x and y axis separately by a scale factor
@@ -295,24 +304,31 @@ def _set_lim_and_transforms(self):
295
304
296
305
# A (possibly non-linear) projection on the (already scaled)
297
306
# 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 )
299
310
300
311
# 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 )
302
316
303
317
# An affine transformation on the data, generally to limit the
304
318
# range of the axes
305
319
self .transProjectionAffine = self .PolarAffine (self .transScale , self .viewLim )
306
320
307
321
# The complete data transformation stack -- from data all the
308
322
# 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 ))
311
326
312
327
# This is the transform for theta-axis ticks. It is
313
328
# equivalent to transData, except it always puts r == 1.0 at
314
329
# the edge of the axis circle.
315
330
self ._xaxis_transform = (
331
+ self .transShift +
316
332
self .transPureProjection +
317
333
self .PolarAffine (mtransforms .IdentityTransform (),
318
334
mtransforms .Bbox .unit ()) +
@@ -333,16 +349,13 @@ def _set_lim_and_transforms(self):
333
349
# axis so the gridlines from 0.0 to 1.0, now go from 0.0 to
334
350
# 2pi.
335
351
self ._yaxis_transform = (
352
+ self .transShift +
336
353
mtransforms .Affine2D ().scale (np .pi * 2.0 , 1.0 ) +
337
354
self .transData )
338
355
# The r-axis labels are put at an angle and padded in the r-direction
339
356
self ._r_label_position = mtransforms .ScaledTranslation (
340
357
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
346
359
347
360
def get_xaxis_transform (self ,which = 'grid' ):
348
361
if which not in ['tick1' ,'tick2' ,'grid' ]:
@@ -407,13 +420,15 @@ def set_theta_offset(self, offset):
407
420
"""
408
421
Set the offset for the location of 0 in radians.
409
422
"""
410
- self ._theta_offset = offset
423
+ mtx = self ._theta_offset .get_matrix ()
424
+ mtx [0 , 2 ] = offset
425
+ self ._theta_offset .invalidate ()
411
426
412
427
def get_theta_offset (self ):
413
428
"""
414
429
Get the offset for the location of 0 in radians.
415
430
"""
416
- return self ._theta_offset
431
+ return self ._theta_offset . get_matrix ()[ 0 , 2 ]
417
432
418
433
def set_theta_zero_location (self , loc ):
419
434
"""
@@ -443,14 +458,16 @@ def set_theta_direction(self, direction):
443
458
counterclockwise, anticlockwise, 1:
444
459
Theta increases in the counterclockwise direction
445
460
"""
461
+ mtx = self ._direction .get_matrix ()
446
462
if direction in ('clockwise' ,):
447
- self . _direction = - 1
463
+ mtx [ 0 , 0 ] = - 1
448
464
elif direction in ('counterclockwise' , 'anticlockwise' ):
449
- self . _direction = 1
465
+ mtx [ 0 , 0 ] = 1
450
466
elif direction in (1 , - 1 ):
451
- self . _direction = direction
467
+ mtx [ 0 , 0 ] = direction
452
468
else :
453
469
raise ValueError ("direction must be 1, -1, clockwise or counterclockwise" )
470
+ self ._direction .invalidate ()
454
471
455
472
def get_theta_direction (self ):
456
473
"""
@@ -462,7 +479,7 @@ def get_theta_direction(self):
462
479
1:
463
480
Theta increases in the counterclockwise direction
464
481
"""
465
- return self ._direction
482
+ return self ._direction . get_matrix ()[ 0 , 0 ]
466
483
467
484
def set_rlim (self , * args , ** kwargs ):
468
485
if 'rmin' in kwargs :
@@ -478,7 +495,7 @@ def get_rlabel_position(self):
478
495
float
479
496
The theta position of the radius labels in degrees.
480
497
"""
481
- return self ._r_label_position .to_values ()[4 ]
498
+ return np . rad2deg ( self ._r_label_position .to_values ()[4 ])
482
499
483
500
def set_rlabel_position (self , value ):
484
501
"""Updates the theta position of the radius labels.
@@ -488,7 +505,7 @@ def set_rlabel_position(self, value):
488
505
value : number
489
506
The angular position of the radius labels in degrees.
490
507
"""
491
- self ._r_label_position ._t = (value , 0.0 )
508
+ self ._r_label_position ._t = (np . deg2rad ( value ) , 0.0 )
492
509
self ._r_label_position .invalidate ()
493
510
494
511
def set_yscale (self , * args , ** kwargs ):
@@ -596,6 +613,8 @@ def format_coord(self, theta, r):
596
613
Return a format string formatting the coordinate using Unicode
597
614
characters.
598
615
"""
616
+ if theta < 0 :
617
+ theta += 2 * math .pi
599
618
theta /= math .pi
600
619
return ('\N{GREEK SMALL LETTER THETA} =%0.3f\N{GREEK SMALL LETTER PI} '
601
620
'(%0.3f\N{DEGREE SIGN} ), r=%0.3f' ) % (theta , theta * 180.0 , r )
0 commit comments