class PolarAffine(mtransforms.Affine2DBase): """ The affine part of the polar projection. Scales the output so that maximum radius rests on the edge of the axes circle. """ def __init__(self, scale_transform, limits): """ *limits* is the view limit of the data. The only part of its bounds that is used is the y limits (for the radius limits). The theta range is handled by the non-affine transform. """ mtransforms.Affine2DBase.__init__(self) self._scale_transform = scale_transform self._limits = limits self.set_children(scale_transform, limits) self._mtx = None __str__ = mtransforms._make_str_method("_scale_transform", "_limits") def get_matrix(self): # docstring inherited if self._invalid: limits_scaled = self._limits.transformed(self._scale_transform) yscale = limits_scaled.ymax - limits_scaled.ymin affine = mtransforms.Affine2D() \ .scale(0.5 / yscale) \ .translate(0.5, 0.5) self._mtx = affine.get_matrix() self._inverted = None self._invalid = 0 return self._mtx
class _WedgeBbox(mtransforms.Bbox): """ Transform (theta, r) wedge Bbox into axes bounding box. Parameters ---------- center : (float, float) Center of the wedge viewLim : `~matplotlib.transforms.Bbox` Bbox determining the boundaries of the wedge originLim : `~matplotlib.transforms.Bbox` Bbox determining the origin for the wedge, if different from *viewLim* """ def __init__(self, center, viewLim, originLim, **kwargs): mtransforms.Bbox.__init__(self, [[0, 0], [1, 1]], **kwargs) self._center = center self._viewLim = viewLim self._originLim = originLim self.set_children(viewLim, originLim) __str__ = mtransforms._make_str_method("_center", "_viewLim", "_originLim") def get_points(self): # docstring inherited if self._invalid: points = self._viewLim.get_points().copy() # Scale angular limits to work with Wedge. points[:, 0] *= 180 / np.pi if points[0, 0] > points[1, 0]: points[:, 0] = points[::-1, 0] # Scale radial limits based on origin radius. points[:, 1] -= self._originLim.y0 # Scale radial limits to match axes limits. rscale = 0.5 / points[1, 1] points[:, 1] *= rscale width = min(points[1, 1] - points[0, 1], 0.5) # Generate bounding box for wedge. wedge = mpatches.Wedge(self._center, points[1, 1], points[0, 0], points[1, 0], width=width) self.update_from_path(wedge.get_path()) # Ensure equal aspect ratio. w, h = self._points[1] - self._points[0] deltah = max(w - h, 0) / 2 deltaw = max(h - w, 0) / 2 self._points += np.array([[-deltaw, -deltah], [deltaw, deltah]]) self._invalid = 0 return self._points
class _ThetaShift(mtransforms.ScaledTranslation): """ Apply a padding shift based on axes theta limits. This is used to create padding for radial ticks. Parameters ---------- axes : `~matplotlib.axes.Axes` The owning axes; used to determine limits. pad : float The padding to apply, in points. mode : {'min', 'max', 'rlabel'} Whether to shift away from the start (``'min'``) or the end (``'max'``) of the axes, or using the rlabel position (``'rlabel'``). """ def __init__(self, axes, pad, mode): mtransforms.ScaledTranslation.__init__(self, pad, pad, axes.figure.dpi_scale_trans) self.set_children(axes._realViewLim) self.axes = axes self.mode = mode self.pad = pad __str__ = mtransforms._make_str_method("axes", "pad", "mode") def get_matrix(self): if self._invalid: if self.mode == 'rlabel': angle = ( np.deg2rad(self.axes.get_rlabel_position()) * self.axes.get_theta_direction() + self.axes.get_theta_offset() ) else: if self.mode == 'min': angle = self.axes._realViewLim.xmin elif self.mode == 'max': angle = self.axes._realViewLim.xmax if self.mode in ('rlabel', 'min'): padx = np.cos(angle - np.pi / 2) pady = np.sin(angle - np.pi / 2) else: padx = np.cos(angle + np.pi / 2) pady = np.sin(angle + np.pi / 2) self._t = (self.pad * padx / 72, self.pad * pady / 72) return mtransforms.ScaledTranslation.get_matrix(self)
class PolarTransform(mtransforms.Transform): """ The base polar transform. This handles projection *theta* and *r* into Cartesian coordinate space *x* and *y*, but does not perform the ultimate affine transformation into the correct position. """ input_dims = output_dims = 2 def __init__(self, axis=None, use_rmin=True, _apply_theta_transforms=True): mtransforms.Transform.__init__(self) self._axis = axis self._use_rmin = use_rmin self._apply_theta_transforms = _apply_theta_transforms __str__ = mtransforms._make_str_method( "_axis", use_rmin="_use_rmin", _apply_theta_transforms="_apply_theta_transforms") def transform_non_affine(self, tr): # docstring inherited t, r = np.transpose(tr) # PolarAxes does not use the theta transforms here, but apply them for # backwards-compatibility if not being used by it. if self._apply_theta_transforms and self._axis is not None: t *= self._axis.get_theta_direction() t += self._axis.get_theta_offset() if self._use_rmin and self._axis is not None: r = (r - self._axis.get_rorigin()) * self._axis.get_rsign() r = np.where(r >= 0, r, np.nan) return np.column_stack([r * np.cos(t), r * np.sin(t)]) def transform_path_non_affine(self, path): # docstring inherited vertices = path.vertices if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]: return mpath.Path(self.transform(vertices), path.codes) ipath = path.interpolated(path._interpolation_steps) return mpath.Path(self.transform(ipath.vertices), ipath.codes) def inverted(self): # docstring inherited return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin, self._apply_theta_transforms)
class InvertedPolarTransform(mtransforms.Transform): """ The inverse of the polar transform, mapping Cartesian coordinate space *x* and *y* back to *theta* and *r*. """ input_dims = output_dims = 2 def __init__(self, axis=None, use_rmin=True, _apply_theta_transforms=True): mtransforms.Transform.__init__(self) self._axis = axis self._use_rmin = use_rmin self._apply_theta_transforms = _apply_theta_transforms __str__ = mtransforms._make_str_method( "_axis", use_rmin="_use_rmin", _apply_theta_transforms="_apply_theta_transforms") def transform_non_affine(self, xy): # docstring inherited x, y = xy.T r = np.hypot(x, y) theta = (np.arctan2(y, x) + 2 * np.pi) % (2 * np.pi) # PolarAxes does not use the theta transforms here, but apply them for # backwards-compatibility if not being used by it. if self._apply_theta_transforms and self._axis is not None: theta -= self._axis.get_theta_offset() theta *= self._axis.get_theta_direction() theta %= 2 * np.pi if self._use_rmin and self._axis is not None: r += self._axis.get_rorigin() r *= self._axis.get_rsign() return np.column_stack([theta, r]) def inverted(self): # docstring inherited return PolarAxes.PolarTransform(self._axis, self._use_rmin, self._apply_theta_transforms)