Home > Software design >  Matplotlib Subfigures: equal figure sizes regardless of ylabel
Matplotlib Subfigures: equal figure sizes regardless of ylabel

Time:01-12

I want to create a plot containing

  • 3 subfigures (1 row, 3 cols)
  • each of these subfigures contains 3x2 subplots with square image plots
  • the figure has a title, each subfigure has a title and the first row of subplots also has a title per subplot
  • the plots of the first column have a ylabel

Problem: if I use a ylabel for the leftmost plots, the images of the first subfigure get downscaled in order to keep the subfigure sizes identical. If I don't use ylabels, the scaling is correct.

With a ylabel:

With ylabel

Without a ylabel:

Without ylabel

Code:

import matplotlib.pyplot as plt


def example_plot(axis):
    axis.plot([0, 1])
    axis.set_aspect("equal")


if __name__ == "__main__":
    fig = plt.figure(constrained_layout=True)
    fig.suptitle("Title")
    subfigs = fig.subfigures(1, 3)
    for idx, subfig in enumerate(subfigs):
        ax = subfig.subplots(3, 2)
        subfig.suptitle("Subfig title")
        ax[0][0].set_title("Col 1")
        ax[0][1].set_title("Col 2")
        if idx == 0:
            ax[0][0].set_ylabel("Row 1")
            ax[1][0].set_ylabel("Row 2")
            ax[2][0].set_ylabel("Row 3")
        for row in range(3):
            for col in range(2):
                example_plot(ax[row][col])

    plt.show()

CodePudding user response:

A slightly ugly solution is to just have empty y-labels on the other subfigures, e.g.:

if __name__ == "__main__":
    fig = plt.figure(constrained_layout=True)
    fig.suptitle("Title")
    subfigs = fig.subfigures(1, 3)
    for idx, subfig in enumerate(subfigs):
        ax = subfig.subplots(3, 2)
        subfig.suptitle("Subfig title")
        ax[0][0].set_title("Col 1")
        ax[0][1].set_title("Col 2")
        if idx == 0:
            ax[0][0].set_ylabel("Row 1")
            ax[1][0].set_ylabel("Row 2")
            ax[2][0].set_ylabel("Row 3")
        else:
            for i in range(3):
                ax[i][0].set_ylabel(" ")

        for row in range(3):
            for col in range(2):
                example_plot(ax[row][col])

    plt.show()

Another option is to just set the y-labels as text boxes by working out where they should be, e.g.,

fig = plt.figure(constrained_layout=True)

# get axes object for main figure
axouter = fig.gca()
axouter.axis("off")  # make sure no axes show

fig.suptitle("Title")
subfigs = fig.subfigures(1, 3)
for idx, subfig in enumerate(subfigs):
    ax = subfig.subplots(3, 2)
    subfig.suptitle("Subfig title")
    ax[0][0].set_title("Col 1")
    ax[0][1].set_title("Col 2")
    if idx == 0:
        for i in range(3):
            # set text box within outer axes
            bbox = ax[i][0].get_position().bounds
            if i == 0:
                ypos = bbox[1]   (2/3) * bbox[3]
            else:
                ypos -= (4/3) * bbox[3]
            xpos = bbox[0] - 1.75 * bbox[2]

            axouter.text(xpos, ypos, f"Row {i   1}", rotation="vertical", va="center_baseline", ha="left")
    for row in range(3):
        for col in range(2):
            example_plot(ax[row][col])

I can't guarantee that the labels will appear where you want them, but you can probably tweak the positions as you need.

  • Related