From fc0ee8f41b3f83402af7811fca9e5cae522802cd Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:39:29 +0200 Subject: [PATCH] Backport PR #30223: Polar log scale: fix inner patch boundary and spine location --- lib/matplotlib/projections/polar.py | 8 +++++++- lib/matplotlib/spines.py | 8 +++++++- lib/matplotlib/tests/test_polar.py | 20 ++++++++++++++++++++ lib/matplotlib/tests/test_spines.py | 12 ++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 7fe6045039b1..2630a7dac3aa 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -857,6 +857,10 @@ def _init_axis(self): self.xaxis = ThetaAxis(self, clear=False) self.yaxis = RadialAxis(self, clear=False) self.spines['polar'].register_axis(self.yaxis) + inner_spine = self.spines.get('inner', None) + if inner_spine is not None: + # Subclasses may not have inner spine. + inner_spine.register_axis(self.yaxis) def _set_lim_and_transforms(self): # A view limit where the minimum radius can be locked if the user @@ -1002,7 +1006,9 @@ def draw(self, renderer): thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx) if thetamin > thetamax: thetamin, thetamax = thetamax, thetamin - rmin, rmax = ((self._realViewLim.intervaly - self.get_rorigin()) * + rscale_tr = self.yaxis.get_transform() + rmin, rmax = ((rscale_tr.transform(self._realViewLim.intervaly) - + rscale_tr.transform(self.get_rorigin())) * self.get_rsign()) if isinstance(self.patch, mpatches.Wedge): # Backwards-compatibility: Any subclassed Axes might override the diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index 7e77a393f2a2..9732a2f3347a 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -265,11 +265,17 @@ def _adjust_location(self): self._path = mpath.Path.arc(np.rad2deg(low), np.rad2deg(high)) if self.spine_type == 'bottom': - rmin, rmax = self.axes.viewLim.intervaly + if self.axis is None: + tr = mtransforms.IdentityTransform() + else: + tr = self.axis.get_transform() + rmin, rmax = tr.transform(self.axes.viewLim.intervaly) try: rorigin = self.axes.get_rorigin() except AttributeError: rorigin = rmin + else: + rorigin = tr.transform(rorigin) scaled_diameter = (rmin - rorigin) / (rmax - rorigin) self._height = scaled_diameter self._width = scaled_diameter diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index 87c8efe3045e..844ab8cf3a06 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -482,6 +482,26 @@ def test_polar_log(): ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n)) +@check_figures_equal() +def test_polar_log_rorigin(fig_ref, fig_test): + # Test that equivalent linear and log radial settings give the same axes patch + # and spines. + ax_ref = fig_ref.add_subplot(projection='polar', facecolor='red') + ax_ref.set_rlim(0, 2) + ax_ref.set_rorigin(-3) + ax_ref.set_rticks(np.linspace(0, 2, 5)) + + ax_test = fig_test.add_subplot(projection='polar', facecolor='red') + ax_test.set_rscale('log') + ax_test.set_rlim(1, 100) + ax_test.set_rorigin(10**-3) + ax_test.set_rticks(np.logspace(0, 2, 5)) + + for ax in ax_ref, ax_test: + # Radial tick labels should be the only difference, so turn them off. + ax.tick_params(labelleft=False) + + def test_polar_neg_theta_lims(): fig = plt.figure() ax = fig.add_subplot(projection='polar') diff --git a/lib/matplotlib/tests/test_spines.py b/lib/matplotlib/tests/test_spines.py index 29b4ecf6f330..99b8461ad851 100644 --- a/lib/matplotlib/tests/test_spines.py +++ b/lib/matplotlib/tests/test_spines.py @@ -154,3 +154,15 @@ def test_spines_black_axes(): ax.set_xticks([]) ax.set_yticks([]) ax.set_facecolor((0, 0, 0)) + + +def test_arc_spine_inner_no_axis(): + # Backcompat: smoke test that inner arc spine does not need a registered + # axis in order to be drawn + fig = plt.figure() + ax = fig.add_subplot(projection="polar") + inner_spine = ax.spines["inner"] + inner_spine.register_axis(None) + assert ax.spines["inner"].axis is None + + fig.draw_without_rendering()