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 with theme() to labs and scale, all the way to high-resolution output with ggsave.

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



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

R
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

R
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

R
# 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

100%
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

R
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

R
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

R
theme(
  panel.background = element_rect(
    fill = "lightyellow",
    color = "black",
    linewidth = 0.5
  ),
  plot.background = element_rect(fill = "white")
)

(6) element_blank() Hide

R
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

R
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

R
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

R
# 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

R
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

R
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

R
# 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)

R
# 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)

R
# 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

R
# 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

R
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

R
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)

R
# 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

R
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

R
# 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 .pdf Thesis (Vector)
SVG .svg Web Vector Graphics
JPEG .jpg Photo (not recommended for graphics)
TIFF .tiff Printing
R
# 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

R 📖 Display only
# ============================================
# 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")
91 logic lines (exceeds 40-line limit, display only)

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

Q What is the difference between theme_minimal and theme_bw?
A theme_minimal Minimal (white background, light gray grid), theme_bw Black and White (with borders, distinct grid).
Q How do I remove the secondary grid?
A theme(panel.grid.minor = element_blank()). This makes the image cleaner.
Q Where should the legend be placed?
A It depends on the complexity of the chart. For 1–2 variables, use 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.
Q What is the difference between scale_color and scale_fill?
A 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.
Q 300 dpi or 150 dpi?
A 300 dpi for printing, 96–150 dpi for screens, and vector PDFs do not require dpi. ggsave The default is 300 dpi.
Q How do I change the Chinese font?
A theme_minimal(base_family = "SimHei") (Windows) or "PingFang SC" (macOS) or "WenQuanYi Zen Hei" (Linux).

📖 Summary


📝 Exercises

  1. Basic Exercise: Use mtcars to create a scatter plot of wt vs. mpg, and use the four themes theme_minimal, theme_bw, theme_classic, and theme_void to compare the differences in the resulting plots. Take screenshots for comparison.

  2. 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.

  3. Basic Exercise: Draw the same image using scale_color_brewer(palette = "Set1") and scale_color_brewer(palette = "Set2"), then take screenshots to compare the color differences.

  4. 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.

  5. Challenge: Use ggthemes’s theme_economist() or theme_wsj() to draw a picture, and compare the differences with the default theme_minimal(). Save the image in both PNG and PDF formats (use the Cairo PDF device for the PDF to support Chinese characters).

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