+ - 0:00:00
Notes for current slide
Notes for next slide

Assembling Your Dream Theme

Crafting Custom Designs in ggplot2 for Effortless Visualisation Enhancement

Long Nguyen & Andreas Neumann

2024-05-28

1 / 34

Why create ggplot2 themes and colour scales?

  • What Andreas said
2 / 34

Why create ggplot2 themes and colour scales?

  • What Andreas said

  • Save time

Meme video "R users explaining why spending 45 minutes customizing a ggplot is better than to accomplish the same result in 2 minutes in Excel". Top post of reddit.com/r/rstats.

2 / 34

Prerequisites

  • {tidyverse}, which includes (among others):

    • {ggplot2}

    • {ragg}: high-quality graphic backend for R

    • {systemfonts}: finds the correct font file for a specific font and style (we'll see it in action later)

  • In RStudio Global OptionsGraphics, choose AGG as Backend

3 / 34
library(ggplot2)
3 / 34
library(ggplot2)
library(palmerpenguins)
3 / 34
library(ggplot2)
library(palmerpenguins)
p <- ggplot(
data = penguins,
aes(bill_length_mm, bill_depth_mm)
)
3 / 34
library(ggplot2)
library(palmerpenguins)
p <- ggplot(
data = penguins,
aes(bill_length_mm, bill_depth_mm)
) +
geom_point(
aes(colour = species),
size = 2
)
3 / 34
library(ggplot2)
library(palmerpenguins)
p <- ggplot(
data = penguins,
aes(bill_length_mm, bill_depth_mm)
) +
geom_point(
aes(colour = species),
size = 2
) +
labs(
title = "Title",
subtitle = "Subtitle",
caption = "Caption"
)
3 / 34
library(ggplot2)
library(palmerpenguins)
p <- ggplot(
data = penguins,
aes(bill_length_mm, bill_depth_mm)
) +
geom_point(
aes(colour = species),
size = 2
) +
labs(
title = "Title",
subtitle = "Subtitle",
caption = "Caption"
)
p

Scatterplot of bill length by bill depth of 3 penguin species

3 / 34


The easiest way to extend ggplot2 is to make a new theme.

— Thomas Lin Pedersen, Extending your ability to extend ggplot2

Screenshot from Thomas Lin Pedersen's talk "Extending your ability to extend ggplot2" at RStudio::conf 2020. Extension points for ggplot2, sorted by increasing complexity. Custom themes and scales are the easiest to write.

4 / 34

Themes

5 / 34

Arguments of the theme() function

6 / 34
?theme
7 / 34
p

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
base_size = 14,
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
base_size = 14,
base_family = "Atkinson Hyperlegible"
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
base_size = 14,
base_family = "Atkinson Hyperlegible"
) +
theme(
legend.position = "top",
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
base_size = 14,
base_family = "Atkinson Hyperlegible"
) +
theme(
legend.position = "top",
plot.title.position = "plot",
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
base_size = 14,
base_family = "Atkinson Hyperlegible"
) +
theme(
legend.position = "top",
plot.title.position = "plot",
plot.title = element_text(
size = rel(1.5)
),
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34
p +
theme_minimal(
base_size = 14,
base_family = "Atkinson Hyperlegible"
) +
theme(
legend.position = "top",
plot.title.position = "plot",
plot.title = element_text(
size = rel(1.5)
),
panel.grid.minor = element_blank()
)

Modifying the theme of a plot interactively by calling theme_minimal() and theme()

7 / 34

Writing a theme function

8 / 34

We can simply pack the regular R code for theming into a function and save ourselves from mindless repeating or copy-pasting!

my_theme <- function(base_size = 14,
base_family = "Atkinson Hyperlegible") {
theme_minimal(base_size = base_size, base_family = base_family) +
theme(
legend.position = "top",
plot.title.position = "plot",
plot.title = element_text(size = rel(1.5)),
panel.grid.minor = element_blank()
)
}
9 / 34
p

Applying the theme defined in the my_theme() function to a plot

9 / 34
p +
my_theme(
)

Applying the theme defined in the my_theme() function to a plot

9 / 34
p +
my_theme(
base_size = 20,
)

Applying the theme defined in the my_theme() function to a plot

9 / 34
p +
my_theme(
base_size = 20,
base_family = "Menlo"
)

Applying the theme defined in the my_theme() function to a plot

9 / 34
p +
my_theme(
base_size = 20,
base_family = "Menlo"
) +
theme(
plot.title = element_text(
face = "bold"
)
)

Applying the theme defined in the my_theme() function to a plot

9 / 34

More customisations for your theme function
(on top of passing arguments to theme())

10 / 34

Example: Showing/hiding gridlines more easily

(Inspired by hrbrthemes)

my_theme <- function(base_size = 14,
base_family = "Atkinson Hyperlegible",
grid = "XY") {
t <- theme_minimal(base_size = base_size, base_family = base_family) +
theme(
legend.position = "top",
plot.title.position = "plot",
plot.title = element_text(size = rel(1.5))
)
if (!grepl("X", grid)) t <- t + theme(panel.grid.major.x = element_blank())
if (!grepl("Y", grid)) t <- t + theme(panel.grid.major.y = element_blank())
if (!grepl("x", grid)) t <- t + theme(panel.grid.minor.x = element_blank())
if (!grepl("y", grid)) t <- t + theme(panel.grid.minor.y = element_blank())
t
}
11 / 34
p +
my_theme()

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

11 / 34
p +
my_theme(grid = "Y")

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

11 / 34
p +
my_theme(grid = "Xx")

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

11 / 34
p +
my_theme(grid = "xy")

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

11 / 34

Making sure that fonts work

12 / 34

Applying an unavailable font just quietly falls back to default font (or throws errors depending on your OS and graphics device).

p +
my_theme(
base_family = "Expensive Designer Font"
)

Applying an unavailable font just quietly falls back to default font.

13 / 34

We can control this behaviour and make it explicit using systemfonts::system_fonts():

my_theme <- function(base_size = 14,
base_family = "Atkinson Hyperlegible",
grid = "XY") {
if (!(base_family %in% systemfonts::system_fonts()$family)) {
message("Font '", base_family, "' not installed.\nUsing system's default font.")
base_family <- ""
}
[...]
}
14 / 34
p +
my_theme(
base_family = "Expensive Designer Font"
)
#> Font 'Expensive Designer Font' not installed.
#> Using system's default font.

Applying an unavailable font throws a message and falls back to default font

15 / 34

Enabling extra font features

16 / 34

Example: tabular numbers (or monospaced digits)

Tabular numbers are monospaced, which keeps their sizes consistent for display on axes

(Source: https://blog.datawrapper.de/fonts-for-data-visualization/)

17 / 34

Example: tabular numbers (or monospaced digits)

  • Solution 1: choose fonts with tabular numbers enabled by default
18 / 34

Example: tabular numbers (or monospaced digits)

  • Solution 2: use {systemfonts} enable tabular numbers (if your font has this feature and it is not enabled by default)
library(systemfonts)
register_font(
"Atkinson Hyperlegible tnum",
"/path/to/Atkinson-Hyperlegible.otf",
features = font_feature(numbers = "tabular")
)

The newly registered font can be found in registry_fonts() (instead of system_fonts). New fallback condition:

!(base_family %in% c(
systemfonts::system_fonts()$family,
systemfonts::registry_fonts()$family
))
19 / 34

Example: tabular numbers (or monospaced digits)

p +
my_theme(base_family =
"Atkinson Hyperlegible",
)

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

19 / 34

Example: tabular numbers (or monospaced digits)

p +
my_theme(base_family =
"Menlo",
)

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

19 / 34

Example: tabular numbers (or monospaced digits)

p +
my_theme(base_family =
"Atkinson Hyperlegible tnum",
)

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

19 / 34

Example: tabular numbers (or monospaced digits)

p +
my_theme(base_family =
"Atkinson Hyperlegible",
)

Applying the theme defined in the my_theme() function to a plot – now with the grid argument to show/hide gridlines more easily

19 / 34

Check out the theme_correlaid() function:

https://github.com/CorrelAid/correltools/blob/main/R/ggplot-theme.R

20 / 34

Hands-on session

Create your own theme function! 💻🚀

21 / 34

Colour scales

22 / 34

Colour palettes

#bcd259#6fa07f#214f8f

#f04451#85638c#214f8f

23 / 34

Colour palettes

#bcd259#6fa07f#214f8f

#f04451#85638c#214f8f

Qualitative palette

#96c342#3863a2#f04451

Greyscale palette

#3c3c3b#727375#9e9fa3#cdced0

23 / 34

Steps to create a colour scale function

  1. Save the colour palette(s) as charactor vector(s)

  2. Create a function factory that outputs a palette with the desired number of colours

  3. Create a scale function by wrapping a scale constructor (e.g., ggplot2::continuous_scale()) around the palette function in step 2

24 / 34

Step 1

correlaid_colours <- list(
gradient = c("#bcd259", "#6fa07f", "#214f8f"),
gradient_x = c("#f04451", "#85638c", "#214f8f"),
qualitative = c("#96c342", "#3863a2", "#f04451"),
grey = c("#3c3c3b", "#727375", "#9e9fa3", "#cdced0")
)
25 / 34

Step 2

correlaid_pal <- function(direction = 1, option = "qualitative") {
stopifnot(length(option) == 1 && option %in% names(correlaid_colours))
cols <- correlaid_colours[[option]]
function(n) {
cols <- grDevices::colorRampPalette(cols, space = "Lab", interpolate = "spline")(n)
if (direction < 0) rev(cols) else cols
}
}
  • Takes two argument: option (which colour palette?) and direction (revert or not?)

  • Returns a function that takes the desired number of colours (e.g., number of categories) and produces as many colours by interpolation

26 / 34

Step 2

correlaid_pal()(n = 2)

#96C341#F04451

correlaid_pal(option = "gradient")(n = 7)

#BBD259#A0C568#86B475#6E9F7F#588786#406C8B#204E8E

correlaid_pal(direction = -1, option = "gradient")(n = 7)

#204E8E#406C8B#588786#6E9F7F#86B475#A0C568#BBD259

27 / 34

Step 3

scale_colour_correlaid_d <- function(direction = 1,
option = "qualitative",
...) {
ggplot2::discrete_scale(
aesthetics = "colour",
scale_name = "correlaid",
palette = correlaid_pal(direction, option),
...
)
}
28 / 34

Step 3

scale_colour_correlaid_d <- function(direction = 1,
option = "qualitative",
...) {
ggplot2::discrete_scale(
aesthetics = "colour",
scale_name = "correlaid",
palette = correlaid_pal(direction, option),
...
)
}
  • Same for scale_fill_correlaid_d
28 / 34

Step 3

scale_colour_correlaid_c <- function(direction = 1,
option = "gradient",
guide = "colourbar",
...) {
ggplot2::continuous_scale(
aesthetics = "colour",
scale_name = "correlaid",
palette = scales::gradient_n_pal(correlaid_pal(direction, option)(8)),
guide = guide,
...
)
}
29 / 34
p +
scale_colour_correlaid_d()

Applying the defined colour scales functions to a plot

30 / 34

As expected, applying a continuous colour scale to a discrete variable throws an error:

p +
scale_colour_correlaid_c()
#> Error in `scale_colour_correlaid_c()`:
#> ! Discrete values supplied to continuous scale.
#> ℹ Example values: Adelie, Adelie, Adelie, Adelie, and Adelie
31 / 34

Check out the scale_*_correlaid_*() functions:

https://github.com/CorrelAid/correltools/blob/main/R/ggplot-scales.R

32 / 34

Thank you!

long39ng

https://lo-ng.netlify.app/

nguyen@dezim-institut.de


anneumann1

andneumann@outlook.de

34 / 34

Why create ggplot2 themes and colour scales?

  • What Andreas said
2 / 34
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
oTile View: Overview of Slides
Esc Back to slideshow