Repository: matplotlib/matplotlib

Issue:
[Bug]: Adding suptitle in compressed layout causes weird spacing
### Bug summary

If I create a "compressed" layout with two rows with both subplots having fixed aspect ratios, adding a suptitle will insert the space between the suptitle and the plots. I would expect the excess space to be put in below the subplots.

### Code for reproduction

```Python
fig, (ax0, ax1) = plt.subplots(
    nrows=2, figsize=(4, 10), layout="compressed",
    gridspec_kw={"height_ratios": (1 / 4, 3 / 4), "hspace": 0}
)

ax0.axis("equal")
ax0.set_box_aspect(1/3)

ax1.axis("equal")
ax1.set_box_aspect(1)

# Adding suptitle causes weird spacing
fig.suptitle("Title")
```


### Actual outcome

With suptitle:

![image](https://github.com/matplotlib/matplotlib/assets/5758119/16ebf4fb-5484-4457-b5d5-6a3d4b530e2f)

Without suptitle:

![image](https://github.com/matplotlib/matplotlib/assets/5758119/c6ee87e5-9a4e-4db6-ba78-1d1701ae3540)

Notably, this also happens without defining the gridspec kwargs.

### Expected outcome

I would expect the extra spacing to be added at the bottom, not be inserted between the suptitle and the plots.

### Additional information

_No response_

### Operating system

OSX

### Matplotlib Version

3.8.0

### Matplotlib Backend

module://matplotlib_inline.backend_inline

I am running this inside jupyter lab, but this persists even if I save the figure to a file.

### Python version

_No response_

### Jupyter version

_No response_

### Installation

pip


Discussion:
Hi as I want to working on it, please let me known this is your expected output
<img width="548" alt="Screenshot 2024-03-06 at 1 04 10 AM" src="https://github.com/matplotlib/matplotlib/assets/135497031/2990fdfc-d687-4b4b-9e89-bee2f87d733e">
please tell me in which file I need to make pull request for this issue
How can I make pull request for this issue by making new file in this repo or by editing in any other folder????
It seems like your fix involves adding the following code:

```python
plt.subplots_adjust(top=0.9)
```

However, this seems to produce the following error:
```
UserWarning: This figure was using a layout engine that is incompatible with subplots_adjust and/or tight_layout; not calling subplots_adjust.
```

This seems to indicate that this particular line of code isn't doing anything. And really, I don't see any difference in the figure.
I would not say that this is a bug: `suptitle` is adding the title to the top of the figure, as expected.  Compressed layout removes space from between the subplots by moving them together, as expected.  The inline backend automatically removes excess space from around the figure, but that is not the default in Matplotlib generally.

It could be a feature request to have compressed layout move `suptitle`, `supxlabel` and `supylabel` close to the axes.  I have no idea how easy that would be to implement.  I would want such a feature to be optional as I have use-cases where I want the axes central but a main title at the top, and I keep the original figure size.
Ah, I see, so the inline layout is removing the space from the bottom. So, in reality, the two plots are vertically centered, it's just not showing up in jupyter lab. Am I getting this right?

What I really want to achieve is have the plots stack from the top-down. And I know what aspect ratio I want the subplots to have. I don't really care how tall the final plot is, I just want to be able to set its width, and then have the height expand as needed. From my understanding, matplotlib doesn't really support this easily? Do you have any idea how I might go about doing this?
@pavlin-policar you will definitely have to work around this issue. But how depends on why are you choosing to use suptitle in this situation versus a title on the top axes.  
> So, in reality, the two plots are vertically centered, it's just not showing up in jupyter lab. Am I getting this right?

Yes, that’s right.
Yes, you are right.

> Do you have any idea how I might go about doing this?

A cheap workaround would be to use `ax0.set_title()` to set the title above the top axes. This is not really the same as the suptitle is centered over the figure but the Axes title is centered over the Axes box, but might be sufficient.

If you really want a suptitle, you'll have to tune the vertical position:

- either completely manual: `fig.suptitle("Title", y=0.78)`
- or by calculating the position:
  ```
  fig.draw_without_rendering()  # to calculate the Axes positions in the layout
  pad = 0.01  # in fractions of the figure height
  fig.suptitle("Title", y=ax0.get_position().y1 + pad, verticalalignment="bottom")
  ```

Thank you all for the wonderful suggestions!

In my actual use case, I have a 2 by 2 grid of subplots, so setting the axis title for the the top-left axis wouldn't work, since the title wouldn't be centered on the overall plot. I only posted a minimal example to reproduce my problem.

@timhoffm The second solution worked well for me. When saving to a file, it's important then to add `bbox_inches="tight"`, which clips off the extra whitespace from the top and bottom of the plot. Otherwise, the excess whitespace gets saved as well.

Judging by all your (very helpful) comments, I suppose this isn't really a bug, but expected behavior, so I'll close the issue. The solution does feel a bit hacky though and I do wish there was like a `layout="stack_vertical_and_ignore_height"` option that would do what I wanted, but I'm sure it's a fairly uncommon use-case.
I think this could stay open if someone wanted to fix it.  I think it would just take a bit of logic in compressed layout to check for suptitle 



Patch:
diff --git a/doc/api/next_api_changes/behavior/28734-REC.rst b/doc/api/next_api_changes/behavior/28734-REC.rst
new file mode 100644
index 000000000000..825922f4fafb
--- /dev/null
+++ b/doc/api/next_api_changes/behavior/28734-REC.rst
@@ -0,0 +1,7 @@
+``suptitle`` in compressed layout
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Compressed layout now automatically positions the `~.Figure.suptitle` just
+above the top row of axes.  To keep this title in its previous position,
+either pass ``in_layout=False`` or explicitly set ``y=0.98`` in the
+`~.Figure.suptitle` call.
diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py
index b960f363e9d4..28dbe1104004 100644
--- a/lib/matplotlib/_constrained_layout.py
+++ b/lib/matplotlib/_constrained_layout.py
@@ -140,6 +140,13 @@ def do_constrained_layout(fig, h_pad, w_pad,
                                     w_pad=w_pad, hspace=hspace, wspace=wspace)
                 else:
                     _api.warn_external(warn_collapsed)
+
+                if ((suptitle := fig._suptitle) is not None and
+                        suptitle.get_in_layout() and suptitle._autopos):
+                    x, _ = suptitle.get_position()
+                    suptitle.set_position(
+                        (x, layoutgrids[fig].get_inner_bbox().y1 + h_pad))
+                    suptitle.set_verticalalignment('bottom')
         else:
             _api.warn_external(warn_collapsed)
         reset_margins(layoutgrids, fig)


Test patch:
diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py
index 4dc4d9501ec1..e42e2ee9bfd8 100644
--- a/lib/matplotlib/tests/test_constrainedlayout.py
+++ b/lib/matplotlib/tests/test_constrainedlayout.py
@@ -662,6 +662,30 @@ def test_compressed1():
     np.testing.assert_allclose(pos.y0, 0.1934, atol=1e-3)
 
 
+def test_compressed_suptitle():
+    fig, (ax0, ax1) = plt.subplots(
+        nrows=2, figsize=(4, 10), layout="compressed",
+        gridspec_kw={"height_ratios": (1 / 4, 3 / 4), "hspace": 0})
+
+    ax0.axis("equal")
+    ax0.set_box_aspect(1/3)
+
+    ax1.axis("equal")
+    ax1.set_box_aspect(1)
+
+    title = fig.suptitle("Title")
+    fig.draw_without_rendering()
+    assert title.get_position()[1] == pytest.approx(0.7457, abs=1e-3)
+
+    title = fig.suptitle("Title", y=0.98)
+    fig.draw_without_rendering()
+    assert title.get_position()[1] == 0.98
+
+    title = fig.suptitle("Title", in_layout=False)
+    fig.draw_without_rendering()
+    assert title.get_position()[1] == 0.98
+
+
 @pytest.mark.parametrize('arg, state', [
     (True, True),
     (False, False),