Saving Figures
In one line: vector (PDF/SVG) for publication, PNG at 300+ DPI for everything else,
fonttype=42so the text stays editable, andrasterized=Trueon huge scatters.
The call
fig.savefig("figure.pdf", bbox_inches="tight")
fig.savefig("figure.png", dpi=300, bbox_inches="tight")
fig.savefig("figure.svg")
fig.savefig("figure.pdf", bbox_inches="tight", transparent=True)
plt.close(fig)
bbox_inches="tight" crops whitespace and — importantly — rescues labels that would otherwise be clipped off the edge.
fig.savefig, not plt.savefig, so you know which figure you saved. See pyplot vs Object-Oriented API.
Format choice
| Format | Use for | Notes |
|---|---|---|
| journals, LaTeX | vector, embeds fonts, universal | |
| SVG | web, editing in Illustrator/Inkscape | vector, XML |
| EPS | legacy journals | vector, no transparency |
| PNG | slides, notebooks, GitHub | raster; set dpi |
| TIFF | some journals demand it | raster, large |
| JPG | never for plots | lossy compression creates artefacts on sharp lines |
Most journals want vector for line art (300–600 DPI equivalent) and accept 300 DPI raster for photographs and dense scatters.
The font problem
import matplotlib as mpl
mpl.rcParams["pdf.fonttype"] = 42 # TrueType — text stays text
mpl.rcParams["ps.fonttype"] = 42
mpl.rcParams["svg.fonttype"] = "none" # SVG: don't convert text to paths
By default matplotlib embeds Type 3 fonts, which many PDF editors cannot edit and some journals reject. With fonttype = 42 your labels remain selectable, searchable, and editable in Illustrator — which matters because the last round of figure revision almost always happens there.
Set this once at the top of every plotting script. It costs nothing and saves a resubmission.
Rasterizing selected elements
ax.scatter(x, y, s=1, alpha=0.3, rasterized=True)
fig.savefig("volcano.pdf", dpi=300)
A vector PDF stores every one of your 500,000 scatter points as a separate object. The file becomes hundreds of megabytes and crashes Illustrator and some PDF viewers.
rasterized=True renders that Artist as pixels while keeping axes, ticks and text as vectors. You get a small file with sharp, editable text and a picture-quality point cloud. Essential for volcano plots, Manhattan plots, and single-cell embeddings.
fig.savefig(..., dpi=300) controls the resolution of the rasterized parts even in a vector file.
Sizing for publication
Get this right at creation, not afterwards:
fig, ax = plt.subplots(figsize=(3.5, 2.6)) # single column
fig, axes = plt.subplots(1, 2, figsize=(7.2, 3)) # double column
| Target | Width (inches) | Width (mm) |
|---|---|---|
| Single column (most journals) | 3.3–3.5 | 85–89 |
| 1.5 column | 5.0–5.5 | 127–140 |
| Double column | 6.9–7.2 | 175–183 |
| Nature single | 3.5 | 89 |
| Nature double | 7.2 | 183 |
Never scale a figure in the manuscript. If you draw at 10 inches wide with 10 pt fonts and the journal shrinks it to 3.5 inches, your fonts become 3.5 pt and are illegible. Draw at final size and set fonts to what you want in print (7–9 pt for labels is typical).
Check by printing at 100% and reading it.
A reusable save function
from pathlib import Path
import matplotlib as mpl
mpl.rcParams.update({
"pdf.fonttype": 42, "ps.fonttype": 42, "svg.fonttype": "none",
"savefig.bbox": "tight", "savefig.dpi": 300,
"figure.dpi": 100,
})
def save(fig, name, outdir="figures", formats=("pdf", "png")):
outdir = Path(outdir); outdir.mkdir(parents=True, exist_ok=True)
for fmt in formats:
fig.savefig(outdir / f"{name}.{fmt}", format=fmt)
plt.close(fig)
Saving both PDF (for the manuscript) and PNG (for the slide deck, the Slack message, the lab meeting) every time removes a whole category of "can you send me that as a PNG" round-trips.
Memory in loops
for gene in genes:
fig, ax = plt.subplots()
ax.plot(...)
fig.savefig(f"plots/{gene}.png", dpi=150)
plt.close(fig) # ← without this, memory grows until the job dies
Matplotlib keeps every unclosed figure alive. Past 20 you get a warning; past a few thousand you get an OOM kill. plt.close(fig) is not optional in a loop.
Reproducible metadata
fig.savefig("fig.pdf", metadata={
"Creator": "analysis.py",
"Title": "Figure 2",
"Subject": f"commit {git_sha}, seed {SEED}",
})
Embedding the git commit and random seed in the file means that in eighteen months, when a reviewer asks how a figure was made, the figure itself tells you.
Transparent backgrounds
fig.savefig("fig.png", transparent=True, dpi=300)
Good for slides over a coloured background. Bad if you also drew white text or white-edged markers — they vanish.
Common mistakes
- Default
fonttypeproducing uneditable Type 3 fonts. - No
bbox_inches="tight", clipping the y-label. - Not rasterizing a 500k-point scatter, producing a 200 MB PDF.
- Not closing figures in a loop.
- Drawing at the wrong size and scaling later.
- JPEG for plots.
- PNG at default 100 DPI for a journal.
- Saving before drawing is finished —
savefigcaptures the current state. plt.savefig()with several figures open, saving the wrong one.- Forgetting the PNG version and being asked for it later.
See also
Figure and Axes · rcParams and Style Sheets · Matplotlib Artists · Subplots and Layout · Applied - Reproducible Environment