R ggplot2 - Customize Colors and Themes

ggplot2 provides dedicated scale functions for every aesthetic mapping. For discrete data, `scale_color_manual()` and `scale_fill_manual()` offer complete control over color assignment.

Key Insights

  • ggplot2’s color customization spans three primary approaches: manual scale functions for precise control, built-in palettes like Brewer and viridis for accessibility, and complete theme systems that define every visual element
  • Theme modifications follow a hierarchy where complete themes set defaults, theme() function overrides specific elements, and custom theme functions enable reusable styling across multiple plots
  • Color choices directly impact data interpretation—sequential palettes for ordered data, diverging palettes for data with meaningful midpoints, and qualitative palettes for categorical variables without inherent order

Manual Color Control with Scale Functions

ggplot2 provides dedicated scale functions for every aesthetic mapping. For discrete data, scale_color_manual() and scale_fill_manual() offer complete control over color assignment.

library(ggplot2)

# Create sample data
df <- data.frame(
  category = rep(c("Product A", "Product B", "Product C"), each = 10),
  month = rep(1:10, 3),
  sales = c(rnorm(10, 100, 15), rnorm(10, 150, 20), rnorm(10, 120, 18))
)

# Manual color specification
ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(
    values = c("Product A" = "#E63946", 
               "Product B" = "#457B9D", 
               "Product C" = "#2A9D8F"),
    name = "Product Line"
  ) +
  labs(title = "Monthly Sales by Product", x = "Month", y = "Sales ($)")

For continuous variables, scale_color_gradient() and related functions create smooth color transitions. Use scale_color_gradient2() when your data has a meaningful midpoint.

# Continuous color scale with diverging palette
df_heatmap <- expand.grid(x = 1:10, y = 1:10)
df_heatmap$value <- with(df_heatmap, sin(x/2) * cos(y/2))

ggplot(df_heatmap, aes(x = x, y = y, fill = value)) +
  geom_tile() +
  scale_fill_gradient2(
    low = "#d7191c",
    mid = "#ffffbf",
    high = "#2c7bb6",
    midpoint = 0,
    name = "Correlation"
  ) +
  coord_fixed()

Built-in Color Palettes

ColorBrewer palettes provide carefully designed color schemes optimized for different data types. The scale_color_brewer() and scale_fill_brewer() functions access these palettes with three palette types: sequential, diverging, and qualitative.

# Sequential palette for ordered categories
df_ordered <- data.frame(
  region = factor(c("Low", "Medium", "High", "Very High"), 
                  levels = c("Low", "Medium", "High", "Very High")),
  value = c(25, 45, 70, 90)
)

ggplot(df_ordered, aes(x = region, y = value, fill = region)) +
  geom_col() +
  scale_fill_brewer(palette = "YlOrRd", direction = 1) +
  theme_minimal() +
  theme(legend.position = "none")

# Qualitative palette for categorical data
ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  scale_color_brewer(palette = "Set2") +
  theme_minimal()

The viridis color scales provide perceptually uniform palettes that maintain contrast when printed in grayscale and are accessible to colorblind viewers.

# Viridis palette options: viridis, magma, plasma, inferno, cividis
ggplot(df_heatmap, aes(x = x, y = y, fill = value)) +
  geom_tile() +
  scale_fill_viridis_c(option = "plasma") +
  coord_fixed() +
  theme_minimal()

# Discrete viridis for categorical data
ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  scale_color_viridis_d(option = "cividis", end = 0.8) +
  theme_minimal()

Complete Theme Systems

ggplot2 includes several complete themes that control all non-data plot elements. These themes serve as starting points for customization.

# Compare built-in themes
p <- ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  labs(title = "Sales Comparison")

# theme_minimal: clean, minimal gridlines
p + theme_minimal()

# theme_classic: no gridlines, axis lines present
p + theme_classic()

# theme_bw: black and white with gridlines
p + theme_bw()

# theme_void: completely empty canvas
p + theme_void()

The ggthemes package extends these options with publication-ready themes.

library(ggthemes)

# Wall Street Journal style
p + theme_wsj() + scale_color_wsj()

# Economist style
p + theme_economist() + scale_color_economist()

