Publication-Ready Figures in R

· 8 min read · Updated March 28, 2026 · intermediate
r ggplot2 data-visualization

When a reviewer opens your manuscript and your figures look blurry, use the wrong font, or overflow the column width, it signals sloppiness before they read a single word of your analysis. A publication-ready figure is not a stylistic preference — it is a technical requirement. Journals have explicit submission guidelines covering dimensions, resolution, and font embedding, and failing any of them is grounds for immediate rejection.

This tutorial covers everything you need to produce figures that sail through peer review: correct sizing, resolution, font embedding, accessible color choices, and clean multi-panel layouts.

What Makes a Figure Publication-Ready

A figure earns the label “publication-ready” by satisfying three technical criteria:

  1. Correct dimensions in inches, matching the target journal’s column spec
  2. Sufficient resolution — at least 300 DPI for raster formats
  3. Embedded fonts — every text element renders identically on any reviewer’s machine

If any one of these is wrong, your figure will be sent back. The good news is that R gives you complete control over all three.

Setting Figure Dimensions

Journal figures fall into predictable width categories. Single-column articles typically use 3.5 inches; double-column runs 7 inches. Full-page figures often land around 6.5 to 7 inches wide and 9 inches tall.

The ggsave() function handles dimensions cleanly:

ggsave("fig1.pdf", width = 3.5, height = 3.5, units = "in")
```r

The `units` argument accepts `"in"`, `"cm"`, `"mm"`, `"pt"`, `"sp"`, or `"px"`. Always use inches for print submissions — it maps directly to the DPI calculation and matches what journals expect.

Aspect ratio matters as much as absolute size. Square (1:1) is a safe default for general use. Bar charts and box plots look better at 1.3–1.5:1 (width:height). Heatmaps and landscape-oriented plots often need wider ratios. When in doubt, test your figure at the actual target size by printing a test page.

## Choosing DPI and File Format

The right DPI depends entirely on how the figure will be reproduced:

| Output target       | DPI  | Best format          |
|---------------------|------|----------------------|
| Print journals      | 300–600 | PDF, TIFF, EPS    |
| Presentations       | 150     | PNG at high res   |
| Web / screen        | 72–150   | PNG, SVG         |

For line charts, scatter plots, bar charts, and anything made of geometric shapes, **always prefer vector formats** — PDF is the default for journal submissions:

```r
# Vector PDF — infinitely scalable, no DPI needed
ggsave("figure1.pdf", width = 3.5, height = 3.5, units = "in")

# High-res PNG — specify DPI
ggsave("figure1.png", width = 3.5, height = 3.5, dpi = 600, units = "in")

# TIFF with LZW compression — some journals prefer this
ggsave("figure1.tif", width = 3.5, height = 3.5, dpi = 600,
       compression = "lzw", units = "in")
```r

Switch to a raster format (TIFF or PNG at 600 DPI) only when you have continuous-tone images like photographs, or heatmaps with millions of cells. Even then, a 600 DPI PNG is often acceptable and produces smaller files than TIFF.

## Embedding Fonts in PDFs

This is the step most R users skip, and it causes the most heartache at submission. A PDF opened on a reviewer's machine will substitute missing fonts, which can shift text out of alignment or change character spacing. The solution is to embed fonts before you submit.

The `showtext` package is the most reliable cross-platform option. It downloads Google Fonts and renders them as polygons directly in the PDF — no Ghostscript needed:

```r
library(showtext)
font_add_google("Source Sans Pro", "ssp")
showtext_auto()

ggplot(mtcars, aes(wt, mpg)) +
  geom_point(color = "#5C5C5C") +
  theme(text = element_text(family = "ssp", size = 10))

ggsave("figure1.pdf", width = 3.5, height = 3.5)
```r

The file size grows because text is rendered as vector outlines, but every copy of the PDF will display identically regardless of installed fonts.

The `extrafont` package is an alternative that imports your system's installed fonts. It works well on macOS and Linux, but requires Ghostscript on Windows and can be unreliable across platforms:

```r
library(extrafont)
embed_fonts("my_plot.pdf", outfile = "my_plot_embedded.pdf")
```r

To verify your fonts are embedded, open the PDF in Acrobat Reader, go to File → Properties → Fonts, and confirm every font shows as "Embedded" or "Embedded Subset."

## Choosing Colorblind-Safe Palettes

Most default ggplot2 colors will not survive review by a colorblind reader. The viridis palette is the gold standard — perceptually uniform, readable in greyscale, and designed for all types of color vision deficiency:

```r
library(viridis)

ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
  geom_tile() +
  scale_fill_viridis(option = "D")  # default perceptually uniform
```r

The viridis package offers `magma`, `plasma`, `inferno`, and `cividis`. The cividis variant is specifically tuned for deuteranopia and protanopia. Use whichever looks right for your data — they all pass accessibility standards.

For discrete categories, the Okabe-Ito palette from `ggthemes` is well-regarded:

```r
library(ggthemes)
scale_color_colorblind()
```r

If you need to match a journal's palette exactly, extract the hex codes from the journal's style guide and pass them to `scale_color_manual()`:

```r
nature_cols <- c("#E64B35", "#4DBBD5", "#00A087", "#3C5488",
                 "#F39B7F", "#8491B4", "#91D1C2", "#DC0000")

