Repository: matplotlib/matplotlib

Issue:
[Bug]: `Axis.set_in_layout` not respected?
### Bug summary

If a tick label appears right at the end of an axis, constrained layout increases the gap between the subplots to accommodate it.  This is reasonable behaviour, but sometimes I would like to turn it off.  I tried setting `xaxis.set_in_layout(False)`, but that doesn't seem to make a difference.  Should it work?

### Code for reproduction

```Python
import matplotlib.pyplot as plt


def example_plot(xmax):   
    fig, (ax1, ax2) = plt.subplots(ncols=2, layout='constrained')
    
    ax1.set_xlim([0, xmax])
    ax1.set_xticks([0, 100])
    ax1.xaxis.set_in_layout(False)
    
    fig.savefig(f'example_x{xmax}.png')
    
    plt.close(fig)
    
    
for xmax in 100, 120:
    example_plot(xmax)
```


### Actual outcome

`example_x120.png`
![example_x120](https://github.com/user-attachments/assets/986b6055-55c4-4c8e-beb0-10c61d36fcba)
`example_x100.png`
![example_x100](https://github.com/user-attachments/assets/4a3feefb-b26c-4384-b5ce-2d8ef99e200f)

When the "100" tick label is at the end of the x-axis the gap between the subplots is bigger.

### Expected outcome

I would like the gap between the subplots to not depend on the "100" label.

### Additional information

I haven't tried, but suspect we could make this work by just returning `None` here if `for_layout_only` is `True` and `self.get_in_layout` is `False`.
https://github.com/matplotlib/matplotlib/blob/040091e9ea35df756ee838fe8b83a9deabe16f26/lib/matplotlib/axis.py#L1365-L1366

### Operating system

RHEL7

### Matplotlib Version

3.9.1

### Matplotlib Backend

QtAgg

### Python version

3.12.4

### Jupyter version

N/A

### Installation

conda


Discussion:
I don't see why it should not be allowed to work, and your propped change seems good.
Having semi-recently looked at #28574, I'm fairly certain that none of the `Axis`, `Spine`, or `Tick`s obey `set_in_layout`, but only because they were never set up that way.
I assumed it  would be futile to try to set it on the individual tick because of the way they are dynamically generated (my "real" example uses `MaxNLocator` on a colorbar).

I'm unclear why `Spine`s wouldn't work properly as they appear to be handled within the general list of artists:
https://github.com/matplotlib/matplotlib/blob/30f803b2e9b5e237c5c31df57f657ae69bec240d/lib/matplotlib/axes/_base.py#L4391-L4421



Patch:
diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py
index 483c9a3db15f..158d4a02ee61 100644
--- a/lib/matplotlib/axis.py
+++ b/lib/matplotlib/axis.py
@@ -1362,7 +1362,7 @@ def get_tightbbox(self, renderer=None, *, for_layout_only=False):
         collapsed to near zero.  This allows tight/constrained_layout to ignore
         too-long labels when doing their layout.
         """
-        if not self.get_visible():
+        if not self.get_visible() or for_layout_only and not self.get_in_layout():
             return
         if renderer is None:
             renderer = self.figure._get_renderer()


Test patch:
diff --git a/lib/matplotlib/tests/test_axis.py b/lib/matplotlib/tests/test_axis.py
index 97b5f88dede1..33af30662a33 100644
--- a/lib/matplotlib/tests/test_axis.py
+++ b/lib/matplotlib/tests/test_axis.py
@@ -8,3 +8,24 @@ def test_tick_labelcolor_array():
     # Smoke test that we can instantiate a Tick with labelcolor as array.
     ax = plt.axes()
     XTick(ax, 0, labelcolor=np.array([1, 0, 0, 1]))
+
+
+def test_axis_not_in_layout():
+    fig1, (ax1_left, ax1_right) = plt.subplots(ncols=2, layout='constrained')
+    fig2, (ax2_left, ax2_right) = plt.subplots(ncols=2, layout='constrained')
+
+    # 100 label overlapping the end of the axis
+    ax1_left.set_xlim([0, 100])
+    # 100 label not overlapping the end of the axis
+    ax2_left.set_xlim([0, 120])
+
+    for ax in ax1_left, ax2_left:
+        ax.set_xticks([0, 100])
+        ax.xaxis.set_in_layout(False)
+
+    for fig in fig1, fig2:
+        fig.draw_without_rendering()
+
+    # Positions should not be affected by overlapping 100 label
+    assert ax1_left.get_position().bounds == ax2_left.get_position().bounds
+    assert ax1_right.get_position().bounds == ax2_right.get_position().bounds