25 Best ggplot2 Extensions in R: ggrepel, ggforce, ggtext & Beyond
The ggplot2 package powers most R visualizations, but over 100 extension packages expand it with better labels, new chart types, richer themes, and even animation — each one plugging in with the same ggplot() + layer syntax you already know.
How Do You Fix Overlapping Labels and Add Rich Text?
Overlapping labels are the single most common ggplot2 frustration. You add geom_text() to a scatter plot and half the labels pile on top of each other. The ggrepel package fixes this with physics-based repulsion — labels push away from each other and from data points automatically.
1. ggrepel — Non-Overlapping Labels
Let's see ggrepel in action on a scatter plot of car data. Notice how every label is readable without manual positioning.
Every label connects to its point with a subtle line segment. The max.overlaps argument controls how aggressively labels dodge — higher values allow more labels to appear in crowded regions. Use geom_label_repel() instead for labels with a background box.
2. ggtext — Rich Text Formatting in Plots
Standard ggplot2 titles are plain text. With ggtext, you can make individual words bold, colored, or italic using HTML/Markdown syntax right inside your plot titles and annotations.
The key function is element_markdown() — use it in theme() for any text element (title, subtitle, axis labels, caption). For in-plot annotations, use geom_richtext() which supports the same HTML tags. This technique eliminates the need for a separate legend when you color-code your title.
3. geomtextpath — Curved Text Along Lines (Local only)
geomtextpath places text directly on lines and curves, replacing the need for a separate legend on line charts.
The geom_textline() function replaces geom_line() and bends the text to follow the line's path. Readers see the label right on the data — no legend lookup required.
Try it: Load ggrepel and create a scatter plot of iris using Petal.Length vs Petal.Width. Label only the 5 flowers with the largest Petal.Length using geom_label_repel(). Use ex_top5 for your filtered data.
Click to reveal solution
Explanation: geom_label_repel() works like geom_text_repel() but draws a filled rectangle behind each label for better readability.
What Extensions Add Powerful New Geoms?
ggplot2 ships with about 30 geoms (points, lines, bars, etc.), but many common visualizations need specialised geometries. These four packages fill the biggest gaps.
4. ggforce — Zoom, Shapes, and Annotations
ggforce gives you facet_zoom() — a way to zoom into a region of your plot while keeping the full view visible side-by-side.
The zoomed panel links to the full view with a shaded region, so readers instantly see which portion is enlarged. ggforce also provides geom_mark_ellipse() for annotating clusters, geom_arc_bar() for donut charts, and geom_sina() for jittered violin-style point plots.
5. ggridges — Ridgeline Density Plots
When you need to compare distributions across many groups, ridgeline plots stack density curves vertically so every group is visible without faceting.
The scale parameter controls how much the ridges overlap — values above 1 let them overlap slightly for a compact look. Each ridge is a full density estimate, making it easy to spot bimodal distributions or outlier groups at a glance.
geom_density() with fill works fine. With 10+ groups, ridgelines keep everything readable where facets would need scrolling.6. ggbeeswarm — Jittered Points That Don't Lie
Standard geom_jitter() places points randomly, which can misrepresent density. ggbeeswarm arranges points in a structured pattern so dense regions look dense and sparse regions look sparse.
Unlike jitter, every point position is deterministic — run it twice and you get the same layout. The cex parameter controls point spacing. Use geom_quasirandom() from the same package for a softer, violin-shaped alternative.
7. ggdist — Distribution Visualisation and Raincloud Plots
ggdist combines density plots, dot plots, and interval summaries into single composite visualizations. Its signature output is the "raincloud plot" — a half-violin plus jittered dots plus a summary interval.
The stat_halfeye() function draws a half-density shape, while the layered boxplot adds the familiar median and quartile markers. Set .width = c(0.66, 0.95) to show 66% and 95% credible intervals instead of a boxplot.
Try it: Create a ridgeline plot of airquality$Temp grouped by Month using ggridges. Use factor(Month) for the y-axis and add fill = factor(Month). Store your data prep in ex_aq.
Click to reveal solution
Explanation: geom_density_ridges() stacks density estimates vertically. The scale controls overlap, and alpha makes overlapping regions visible.
How Do You Combine Multiple Plots Into One Figure?
Scientific papers and reports almost always need multi-panel figures. Two packages dominate this space, and both work with any ggplot object.
8. patchwork — Combine Plots With Math Operators
patchwork is the most intuitive way to arrange plots. Use + to place plots side by side, / to stack vertically, and | for explicit horizontal layout.
The parentheses control grouping: (p1 | p2) / p3 puts plots 1 and 2 on the top row and plot 3 spanning the full width below. The tag_levels = "A" argument auto-labels each panel as (A), (B), (C) — exactly what journals expect.
theme_*() to all plots before combining, or use & theme_minimal() after the patchwork expression to apply a theme to every panel at once.+ and | place plots horizontally, / stacks vertically, and parentheses group sub-layouts. This reads like a formula: (A | B) / C is a 2-over-1 layout.9. cowplot — Publication-Ready Multi-Panel Layouts
cowplot predates patchwork and offers similar functionality via plot_grid(), plus the ability to add inset plots with draw_plot().
The ggdraw() + draw_plot() pattern places one plot inside another at exact coordinates (0-1 range). This is perfect for overview + detail views. cowplot also provides theme_cowplot() and theme_minimal_grid() — clean themes designed for multi-panel figures.
Try it: Create three plots from iris (histogram of Sepal.Length, scatter of Sepal.Length vs Petal.Length, boxplot of Sepal.Width by Species). Combine them into a 1-row layout with patchwork using | and add panel tags with plot_annotation(tag_levels = "A").
Click to reveal solution
Explanation: The | operator arranges plots horizontally. plot_annotation(tag_levels = "A") adds sequential letter labels to each panel.
Which Extensions Offer the Best Themes and Color Palettes?
ggplot2 ships with theme_gray(), theme_minimal(), and a handful of others. These five packages give you hundreds of ready-made themes and thousands of color palettes.
10. ggthemes — Ready-Made Publication Themes
ggthemes replicates the visual style of well-known publications and data visualization experts.
Other popular options include theme_fivethirtyeight() (bold, minimal), theme_tufte() (maximum data-ink ratio), and theme_wsj() (Wall Street Journal). Each theme comes with a matching scale_color_*() function.
11. hrbrthemes — Typography-First Themes
If you want clean, modern-looking plots with excellent font choices, hrbrthemes delivers with theme_ipsum().
theme_ipsum() uses a condensed sans-serif font with generous spacing. The result looks professional without any manual theme tweaking. Variants include theme_ipsum_rc() (Roboto Condensed) and theme_ipsum_tw() (Titillium Web).
12. ggsci — Journal and Sci-Fi Color Palettes
ggsci provides color palettes inspired by scientific journals (Lancet, Nature, JAMA), sci-fi franchises, and data tools.
Other palettes include scale_color_lancet(), scale_color_jama(), scale_color_d3() (D3.js colors), and scale_color_startrek(). Each comes in both scale_color_*() and scale_fill_*() variants.
13. paletteer — One Package, 2,500+ Palettes
Instead of installing 10 palette packages, paletteer gives you unified access to all of them through a single interface.
The naming convention is "package::palette_name". Use paletteer_d() for discrete palettes, paletteer_c() for continuous, and paletteer_dynamic() for palettes that adapt to the number of categories. Run palettes_d_names to browse all 2,500+ available palettes.
14. ggnewscale — Two Color Scales in One Plot
By default, ggplot2 only allows one color scale and one fill scale per plot. ggnewscale breaks this limit so you can map different variables to different color scales.
The new_scale_color() call resets the color aesthetic so you can assign a fresh scale. Place it between two sets of geom + scale layers. The same pattern works with new_scale_fill().
Try it: Create a scatter plot of mtcars (wt vs mpg) and apply theme_solarized() from ggthemes with scale_color_jama() from ggsci, coloring by factor(cyl). Store your result in ex_themed.
Click to reveal solution
Explanation: ggplot2 themes and color scales are independent layers — you can freely mix packages. theme_solarized() controls the background and grid; scale_color_jama() controls the point colors.
How Can You Highlight, Annotate, and Add Statistics?
Sometimes you need to draw the reader's eye to specific data points, add statistical test results, or display correlation patterns. These five extensions handle those needs.
15. gghighlight — Conditional Data Highlighting (Local only)
gghighlight lets you emphasise a subset of data while graying out the rest — no manual filtering needed.
The gghighlight() function works with any geom — scatter, line, bar. It accepts any logical condition and automatically fades non-matching data to grey. Use label_key = car_name to auto-label the highlighted points.
16. ggpubr — Publication-Ready Plots With Statistical Tests
ggpubr wraps common plot types in convenience functions and adds built-in statistical comparisons.
The stat_compare_means() function runs statistical tests (t-test, Wilcoxon, ANOVA, Kruskal-Wallis) and displays the p-value directly on the plot. The comparisons argument specifies which pairs to test. The palette = "jco" uses Journal of Clinical Oncology colors.
17. ggcorrplot — Correlation Matrix Heatmaps
ggcorrplot turns a correlation matrix into a publication-ready heatmap with one function call.
The method = "circle" uses circle size to encode correlation strength (large circle = strong correlation). Set type = "lower" to show only the lower triangle, avoiding redundancy. The lab = TRUE overlays the actual correlation values.
18. GGally — Scatter Plot Matrices and More
GGally's ggpairs() creates comprehensive scatter-plot matrices showing correlations, distributions, and scatter plots for every variable pair.
The upper triangle shows correlation coefficients, the lower triangle shows scatter plots, and the diagonal shows density distributions — all colored by Species. This single chart replaces six separate scatter plots and gives you a complete overview of multivariate relationships.
19. ggstatsplot — Automated Statistical Visualisations (Local only)
ggstatsplot creates publication-ready plots with statistical test results embedded directly in the subtitle.
One function call runs the appropriate test, computes effect sizes, performs pairwise comparisons, and displays everything on the plot. Other variants include ggscatterstats() (correlation), gghistostats() (distribution tests), and ggcorrmat() (correlation matrices with p-values).
type = "parametric" or type = "nonparametric" explicitly when you know which is appropriate.Try it: Use ggcorrplot() to create a correlation heatmap of airquality (columns: Ozone, Solar.R, Wind, Temp). Use method = "circle", type = "lower", and add labels. Store the correlation matrix in ex_cor.
Click to reveal solution
Explanation: na.omit() removes rows with missing values before computing correlations. The circle sizes immediately show that Ozone and Temp have the strongest positive correlation.
How Do You Make ggplot2 Charts Interactive, Animated, or Specialised?
The final group covers extensions that take ggplot2 beyond static PNG output — plus three packages for niche chart types you can't build with base geoms.
20. gganimate — Animated Plots (Local only)
gganimate adds a time dimension to any ggplot by treating "frame" as an aesthetic. It requires the gifski package for rendering GIFs — install both with install.packages(c("gganimate", "gifski")).
Key transition functions: transition_time() for temporal data, transition_states() for categorical groups, and transition_reveal() for cumulative line charts. Use shadow_wake() to leave trails behind moving points.
21. plotly — Interactive HTML Charts With ggplotly() (Local only)
plotly's ggplotly() function converts any ggplot into an interactive chart with zoom, pan, and hover tooltips. Install locally with install.packages("plotly").
The tooltip argument controls what appears on hover. Map extra information to the text aesthetic and include it in tooltip for rich hover cards. For dashboards, combine with Shiny or save standalone HTML with htmlwidgets::saveWidget().
22. treemapify — Treemap Charts
treemapify turns hierarchical or proportional data into space-filling rectangles.
Map area to the variable that controls rectangle size, and fill to a second variable for color encoding. Use geom_treemap_text() to label each rectangle. For hierarchical treemaps, add subgroup aesthetics.
23. waffle — Waffle Charts (Square Pie Charts)
Waffle charts display proportions as grids of squares — a more readable alternative to pie charts.
Each square represents one unit. The n_rows parameter sets how many rows tall the grid is, and coord_equal() keeps the squares square. Waffle charts work best when your total count is under 100 — otherwise each square is too small to see.
24. ggiraph — Interactive SVG Graphics (Local only)
ggiraph makes ggplot elements interactive — hover for tooltips, click to select, and link data across plots.
Replace standard geoms with _interactive versions (geom_point_interactive, geom_bar_interactive, etc.) and map tooltip and data_id aesthetics. The data_id enables linked brushing — clicking a point in one chart highlights the same observation in another.
25. ggalluvial — Alluvial and Sankey Diagrams (Local only)
ggalluvial visualises flow between categorical variables — tracking how observations move across stages.
The geom_alluvium() draws flowing ribbons between strata, and geom_stratum() draws the category blocks. This chart type works best for 3-5 categorical variables with limited categories each (under 6 per axis).
Try it: Create a treemap of the diamonds dataset showing proportions by cut. Group by cut, count the rows in each group, and map area = n and fill = cut. Use geom_treemap() and geom_treemap_text().
Click to reveal solution
Explanation: geom_treemap() sizes rectangles proportionally by the area aesthetic. Ideal cut dominates because it has the most diamonds in the dataset.
Practice Exercises
Exercise 1: Multi-Panel Figure With Labels and Ridgelines
Create a two-panel figure using patchwork: (A) a scatter plot of mtcars (wt vs mpg) with the 5 most fuel-efficient cars labeled using ggrepel, and (B) a ridgeline plot of mpg by factor(cyl) using ggridges. Apply theme_minimal() to both and add panel tags.
Click to reveal solution
Explanation: Each plot is built independently with its own extension, then combined with | for side-by-side layout. plot_annotation(tag_levels = "A") labels them (A) and (B).
Exercise 2: Styled Correlation Matrix
Build a correlation matrix of mtcars columns (mpg, hp, wt, qsec, drat) using ggcorrplot. Use method = "square" and type = "upper". Apply a Nature Publishing Group (scale_fill_npg() won't work here — use custom colors from ggsci's NPG palette). Add a ggtext-styled title where "positive" is blue and "negative" is red.
Click to reveal solution
Explanation: ggcorrplot's colors argument takes three values (low, mid, high). The NPG blue (#3C5488) and red (#E64B35) are from ggsci's palette. element_markdown() from ggtext renders the HTML spans in the title.
Exercise 3: Zoom + Rich Labels + Custom Palette
Create a scatter plot of diamonds (carat vs price, colored by clarity, sampled to 2000 rows) with: (1) facet_zoom(x = carat < 0.5) from ggforce to zoom into small diamonds, (2) a ggtext-styled subtitle that highlights the word "small" in bold red, and (3) a ggsci scale_color_d3() palette. Save as my_zoom_plot.
Click to reveal solution
Explanation: facet_zoom() creates a linked zoom panel. The ggtext subtitle uses HTML to make "small" bold and red. scale_color_d3() provides a distinctive 10-color palette from the D3.js library.
Putting It All Together
Let's build a polished multi-panel analysis of mtcars that combines five extensions in one figure: ggrepel for labels, ggtext for a rich title, ggsci for a professional palette, ggcorrplot for correlations, and patchwork to combine everything.
This figure tells a complete story: Panel A reveals the weight-efficiency trade-off with standout cars labeled, while Panel B quantifies how each variable relates to the others. The NPG colors and ggtext title make it publication-ready. Five extensions, one unified figure — that's the power of the ggplot2 ecosystem.
Summary
Here's every extension covered in this article, with its category, key function, and whether it runs in the browser code blocks.
| # | Package | Category | Key Function | Browser? | |
|---|---|---|---|---|---|
| 1 | ggrepel | Labels | geom_text_repel() |
Yes | |
| 2 | ggtext | Labels | element_markdown() |
Yes | |
| 3 | geomtextpath | Labels | geom_textline() |
No | |
| 4 | ggforce | Geoms | facet_zoom() |
Yes | |
| 5 | ggridges | Geoms | geom_density_ridges() |
Yes | |
| 6 | ggbeeswarm | Geoms | geom_beeswarm() |
Yes | |
| 7 | ggdist | Geoms | stat_halfeye() |
Yes | |
| 8 | patchwork | Composition | +, /, ` |
` operators | Yes |
| 9 | cowplot | Composition | plot_grid(), draw_plot() |
Yes | |
| 10 | ggthemes | Themes | theme_economist() |
Yes | |
| 11 | hrbrthemes | Themes | theme_ipsum() |
Yes | |
| 12 | ggsci | Palettes | scale_color_npg() |
Yes | |
| 13 | paletteer | Palettes | scale_color_paletteer_d() |
Yes | |
| 14 | ggnewscale | Palettes | new_scale_color() |
Yes | |
| 15 | gghighlight | Annotation | gghighlight() |
No | |
| 16 | ggpubr | Statistics | stat_compare_means() |
Yes | |
| 17 | ggcorrplot | Statistics | ggcorrplot() |
Yes | |
| 18 | GGally | Statistics | ggpairs() |
Yes | |
| 19 | ggstatsplot | Statistics | ggbetweenstats() |
No | |
| 20 | gganimate | Animation | transition_time() |
No | |
| 21 | plotly | Interactive | ggplotly() |
No | |
| 22 | treemapify | Specialised | geom_treemap() |
Yes | |
| 23 | waffle | Specialised | geom_waffle() |
Yes | |
| 24 | ggiraph | Interactive | geom_point_interactive() |
No | |
| 25 | ggalluvial | Specialised | geom_alluvium() |
No |
Every extension follows the same pattern: install the package, load it with library(), and add its geoms, themes, or scales to your existing ggplot code. Start with the ones that solve your most pressing pain point — for most people, that's ggrepel (labels) and patchwork (multi-panel figures).
References
- Official ggplot2 Extensions Gallery — curated list of 100+ community packages. Link
- Wickham, H. — ggplot2: Elegant Graphics for Data Analysis, 3rd Edition. Springer (2024). Link
- Slowkowski, K. — ggrepel documentation: repulsive text and label geoms. Link
- Pedersen, T.L. — patchwork: the composer of plots. Link
- Pedersen, T.L. — ggforce: accelerating ggplot2. Link
- Wilke, C.O. — ggtext: improved text rendering for ggplot2. Link
- Hvitfeldt, E. — paletteer: comprehensive collection of colour palettes. Link
- Pedersen, T.L. & Robinson, D. — gganimate: a grammar of animated graphics. Link
- Kassambara, A. — ggpubr: publication-ready plots. Link
- Patil, I. — ggstatsplot: ggplot2-based plots with statistical details. Link
Continue Learning
- ggforce Package in R — Deep dive into facet_zoom, mark ellipses, arc bars, sina plots, and 30+ advanced ggplot2 geoms
- patchwork in R — Master multi-panel figures with aligned axes, shared legends, and complex nested layouts
- ggdist Package in R — Raincloud plots, half-eye plots, uncertainty bands, and distribution visualisation with stat_halfeye and stat_dots