Exports publication-ready figures with the correct vector/raster split, embedded editable fonts, color-space-robust palettes, and journal-correct sizing and resolution in matplotlib and ggplot2. Use when preparing figures for journal submission, exporting a dense single-cell or GWAS plot without producing an unopenable vector file, or fixing fonts and colors that break in print.
Exports publication-ready figures with the correct vector/raster split, embedded editable fonts, color-space-robust palettes, and journal-correct sizing and resolution in matplotlib and ggplot2. Use when preparing figures for journal submission, exporting a dense single-cell or GWAS plot without producing an unopenable vector file, or fixing fonts and colors that break in print.
Before using code patterns, verify installed versions match. If versions differ:
Python: pip show matplotlib then help(matplotlib.figure.Figure.savefig); introspect matplotlib.rcParams if a key is renamed
R: packageVersion('ggplot2') then ?ggsave
If code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
Publication-Ready Figure Export
"Export this figure for the journal" -> Save the plot so each part of it survives production: vector structure stays crisp, the dense data layer stays a reasonable file size, text stays editable, and color survives the print conversion.
Python: fig.savefig('fig.pdf', dpi=600) with publication rcParams set
A publication figure is not one object; it is four superimposed layers, and export means giving each the representation it needs:
Vector structure - axes, ticks, spines, gridlines, fit lines, error bars, annotations. Resolution-independent; must stay vector so the typesetter can scale it to 89 mm without pixelation.
Raster data layer - the dense part: a scatter with 10^5-10^7 points, a heatmap, a micrograph. Drawing a million points as a million vector circles makes a hundreds-of-MB PDF that crashes Illustrator. This layer wants to be pixels.
Type - every glyph. Editors need it to stay selectable text, not flattened paths or pixels baked into the raster.
Color encoding - the data-to-color mapping. A scientific choice (perceptual uniformity, color-vision-deficiency safety, grayscale survival) that also interacts with the RGB->CMYK conversion the journal performs without asking.
The expert move is the hybrid figure: rasterize only layer 2, keep layers 1 and 3 vector, embed editable fonts, and pick a colormap that survives CMYK and grayscale. Everything below serves that.
A reproducibility framing: a figure is a pure function of (data, code, theme, font availability). If any of those is unpinned, the figure is not reproducible.
The Hybrid Figure (rasterize only the dense layer)
The single most important export skill for single-cell (UMAP/tSNE) and GWAS (Manhattan) figures. A fully-vector million-point scatter is unopenable and gets rejected by the typesetter's RIP; rasterizing just the data layer keeps file size sane while axes and text stay crisp at any zoom.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(3.5, 3.0)) # physical inches = the real size control
ax.scatter(x, y, s=2, rasterized=True, zorder=0) # 10^6 points -> embedded raster
ax.plot(xfit, yfit, color='black', zorder=2) # stays vector
ax.set_xlabel('UMAP1') # stays vector text
fig.savefig('fig.pdf', dpi=600) # dpi governs ONLY the rasterized layer
In a vector container, savefig(dpi=...) sets the resolution of the embedded raster patch and does nothing to the vector parts. ax.set_rasterization_zorder(0) rasterizes every artist below a z-order cleanly. In R use ggrastr::rasterise(geom_point(size=0.3), dpi=600) (wraps any geom since 0.2.0), keeping theme_* vector.
Fonts: Keep the Text Editable
The most common typesetter complaint about matplotlib PDFs is un-editable text. matplotlib's default pdf.fonttype is 3 (Type 3), which embeds glyphs as PostScript procedures that import into Illustrator as ungrouped paths and cannot be re-selected as text. Set 42 (TrueType, wrapped) so text stays selectable and editable. Fix it once, globally:
import matplotlib as mpl
mpl.rcParams['pdf.fonttype'] = 42# TrueType, editable text in PDF
mpl.rcParams['ps.fonttype'] = 42# same for EPS/PS
mpl.rcParams['svg.fonttype'] = 'none'# SVG: emit real <text>, reference the font by name
Type 42/TrueType/Type 3 fonts are subsetted (only used glyphs embedded); Type 1 are not. With large glyph sets (CJK) Type 42 can bloat the PDF - a real tradeoff. svg.fonttype='none' keeps words editable in Inkscape/Illustrator but the viewer must have the font (else it substitutes); the default 'path' outlines every glyph (portable, uneditable). Default to editable text; only outline if a specific production desk asks.
In R, the base pdf() device has weak font handling and inconsistent cross-OS rendering; use cairo_pdf (embeds fonts and supports alpha). For raster, ragg::agg_png()/agg_tiff() render anti-aliased text better than cairo/base devices. Rule of thumb: showtext for vector devices, ragg for raster.
Color Space: The Author Works in RGB, Print Is CMYK
Screens are additive RGB; offset print is subtractive CMYK. matplotlib and ggplot2 author in RGB only - there is no honest path to a true CMYK figure from them. The journal's pipeline converts RGB->CMYK, and because the CMYK gamut is smaller than sRGB, saturated out-of-gamut colors shift: pure RGB blue (#0000FF) and vivid green/cyan come back muddier and darker on paper. The neon scatter that pops on screen can print gray.
What to do: pull colors slightly off full saturation (they survive conversion better); soft-proof downstream in Illustrator/Photoshop with a CMYK profile if it matters; and submit RGB - Nature, Science, Cell, and PLOS all explicitly want RGB, not CMYK, because their pipeline does the conversion and online is RGB anyway. If a legacy desk demands CMYK, convert downstream with an explicit profile and re-check that nothing shifted; do not fake it with a colorspace flag.
Transparency: EPS Has No Alpha
EPS/PostScript do not support alpha - matplotlib's PS backend renders partially-transparent artists as opaque, so an alpha-blended overplotted scatter loses its density information on EPS export. PDF and SVG support alpha natively; prefer them when transparency carries meaning. If a journal forces EPS and transparency is needed, rasterize that layer (rasterized=True) or re-encode density as hexbin/2D-KDE. savefig(transparent=True) makes the background transparent for slide overlays, not for print.
DPI Is Meaningless for Vector
A vector PDF has no inherent resolution; it renders sharp at any zoom. DPI governs only raster formats (PNG/TIFF) and the rasterized data layer inside a vector file. The real size control is the physical figure size in inches/mm - design at the journal's exact column width from the start; rescaling a 300-dpi raster to 200% halves its effective resolution. Font sizes are in points (1 pt = 1/72 in) independent of DPI.
The DPI tiers follow the IMAGE CLASS, because print reproduces tone via halftone dots (follow the target journal's own numbers; these are the common production convention):
Image class
DPI
Why
Halftone / grayscale / color photo
300
continuous tone matches typical screen rulings
Combination (halftone + line/text)
500-600
thin lines and small type must not jag against toned background
Line art (pure black/white)
1000-1200
hard edges alias badly at low DPI - or keep it vector and DPI is moot
savefig.dpi is the file resolution; figure.dpi is the on-screen resolution. Independently of DPI, very thin strokes (below ~0.25 pt / 0.1 mm) can drop out or thicken unpredictably at the printer's RIP even in a vector file - keep hairlines at or above the journal's minimum line weight.
Format Decision
Format
Type
Use for
Avoid for
PDF
vector(+raster)
default for most journals; hybrid figures; alpha works
-
EPS
vector(+raster)
legacy journal requirement
anything with alpha (flattened opaque)
SVG
vector
web; handoff to Illustrator/Inkscape for editing
final print at some desks (support varies)
TIFF (LZW)
raster, lossless
print production when a journal demands raster (Cell, PLOS)
large vector-friendly line figures
PNG
raster, lossless
online, previews, slides, README figures
print where vector is accepted
JPEG
raster, LOSSY
photographs only
any figure with text/lines/edges (DCT ringing)
Never JPEG for line/text figures - block compression rings along high-contrast edges (gray halos on text, fringing on thin lines). For TIFF, use LZW (near-universal reader support) for 8-bit figures; use ZIP for 16-bit (LZW can inflate 16-bit files).
Colormaps Are a Scientific Choice, Not Taste
Perceptually-uniform maps (viridis, cividis, magma, inferno, plasma) are constructed in CAM02-UCS so equal data steps map to equal perceived steps with monotonically increasing lightness - which is exactly why they survive grayscale and avoid inventing false gradients. cividis is additionally optimized so viewers with and without red-green color-vision deficiency see nearly the same image. By contrast, jet/rainbow has non-monotonic luminance that invents bright/dark bands the data does not have (false edges at yellow/cyan) and collapses to mush in grayscale - a correctness failure, not an aesthetic one.
Sequential map for ordered data; diverging map (with a meaningful midpoint) for signed data; a categorical CVD-safe palette for discrete classes - never a continuous rainbow for categories.
Use the Okabe-Ito 8-color Color Universal Design palette for categories. Red-green CVD affects up to ~8% of males (population-dependent), so add redundant encoding (shape + color, linetype + color, direct labels) so color is never the sole channel.
Run the grayscale-photocopy test: convert to grayscale and confirm the figure still reads.
Reproducible Export
Byte-stable PDFs: matplotlib stamps a CreationDate into every PDF, so two identical runs differ byte-for-byte (noisy git diffs). Pass metadata={'CreationDate': None} to savefig, or set the SOURCE_DATE_EPOCH env var, for deterministic output.
bbox_inches='tight' breaks exact widths. It recomputes the bounding box from drawn content, so output dimensions depend on tick-label lengths and the renderer's font metrics - the same script on two machines (different fonts) yields different-sized PDFs, and it can clip annotations. For camera-ready figures at an exact 89 mm, design to size with constrained_layout=True and save without bbox_inches='tight'; if cropping is unavoidable, pair it with explicit pad_inches.
Font-availability nondeterminism: Helvetica on a Mac vs DejaVu Sans on CI gives different glyph widths, line breaks, and (with tight bbox) different sizes. Pin the font or accept the default and don't crop-to-content.
Headless rendering: call matplotlib.use('Agg') before importing pyplot on a cluster/CI box, or just use the file backends, so no display is required.
Journal Specs (verify against the target journal at submission)
Specs change and vary by sub-journal; re-pull the target's author-guideline page. Snapshot, June 2026:
Journal
Widths
Min DPI
Formats
Color
Nature
89 mm single / 183 mm double; <=170 mm tall
300 photo, 600+ line
vector AI/EPS/PDF preferred; TIFF raster
RGB
Science
5.7 / 12.1 / 18.4 cm
>=300 at final size; vector preferred
Illustrator-openable vector; no PowerPoint
RGB (not CMYK)
Cell Press
85 / 114 / 174 mm
300 color, 500 grayscale, 1000 line
TIFF (LZW) or vector
RGB
PLOS
789-2250 px wide; <=2625 px tall
300-600 (do not exceed 600)
TIFF or EPS only; flattened, LZW, no alpha/layers
RGB or grayscale, 8-bit; no CMYK
PLOS is strictest (8-bit RGB/grayscale TIFF, no alpha channel, no layers). Cell's grayscale (500) and line (1000) tiers exceed the generic numbers - follow the journal's own.
data-visualization/color-palettes - Choosing perceptual and CVD-safe palettes
reporting/publication-tables - The table counterpart to figure export
References
Borland D, Taylor RM 2nd. Rainbow Color Map (Still) Considered Harmful. IEEE Comput Graph Appl. 2007;27(2):14-17. doi:10.1109/MCG.2007.323435
Nuñez JR, Anderton CR, Renslow RS. Optimizing colormaps with consideration for color vision deficiency to enable accurate interpretation of scientific data. PLoS ONE. 2018;13(7):e0199239. doi:10.1371/journal.pone.0199239
Okabe M, Ito K. Color Universal Design (CUD): how to make figures and presentations friendly to colorblind people. jfly.uni-koeln.de/color/ (8-color CVD-safe palette)
van der Walt S, Smith N. A Better Default Colormap for Matplotlib. SciPy 2015 (conference talk; viridis/magma/inferno/plasma constructed in CAM02-UCS). bids.github.io/colormap