rguides

ggplot2 Extensions: patchwork, ggrepel, gganimate

ggplot2 is the most popular data visualization package in R, but its core functionality gets you only so far. The real power unlocks when you add extension packages from the ggplot2 library. Three extensions stand out for everyday use: patchwork for combining plots, ggrepel for readable labels, and gganimate for animations.

This guide shows you when and how to use each extension with practical examples.

Installing the extensions

All three packages are available from CRAN:

install.packages(c("patchwork", "ggrepel", "gganimate"))

Load them alongside ggplot2:

library(ggplot2)
library(patchwork)
library(ggrepel)
library(gganimate)

ggplot2 extensions: combining plots with patchwork

The patchwork package solves a common problem: how do you arrange multiple plots in one figure? Base R’s layout system is clunky, and gridExtra feels limited. patchwork integrates directly with ggplot2 using intuitive operators that feel natural.

The + operator

The + operator places plots side by side in a row:

p1 <- ggplot(mtcars, aes(mpg, disp)) + geom_point()
p2 <- ggplot(mtcars, aes(mpg, wt)) + geom_point()

p1 + p2

This creates a two-column layout with p1 on the left and p2 on the right.

The / operator

The / operator stacks plots vertically:

p1 / p2

This creates two rows—one plot on top, another below.

Complex layouts

Combine operators for complex arrangements:

(p1 + p2) / p1

This creates two plots on top (side by side) and one plot spanning the full width below.

Controlling layout

Use plot_layout() for fine control over dimensions:

p1 + p2 + 
  plot_layout(widths = c(1, 2))

This makes the second plot twice as wide as the first.

You can also control heights and add guides:

p1 + p2 +
  plot_layout(heights = c(2, 1), guides = "collect")

The guides = "collect" option gathers legends from individual plots into one—useful when all plots share the same color mapping.

Adding annotations across plots

Patchwork can add text annotations that span the entire figure:

p1 + p2 + 
  plot_annotation(
    title = "Motor Trend Car Data",
    subtitle = "Comparing displacement and weight to MPG",
    caption = "Source: mtcars dataset"
  )

This is much cleaner than manually adding text to each subplot.

Preventing overlapping labels with ggrepel

When you add text labels to a plot, they often overlap—especially with many points or long label text. ggrepel pushes labels away from each other and from the data points, keeping everything readable.

Basic usage

Replace geom_text() with geom_text_repel(). The repulsion algorithm nudges labels away from each other and their associated data points, leaving connecting segments to show which label belongs to which point. This eliminates the manual coordinate adjustment that standard geom_text() often requires:

ggplot(mtcars, aes(mpg, disp, label = rownames(mtcars))) +
  geom_point() +
  geom_text_repel()

The labels now have room to breathe. The algorithm finds positions that minimize overlaps while keeping each label near its data point.

Highlighting selected points

You rarely want to label every single point in a dense scatter plot. Use the data argument to label only specific observations — typically the top or bottom performers, outliers, or points of special interest. This keeps the plot clean while drawing attention to what matters:

top_cars <- mtcars[order(mtcars$mpg, decreasing = TRUE)[1:5], ]

ggplot(mtcars, aes(mpg, disp)) +
  geom_point() +
  geom_text_repel(data = top_cars, aes(label = rownames(top_cars)))

This approach works well for highlighting outliers, top performers, or any subset worth emphasizing.

Styling the labels

Control appearance through standard ggplot2 aesthetics:

ggplot(mtcars, aes(mpg, disp, label = rownames(mtcars))) +
  geom_point() +
  geom_text_repel(
    color = "red",
    size = 3,
    fontface = "bold"
  )

Using boxes instead of text

For better visibility against busy backgrounds, use geom_label_repel() instead:

ggplot(mtcars, aes(mpg, disp, label = rownames(mtcars))) +
  geom_point() +
  geom_label_repel(
    box.color = "darkgray",
    fill = "white",
    alpha = 0.8
  )

Controlling label behavior

Fine-tune the algorithm with parameters:

geom_text_repel(
  max.overlaps = 15,      # Skip labels if too many
  min.segment.length = 0, # Always draw segment
  nudge_x = 0.5,          # Offset from point
  direction = "both"     # Allow movement in any direction
)

Creating animations with gganimate

Static plots show one moment in time. gganimate extends ggplot2 to show how your data changes across states—whether time periods, categories, or steps in an algorithm.

Your first animation

Start with a static ggplot2 plot, then add a transition function. transition_states() animates between discrete categories — each category becomes a separate frame, and points fade between states based on the transition length settings:

ggplot(iris, aes(Petal.Length, Petal.Width, color = Species)) +
  geom_point(size = 3) +
  transition_states(Species, transition_length = 2, state_length = 1)

Each species appears sequentially, with points fading in and out as the animation progresses.

Animating over time

Use a dataset with a time variable and transition_reveal() to draw the line progressively from left to right. This creates a line-drawing effect where the path appears to trace itself, useful for showing trends, stock prices, or cumulative metrics as they develop:

# Sample time series data
set.seed(123)
dates <- seq(as.Date("2024-01-01"), by = "day", length.out = 30)
df <- data.frame(
  date = dates,
  value = cumsum(rnorm(30))
)

ggplot(df, aes(date, value)) +
  geom_line() +
  geom_point() +
  transition_reveal(date)

The line draws itself as time progresses—the reveal animation traces from left to right.

Animation easing

Control how values transition between states:

ggplot(iris, aes(Petal.Length, Petal.Width, color = Species)) +
  geom_point(size = 3) +
  transition_states(Species, transition_length = 2, state_length = 1) +
  ease_aes("cubic-in-out")

