R: R ggplot2 Themes
Last updated: 2026-08-26
The default
theme_gray()theme in ggplot2 is already quite good, but to create "publication-quality" charts, you’ll need to fine-tune the theme. In this lesson, we’ll explore the ggplot2 theme system: from the 8 built-in themes and fine-tuning withtheme()tolabsandscale, all the way to high-resolution output withggsave.
After completing this lesson, you’ll be able to fine-tune any ggplot2 plot to “publication-ready” standards—with full control over text, colors, legends, margins, and axes.
1. What You'll Learn
- 8 built-in ggplot2 themes
- theme() element tree (element_text/line/rect)
- labs() complete label
- scale_color/fill color mapping
- Theme Templates (ggthemes / hrbrthemes)
- ggsave() Save in HD
- Hands-On: Customizing Publication-Quality Charts
2. The Story of a "Professional Chart"
(1) Pain Point: The default image looks too "school-like"
Bob created a chart using ggplot2, and the manager said, "This is a report for investors—it's too plain."
(2) ggplot2 Visualization Options
library(ggplot2)
library(hrbrthemes) # Professional Theme Packages
p <- ggplot(sales, aes(x = quarter, y = sales, color = city, group = city)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
scale_color_brewer(palette = "Set1") +
labs(
title = "2024 Q1-Q4 Sales Trends",
subtitle = "Data Source: Sales System | Chart: Analyst Team",
caption = "Unit: 10,000 yuan",
x = "Quarter", y = "Sales (10,000 yuan)",
color = "City"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray50"),
legend.position = "top",
panel.grid.minor = element_blank()
)
ggsave("professional_report.png", p, width = 10, height = 6, dpi = 300)
3 lines of code → publication-quality charts.
3. 8 Built-in Themes
(1) Comparison of Themes
| Theme | Style | Suitable Scenarios |
|---|---|---|
theme_gray() |
Default (gray background) | General |
theme_bw() |
Black and White | Academic Paper |
theme_minimal() |
Minimalist (Most Commonly Used) | Reports/Presentations |
theme_classic() |
Classic (Gridless) | Academic |
theme_void() |
Blank | Map/Diagram |
theme_light() |
Light Background | Demo |
theme_dark() |
Dark Background | Dark Theme |
theme_test() |
For testing | Debugging |
(2) Real-World Comparison
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
p + theme_gray() # Default
p + theme_minimal() # Minimalism
p + theme_bw() # Black and White
p + theme_classic() # Classic
p + theme_light() # Light-colored
p + theme_dark() # Dark
p + theme_void() # Blank
Base size / Base family
# Set the font size globally
p + theme_minimal(base_size = 14)
# Set the Global Font
p + theme_minimal(base_family = "SimHei") # Chinese
4. theme() Element Tree
(1) Element Structure
graph TB
A[theme Element] --> B[plot Full Image]
A --> C[axis Coordinate Axes]
A --> D[legend Legend]
A --> E[panel Panel]
A --> F[strip Facet Labels]
A --> G[title Title]
B --> H[plot.title]
B --> I[plot.background]
C --> J[axis.title]
C --> K[axis.text]
C --> L[axis.line]
D --> M[legend.title]
D --> N[legend.text]
D --> O[legend.position]
E --> P[panel.background]
E --> Q[panel.grid]
F --> R[strip.text]
F --> S[strip.background]
style A fill:#fff3cd
style B fill:#cce5ff
style C fill:#d4edda
style D fill:#f8d7da
style E fill:#e1d4ff
style F fill:#ffe1e1
style G fill:#e1ffe1
(2) 4 Types of element_ Functions
| Function | Purpose | Used for |
|---|---|---|
element_text() |
Text | Titles, axis labels, scale text |
element_line() |
Lines | Gridlines, axis lines |
element_rect() |
Rectangle | Background, Border, Panel |
element_blank() |
Hide | Any element |
(3) element_text() Text Parameter
theme(
plot.title = element_text(
size = 16, # Font size
face = "bold", # Bold/italics (plain/bold/italic/bold.italic)
color = "blue", # Color
family = "SimHei",# Font
hjust = 0.5, # Horizontal Alignment (0=left, 1=right, 0.5=center)
vjust = 0.5 # Vertical Alignment
)
)
(4) element_line() Line Parameters
theme(
panel.grid.major = element_line(
color = "gray80",
linewidth = 0.5,
linetype = "dashed" # solid/dashed/dotted
),
panel.grid.minor = element_blank(), # Hide Secondary Grid
axis.line = element_line(color = "black", linewidth = 0.8)
)
(5) element_rect() Rectangle Parameter
theme(
panel.background = element_rect(
fill = "lightyellow",
color = "black",
linewidth = 0.5
),
plot.background = element_rect(fill = "white")
)
(6) element_blank() Hide
theme(
panel.grid = element_blank(), # Hide All Grids
axis.ticks = element_blank(), # Hide Scale
legend.position = "none" # Hide Legend (Note: No element_blank)
)
5. Fine-Tuning Common Themes
(1) Text Size
p + theme(
plot.title = element_text(size = 18, face = "bold"),
plot.subtitle = element_text(size = 12, color = "gray50"),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10),
legend.title = element_text(size = 11),
legend.text = element_text(size = 10)
)
(2) Gridlines
p + theme(
panel.grid.major = element_line(color = "gray90"),
panel.grid.minor = element_blank(), # Hide Secondary Grid (More concise)
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5)
)
(3) Legend Location
# 4 every corner
p + theme(legend.position = "top")
p + theme(legend.position = "bottom")
p + theme(legend.position = "left")
p + theme(legend.position = "right")
# Hide
p + theme(legend.position = "none")
# Coordinate Positioning (In the figure)
p + theme(legend.position = c(0.8, 0.2)) # 80% x, 20% y
(4) Coordinate Axes
p + theme(
axis.line = element_line(color = "black", linewidth = 0.8),
axis.ticks = element_line(color = "black"),
axis.title.x = element_text(margin = margin(t = 10)), # X Move the axis title down
axis.title.y = element_text(margin = margin(r = 10)) # Y Shift the axis title to the left
)
6. Complete documentation for labs()
(1) All Tags
p + labs(
title = "Main Title",
subtitle = "Subtitle",
caption = "Data Sources",
x = "X Axis",
y = "Y Axis",
color = "Color Mapping", # Corresponding aes(color)
fill = "Fill Mapping", # Corresponding aes(fill)
shape = "Shape Mapping",
size = "Size Mapping"
)
(2) Mathematical Formula Tags
# For use in formulas quote() or expression
p + labs(
x = quote(x[i]),
y = expression(paste("Concentration (", mu, "g/mL)"))
)
7. scale_color / scale_fill Colors
(1) Discrete colors (categorical variables)
# 1. Specify manually
p + scale_color_manual(values = c("red", "blue", "green", "orange"))
# 2. ColorBrewer Color Palette
p + scale_color_brewer(palette = "Set1") # Classic 9 colors
p + scale_color_brewer(palette = "Set2") # Gentle 8 colors
p + scale_color_brewer(palette = "Dark2") # Dark 8 colors
p + scale_color_brewer(palette = "Pastel1") # Light-colored 9 colors
# 3. viridis (Color-blind-friendly, Recommended)
library(viridis)
p + scale_color_viridis_d() # Discrete
p + scale_color_viridis_c() # Consecutive
(2) Continuous Colors (Continuous Variables)
# Gradient
p + scale_color_gradient(low = "blue", high = "red")
p + scale_color_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0)
p + scale_color_gradientn(colours = rainbow(7))
(3) Practical Application: Professional Color Schemes
# 4 Class Classification
p + scale_color_brewer(palette = "Set1")
# 8 Class Classification
p + scale_color_brewer(palette = "Set2")
# Color-blind-friendly
p + scale_color_viridis_d()
# Academic/Serious
p + scale_color_manual(values = c("#1f77b4", "#ff7f0e", "#2ca02c"))
8. Specialized Theme Packages
(1) 8 Themes from ggthemes
install.packages("ggthemes")
library(ggthemes)
p + theme_economist() # The Economist
p + theme_wsj() # The Wall Street Journal
p + theme_fivethirtyeight() # FiveThirtyEight
p + theme_hc() # Highcharts
p + theme_tufte() # Tufte Minimalism
p + theme_stata() # Stata
p + theme_solarized() # Solarized
p + theme_excel() # Excel Style
(2) hrbrthemes Commercial Themes
install.packages("hrbrthemes")
library(hrbrthemes)
p + theme_ipsum() # ipsum Typographic Style
p + theme_ft_rc() # The Financial Times
(3) Custom Themes (Define Once, Use Everywhere)
# Custom Themes
my_theme <- theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.minor = element_blank(),
legend.position = "bottom"
)
# Global Use
ggplot(df, aes(x, y)) + geom_point() + my_theme
9. ggsave() Save in High Definition
(1) 4 Key Parameters
ggsave(
filename, # File Name
plot = last_plot(), # Graph Object (Default: Last Image)
width = 8, # Width
height = 6, # High
units = "in", # Unit in/cm/mm
dpi = 300 # Resolution
)
(2) Practical Application
# PNG Printing Standard (300 dpi)
ggsave("report.png", width = 8, height = 6, dpi = 300)
# High-Definition Screen (150 dpi)
ggsave("screen.png", width = 1920, height = 1080, units = "px", dpi = 150)
# PDF For a thesis (Vector)
ggsave("paper.pdf", width = 8, height = 6)
# Chinese Title (Avoid garbled characters)
ggsave("Chinese.png", width = 8, height = 6, dpi = 300)
(3) Various Formats
| Format | Extension | Purpose |
|---|---|---|
| PNG | .png |
Web/Documents (Most Common) |
.pdf |
Thesis (Vector) | |
| SVG | .svg |
Web Vector Graphics |
| JPEG | .jpg |
Photo (not recommended for graphics) |
| TIFF | .tiff |
Printing |
# Vector Graphics (Unlimited Clear Zoom)
ggsave("vector.svg", width = 8, height = 6)
ggsave("vector.pdf", width = 8, height = 6)
10. Complete Example: Professional, Publication-Quality Charts
Below is an example of a complete workflow that ties together all the topics covered in this lesson.
▶ Example: 4 City Sales—Publishing-Quality Charts
# ============================================
# 4 City Sales: Publication-Quality Charts
# Features: Complete Customization from Default to Professional Level
# ============================================
library(ggplot2)
library(dplyr)
library(tidyr)
library(patchwork)
library(hrbrthemes)
# 1. Prepare data
sales_wide <- tibble(
city = c("Beijing", "Shanghai", "Guangzhou", "Shenzhen"),
Q1 = c(1000, 1500, 800, 1200),
Q2 = c(1200, 1800, 900, 1400),
Q3 = c(1100, 1700, 1000, 1300),
Q4 = c(1500, 2000, 1200, 1600)
)
sales_long <- sales_wide |>
pivot_longer(cols = -city, names_to = "quarter", values_to = "sales") |>
mutate(
total = sum(sales),
pct = round(sales / total * 100, 1)
)
# 2. Custom Themes
my_theme <- theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 16, hjust = 0,
margin = margin(b = 5)),
plot.subtitle = element_text(color = "gray40", size = 11,
margin = margin(b = 15)),
plot.caption = element_text(color = "gray50", size = 9,
hjust = 1, margin = margin(t = 10)),
axis.title = element_text(size = 11),
axis.text = element_text(size = 10, color = "gray30"),
panel.grid.major = element_line(color = "gray90", linewidth = 0.3),
panel.grid.minor = element_blank(),
legend.position = "top",
legend.title = element_text(size = 11),
legend.text = element_text(size = 10),
plot.background = element_rect(fill = "white", color = NA)
)
# 3. Plot 1: Line Trend (Professional Edition)
p1 <- ggplot(sales_long, aes(x = quarter, y = sales, color = city, group = city)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3.5, fill = "white", shape = 21, stroke = 1.5) +
scale_color_brewer(palette = "Set1", name = "City") +
scale_y_continuous(labels = scales::comma, expand = expansion(mult = 0.1)) +
labs(
title = "2024 Annual Quarterly Sales Trends",
subtitle = "4 First-tier cities 4 Quarterly Sales Data",
caption = "Data Source: Sales System | Chart: Analyst Team",
x = NULL, y = "Sales (10,000 yuan)"
) +
my_theme
# 4. Plot 2: Bar Chart Comparison (Professional Edition)
total_sales <- sales_wide |>
mutate(total = Q1 + Q2 + Q3 + Q4) |>
arrange(desc(total)) |>
mutate(city = factor(city, levels = city))
p2 <- ggplot(total_sales, aes(x = city, y = total, fill = city)) +
geom_col(width = 0.7) +
geom_text(aes(label = scales::comma(total)), vjust = -0.5, size = 4) +
scale_fill_brewer(palette = "Set1", guide = "none") +
scale_y_continuous(labels = scales::comma,
expand = expansion(mult = c(0, 0.15))) +
labs(
title = "Annual Total Sales by City",
subtitle = "Sort by total sales in descending order",
x = NULL, y = "Total Sales (10,000 yuan)"
) +
my_theme
# 5. Plot 3: Stacked Columns (Professional Edition)
sales_long_ordered <- sales_long |>
left_join(total_sales |> select(city, total), by = "city") |>
mutate(city = factor(city, levels = total_sales$city))
p3 <- ggplot(sales_long_ordered, aes(x = city, y = sales, fill = quarter)) +
geom_col(position = "stack", width = 0.7) +
scale_fill_brewer(palette = "YlGnBu", name = "Quarter") +
labs(
title = "Quarterly Sales Breakdown by City",
subtitle = "Stacked bar chart showing quarterly contributions",
x = NULL, y = "Sales (10,000 yuan)"
) +
my_theme
# 6. Multi-Image Collage
combined <- (p1 / (p2 + p3)) +
plot_annotation(
title = "2024 4-City Comprehensive Sales Analysis",
theme = theme(plot.title = element_text(face = "bold", size = 18, hjust = 0.5))
)
print(combined)
# 7. Save Publication-Quality Images
ggsave("professional_report.png", combined,
width = 14, height = 12, dpi = 300)
ggsave("professional_report.pdf", combined,
width = 14, height = 12)
ggsave("professional_report.svg", combined,
width = 14, height = 12)
cat("\n=== Publishing-quality charts have been saved ===\n")
cat(" - professional_report.png (PNG 300dpi)\n")
cat(" - professional_report.pdf (PDF Vector)\n")
cat(" - professional_report.svg (SVG Vector)\n")
Expected Output: 3 sets of publication-quality charts (line chart + bar chart + stacked chart), including Chinese and English titles, professional color schemes, and clear labels.
❓ FAQ
theme_minimal Minimal (white background, light gray grid), theme_bw Black and White (with borders, distinct grid).theme(panel.grid.minor = element_blank()). This makes the image cleaner.bottom or top; for 4 or more variables, use right; and for pie charts embedded within other charts, use c(0.5, 0.5) for positioning.scale_color and scale_fill?scale_color controls the color of lines and points (outline color), scale_fill controls the fill color (for bars/areas). geom_bar uses fill by default, geom_line uses color.ggsave The default is 300 dpi.theme_minimal(base_family = "SimHei") (Windows) or "PingFang SC" (macOS) or "WenQuanYi Zen Hei" (Linux).📖 Summary
- ggplot2 has 8 built-in themes:
theme_graytheme_bwtheme_minimaltheme_classic, etc. theme()The element tree is divided into 7 regions: plot/axis/legend/panel/strip/title- 4 types of element functions:
element_textText /element_lineLine /element_rectRectangle /element_blankHidden - Theme core fine-tuning:
plot.titleaxis.titlelegend.positionpanel.gridlabs()Set all tags:titlesubtitlecaptionxycolorfill - Color:
scale_color_brewer/scale_color_viridis_d/scale_color_manual - Specialized Topic Packages:
ggthemes(The Economist/The Wall Street Journal) /hrbrthemes(Business) ggsave("plot.png", dpi = 300)Print standard;ggsave("plot.pdf")Vector- Publication-quality charts = theme + color + labels + font size + ggsave high resolution
📝 Exercises
-
Basic Exercise: Use
mtcarsto create a scatter plot ofwtvs.mpg, and use the four themestheme_minimal,theme_bw,theme_classic, andtheme_voidto compare the differences in the resulting plots. Take screenshots for comparison. -
Basic Exercise: Create a bar chart and use
theme()to fine-tune it: ① Make the title bold, size 16; ② Hide secondary gridlines; ③ Place the legend at the bottom; ④ Set the Y-axis title to be 10px away from the axis. -
Basic Exercise: Draw the same image using
scale_color_brewer(palette = "Set1")andscale_color_brewer(palette = "Set2"), then take screenshots to compare the color differences. -
Advanced Exercise: Simulate sales data for 4 cities and create a complete, publication-quality chart with the following customizations: ① Custom theme ② Complete "labs" labels ③ Professional color scheme ④ Save in high definition. Save screenshots of both the process and the results.
-
Challenge: Use
ggthemes’stheme_economist()ortheme_wsj()to draw a picture, and compare the differences with the defaulttheme_minimal(). Save the image in both PNG and PDF formats (use the Cairo PDF device for the PDF to support Chinese characters).