Skip to Content
Course content

155: Practice Exercise: Building a Custom ggplot2 Theme

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, do I have to call the theme function inside every single plot?

Short answer: No. If you find yourself copying and pasting ten lines of theme() code into every plot in your script, you're doing it the hard way. The "engineer's approach" here is to wrap your preferences into a custom function. This makes your code cleaner and, more importantly, if your boss decides the axis text should be navy blue instead of grey, you only have to change it in one place.

# Here is how I usually structure a custom theme function
theme_corporate <- function() {
  theme_minimal() + 
    theme(
      text = element_text(family = "serif", color = "#333333"),
      plot.title = element_text(face = "bold", size = 14, hjust = 0.5),
      panel.grid.minor = element_blank(),
      axis.line = element_line(color = "black")
    )
}

# Now you just add it like any other ggplot component
ggplot(mtcars, aes(wt, mpg)) + 
  geom_point() + 
  theme_corporate()

By returning a theme() object, your custom function behaves exactly like the built-in themes you're already using.

Why can't I just pass a color string directly to the theme?

This is a common point of frustration. You'll try to do something like axis.text = "blue" and R will throw an error. It's because ggplot2 needs to know what it's styling. Is it a piece of text? A rectangle? A line?

That's why we use "element" functions. If you're touching text, use element_text(). If you're touching a background or a border, use element_rect(). If you're touching a line, use element_line(). I like to think of these as "style containers" that hold the actual properties like color, size, or line type.

# Wrong: axis.title = "red"
# Right:
theme(axis.title = element_text(color = "red", size = 12))

# Wrong: panel.background = "white"
# Right:
theme(panel.background = element_rect(fill = "white", color = "grey80"))

Can I just "tweak" an existing theme instead of defining everything from scratch?

Absolutely. In fact, I almost always do this. Defining every single element of a plot—from the legend key spacing to the plot margin—is a nightmare and a waste of your time. The trick is to call a base theme (like theme_minimal() or theme_bw()) inside your custom function before you add your own theme() overrides.

Because ggplot2 processes these layers sequentially, your custom theme() call will overwrite only the specific elements you mention, leaving the rest of the base theme's sensible defaults intact. It's essentially "inheritance" for your plot styles.

theme_my_style <- function() {
  # Start with a clean slate
  theme_bw() + 
  # Overwrite only the things I actually care about
  theme(
    panel.grid.major = element_line(color = "lightgrey", linetype = "dotted"),
    legend.position = "bottom"
  )
}



📋 Practical Task

Exercise: Create a High-Contrast 'Dark Mode' Executive Dashboard Theme

In this exercise, you will build a professional "Dark Mode" theme intended for a high-contrast executive dashboard. Your goal is to create a function called theme_exec_dark() that makes the plot look sleek and modern.

Requirements:

  • Base the theme on theme_minimal().
  • Set the overall plot background and panel background to a very dark grey (e.g., "#222222"). Hint: You'll need to use plot.background and panel.background.
  • Change all text elements to white or an off-white color so they are legible.
  • Remove all gridlines entirely using element_blank().
  • Make the plot title centered, bold, and size 16.

Testing your theme: Apply your function to a plot of the iris dataset (plotting Sepal.Length vs Sepal.Width) to ensure the colors contrast correctly and the gridlines are gone.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.