pythonmatplotlibsubplotaxis-labelsmatplotlib-gridspec

Align yaxis label spanning two axes with yaxis labels of one axes in subplots


I have a plot with 3 subplots: the top plot is larger than the two subsequent plots. They all share the same x- and y-axis. However, the ylabel is rather long, so the ylabels of the two bottom plots overlap (see img below). Here is the code:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.transforms import blended_transform_factory


imgs = [
    np.random.randn(200, 200),
    np.random.randn(50, 70),
    np.random.randn(100, 100),
]

figsize = [4, 6]
ylabel = "Long label for radius (cm)"

fig = plt.figure(figsize=figsize, layout="constrained")

gs = fig.add_gridspec(3, 1, height_ratios=[2, 1, 1])

axes = [fig.add_subplot(gs[i]) for i in range(3)]

for i in range(len(axes)):
    ax = axes[i]
    
    ax.imshow(imgs[i], aspect="auto")
    ax.set_xlabel("time (min)")
    
# from here on come different trials
for ax in axes:
    ax.set_ylabel(ylabel) 
fig.align_ylabels(axes)  # needed because of different y-ticks (100 vs 50)

enter image description here

I want to have a ylabel that spans the bottom two plots. I tried several things, that all do not work properly.

My first attempt was

axes[0].set_ylabel(ylabel)
axes[1].set_ylabel(ylabel, loc="bottom")
    
fig.align_ylabels(axes)

which gives correct x alignment but wrong y position:

enter image description here

Then, I tried

axes[0].set_ylabel(ylabel)
axes[1].set_ylabel(ylabel, loc="bottom", va="center")
fig.align_ylabels(axes)

giving a result failing with both, x and y alignment.

enter image description here

The best try so far was

axes[0].set_ylabel(ylabel)
axes[1].set_ylabel(ylabel)

ax = axes[1]
new_trafo = blended_transform_factory(
    x_transform=ax.yaxis.label.get_transform(),
    y_transform=ax.transAxes,
)
axes[1].set_ylabel(ylabel, y=0.0, va="center")
fig.align_ylabels(axes)

with this result (okayish y alignment that could be solved, but wrong x alignment).

enter image description here

None of the approaches worked properly. But there must be a way, to tell matplotlib that the bottom ylabel should have exactly the same x position (in figure pixels) as the top one even after recalculating the ylabel positions for layout="constrained" and fig.align_ylabels(axes). How can I fix this "alignment problem"?

EDIT: I am looking for a way to automatically get this desired output, where the two ylabels are aligned (having the same x position).

enter image description here

EDIT2: I want a matplotlib based solution. I could save/export the first try to svg and edit that output for correct y position of the bottom ylabel. But I think there is a solution within matplotlib.

Disclaimer: Splitting the ylabels of the bottom two plots in two lines is no solution for me, as it creates space between the top ylabel and its left axes neighbor. For simplicity, I have left out the left axes neighbor of the original problem, as described with the image below.

axes[0].set_ylabel(ylabel) 
for ax in axes[1:]:
    ax.set_ylabel("Long label\nfor radius (cm)")
fig.align_ylabels(axes)

enter image description here


Solution

  • After your base code, I used these 2 instructions to get the desired display:

    axes[2].set_ylabel("")
    axes[1].set_ylabel(ylabel, y=-0.4)
    

    plot