# FiveThirtyEight style
p + theme_fivethirtyeight() + scale_color_fivethirtyeight()

Granular Theme Customization

The theme() function modifies individual theme elements. Elements follow a hierarchy: element_text() for text, element_line() for lines, element_rect() for rectangles, and element_blank() to remove elements.

ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(values = c("#E63946", "#457B9D", "#2A9D8F")) +
  labs(
    title = "Quarterly Sales Performance",
    subtitle = "Q1-Q2 2024",
    x = "Month",
    y = "Sales Revenue ($)",
    color = "Product"
  ) +
  theme_minimal() +
  theme(
    # Title formatting
    plot.title = element_text(size = 18, face = "bold", hjust = 0),
    plot.subtitle = element_text(size = 12, color = "gray40", hjust = 0),
    
    # Axis text and titles
    axis.title = element_text(size = 12, face = "bold"),
    axis.text = element_text(size = 10),
    axis.text.x = element_text(angle = 0),
    
    # Legend positioning and styling
    legend.position = "bottom",
    legend.title = element_text(size = 11, face = "bold"),
    legend.text = element_text(size = 10),
    legend.background = element_rect(fill = "gray95", color = "gray70"),
    legend.key = element_blank(),
    
    # Panel and grid
    panel.grid.major = element_line(color = "gray85", linewidth = 0.5),
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "white"),
    
    # Plot margins
    plot.margin = margin(t = 20, r = 20, b = 20, l = 20)
  )

Creating Reusable Custom Themes

Package your theme modifications into functions for consistency across multiple plots.

theme_custom <- function(base_size = 12, base_family = "") {
  theme_minimal(base_size = base_size, base_family = base_family) +
    theme(
      # Grid customization
      panel.grid.major = element_line(color = "gray90", linewidth = 0.3),
      panel.grid.minor = element_blank(),
      
      # Text elements
      plot.title = element_text(
        size = base_size * 1.4,
        face = "bold",
        hjust = 0,
        margin = margin(b = 10)
      ),
      plot.subtitle = element_text(
        size = base_size * 1.1,
        color = "gray30",
        hjust = 0,
        margin = margin(b = 15)
      ),
      
      # Axis styling
      axis.title = element_text(size = base_size * 1.1, face = "bold"),
      axis.text = element_text(size = base_size * 0.9),
      axis.line = element_line(color = "gray20", linewidth = 0.5),
      
      # Legend
      legend.position = "top",
      legend.justification = "left",
      legend.title = element_text(face = "bold"),
      legend.background = element_blank(),
      legend.key = element_blank(),
      
      # Overall appearance
      plot.background = element_rect(fill = "white", color = NA),
      panel.background = element_rect(fill = "white", color = NA)
    )
}

# Apply custom theme
ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_viridis_d(option = "plasma", end = 0.8) +
  labs(
    title = "Sales Trends Analysis",
    subtitle = "Performance metrics across product lines",
    x = "Month", y = "Sales ($)", color = "Product"
  ) +
  theme_custom(base_size = 11)

Color Palette Generation

Create custom color palettes using color theory principles. The colorspace package provides tools for generating harmonious color schemes.

library(colorspace)

# Generate sequential palette
seq_palette <- sequential_hcl(n = 5, palette = "Blues 3")

# Generate diverging palette
div_palette <- diverging_hcl(n = 7, palette = "Blue-Red 3")

# Generate qualitative palette
qual_palette <- qualitative_hcl(n = 4, palette = "Dark 3")

# Use generated palette
ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  scale_color_manual(values = qual_palette) +
  theme_custom()

For accessibility, test color combinations with sufficient contrast ratios and verify appearance in colorblind simulations using the colorBlindness package.

library(colorBlindness)

# Create plot
p <- ggplot(df, aes(x = month, y = sales, color = category)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(values = c("#E63946", "#457B9D", "#2A9D8F")) +
  theme_custom()

# Simulate colorblind vision
cvdPlot(p)

These techniques provide complete control over ggplot2 visualizations, from individual color selections to comprehensive theme systems. Combine manual specifications with built-in palettes and custom themes to create publication-ready graphics that effectively communicate your data.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.