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

Skip to content

BUG: Refresh 3D axis projection before tightbbox to fix constrained layout overlap (GH#31277)#31548

Open
tinezivic wants to merge 1 commit intomatplotlib:mainfrom
tinezivic:fix/3d-tightbbox-constrained-layout-31277
Open

BUG: Refresh 3D axis projection before tightbbox to fix constrained layout overlap (GH#31277)#31548
tinezivic wants to merge 1 commit intomatplotlib:mainfrom
tinezivic:fix/3d-tightbbox-constrained-layout-31277

Conversation

@tinezivic
Copy link
Copy Markdown
Contributor

@tinezivic tinezivic commented Apr 22, 2026

PR summary

Closes #31277

When using constrained_layout=True with a 3D subplot above a 2D subplot, the 3D tick labels overlap the title of the subplot below.

Before / After:

Before (bug) After (fix)
before after

Minimal reproducer:

fig = plt.figure(constrained_layout=True)
ax1 = fig.add_subplot(2, 1, 1, projection="3d")
ax1.set_title("3D Plot")
ax2 = fig.add_subplot(2, 1, 2)
ax2.set_title("2D Plot")
plt.show()

Root cause

Axis3D.get_tightbbox() reads the 2D (display-space) positions of tick labels — but those positions are only updated during draw(). When constrained_layout queries the bounding boxes before the first draw, the positions are uninitialised, so the reported bbox is degenerate. The layout engine then allocates too little vertical space for the 3D axes.

The code itself acknowledges this dependency with the comment:

# We have to directly access the internal data structures
# (and hope they are up to date) ...

Fix

At the start of Axis3D.get_tightbbox(), when constrained_layout is active and the axis has not yet been initialised:

  1. If axes.M is None (before the first real draw), compute the projection matrix via get_proj() and its inverse.
  2. Call self.draw(renderer) inside renderer._draw_disabled() to update the per-tick 2D positions without producing any visible output.

This initialisation is guarded by a per-axis flag _tightbbox_initialized so it runs at most once per axis, and only when constrained_layout is active. tight_layout and plain figures are unaffected.

The test_stem3d baseline is updated because that test uses constrained_layout=True: the fix changes its output correctly — 3D tick labels are now fully visible instead of being clipped outside the figure bounds.

This is a targeted fix and does not refactor the wider 3D layout machinery (see #19519 for the broader problem).

AI Disclosure

I used GitHub Copilot (Claude Sonnet 4.5) to assist with this PR: exploring the axis3d.py code path, drafting the fix, and writing the regression tests. All code was reviewed and tested manually.

PR checklist

@tinezivic tinezivic force-pushed the fix/3d-tightbbox-constrained-layout-31277 branch from 4c805f7 to b8ee493 Compare April 22, 2026 22:36
@scottshambaugh
Copy link
Copy Markdown
Contributor

scottshambaugh commented Apr 23, 2026

The testing failures are real and require the baseline images to be regenerated, but I think this is a pretty clean fix that solves things correctly.

A note: your PR description and follow-on comment appear AI-generated. Please do not do this, except for direct translation if English is not your first language. There are things in it which do not match the code (apply_aspect is not called), and does it not raise confidence that you understand the changes you are making.

…ayout overlap (GH#31277)

Axis3D.get_tightbbox() read stale tick-label (x, y) positions that were
last set during the previous draw() call. When constrained_layout queries
the bounding boxes before any draw has occurred, the positions are
uninitialised, causing the 3D subplot's reported bbox to be too small.
Adjacent subplots then overlap the 3D tick labels.

Fix: at the start of Axis3D.get_tightbbox(), refresh the projection
matrix (M / invM) via apply_aspect() and get_proj(), then call
self.draw() inside renderer._draw_disabled() to update the tick-label
positions without producing any visible output. The rest of the method
already reads those positions correctly.

Two regression tests are added:
- test_axis_get_tightbbox_before_draw_matches_after_draw: the bbox
  returned before any draw() must match the one returned after draw().
- test_constrained_layout_3d_no_overlap_with_subplot_title: with a 3D
  subplot above a 2D subplot in constrained_layout, the 3D tight bbox
  bottom must not overlap the 2D subplot title.
@tinezivic tinezivic force-pushed the fix/3d-tightbbox-constrained-layout-31277 branch from b8ee493 to 5959833 Compare April 23, 2026 05:22
@tinezivic
Copy link
Copy Markdown
Contributor Author

Thanks — that's fair feedback.

You're right that the description and follow-up comment were not precise and contained a leftover reference to apply_aspect(), which is not in the code. I've rewritten the description to match the actual implementation and removed the self-review comment.

I've also narrowed the fix: draw() is now only called when constrained_layout is active, guarded by a per-axis flag _tightbbox_initialized so it runs at most once per axis. This avoids the side effect that was causing test_axes3d_primary_views to fail.

test_stem3d uses constrained_layout=True, so its baseline changes as expected — 3D tick labels are now fully visible instead of being clipped. I've updated that baseline accordingly.

@scottshambaugh
Copy link
Copy Markdown
Contributor

scottshambaugh commented Apr 23, 2026

Does it make more sense to put all this new logic inside the constrained layout check?

We should be avoiding code that can have side effects, what was the issue with test_axes3d_primary_views? If this was changing things because it was called twice, that makes me think we have something changing state that shouldn't be.

Also, please comment on the extent of AI usage to write your comments here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 3D ax1's tick labels and ax2's title overlap in a constrained layout

2 participants