Repository: matplotlib/matplotlib

Issue:
[Bug]: Figure.autofmt_xdate() not working in presence of colorbar with constrained layout
### Bug summary

If a colorbar is present and with constrained layout, `Figure.autofmt_xdate()` is not doing anything. I guess that might be a limitation of the constrained layout mechanism in which case this can get closed right away probably. Just wanted to report it here anyway.

### Code for reproduction

```Python
import matplotlib.pyplot as plt 

fig, ax = plt.subplots(layout="constrained")
im = ax.imshow([[1, 4, 6], [2, 3, 5]])
plt.colorbar(im)
fig.autofmt_xdate()
plt.show()
```


### Actual outcome

![Figure_1](https://github.com/user-attachments/assets/96f41a95-a72f-48ce-9492-664a0c9c1342)


### Expected outcome

If not for the colorbar, x-Axis ticklabels would get rotated.

```python
import matplotlib.pyplot as plt

fig, ax = plt.subplots(layout="constrained")
im = ax.imshow([[1, 4, 6], [2, 3, 5]])
# plt.colorbar(im)
fig.autofmt_xdate()
plt.show()
```

![Figure_1](https://github.com/user-attachments/assets/886af9e1-65e2-4a3d-8c77-d5ed5150dcc8)


### Additional information

_No response_

### Operating system

Debian

### Matplotlib Version

3.9.2

### Matplotlib Backend

qtagg

### Python version

3.10.5

### Jupyter version

_No response_

### Installation

conda


Discussion:
OK, that was a strange one - colorbar has two different ways of being laid out, and the default changes no not use the subplotspec for the colorbar if `layout='constrained'`.  `fig.autofmt_xdate` checks if all the axes have a subplotspec, and if they don't it doesn't do anything.  

`autofmt_xdate` is ancient, and I'm not sure we would use it today.  However, I'd propose we ignore colorbars in the list of axes.  
Note you get the same behaviour with 
```
fig, ax = plt.subplots() #layout="constrained")
im = ax.imshow([[1, 4, 6], [2, 3, 5]])
plt.colorbar(im, use_gridspec=False)
fig.autofmt_xdate()
plt.show()
```
Yeah, like I said, it's probably too much of an edge case to worry about it, feel free to just close if you think thats best :+1: 

I always liked `autofmt_xdate()` but I agree it might be a bit weird to keep maintaining it  on the `Figure` object with all the different things that can be in a figure probably. Maybe adding the same functionality on `Axis` level would be an option? =)
I think the fix is relatively straightforward - can just check for `ax.label` has `colorbar` in it.   



Patch:
diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index d3623d1e82b8..e1922e8dde84 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -194,14 +194,15 @@ def autofmt_xdate(
             Selects which ticklabels to rotate.
         """
         _api.check_in_list(['major', 'minor', 'both'], which=which)
-        allsubplots = all(ax.get_subplotspec() for ax in self.axes)
-        if len(self.axes) == 1:
+        axes = [ax for ax in self.axes if ax._label != '<colorbar>']
+        allsubplots = all(ax.get_subplotspec() for ax in axes)
+        if len(axes) == 1:
             for label in self.axes[0].get_xticklabels(which=which):
                 label.set_ha(ha)
                 label.set_rotation(rotation)
         else:
             if allsubplots:
-                for ax in self.get_axes():
+                for ax in axes:
                     if ax.get_subplotspec().is_last_row():
                         for label in ax.get_xticklabels(which=which):
                             label.set_ha(ha)
@@ -211,7 +212,8 @@ def autofmt_xdate(
                             label.set_visible(False)
                         ax.set_xlabel('')
 
-        if allsubplots:
+        engine = self.get_layout_engine()
+        if allsubplots and (engine is None or engine.adjust_compatible):
             self.subplots_adjust(bottom=bottom)
         self.stale = True
 


Test patch:
diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py
index 61fd71875e2a..edf5ea05f119 100644
--- a/lib/matplotlib/tests/test_figure.py
+++ b/lib/matplotlib/tests/test_figure.py
@@ -496,6 +496,20 @@ def test_autofmt_xdate(which):
             assert int(label.get_rotation()) == angle
 
 
+def test_autofmt_xdate_colorbar_constrained():
+    # check works with a colorbar.
+    # with constrained layout, colorbars do not have a gridspec,
+    # but autofmt_xdate checks if all axes have a gridspec before being
+    # applied.
+    fig, ax = plt.subplots(layout="constrained")
+    im = ax.imshow([[1, 4, 6], [2, 3, 5]])
+    plt.colorbar(im)
+    fig.autofmt_xdate()
+    fig.draw_without_rendering()
+    label = ax.get_xticklabels(which='major')[1]
+    assert label.get_rotation() == 30.0
+
+
 @mpl.style.context('default')
 def test_change_dpi():
     fig = plt.figure(figsize=(4, 4))