Skip to content

Commit 2b33d7f

Browse files
committed
MNT: Use transforms for polar direction&offset.
This enables some nicer automatic invalidation for later work.
1 parent 2b77dbc commit 2b33d7f

File tree

1 file changed

+71
-52
lines changed

1 file changed

+71
-52
lines changed

lib/matplotlib/projections/polar.py

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)