Build publication-quality figures with matplotlib using the object-oriented Figure/Axes API, constrained_layout, rcParams customization, TrueType (Type-42) font embedding for journal submission, and CVD-safe palettes. Covers seaborn integration, common chart types, axis formatting, and the small gotchas that distinguish reproducible matplotlib from notebook scratch. Use when producing publication figures in Python — RNA-seq scatter, single-cell embeddings, generic biological plotting.
Build publication-quality figures with matplotlib using the object-oriented Figure/Axes API, constrained_layout, rcParams customization, TrueType (Type-42) font embedding for journal submission, and CVD-safe palettes. Covers seaborn integration, common chart types, axis formatting, and the small gotchas that distinguish reproducible matplotlib from notebook scratch. Use when producing publication figures in Python — RNA-seq scatter, single-cell embeddings, generic biological plotting.
Before using code patterns, verify installed versions match. If versions differ:
Python: pip show <package> then help(module.function) to check signatures
If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
matplotlib Fundamentals
"Make a publication figure in Python" -> Build via the object-oriented Figure/Axes API (not pyplot state-machine), with for axes alignment, for journal-compliant TrueType fonts, CVD-safe palettes, and rasterized point layers for large scatter. The pyplot interface is for notebook scratch; the Figure/Axes API is for reproducible figures.
constrained_layout
pdf.fonttype=42
Python: fig, ax = plt.subplots() -> ax.scatter / ax.plot / ax.bar; seaborn.objects (new grammar API) for ggplot-like
The Three Modern Defaults
Object-oriented API — fig, ax = plt.subplots(figsize=(4, 3)) then ax.scatter(x, y), ax.set_xlabel(...). The pyplot state-machine (plt.scatter, plt.xlabel) hides which axes are being modified and breaks in multi-subplot figures.
constrained_layout — plt.subplots(constrained_layout=True) automatically prevents axis-label clipping and tight-packs subplots. Replaces the older tight_layout() and is the default in matplotlib 3.6+.
Type-42 (TrueType) font embedding — plt.rcParams['pdf.fonttype']=42 produces searchable/editable PDF text. Default Type-3 PostScript glyphs are not searchable and rejected by Nature, IEEE, ACM, and many other publishers.
See data-visualization/color-palettes for full palette decision tree.
Saving
# PDF for vector text + raster scatter (best of both)
fig.savefig('figure.pdf', dpi=300, bbox_inches='tight')
# PNG for raster (web, presentations)
fig.savefig('figure.png', dpi=300, bbox_inches='tight')
# TIFF for some journals
fig.savefig('figure.tiff', dpi=300, pil_kwargs={'compression': 'tiff_lzw'})
# SVG for editable vector
fig.savefig('figure.svg', bbox_inches='tight')
Common Failure Modes
Default Type-3 fonts rejected by journals
Trigger: Default pdf.fonttype=3 (PostScript Type 3 glyphs as drawing operators).
Mechanism: Type-3 glyphs are not searchable or selectable; many journals reject.
Symptom: Submission rejected at automated check; "Type 3 fonts not permitted."
Fix:mpl.rcParams['pdf.fonttype']=42 AND ps.fonttype=42. Verify with pdffonts figure.pdf showing TrueType.
tight_layout fails on complex grids
Trigger:plt.tight_layout() on a figure with colorbars or shared axes.
Mechanism: tight_layout doesn't account for axes added after-the-fact (colorbars).