8000 [Bug]: Axes.label_outer() does not work when there is a colorbar · Issue #27305 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

[Bug]: Axes.label_outer() does not work when there is a colorbar #27305

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

Open
chrisyeh96 opened this issue Nov 10, 2023 · 7 comments · May be fixed by #30098
Open

[Bug]: Axes.label_outer() does not work when there is a colorbar #27305

chrisyeh96 opened this issue Nov 10, 2023 · 7 comments · May be fixed by #30098
Labels
status: has patch patch suggested, PR still needed topic: color/colorbar topic: geometry manager LayoutEngine, Constrained layout, Tight layout

Comments

@chrisyeh96
Copy link

Bug summary

Axes.label_outer() fails to hide axis and tick labels for plots that have a colorbar.

Code for reproduction

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)

fig, axs = plt.subplots(2, 2)
axs[0, 0].plot(x, y)
cl2c = axs[0, 1].scatter(x, y, c=x)
fig.colorbar(cl2c, ax=axs[0, 1], label='c-label')
axs[1, 0].plot(x, y)
axs[1, 1].plot(x, y)

for ax in axs.flat:
    ax.set(xlabel='x-label', ylabel='y-label')

# try to hide x labels and tick labels for top plots and y ticks for right plots,
# but this fails to hide the labels for axs[0, 1] due to its colorbar
for ax in axs.flat:
    ax.label_outer()

Actual outcome

The output of the code above gives:

image

Expected outcome

I would have expected the plot to look like:

image

This expected plot was created by creating the colorbar after the ax.label_outer() calls. However, I would not have expected this to have affected whether the labels/ticks are removed.

fig, axs = plt.subplots(2, 2)
axs[0, 0].plot(x, y)
cl2c = axs[0, 1].scatter(x, y, c=x)
axs[1, 0].plot(x, y)
axs[1, 1].plot(x, y)

for ax in axs.flat:
    ax.set(xlabel='x-label', ylabel='y-label')

# Hide x labels and tick labels for top plots and y ticks for right plots.
for ax in axs.flat:
    ax.label_outer()

fig.colorbar(cl2c, ax=axs[0, 1], label='c-label')

fig.tight_layout()

Additional information

No response

Operating system

Ubuntu 20.04.5 LTS

Matplotlib Version

3.8.0

Matplotlib Backend

module://matplotlib_inline.backend_inline

Python version

Python 3.11.6

Jupyter version

7.0.6

Installation

conda

@jklymak
Copy link
Member
jklymak commented Nov 10, 2023

Can you try w layout='constrained'. I think with normal colorbars the parent axes gets shoved down a level in the subplot spec and hence becomes "outer" to the axes/colorbar pair. This doesn't happen for layout='constrained' or in the colorbar you can also do use_gridspec=False and I think that will fix the issue

@jklymak
Copy link
Member
jklymak commented Nov 11, 2023

I checked, and my diagnosis above is correct. Given that there are at least three work arounds for this, I don't know what the appetite is to check if the axes is embedded in a subplotsepc with a colorbar, and that the parent subplotspec is "inner". I'd recommend using layout='constrained'

@anntzer
Copy link
Contributor
anntzer commented Nov 11, 2023

The following patch implements the necessary checking of gridspecs for non-constrainedlayout-positioned colorbars, AFAICT:

diff --git i/lib/matplotlib/axes/_base.py w/lib/matplotlib/axes/_base.py
index b113b0f211..ac991167e1 100644
--- i/lib/matplotlib/axes/_base.py
+++ w/lib/matplotlib/axes/_base.py
@@ -4560,13 +4560,41 @@ class _AxesBase(martist.Artist):
         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()
+        if (isinstance(gs, mpl.gridspec.GridSpecFromSubplotSpec)
+                and gs.nrows * gs.ncols == 2):
+            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 isinstance(ax.get_subplotspec().get_gridspec()
+                                       ._subplot_spec.get_gridspec(),
+                                       mpl.gridspec.GridSpecFromSubplotSpec)
+                        and (ax.get_subplotspec().get_gridspec()
+                             ._subplot_spec.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()
+        ss = self._get_subplotspec_with_optional_colorbar()
         if not ss:
             return
         label_position = self.xaxis.get_label_position()
@@ -4593,7 +4621,7 @@ class _AxesBase(martist.Artist):
         if skip_non_rectangular_axes and not isinstance(self.patch,
                                                         mpl.patches.Rectangle):
             return
-        ss = self.get_subplotspec()
+        ss = self._get_subplotspec_with_optional_colorbar()
         if not ss:
             return
         label_position = self.yaxis.get_label_position()

@jklymak
Copy link
Member
jklymak commented Nov 11, 2023

Ha, cross-post. I'm still not convinced the above is worth the bother, but wouldn't block if someone wanted to add it.

@rcomer
Copy link
Member
rcomer commented Nov 14, 2023

Will #27306 simplify the patch?

@anntzer
Copy link
Contributor
anntzer commented Nov 14, 2023

Yes, this patch is what motivated #27306 in fact (you'll only need to dig one gridspec in rather than two).

@anntzer
Copy link
Contributor
anntzer commented Nov 15, 2023

The simplified patch now that #27306 has been merged:

diff --git i/lib/matplotlib/axes/_base.py w/lib/matplotlib/axes/_base.py
index b113b0f211..07f767c253 100644
--- i/lib/matplotlib/axes/_base.py
+++ w/lib/matplotlib/axes/_base.py
@@ -4560,13 +4560,37 @@ class _AxesBase(martist.Artist):
         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()
+        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()
+        ss = self._get_subplotspec_with_optional_colorbar()
         if not ss:
             return
         label_position = self.xaxis.get_label_position()
@@ -4593,7 +4617,7 @@ class _AxesBase(martist.Artist):
         if skip_non_rectangular_axes and not isinstance(self.patch,
                                                         mpl.patches.Rectangle):
             return
-        ss = self.get_subplotspec()
+        ss = self._get_subplotspec_with_optional_colorbar()
         if not ss:
             return
         label_position = self.yaxis.get_label_position()

@rcomer rcomer added topic: geometry manager LayoutEngine, Constrained layout, Tight layout topic: color/colorbar status: has patch patch suggested, PR still needed labels Nov 15, 2023
@anntzer anntzer linked a pull request May 23, 2025 that will close this issue
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: has patch patch suggested, PR still needed topic: color/colorbar topic: geometry manager LayoutEngine, Constrained layout, Tight layout
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants
0