diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 3fbb4d2b9cfd..3243c76d8661 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -16,13 +16,18 @@ class PolarTransform(mtransforms.Transform): - """ + r""" The base polar transform. - This transform maps polar coordinates ``(theta, r)`` into Cartesian - coordinates ``(x, y) = (r * cos(theta), r * sin(theta))`` (but does not + This transform maps polar coordinates :math:`\theta, r` into Cartesian + coordinates :math:`x, y = r \cos(\theta), r \sin(\theta)` + (but does not fully transform into Axes coordinates or handle positioning in screen space). + This transformation is designed to be applied to data after any scaling + along the radial axis (e.g. log-scaling) has been applied to the input + data. + Path segments at a fixed radius are automatically transformed to circular arcs as long as ``path._interpolation_steps > 1``. """ @@ -30,7 +35,7 @@ class PolarTransform(mtransforms.Transform): input_dims = output_dims = 2 def __init__(self, axis=None, use_rmin=True, - _apply_theta_transforms=True): + _apply_theta_transforms=True, *, scale_transform=None): """ Parameters ---------- @@ -46,12 +51,18 @@ def __init__(self, axis=None, use_rmin=True, self._axis = axis self._use_rmin = use_rmin self._apply_theta_transforms = _apply_theta_transforms + self._scale_transform = scale_transform __str__ = mtransforms._make_str_method( "_axis", use_rmin="_use_rmin", _apply_theta_transforms="_apply_theta_transforms") + def _get_rorigin(self): + # Get lower r limit after being scaled by the radial scale transform + return self._scale_transform.transform( + (0, self._axis.get_rorigin()))[1] + def transform_non_affine(self, tr): # docstring inherited theta, r = np.transpose(tr) @@ -61,7 +72,7 @@ def transform_non_affine(self, tr): theta *= self._axis.get_theta_direction() theta += 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 = (r - self._get_rorigin()) * self._axis.get_rsign() r = np.where(r >= 0, r, np.nan) return np.column_stack([r * np.cos(theta), r * np.sin(theta)]) @@ -85,7 +96,7 @@ def transform_path_non_affine(self, path): # that behavior here. last_td, td = np.rad2deg([last_t, t]) if self._use_rmin and self._axis is not None: - r = ((r - self._axis.get_rorigin()) + r = ((r - self._get_rorigin()) * self._axis.get_rsign()) if last_td <= td: while td - last_td > 360: @@ -877,7 +888,9 @@ def _set_lim_and_transforms(self): # data. This one is aware of rmin self.transProjection = self.PolarTransform( self, - _apply_theta_transforms=False) + _apply_theta_transforms=False, + scale_transform=self.transScale + ) # Add dependency on rorigin. self.transProjection.set_children(self._originViewLim) @@ -888,9 +901,25 @@ def _set_lim_and_transforms(self): # The complete data transformation stack -- from data all the # way to display coordinates + # + # 1. Remove any radial axis scaling (e.g. log scaling) + # 2. Shift data in the theta direction + # 3. Project the data from polar to cartesian values + # (with the origin in the same place) + # 4. Scale and translate the cartesian values to Axes coordinates + # (here the origin is moved to the lower left of the Axes) + # 5. Move and scale to fill the Axes + # 6. Convert from Axes coordinates to Figure coordinates self.transData = ( - self.transScale + self.transShift + self.transProjection + - (self.transProjectionAffine + self.transWedge + self.transAxes)) + self.transScale + + self.transShift + + self.transProjection + + ( + self.transProjectionAffine + + self.transWedge + + self.transAxes + ) + ) # This is the transform for theta-axis ticks. It is # equivalent to transData, except it always puts r == 0.0 and r == 1.0 diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_log.png b/lib/matplotlib/tests/baseline_images/test_polar/polar_log.png new file mode 100644 index 000000000000..ab8e20b482a5 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_polar/polar_log.png differ diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index 1bde5bcc1142..1f8e6a75baca 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -434,3 +434,15 @@ def test_cursor_precision(): assert ax.format_coord(2, 0) == "θ=0.6π (115°), r=0.000" assert ax.format_coord(2, .1) == "θ=0.64π (115°), r=0.100" assert ax.format_coord(2, 1) == "θ=0.637π (114.6°), r=1.000" + + +@image_comparison(['polar_log.png'], style='default') +def test_polar_log(): + fig = plt.figure() + ax = fig.add_subplot(polar=True) + + ax.set_rscale('log') + ax.set_rlim(1, 1000) + + n = 100 + ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n))