Common options include linear, cubic, and elastic easing.

Saving animations

Use anim_save() to export your work:

animated_plot <- ggplot(df, aes(date, value)) +
  geom_line() +
  transition_reveal(date)

anim_save("animation.gif", animation = animated_plot)

For higher quality, save as MP4:

anim_save("animation.mp4", animation = animated_plot, renderer = av_renderer())

Performance tips

Animations can get heavy. Keep these tips in mind:

  • Fewer frames render faster. Use nframes to control this.
  • Complex geometries slow things down. Keep animations simple.
  • Test with print() before saving to catch errors quickly.

patchwork layout options

patchwork provides a simple algebra for combining ggplot2 plots. p1 + p2 places plots side by side. p1 / p2 stacks them vertically. (p1 + p2) / p3 creates a two-column top row with a single full-width bottom row. plot_layout(widths = c(2, 1)) controls relative widths. plot_annotation(title = "Overall title") adds a title spanning all plots.

For complex layouts, patchwork::plot_layout(design = c("AB CC")) uses a string template where each letter maps to a plot in order. This gives full control over arbitrary grid layouts.

gganimate timing and transitions

gganimate maps an aesthetics dimension to animation frames. transition_time(year) animates over a time variable. transition_states(group) transitions between discrete states. enter_fade() and exit_shrink() control how new elements appear and old ones disappear.

animate(p, nframes = 100, fps = 20, width = 800, height = 600) renders the animation. For web output, anim_save("animation.gif") saves a GIF. gganimate uses gifski or magick for GIF rendering and ffmpeg for video output.

Other useful extensions

ggtext provides Markdown-formatted axis labels and titles: labs(title = "**Bold title** with *italics*") with theme(plot.title = element_markdown()). ggridges creates ridge plots for comparing distributions across categories. ggalluvial creates alluvial/Sankey diagrams for flow data. ggbump creates bump charts for ranking over time.

The ggplot2 extensions ecosystem includes over 100 packages. ggforce adds zoom facets, sina plots, and hull geoms. ggdist provides uncertainty visualizations (halfeye, dotplot, interval). ggbeeswarm adds beeswarm plots for comparing distributions. Browse the full list at exts.ggplot2.tidyverse.org.

Choosing extensions

The ggplot2 extension ecosystem is large, over 100 packages extend it. Before adding a dependency, check whether the effect can be achieved with ggplot2 itself: facet_wrap() for small multiples, scale_*_manual() for custom colors, theme() for most visual adjustments. Add extension packages when you need specific features: patchwork for multi-panel layouts, ggrepel for non-overlapping text labels, ggiraph for interactive tooltips, ggridges for ridge plots, ggforce for additional geoms. Each extension follows the same grammar, a new geom_*, stat_*, scale_*, or theme_* that composes with the rest of the ggplot2 system. Read the package vignette before use; most extensions require specific aes() mappings that differ from standard ggplot2.

The extension ecosystem

ggplot2’s design explicitly supports extension. The package exposes its geom, stat, scale, and coord infrastructure as base classes that anyone can subclass. This has produced a rich ecosystem of extension packages that add capabilities that were impractical to include in the core package: network visualization, treemaps, alluvial diagrams, waffle charts, annotated statistical tests, and many others all exist as ggplot2 extensions that compose naturally with the rest of the system.

The value of the extension model is that extensions are first-class citizens in a ggplot2 figure. A network geom from ggraph and a bar chart from base ggplot2 can appear in the same figure with the same theme, the same faceting, and the same aesthetic scales. Extensions do not require separate rendering calls or post-processing to integrate.

Common useful extensions

ggrepel is one of the most universally useful extensions. When labeling points in a scatter plot, labels frequently overlap each other and the points. ggrepel’s geom_label_repel and geom_text_repel position labels to avoid overlaps, using force-directed algorithms to push labels apart. The result is readable labeled scatter plots where base ggplot2’s static label positioning produces illegible piles of text.

patchwork composes multiple ggplot2 figures into one layout using arithmetic operators. Adding two plots side by side, stacking them vertically, or arranging them in a grid uses plus and division operators in an intuitive way. The resulting combined figure has consistent sizing and alignment, shared or independent axes, and can receive a single shared title and caption. For multi-panel figures in publications or reports, patchwork replaces manual layout gymnastics.

Writing your own geoms

Creating a custom geom requires writing a Geom R5 class that inherits from Geom or one of its subclasses. The draw_group or draw_panel method receives a data frame of the aesthetic mappings and returns a grid grob, a graphical object from R’s grid graphics system. For simple geoms that are composites of existing geoms, the layer function with a custom stat or position transformation is often sufficient without writing a full Geom class.

The ggproto inheritance system that ggplot2 uses differs from S3 and S4. Understanding how ggproto inherits defaults and how the draw methods receive data is the main learning curve for extension authors. The “Extending ggplot2” vignette in the ggplot2 package documentation is the authoritative resource.

Conclusion

Each extension serves a different purpose in your visualization workflow:

  • Use patchwork when you need to combine multiple plots into one figure. The operator syntax makes layouts intuitive, and the annotation features add professional polish.
  • Use ggrepel whenever text labels crowd your visualization. It handles the hard work of positioning so you don’t have to manually adjust coordinates.
  • Use gganimate to show change over time or across categories. Keep animations simple—too much motion confuses rather than informs.

All three ggplot2 extensions integrate smoothly with ggplot2, so your existing knowledge transfers directly. Start with patchwork if you combine plots regularly; add ggrepel next time your labels overlap. Once you’re comfortable, gganimate opens up new ways to tell data stories with ggplot2 extensions.

See also