From 955e447be0f5fc2af60b84b7a5b41a4ad817dd18 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 23 May 2025 11:42:06 +0200 Subject: [PATCH] Fix label_outer in the presence of colorbars. The subgridspec to be considered should be the one containing both the axes and the colorbar, not the sub-subgridspec of just the axes. --- lib/matplotlib/axes/_base.py | 33 +++++++++++++++++++++++---- lib/matplotlib/tests/test_subplots.py | 8 +++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 15f8e97b449f..b8f31b36a3c5 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -4749,14 +4749,39 @@ def label_outer(self, remove_inner_ticks=False): self._label_outer_yaxis(skip_non_rectangular_axes=False, remove_inner_ticks=remove_inner_ticks) + def _get_subplotspec_with_optional_colorbar(self): + """ + Return the subplotspec for this Axes, except that if this Axes has been + moved to a subgridspec to make room for a colorbar, then return the + subplotspec that encloses both this Axes and the colorbar Axes. + """ + ss = self.get_subplotspec() + if not ss: + return + gs = ss.get_gridspec() + # Match against subgridspec hierarchy set up by colorbar.make_axes_gridspec. + if (isinstance(gs, mpl.gridspec.GridSpecFromSubplotSpec) + and gs.nrows * gs.ncols == 6): + for ax in self.figure.axes: + if (ax is not self + and hasattr(ax, "_colorbar_info") + and ax.get_subplotspec() + and isinstance(ax.get_subplotspec().get_gridspec(), + mpl.gridspec.GridSpecFromSubplotSpec) + and (ax.get_subplotspec().get_gridspec()._subplot_spec + is gs._subplot_spec)): + ss = gs._subplot_spec + break + return ss + def _label_outer_xaxis(self, *, skip_non_rectangular_axes, remove_inner_ticks=False): # see documentation in label_outer. if skip_non_rectangular_axes and not isinstance(self.patch, mpl.patches.Rectangle): return - ss = self.get_subplotspec() - if not ss: + ss = self._get_subplotspec_with_optional_colorbar() + if ss is None: return label_position = self.xaxis.get_label_position() if not ss.is_first_row(): # Remove top label/ticklabels/offsettext. @@ -4782,8 +4807,8 @@ def _label_outer_yaxis(self, *, skip_non_rectangular_axes, if skip_non_rectangular_axes and not isinstance(self.patch, mpl.patches.Rectangle): return - ss = self.get_subplotspec() - if not ss: + ss = self._get_subplotspec_with_optional_colorbar() + if ss is None: return label_position = self.yaxis.get_label_position() if not ss.is_first_col(): # Remove left label/ticklabels/offsettext. diff --git a/lib/matplotlib/tests/test_subplots.py b/lib/matplotlib/tests/test_subplots.py index a899110ac77a..d368257ae0e3 100644 --- a/lib/matplotlib/tests/test_subplots.py +++ b/lib/matplotlib/tests/test_subplots.py @@ -4,6 +4,7 @@ import numpy as np import pytest +import matplotlib as mpl from matplotlib.axes import Axes, SubplotBase import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal, image_comparison @@ -111,10 +112,13 @@ def test_shared(): @pytest.mark.parametrize('remove_ticks', [True, False]) -def test_label_outer(remove_ticks): - f, axs = plt.subplots(2, 2, sharex=True, sharey=True) +@pytest.mark.parametrize('with_colorbar', [True, False]) +def test_label_outer(remove_ticks, with_colorbar): + fig, axs = plt.subplots(2, 2, sharex=True, sharey=True) for ax in axs.flat: ax.set(xlabel="foo", ylabel="bar") + if with_colorbar: + fig.colorbar(mpl.cm.ScalarMappable(), ax=ax) ax.label_outer(remove_inner_ticks=remove_ticks) check_ticklabel_visible( axs.flat, [False, False, True, True], [True, False, True, False])