Thanks to visit codestin.com
Credit goes to github.com

Skip to content

BUG: Skip removed colorbar axes in constrained layout (fixes #31330)#31549

Closed
tinezivic wants to merge 1 commit intomatplotlib:mainfrom
tinezivic:fix/constrained-layout-colorbar-ax-remove-31330
Closed

BUG: Skip removed colorbar axes in constrained layout (fixes #31330)#31549
tinezivic wants to merge 1 commit intomatplotlib:mainfrom
tinezivic:fix/constrained-layout-colorbar-ax-remove-31330

Conversation

@tinezivic
Copy link
Copy Markdown
Contributor

@tinezivic tinezivic commented Apr 22, 2026

PR summary

Fixes #31330.

When the user calls cbar.ax.remove() directly (instead of the documented cbar.remove()), the colorbar Axes is detached from the figure but the parent axes' _colorbars list still holds a reference to it. On the next draw with constrained layout, make_layout_margins() and reposition_axes() iterate over ax._colorbars and call get_pos_and_bbox(cbax, renderer), which internally calls cbax.get_figure(root=False) — returning None for the removed Axes — and then crashes:

AttributeError: 'NoneType' object has no attribute 'transSubfigure'

Root cause: Artist.remove() sets _parent_figure = None, so get_figure(root=False) returns None after removal. The constrained layout code does not guard against this.

Fix: Add a get_figure(root=False) is None guard in both colorbar iteration loops (make_layout_margins and reposition_axes) to skip colorbar axes that have been removed from the figure.

Why not remove from _colorbars in Axes.remove()?
The current Colorbar.remove() API already handles that cleanup correctly. The crash happens only when users bypass it by calling cbar.ax.remove() directly. A null guard in the layout engine is the appropriate defense-in-depth fix and avoids changing cleanup semantics.

AI Disclosure

I used GitHub Copilot as a pair-programming aid to navigate the codebase and assist with writing the fix and test.

PR checklist

@github-actions github-actions Bot added topic: mplot3d topic: geometry manager LayoutEngine, Constrained layout, Tight layout labels Apr 22, 2026
…ib#31330)

When cbar.ax.remove() is called directly (bypassing Colorbar.remove()),
the parent axes' _colorbars list still holds a reference to the removed
colorbar Axes. On the next draw(), constrained layout crashes in
make_layout_margins() and reposition_axes() with:

    AttributeError: 'NoneType' object has no attribute 'transSubfigure'

because get_pos_and_bbox() calls ax.get_figure(root=False) which
returns None for a removed Axes.

Fix: guard both _colorbars iteration loops against removed axes by
checking get_figure(root=False) is None and continuing.

Adds regression test.
@tinezivic tinezivic force-pushed the fix/constrained-layout-colorbar-ax-remove-31330 branch from 8d454d1 to 4abd6e2 Compare April 22, 2026 21:33
@tinezivic
Copy link
Copy Markdown
Contributor Author

@jklymak Thanks — agreed that cbar.remove() is the documented cleanup path.

This PR is not trying to change that API or make cbar.ax.remove() the preferred approach. The narrower goal is just to avoid a crash when the colorbar Axes has already been removed and constrained layout still encounters a stale reference in ax._colorbars on the next draw.

Since cbar.ax is a public Axes object and Artist.remove() is a public API, skipping already-removed colorbar axes here seemed like a reasonable defense-in-depth fix — a crash is strictly worse than silently continuing.

(Also: force-pushed to rebase onto current main — the previous push accidentally included a commit from a different branch.)

@rcomer
Copy link
Copy Markdown
Member

rcomer commented Apr 23, 2026

IMO the problem is with colorbar and it should not be the layout engine's job to work around it. I have proposed an alternative solution at #31555.

@tinezivic
Copy link
Copy Markdown
Contributor Author

Closing this in favour of #31555 by @rcomer, which fixes the root cause rather than working around it in the layout engine.

My approach skipped dead colorbar axes during layout — that prevents the crash, but the stale reference in _colorbars remains. @rcomer's solution overrides ax._remove_method so that cbar.ax.remove() automatically delegates to cbar.remove(), keeping everything in a consistent state from the start. That's the right place to fix this.

Thanks for the quick alternative @rcomer!

@tinezivic tinezivic closed this Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: geometry manager LayoutEngine, Constrained layout, Tight layout

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Crash when removing colorbar axes in a constrained layout

2 participants