Skip to content

[Bug]: Layout Managers are confused by complex arrangement of sub-figures and gridspec's #30076

Closed

Description

@boffi

Bug summary

When I run the code below

  1. compressed.png and constrained.png are (visually) identical, and the axes in the left of each sub-figure are getting smaller and smaller
  2. tight.png has all the axes in each sub-figure the same sizes, but those sizes are wrong
  3. none.png is correct (all the sizes remain unchanged, the sizes are OK) but the axes decorations overlap the plotting area

For your convenience I've uploaded the image files produced by my code.

Thank you in advance — gb


This behaviour was brought to my attention by
https://stackoverflow.com/questions/79614691/why-are-the-subplots-in-the-subfigures-getting-smaller

Code for reproduction

import matplotlib.pyplot as plt


def test(layout):
    fig = plt.figure(figsize=(12, 16), layout=layout)
    figures = fig.subfigures(4, 2)

    for f in figures.flatten():
        gs = f.add_gridspec(3, 2)
        for i in range(3):
            f.add_subplot(gs[i, 0]).plot()
        f.add_subplot(gs[:, 1]).plot()
    fig.savefig(layout + ".png")


test("compressed")
test("constrained")
test("none")
test("tight")

Actual outcome

Image
Image
Image
Image

Expected outcome

Something like none.png w/o the axes decoration overlapping the plot areas

Additional information

No response

Operating system

Open Suse Tumbleweed

Matplotlib Version

3.10.1

Matplotlib Backend

qtagg

Python version

Python 3.13.3

Jupyter version

No response

Installation

Linux package manager

Activity

  1. tacaswell commented on May 19, 2025

    @tacaswell
    Member

    I have seen similar things with code that set a fixed aspect ratio and manually adjusted the locations before running the layout code.

    dropping to either the figures rows or cols being 1 makes it work and

    import matplotlib.pyplot as plt
    
    
    def test(layout):
        fig = plt.figure(figsize=(12, 16), layout=layout)
        figures = fig.subfigures(4, 2)
        fig.suptitle(layout)
        for f in figures.flatten():
            axd = fig.subplot_mosaic('AD;BD;CD')
            for ax in axd.values():
                ax.plot()
        # fig.savefig(layout + ".png")
    
    
    test("compressed")
    test("constrained")
    test("none")
    test("tight")

    is broken even worse

  2. added this to the v3.11.0 milestone on May 19, 2025
  3. jklymak commented on May 19, 2025

    @jklymak
    Member

    Hmmm, well somehow the margin for the first subfigure is the starting point for the second figure, and then the margin gets made even larger for the second.

    import matplotlib.pyplot as plt
    
    
    
    def test(layout, labelled=False):
        ll = labelled
        fig = plt.figure(figsize=(4, 5), layout=layout)
        fig.suptitle(f'Labelled = {not labelled}', fontsize='small')
        figures = fig.subfigures(2, 1)
        for f in figures.flatten():
            gs = f.add_gridspec(2, 2)
            for i in range(2):
                ax = f.add_subplot(gs[i, 0])
                ax.plot()
                if not labelled:
                    ax.set_xlabel('BOOO', fontsize='large')
                    labelled = True
            f.add_subplot(gs[:, 1]).plot()
        fig.savefig(f'fig_{not ll}.png')
    test("constrained")
    test("constrained", labelled=True)
    plt.show()
    

    No label

    Image

    big label

    Image

  4. jklymak commented on May 19, 2025

    @jklymak
    Member

    I played with this for a while, and I do not see any obvious error in the logic.

    At its root, I think this is a limitation with the we we have chosen to set up the constraint solver, in that the behaviour above is following the constraints we have given it, but they are not rigorous enough to narrow the gap.

    I appreciate that it would be more sensible for the subfigures to not care about each other's layout, but practically, that is not how the constraint engine is set up, and the constraints are all solved at the same time.

    If you want deterministic behaviour here without changing the internals of constrained layout, you can simply create more subfigures so that the left-hand axes are not tied to what the right-hand column is doing:

    import matplotlib.pyplot as plt
    
    def test(layout):
        fig = plt.figure(figsize=(4, 5), layout='constrained')
        figures = fig.subfigures(2, 1)
        figures[0].set_facecolor('grey')
        figures[1].set_facecolor('red')
        for nn, f in enumerate(figures.flatten()):
            ffigs = f.subfigures(1, 2)
            ffigs[0].set_facecolor('lightgreen')
            ax = ffigs[0].subplots(2, 1)
            for i in range(2):
                ax[i].plot()
                if (nn==0) and (i == 0):
                    ax[i].set_xlabel('BOOOOO', fontsize=20)
            ffigs[1].set_facecolor('lightblue')
            ax = ffigs[1].add_subplot()
            ax.plot()
        fig.savefig(f'Fixed.png')
    
    test("constrained")
    plt.show()

    Image

    We can keep this open, but not sure anything will be done about it in the near future. If we were to do anything, it might be to try and make subfigure1 independent of subfigure2. The other thing that could be improved is the assignment of space between axes in one column when the other column does not have any axes spines at that row (eg the case here). That case is mildly under constrained, and as shown here doesn't work consistently.

  5. jklymak commented on May 20, 2025

    @jklymak
    Member

    Actually I found the bug #30089 is an easy fix.

  6. modified the milestones: v3.11.0, v3.10.4 on May 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions