Skip to content

Backport PR #24825 on branch v3.7.x (Allow non-default scales on polar axes) #24960

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions lib/matplotlib/projections/polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@


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``.
"""

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
----------
Expand All @@ -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)
Expand All @@ -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)])

Expand All @@ -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:
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions lib/matplotlib/tests/test_polar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))