ggplot(df, aes(x = group, y = value, color = group)) +
  geom_point() +
  scale_color_manual(values = nature_cols)
```r

## Professional Themes

A clean theme does most of the visual work for you. `theme_classic()` is a reliable starting point for academic figures — it removes the grey background and panel grid, leaving clean axes:

```r
ggplot(mtcars, aes(wt, mpg, color = factor(cyl))) +
  geom_point(size = 2) +
  theme_classic() +
  theme(text = element_text(family = "ssp", size = 10))
```r

For a more polished look, the `hrbrthemes` package provides `theme_ipsum()`, which uses IBM Plex Sans and is designed for professional and academic contexts:

```r
library(hrbrthemes)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  theme_ipsum()
```r

The `ggthemes` package offers dozens of styles modeled after publications like The Economist, FiveThirtyEight, and Stata. These are useful for presentations and reports, though for journal submission you usually want the cleaner `theme_classic()` or a minimal custom theme.

## Adding Mathematical Annotations

Statistical figures often need regression equations, mathematical notation, or annotated significance markers. The `annotate()` function with `parse = TRUE` handles most cases:

```r
ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  annotate("text", x = 4, y = 25,
           label = "y == 5.34 + 0.92*x",
           parse = TRUE, size = 3)
```r

For regression lines with equations and R-squared values automatically placed, `ggpmisc::stat_poly_eq()` is much cleaner than annotating by hand:

```r
library(ggpmisc)

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  stat_poly_eq(aes(label = after_stat(eq.label)),
               formula = y ~ x, parse = TRUE)
```r

This produces a properly formatted equation (e.g., `y = 5.34 + 0.92x  R² = 0.74`) positioned automatically on the plot.

## Building Multi-Panel Figures

Papers often require multiple panels (A, B, C) in a single figure. The `patchwork` package makes this intuitive:

```r
library(patchwork)

p1 <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + theme_classic()
p2 <- ggplot(mtcars, aes(factor(cyl), mpg)) + geom_boxplot() + theme_classic()
p3 <- ggplot(mtcars, aes(disp, hp)) + geom_point() + theme_classic()

# Side by side
p1 + p2

# 2x2 grid with letter labels
(p1 + p2) / (p3 + plot_spacer()) +
  plot_annotation(tag_levels = "A")
```r

The `cowplot` package is a reliable alternative, particularly if you need to save the multi-panel figure directly:

```r
library(cowplot)

save_plot("fig_combined.pdf",
          plot_grid(p1, p2, p3, ncol = 2, labels = c("A", "B", "C")),
          base_width = 3.5, base_height = 5)
```r

Both packages work well. `patchwork` has more intuitive grammar; `cowplot` is better integrated with `ggsave` and `save_plot`.

## R Markdown Workflow

If you are writing your paper in R Markdown, you can set figure options globally in the chunk header and let ` knitr` handle the export:

```r
# In your R Markdown YAML header or setup chunk
knitr::opts_chunk$set(
  fig.width = 3.5, fig.height = 3.5, dpi = 300, dev = "pdf"
)

# Then your code chunks just produce the plot
ggplot(mtcars, aes(wt, mpg)) + geom_point()
```r

For presentations, adjust the dimensions and switch to PNG:

```r
knitr::opts_chunk$set(
  fig.width = 10, fig.height = 7, dpi = 150, dev = "png"
)
```r

This approach keeps your figure export logic in one place and makes it easy to produce different outputs from the same source.

## Pre-Submission Checklist

Before you submit, run through this list:

- Dimensions set in inches (`width`, `height`, `units = "in"`)
- DPI at least 300 for raster formats; vector PDF for line and scatter plots
- Fonts embedded (`showtext` or `embed_fonts()`)
- Colorblind-safe palette used (viridis, Okabe-Ito, or ggpubfigs)
- Axis labels readable at the target print size (6–8 pt minimum)
- Multi-panel figures assembled with `patchwork` or `cowplot`
- Mathematical notation rendered with `parse = TRUE` or `ggpmisc`
- Saved as PDF (preferred) or TIFF at 600 DPI

Getting these details right does not guarantee your science will be accepted, but it removes every avoidable reason for desk rejection.

## See Also

- [Customizing ggplot2 Charts](/tutorials/customizing-ggplot2-charts/) — Fine-tune themes, colors, and legends beyond the defaults
- [Introduction to ggplot2](/tutorials/introduction-to-ggplot2/) — Start here if ggplot2 is new to you
- [R Markdown Getting Started](/tutorials/rmarkdown-getting-started/) — Integrate figures into reproducible documents

## Written

- File: sites/rguides/src/content/tutorials/publication-ready-figures-r.md
- Words: ~1150
- Read time: 6 min
- Topics covered: ggsave dimensions, DPI, font embedding (showtext, extrafont), colorblind-safe palettes (viridis, ggthemes), professional themes, mathematical annotations, multi-panel figures (patchwork, cowplot), R Markdown figure workflow
- Verified via: ggplot2 docs, showtext/viridis package documentation
- Unverified items: none