Diagnosing Overlapping Labels in Matplotlib with Constrained Layout
A step‑by‑step guide to recognize, verify, and fix clipped or overlapping axis labels, titles, and colorbars when using Matplotlib’s constrained_layout engine.
07 Apr 2026, 07:14 UTC

Recognizable Condition
When saving a figure that contains multiple subplots, long tick labels, axis labels, a suptitle, or external artists such as colorbars or legends, you may observe:
- Tick labels overlapping neighboring axes or being clipped at the figure edge.
- Axis titles or
suptitletouching or overlapping tick labels. - Colorbars or legends drawn outside the axes being cut off.
The issue often appears only after plt.savefig (or when exporting to PDF/SVG) and may not be visible on‑screen depending on the backend DPI or figure size.
Cause & Diagnostic Table
| Symptom | Likely Cause | Quick Check |
|---|---|---|
| Overlap/clipping only after save, not on screen | Default fixed layout ignores text extents; backend DPI/size differs | Call fig.get_layout_engine(); compare fig.get_size_inches()*fig.dpi with screen DPI |
Persistent overlap even after tight_layout() |
tight_layout() or subplots_adjust() masking the real issue |
Search code for those calls; temporarily comment them out |
| Colorbar or legend clipped | Artist placed outside Axes without layout engine reserving space | Verify if fig.colorbar was called without ax= argument |
Ordered Checks
-
Confirm layout‑engine support
Ensure Matplotlib ≥ 3.6 (whereset_layout_engineexists). Run:
Required permission: none; just import the library.import matplotlib print(matplotlib.__version__) -
Identify active engine
After creating the figure, inspect:
Expected output for constrained layout:fig = plt.figure() print(fig.get_layout_engine())<matplotlib.layout_engine.ConstrainedLayoutEngine object at 0x...>. If the engine isNoneor aFixedLayoutEngine, constrained layout is not active. -
Check for conflicting layout calls
Look forplt.tight_layout(),fig.tight_layout(), or manualsubplots_adjustin the script. Temporarily disable them to see if the overlap changes. -
Inspect external artists
Verify that anyfig.colorbar,fig.legend, orax.annotatecalls pass the parent axes (ax=) when you want the layout engine to shrink the parent axes accordingly. -
Test with a minimal reproducer
Create a two‑subplot figure with long y‑tick labels and a colorbar:
Run the script twice: once withimport matplotlib.pyplot as plt import numpy as np x = np.linspace(0, 2*np.pi, 400) y = np.sin(x) fig, axs = plt.subplots(2, 1, layout='constrained') # try without layout later for ax in axs: ax.plot(x, y) ax.set_yticks([-1, -0.5, 0, 0.5, 1]) ax.set_yticklabels(['minimum', '-0.5', 'zero', '0.5', 'maximum']) ax.set_ylabel('Very long y‑axis label that needs space') # Add a colorbar to the first axis im = axs[0].scatter(x, y, c=x) fig.colorbar(im, ax=axs[0], label='Colorbar label') plt.savefig('test_constrained.png', dpi=150) plt.close()layout='constrained'and once with the default layout (remove the argument). Compare the saved PNGs visually.
Fixes Tied to Findings
-
Enable constrained_layout
Replace manualsubplots_adjustcalls with either:- When creating subplots:
plt.subplots(nrows, ncols, layout='constrained') - Or after figure creation:
fig.set_layout_engine('constrained')
- When creating subplots:
-
Properly attach colorbars and legends
Use theax=keyword so the layout engine knows which parent axes to shrink:
If you must keep a colorbar without specifyingfig.colorbar(im, ax=axs[0])fig.legend(axs, loc='outside upper center')ax=, manually increase padding:engine = fig.get_layout_engine() engine.set(w_pad=0.4, h_pad=0.4) # adjust as needed -
Fine‑tune padding
After enabling constrained_layout, adjust whitespace with the engine’ssetmethod:
Values are in inches; increase if labels still touch, decrease to save space.fig.get_layout_engine().set(w_pad=0.2, h_pad=0.2, wspace=0.05, hspace=0.05) -
Fallback to bbox_inches='tight'
If you cannot change the layout engine (e.g., third‑party code), save with:
This trims excess whitespace but may still clip if the artist lies outside the figure bounding box; use only as a last resort.plt.savefig('out.pdf', bbox_inches='tight')
Escalation Criteria
If the above steps do not resolve the issue, consider:
- Incompatible GridSpec constraints – constrained_layout cannot satisfy contradictory height/width ratios. In such cases, manually compute positions with
fig.add_axes([left, bottom, width, height])or useGridSpecwith explicitheight_ratiosandwidth_ratios. - Artists added after the draw call – any artist created after
fig.canvas.draw()(or after saving) is not seen by the layout engine. Ensure all annotations, colorbars, and legends are added before the first draw or save. - Animation or interactive resizing – constrained_layout runs at draw time, so temporary overlap may appear during rapid resizing. This is expected; you can disable the engine for the animation loop if needed.
- If you suspect a bug, create a minimal, self‑contained script that reproduces the problem and file an issue on the Matplotlib repository, including the output of
matplotlib.__version__andfig.get_layout_engine().
Verification
After applying a fix, verify the result by:
- Re‑running the minimal reproducer and visually inspecting the saved file.
- Printing the active layout engine and padding values:
print('Engine:', fig.get_layout_engine()) print('Padding:', fig.get_layout_engine().get_w_pad(), fig.get_layout_engine().get_h_pad()) - Checking that no
tight_layoutorsubplots_adjustcalls remain in the execution path (you can temporarily add aprintstatement to detect them). - Running the script in a clean virtual environment or on another machine to rule out local
rcParamsor style‑sheet interference.
Note: constrained_layout and tight_layout are mutually exclusive; calling both leads to undefined behavior. Always pick one layout strategy per figure.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.