Level up your plots

RMedicine 2023 | 6th June 2023

Hi there 👋 !

👩 Cara Thompson

👩‍💻 Love for patterns in music & language, and a fascination with the human brain |> Psychology PhD |> Analysis of postgraduate medical examinations |> Data Visualisation Consultant


💙 Helping others maximise the impact of their expertise

Today’s goal

To equip you with some design tips and coding tricks to make the most of colour and text to create compelling and memorable visualisations.

  • Explore how to be less dependent on annotations
  • Illustrate ways in which we can use colour and fonts to add text hierarchy
  • Apply all of the above to create story-enhancing annotations
  • Package up reusable bits of R code
  • Introduce you to {ggtext} and {geomtextpath}
  • Give feedback on your own plots

A bit of housekeeping

  • Please ask for help!
    • Thanks, Martine!
  • Regular breaks to apply what we’re learning to your own plots
    • hello@cararthompson.com
  • Namespacing (package::function("blah"))
  • Choose your own pipe
  • Reuse as much of the code as you like!

“Intuitive colour palettes?”

Which one is Bouba, and which one is Kiki?
“Sound Symbolism” - Wolfgang Köhler 1929

“Intuitive colour palettes?”

Which one is Bouba, and which one is Kiki?
Predicted by sound properties - Passi & Arun, 2022

Let’s play a quick game…

The Great Penguin Bake Off

The penguins had a baking competition to see which species could make the best banana loaf. Each species was given bananas of a different level of ripeness.

The Great Penguin Bake Off

The penguins had a baking competition to see which species could make the best banana loaf. Each species was given bananas of a different level of ripeness.

The Great Penguin Bake Off

The Adelie penguins decided to experiment with different quantities of banana in their mix. Each island chose a different quantity.

The Great Penguin Bake Off

The Adelie penguins decided to experiment with different quantities of unripe banana in their mix. Each island chose a different quantity.

The Great Penguin Bake Off

They decided to go on a retreat to plan their bakes in different locations

The Great Penguin Bake Off

Each species was allowed to invite a different mentor…

The Great Penguin Bake Off

… and to choose a type of snack between practice bakes

The Great Penguin Bake Off - Bonus round!

The penguins also baked their cakes for different amounts of time. Here are the mean durations per species.

The Great Penguin Bake Off - Bonus round!

The penguins also baked their cakes for different amounts of time. Here are the mean durations per species.

Compelling and memorable visualisations

#1 - Use colour and orientation purposefully

#2 - Add text hierarchy

#3 - Reduce unnecessary eye movement

#4 - Highlight important patterns

Let’s get coding!

Setting up our first plot

Using the ToothGrowth dataset

  • Build into R for easy “codealongability”
  • Intriguing dataset (?ToothGrowth)
  • Excuse for a cute GIF –>

Setting up our first plot

With a few tips along the way

library(tidyverse)

ToothGrowth %>%
  group_by(supp, dose) %>%
  summarise(mean_length = mean(len)) %>%
  ggplot(aes(x = dose,
             y = mean_length,
             fill = supp)) +
  geom_bar(stat = "identity")

Setting up our first plot

With a few tips along the way

ToothGrowth %>%
  group_by(supp, dose) %>%
  summarise(mean_length = mean(len)) %>%
  ggplot(aes(x = dose,
             y = mean_length,
             fill = supp)) +
  geom_bar(stat = "identity",
           position = "dodge")

Setting up our first plot

With a few tips along the way

ToothGrowth %>%
  group_by(supp, dose) %>%
  summarise(mean_length = mean(len)) %>%
  ggplot(aes(x = dose,
             y = mean_length,
             fill = supp)) +
  geom_bar(stat = "identity",
           position = "dodge", 
           colour = "#FFFFFF",
           size = 2)

Setting up our first plot

Mini tip: get rid of abbreviations

ToothGrowth %>%
  mutate(supplement = 
           case_when(supp == "OJ" ~ "Orange Juice",
                     supp == "VC" ~ "Vitamin C",
                     TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  ggplot(aes(x = dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           position = "dodge", 
           colour = "#FFFFFF",
           size = 2)

Setting up our first plot

Mini tip: theme_minimal()

ToothGrowth %>%
  mutate(supplement = 
           case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  ggplot(aes(x = dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           position = "dodge", 
           colour = "#FFFFFF",
           size = 2) +
  theme_minimal()

Setting up our first plot

Turning Dose into a categorical variable (fear not!)

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           position = "dodge",
           colour = "#FFFFFF", 
           size = 2) +
  theme_minimal()

Setting up our first plot

Turning Dose into a categorical variable (fear not!) + facetting

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

Setting up our first plot

Adding some text (finally!)

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

Setting up our first plot

Legend + facet strip + colour + title… Wait, which one is which?

#1 - Use colour purposefully

  • Orange juice is… orange!
  • Vitamin C is… also orange, but more red and “aggressive”
  • Those green leaves look nice with those colours…
  • imagecolorpicker.com

#1 - Use colour purposefully

Generating a colour palette, starting with orange juice! #fab909

monochromeR::generate_palette("#db5a05", blend_colour = "red", n_colours = 3, view_palette = TRUE)

[1] "#DB5A05" "#E93603" "#F71201"
monochromeR::generate_palette("#3c6b30", "go_darker", n_colours = 2, view_palette = TRUE)

[1] "#3C6B30" "#0C1509"
monochromeR::generate_palette("#0C1509", "go_lighter", n_colours = 6, view_palette = TRUE)

[1] "#0C1509" "#323A30" "#595F57" "#80857F" "#A7AAA6" "#CED0CD"

#1 - Use colour purposefully

Creating a named vector

vit_c_palette <- c("Orange Juice" = "#fab909", 
                   "Vitamin C" = "#E93603",
                   light_text = "#323A30",
                   dark_text =  "#0C1509")

monochromeR::view_palette(vit_c_palette)

#1 - Use colour purposefully

Back to the plot!

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Add in our colours

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = c("#fab909", 
                               "#E93603")) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Add in our colours - wait, what?

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose),
         supplement = 
           factor(supplement, 
                  levels = c("Vitamin C", 
                             "Orange Juice"))) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = c("#fab909", 
                               "#E93603")) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Add in our colours - named vector to the rescue!

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose),
         supplement = 
           factor(supplement, 
                  levels = c("Vitamin C", 
                             "Orange Juice"))) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Add in our colours - named vector to the rescue!

Key advantages

  • Know the colours are applied to the right data points!
  • Keep colour-data pairings consistent throughout the project
  • Package up a default palette
  • Easily reuse colours in the text
    • ggtext::element_markdown() later in this workshop

#1 - Use colour purposefully

Get rid of unused colours

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Get rid of unused colours

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, 
                    limits = force) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Use transparency to indicate dose

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(aes(alpha = dose),
           stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, limits = force) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Use transparency to indicate dose - within limits

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(aes(alpha = dose),
           stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, limits = force) +
  scale_alpha(range = c(0.33, 1)) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

What is the dose unit again? ?ToothGrowth

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(aes(alpha = dose),
           stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, limits = force) +
  scale_alpha(range = c(0.33, 1)) +
  scale_x_discrete(breaks = c("0.5", "1", "2"), 
                   labels = function(x) 
                     paste0(x, " mg/day")) +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal()

#1 - Use colour purposefully

Legend has always been redundant!

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(aes(alpha = dose),
           stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, limits = force) +
  scale_alpha(range = c(0.33, 1)) +
  facet_wrap(supplement ~ ., ncol = 1) +
  scale_x_discrete(breaks = c("0.5", "1", "2"), labels = function(x) paste0(x, " mg/day")) +
  theme_minimal() +
  theme(legend.position = "none")

#1 - Use colour purposefully

And I find this so much less confusing!

ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose,
             y = mean_length,
             fill = supplement)) +
  geom_bar(aes(alpha = dose),
           stat = "identity",
           colour = "#FFFFFF", 
           size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, limits = force) +
  scale_alpha(range = c(0.4, 1)) +
  scale_x_discrete(breaks = c("0.5", "1", "2"), labels = function(x) paste0(x, " mg/day")) +
  coord_flip() +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal() +
  theme(legend.position = "none")

#1 - Use colour purposefully

So much clearer, and we haven’t even done any annotating!

#1 Over to you - but first…

It’s all about making it easier to remember what is what


Picking colours is hard! Let others help you!

#1 Use colour purposefully

#1 Use colour purposefully

machine <- "#061939"
human <- "#e25470"
monochromeR::generate_palette(machine, 
                              blend_colour = human, 
                              n_colours = 3, 
                              view_palette = TRUE)




#1 Use colour purposefully

#1 Use colour purposefully

#1 Use colour purposefully

  • Your department brand guidelines
  • A photo + something like imagecolorpicker.com to pick out colours
  • Take inspiration from photos/ other dataviz / art you like

#1 Use colour purposefully



#1 Use colour purposefully

#1 Use colour purposefully

  • Your department brand guidelines
  • A photo + something like imagecolorpicker.com to pick out colours
  • Take inspiration from other dataviz / art you like
  • Google images and “[whatever you like] palette”

#1 Use colour purposefully

#1 Use colour purposefully

  • Your department brand guidelines
  • A photo + something like imagecolorpicker.com to pick out colours
  • Take inspiration from other dataviz / art you like
  • Google images and “[whatever you like] palette”
  • Or… start from the colour wheel and read around how best to use it

#1 Use colour purposefully

Quick tip: Viewing your colours

penguin_locations <- c("#b0a798",
                       "#afba49",
                       "#d8ebfa")

monochromeR::view_palette(penguin_locations)

#1 Use colour purposefully

Quick tip: Naming and viewing your colours

penguin_locations <- c("Adelie" = "#b0a798",
                       "Chinstrap" = "#afba49",
                       "Gentoo" = "#d8ebfa")

monochromeR::view_palette(penguin_locations)

#1 Use colour purposefully

A few things to bear in mind

  • Accessibility
    • colorblindr::cvd_grid()
    • remotes::install_github("clauswilke/colorblindr")

#1 Use colour purposefully

A few things to bear in mind

#1 Use colour purposefully

A few things to bear in mind

  • Accessibility
  • Race/Ethnicity - avoid stereotypes and be mindful of unintended messages
  • Colour intensity - “more is more”

Over to you!

  • Think about the concepts in your own data
  • Look for images related to those concepts
  • Extract the colour codes you need - imagecolorpicker.com
  • Name them & check what they look like together - monochromeR::view_palette()
  • Assess using colorblindr::cvd_grid()

You have 10 minutes! 📊 🎨 ☕

10:00

Compelling and memorable visualisations

#1 - Use colour and orientation purposefully

#2 - Add text hierarchy

#3 - Reduce unnecessary eye movement

#4 - Highlight important patterns

#2 - Add text hierarchy

#2 - Add text hierarchy

Time to start playing with theme()!

basic_plot <- ToothGrowth %>%
  mutate(supplement = case_when(supp == "OJ" ~ "Orange Juice", supp == "VC" ~ "Vitamin C", TRUE ~ as.character(supp))) %>%
  group_by(supplement, dose) %>%
  summarise(mean_length = mean(len)) %>%
  mutate(categorical_dose = factor(dose)) %>%
  ggplot(aes(x = categorical_dose, y = mean_length, fill = supplement)) +
  geom_bar(aes(alpha = dose), stat = "identity", colour = "#FFFFFF", size = 2) + 
  labs(x = "Dose",
       y = "Mean length (mm)",
       title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C",
subtitle = "With the highest dose, the mean recorded length was almost identical.") +
  scale_fill_manual(values = vit_c_palette, limits = force) +
  scale_alpha(range = c(0.4, 1)) +
  scale_x_discrete(breaks = c("0.5", "1", "2"), labels = function(x) paste0(x, " mg/day")) +
  coord_flip() +
  facet_wrap(supplement ~ ., ncol = 1) +
  theme_minimal(base_size = 15)

basic_plot

#2 - Add text hierarchy

Time to start playing with theme()!

basic_plot +
  theme(legend.position = "none")

#2 - Add text hierarchy

Time to start playing with theme()!

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

Time to start playing with theme()!

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"]),
        plot.title = element_text(colour = vit_c_palette["dark_text"]))

#2 - Add text hierarchy

Time to start playing with theme()!

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"]),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold"))

#2 - Add text hierarchy

Move away from the default fonts

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold",
                                  family = "Enriqueta"))

#2 - Add text hierarchy

Move away from the default fonts

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold",
                                  family = "Enriqueta"),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"], 
                                  size = rel(1.1), face = "bold"),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

Choosing fonts can be tricky!

  • Brand guidelines
  • Datawrapper guidance - avoid fonts that are too wide/narrow!
  • Websites + inspector tool
  • Oliver Schöndorfer’s exploration of the Font Matrix

#2 - Add text hierarchy

Getting custom fonts to work can be frustrating!

Install fonts locally, restart R Studio + 📦 {systemfonts} ({ragg} + {textshaping}) + Set graphics device to “AGG” + 🤞



knitr::opts_chunk$set(dev = “ragg_png”)



#2 - Add text hierarchy

  • set of fonts + set of colours + rules…

A Dataviz Design System: A simple set of rules to follow, to effortlessly make your visualisations look on brand, every time you go to create a new plot.

  • Accessibility
  • Meaningful colours within your context
  • “Visual identity”
  • Implement as a R package

#2 - Add text hierarchy

Give everything some space to breathe

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold",
                                  family = "Enriqueta"),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"], 
                                  size = rel(1.1), face = "bold"),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

Give everything some space to breathe

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"], 
                            family = "Cabin"),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold",
                                  family = "Enriqueta",
                                  lineheight = 1.3,
                                  margin = margin(0.5, 0, 1, 0, "lines")),
        plot.subtitle = element_text(size = rel(1.1), lineheight = 1.3,
                                     margin = margin(0, 0, 1, 0, "lines")),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"], 
                                  size = rel(1.1), face = "bold",
                                  margin = margin(2, 0, 0.5, 0, "lines")),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

Remove unnecessary text

basic_plot +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"], 
                            family = "Cabin"),
        axis.title.y = element_blank(),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold",
                                  family = "Enriqueta",
                                  lineheight = 1.3,
                                  margin = margin(0.5, 0, 1, 0, "lines")),
        plot.subtitle = element_text(size = rel(1.1), lineheight = 1.3,
                                     margin = margin(0, 0, 1, 0, "lines")),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"], 
                                  size = rel(1.1), face = "bold",
                                  margin = margin(2, 0, 0.5, 0, "lines")),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

Watch out for that title!

basic_plot +
  labs(title = "In smaller doses, Orange Juice was associated with greater mean tooth growth,
compared to equivalent doses of Vitamin C") +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        axis.title.y = element_blank(),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = 36, 
                                  face = "bold",
                                  family = "Enriqueta",
                                  lineheight = 1.3,
                                  margin = margin(0.5, 0, 1, 0, "lines")),
        plot.subtitle = element_text(size = rel(1.1), lineheight = 1.3,
                                     margin = margin(0, 0, 1, 0, "lines")),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"], 
                                  size = rel(1.1), face = "bold",
                                  margin = margin(2, 0, 0.5, 0, "lines")),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

Watch out for that title!

basic_plot +
  labs(title = "In smaller doses, Orange Juice was associated with greater mean tooth growth, compared to equivalent doses of Vitamin C") +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        axis.title.y = element_blank(),
        plot.title = element_text(colour = vit_c_palette["dark_text"], 
                                  size = rel(1.5), 
                                  face = "bold",
                                  family = "Enriqueta",
                                  lineheight = 1.3,
                                  margin = margin(0.5, 0, 1, 0, "lines")),
        plot.subtitle = element_text(size = rel(1.1), lineheight = 1.3,
                                     margin = margin(0, 0, 1, 0, "lines")),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"], 
                                  size = rel(1.1), face = "bold",
                                  margin = margin(2, 0, 0.5, 0, "lines")),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

I ❤️ 📦 {ggtext}

basic_plot +
  labs(title = "In smaller doses, Orange Juice was associated with greater mean tooth growth, compared to equivalent doses of Vitamin C") +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        axis.title.y = element_blank(),
        plot.title = ggtext::element_textbox_simple(
          colour = vit_c_palette["dark_text"], 
          size = rel(1.5), 
          face = "bold",
          family = "Enriqueta",
          lineheight = 1.3,
          margin = margin(0.5, 0, 1, 0, "lines")),
        plot.subtitle = ggtext::element_textbox_simple(
          size = rel(1.1), 
          lineheight = 1.3,
          margin = margin(0, 0, 1, 0, "lines")),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"],
                                  size = rel(1.1), face = "bold",
                                  margin = margin(2, 0, 0.5, 0, "lines")),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy + colour!

Hello, HTML + CSS!

We can make text <span style='color:green'>green</span> and also <span style='color:green; font-size:60pt'>really big</span>! 🤯


We can make text green and also really big! 🤯

#2 - Add text hierarchy + colour!

I ❤️ 📦 {ggtext}

basic_plot +
  labs(title = 
         paste0("In smaller doses, **<span style='color:",
                vit_c_palette["Orange Juice"], "'>Orange Juice</span>**
                      was associated with greater mean tooth growth,
                      compared to equivalent doses of **<span style='color:",
                vit_c_palette["Vitamin C"], "'>Vitamin C</span>**")
  ) +
  theme(legend.position = "none",
        text = element_text(colour = vit_c_palette["light_text"],
                            family = "Cabin"),
        axis.title.y = element_blank(),
        plot.title = ggtext::element_textbox_simple(colour = vit_c_palette["dark_text"], 
                                                    size = rel(1.5), 
                                                    face = "bold",
                                                    family = "Enriqueta",
                                                    lineheight = 1.3,
                                                    margin = margin(0.5, 0, 1, 0, "lines")),
        plot.subtitle = ggtext::element_textbox_simple(family = "Cabin", size = rel(1.1), lineheight = 1.3,
                                                       margin = margin(0, 0, 1, 0, "lines")),
        strip.text = element_text(family = "Enriqueta",
                                  colour = vit_c_palette["light_text"],
                                  size = rel(1.1), face = "bold",
                                  margin = margin(2, 0, 0.5, 0, "lines")),
        axis.text = element_text(colour = vit_c_palette["light_text"]))

#2 - Add text hierarchy

See for yourselves!

Over to you!

  • Apply the colours you chose earlier to your plot
  • See what fonts are available on your device - systemfonts::systemfonts() |> View()
    • If you want to, install a new one!
  • Apply one to the title, and one to the rest of the text using theme()
  • Create some text colour variants using monochromeR::generate_palette()
  • Apply those to the title and the other text elements
  • Play around with (relative) text sizes
  • Give me a shout if you get stuck!

You have 10 minutes! 📊 🎨 ☕

10:00

Packaging up

  • Package development is a whole other workshop (but it’s easier than you think!)
    • 📦 {usethis}
  • Any function or object you create can be added to a package
carartemplates::carar_colours()
     green       blue  dark_text light_text 
 "#28A569"  "#2C3D4F"  "#1A242F"  "#808A95" 
monochromeR::view_palette(
  carartemplates::carar_colours()
)

Packaging up

The current status:

plot +
  theme_minimal() +
  theme(...,
        ...,
        ...,
        ...,
        ...,
        ...,
        ...,
        ...) # ~ 20+ lines of code

What we want:

plot +
  theme_guineapigs() # 1 line of code!

Packaging up

theme_guineapigs <- function(base_text_size = 15,
                             palette = vit_c_palette) {
  
  theme_minimal(base_size = base_text_size) +
    theme(legend.position = "none",
          text = element_text(colour = palette["light_text"],
                              family = "Cabin"),
          axis.title.y = element_blank(),
          plot.title = ggtext::element_textbox_simple(colour = palette["dark_text"], 
                                                      size = rel(1.5), 
                                                      face = "bold",
                                                      family = "Enriqueta",
                                                      lineheight = 1.3,
                                                      margin = margin(0.5, 0, 1, 0, "lines")),
          plot.subtitle = ggtext::element_textbox_simple(family = "Cabin", size = rel(1.1), lineheight = 1.3,
                                                         margin = margin(0, 0, 1, 0, "lines")),
          strip.text = element_text(family = "Enriqueta",
                                    colour = palette["light_text"],
                                    size = rel(1.1), face = "bold",
                                    margin = margin(2, 0, 0.5, 0, "lines")),
          axis.text = element_text(colour = palette["light_text"])) 
}

Packaging up

theme_guineapigs <- function(base_text_size = 15,
                             palette = vit_c_palette) {
  
  theme_minimal(base_size = base_text_size) +
    theme(legend.position = "none",
          text = element_text(colour = palette["light_text"],
                              family = "Cabin"),
          axis.title.y = element_blank(),
          plot.title = ggtext::element_textbox_simple(colour = palette["dark_text"], 
                                                      size = rel(1.5), 
                                                      face = "bold",
                                                      family = "Enriqueta",
                                                      lineheight = 1.3,
                                                      margin = margin(0.5, 0, 1, 0, "lines")),
          plot.subtitle = ggtext::element_textbox_simple(family = "Cabin", size = rel(1.1), lineheight = 1.3,
                                                         margin = margin(0, 0, 1, 0, "lines")),
          strip.text = element_text(family = "Enriqueta",
                                    colour = palette["light_text"],
                                    size = rel(1.1), face = "bold",
                                    margin = margin(2, 0, 0.5, 0, "lines")),
          axis.text = element_text(colour = palette["light_text"])) 
}

Packaging up

palmerpenguins::penguins %>%
  ggplot() +
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 colour = sex)) +
  labs(title = "Perfectly proportional penguins",
       subtitle = "Look at them go!",
       x = "Flipper length (mm)",
       y = "Bill length(mm)")

Packaging up

palmerpenguins::penguins %>%
  ggplot() +
  geom_point(aes(x = flipper_length_mm,
                 y = bill_length_mm, 
                 colour = sex)) +
  labs(title = "Perfectly proportional penguins",
       subtitle = "Look at them go!",
       x = "Flipper length (mm)",
       y = "Bill length(mm)") +
  theme_guineapigs()

Level up your plots

#1 - Use colour purposefully

#2 - Add text hierarchy

#3 - Reduce unnecessary eye movement

#4 - Highlight important patterns

#3 - Reduce unnecessary eye movement

We’ve made it easy to see what’s what. Now, let’s make it even easier to compare values.

themed_plot

#3 - Reduce unnecessary eye movement

We’ve made it easy to see what’s what. Now, let’s make it even easier to compare values.

themed_plot +
  scale_y_continuous(expand = c(0, 0.5))

#3 - Reduce unnecessary eye movement

We’ve made it easy to see what’s what. Now, let’s make it even easier to compare values.

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03))

#3 - Reduce unnecessary eye movement

Time to add some text boxes!

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  # x (dose) and y (mean_length) are already 
  # set in the global ggplot() call! 
  ggtext::geom_textbox(aes(label = mean_length))

#3 - Reduce unnecessary eye movement

Time to add some text boxes!

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  ggtext::geom_textbox(aes(label = mean_length),
                       size = 6,
                       halign = 1, 
                       hjust = 1)

#3 - Reduce unnecessary eye movement

Time to add some text boxes!

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  ggtext::geom_textbox(aes(label = mean_length),
                       size = 6,
                       halign = 1, 
                       hjust = 1,
                       fill = NA, 
                       box.colour = NA)

#3 - Reduce unnecessary eye movement

Time to add some text boxes!

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  ggtext::geom_textbox(aes(label = mean_length),
                       size = 6,
                       halign = 1, 
                       hjust = 1,
                       fill = NA,
                       box.colour = NA,
                       family = "Cabin",
                       colour = "#FFFFFF",
                       fontface = "bold")

#3 - Reduce unnecessary eye movement

Now for the fun stuff…

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  ggtext::geom_textbox(aes(
    label = mean_length,
    hjust = case_when(mean_length < 15 ~ 0,
                      TRUE ~ 1),
    halign = case_when(mean_length < 15 ~ 0,
                       TRUE ~ 1)),
    size = 6,
    fill = NA,
    box.colour = NA,
    family = "Cabin",
    fontface = "bold")

#3 - Reduce unnecessary eye movement

Now for the fun stuff…

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  ggtext::geom_textbox(aes(
    label = mean_length,
    hjust = case_when(mean_length < 15 ~ 0,
                      TRUE ~ 1),
    halign = case_when(mean_length < 15 ~ 0,
                       TRUE ~ 1),
    colour = case_when(mean_length > 15 ~ "#FFFFFF",
                       TRUE ~ vit_c_palette[supplement])),
    size = 6,
    fill = NA,
    box.colour = NA,
    family = "Cabin",
    fontface = "bold")

#3 - Reduce unnecessary eye movement

??????

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  ggtext::geom_textbox(aes(
    label = mean_length,
    hjust = case_when(mean_length < 15 ~ 0,
                      TRUE ~ 1),
    halign = case_when(mean_length < 15 ~ 0,
                       TRUE ~ 1),
    colour = case_when(mean_length > 15 ~ "#FFFFFF",
                       TRUE ~ vit_c_palette[supplement])),
    size = 6,
    fill = NA,
    box.colour = NA,
    family = "Cabin",
    fontface = "bold")

#3 - Reduce unnecessary eye movement

scale_colour_identity() required!

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  scale_colour_identity() +
  ggtext::geom_textbox(aes(
    label = mean_length,
    hjust = case_when(mean_length < 15 ~ 0,
                      TRUE ~ 1),
    halign = case_when(mean_length < 15 ~ 0,
                       TRUE ~ 1),
    colour = case_when(mean_length > 15 ~ "#FFFFFF",
                       TRUE ~ vit_c_palette[supplement])),
    size = 6,
    fill = NA,
    box.colour = NA,
    family = "Cabin",
    fontface = "bold")

#3 - Reduce unnecessary eye movement

We might as well add a bit of extra info (with text hierarchy!) to our labels…

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  scale_colour_identity() +
  ggtext::geom_textbox(aes(
    label = paste0("<span style=font-size:12pt>", 
                   dose, "mg/day</span><br>", 
                   mean_length, "mm"),
    hjust = case_when(mean_length < 15 ~ 0,
                      TRUE ~ 1),
    halign = case_when(mean_length < 15 ~ 0,
                       TRUE ~ 1),
    colour = case_when(mean_length > 15 ~ "#FFFFFF",
                       TRUE ~ vit_c_palette[supplement])),
    size = 6,
    fill = NA,
    box.colour = NA,
    family = "Cabin",
    fontface = "bold")

#3 - Reduce unnecessary eye movement

We might as well add a bit of extra info (with text hierarchy!) to our labels…

themed_plot +
  scale_y_continuous(expand = c(0, 0.5)) +
  theme(strip.text = element_text(hjust = 0.03)) +
  scale_colour_identity() +
  ggtext::geom_textbox(aes(
    label = paste0("<span style=font-size:12pt>", 
                   dose, "mg/day</span><br>", 
                   janitor::round_half_up(mean_length), 
                   "mm"),
    hjust = case_when(mean_length < 15 ~ 0,
                      TRUE ~ 1),
    halign = case_when(mean_length < 15 ~ 0,
                       TRUE ~ 1),
    colour = case_when(mean_length > 15 ~ "#FFFFFF",
                       TRUE ~ vit_c_palette[supplement])),
    size = 6,
    fill = NA,
    box.colour = NA,
    family = "Cabin",
    fontface = "bold")

Wait, but why?

Wait, but why?

new_data %>% guinea_pig_plot()

#3 - Reduce unnecessary eye movement

Easier than you think and makes a big difference! 🦸

Over to you!

  • Add a textbox (or several!) using ggtext::geom_textbox()
  • Add in some styling and conditional alignments
  • See what happens when you apply your plot code to random subsets of your data

You have 10 minutes! 📊 🎨 ☕

10:00

Level up your plots

#1 - Use colour purposefully

#2 - Add text hierarchy

#3 - Reduce unnecessary eye movement

#4 - Highlight important patterns

#4 - Highlight important patterns

How did those penguins get on anyway…?

  • Means, trends, key data points

#4 - Highlight important patterns | means

Consider text boxes instead of a legend…

# Create a new tibble
penguin_summaries <- palmerpenguins::penguins %>%
  group_by(species) %>%
  summarise(bill_depth_mm = mean(bill_depth_mm, na.rm = TRUE),
            bill_length_mm = mean(bill_length_mm, na.rm = TRUE)) %>%
  mutate(commentary = case_when(
    species == "Adelie" ~
      "The Adelie penguins tried varying the amount of banana in the mix.
       Turns out, even a hint of green banana is detrimental to yumminess!",
    species == "Gentoo" ~
      "Over-ripe bananas and typically shorter baking times.",
    TRUE ~ "Ripe bananas and slightly longer cooking times."))

#4 - Highlight important patterns | means

Consider text boxes instead of a legend…

penguin_summaries
# A tibble: 3 x 4
  species   bill_depth_mm bill_length_mm commentary                             
  <fct>             <dbl>          <dbl> <chr>                                  
1 Adelie             18.3           38.8 "The Adelie penguins tried varying the~
2 Chinstrap          18.4           48.8 "Ripe bananas and slightly longer cook~
3 Gentoo             15.0           47.5 "Over-ripe bananas and typically short~

#4 - Highlight important patterns | means

Consider text boxes instead of a legend…

penguins_themed_plot +
  ggtext::geom_textbox(data = penguin_summaries,
                       aes(label = paste0(
                         "**Team ", species, "**",
                         "<br><span style = \"color:",
                         banana_colours$light_text,
                         "\">", commentary, "</span>")))

#4 - Highlight important patterns | means

Consider text boxes instead of a legend…

penguins_themed_plot +
  ggtext::geom_textbox(data = penguin_summaries,
                       aes(label = paste0(
                         "**Team ", species, "**",
                         "<br><span style = \"color:",
                         banana_colours$light_text,
                         "\">", commentary, "</span>")),
                       family = "DM Sans",
                       size = 3.5,
                       width = unit(9, "line"),
                       alpha = 0.9,
                       box.colour = NA)

#4 - Highlight important patterns | points

What about individual penguins?

penguin_highlights <- palmerpenguins::penguins_raw %>%
  # Housekeeping
  janitor::clean_names() %>%
  rename(bill_depth_mm = culmen_depth_mm,
         bill_length_mm = culmen_length_mm) %>%
  # Find star baker, runner up and lowest score
  filter(bill_length_mm %in% c(max(bill_length_mm, na.rm = TRUE),
                               sort(bill_length_mm, decreasing = TRUE)[2],
                               min(bill_length_mm, na.rm = TRUE))) 

#4 - Highlight important patterns | points

First, a bit of text manipulation!

penguin_highlights <- palmerpenguins::penguins_raw %>%
  janitor::clean_names() %>%
  rename(bill_depth_mm = culmen_depth_mm,
         bill_length_mm = culmen_length_mm) %>%
  filter(bill_length_mm %in% c(max(bill_length_mm, na.rm = TRUE),
                               sort(bill_length_mm, decreasing = TRUE)[2],
                               min(bill_length_mm, na.rm = TRUE))) %>%
  # More housekeeping
  mutate(species = gsub("(.) (.*)", "\\1", species))


raw_species <- unique(palmerpenguins::penguins_raw$Species)
raw_species
[1] "Adelie Penguin (Pygoscelis adeliae)"      
[2] "Gentoo penguin (Pygoscelis papua)"        
[3] "Chinstrap penguin (Pygoscelis antarctica)"
gsub("(.) (.*)", "\\1", raw_species)
[1] "Adelie"    "Gentoo"    "Chinstrap"

#4 - Highlight important patterns | points

First, a bit of text manipulation!

penguin_highlights <- palmerpenguins::penguins_raw %>%
  janitor::clean_names() %>%
  rename(bill_depth_mm = culmen_depth_mm,
         bill_length_mm = culmen_length_mm) %>%
  filter(bill_length_mm %in% c(max(bill_length_mm, na.rm = TRUE),
                               sort(bill_length_mm, decreasing = TRUE)[2],
                               min(bill_length_mm, na.rm = TRUE))) %>%
  mutate(species = gsub("(.) (.*)", "\\1", species),
         # Add commentary!
         commentary = case_when(
           bill_length_mm == max(bill_length_mm) ~
             paste0("Our star baker is **", individual_id,
                    "**, a ", species, " from ", island,
                    ". Congratulations, ", individual_id, "!"),
           bill_length_mm == sort(bill_length_mm, decreasing = TRUE)[2] ~
             paste0("Our runner up is a ", species,
                    " from ", island, ": **", individual_id,
                    "**, proving that ripe and over-ripe bananas are both good options!"),
           TRUE ~ paste0("**", individual_id,
                         "**, did not have a good baking day. The combination of short cooking time and green bananas probably didn't help!")))

#4 - Highlight important patterns | points

First, a bit of text manipulation!

penguin_highlights %>% 
  select(individual_id,
         species,
         island,
         commentary)
# A tibble: 3 x 4
  individual_id species   island commentary                                     
  <chr>         <chr>     <chr>  <chr>                                          
1 N81A1         Adelie    Dream  **N81A1**, did not have a good baking day. The~
2 N56A2         Gentoo    Biscoe Our star baker is **N56A2**, a Gentoo from Bis~
3 N71A2         Chinstrap Dream  Our runner up is a Chinstrap from Dream: **N71~

#4 - Highlight important patterns | points

Next, let’s work out where we want our labels…

#4 - Highlight important patterns | points

… and add box coordinates and text alignment to our data

penguin_highlights <- palmerpenguins::penguins_raw %>%
  janitor::clean_names() %>%
  rename(bill_depth_mm = culmen_depth_mm,
         bill_length_mm = culmen_length_mm) %>%
  filter(bill_length_mm %in% c(max(bill_length_mm, na.rm = TRUE),
                               sort(bill_length_mm, decreasing = TRUE)[2],
                               min(bill_length_mm, na.rm = TRUE))) %>%
  # more housekeeping!
  arrange(bill_length_mm) %>%
  mutate(species = gsub("(.) (.*)", "\\1", species),
         commentary = case_when(
           bill_length_mm == max(bill_length_mm) ~
             paste0("Our star baker is **", individual_id, "**, a ", species, " from ", island, ". Congratulations, ", individual_id, "!"),
           bill_length_mm == sort(bill_length_mm, decreasing = TRUE)[2] ~
             paste0("Our runner up is a ", species, " from ", island, ": **", individual_id, "**, proving that ripe and over-ripe bananas are both good options!"),
           TRUE ~ paste0("**", individual_id, "**, did not have a good baking day. The combination of short cooking time and green bananas probably didn't help!")),
         # Add label and arrow coordinates
         label_x = c(15, 18.15, 16.45),
         label_y = c(34, 57, 59),
         left_to_right = case_when(label_x < bill_depth_mm ~ 1,
                             TRUE ~ 0),
         arrow_x_end = case_when(label_x < bill_depth_mm ~ bill_depth_mm - 0.1,
                                 TRUE ~ bill_depth_mm + 0.1),
         arrow_y_end = case_when(label_y < bill_length_mm ~ bill_length_mm - 0.1,
                                 TRUE ~ bill_length_mm + 0.1))

#4 - Highlight important patterns | points

Let’s add the annotations…

penguins_themed_plot +
  ggtext::geom_textbox(data = penguin_summaries,
               aes(label = paste0("**Team ", species, "**", "<br><span style = \"color:", banana_colours$light_text, "\">", commentary, "</span>")),
               family = "DM Sans", size = 3.5, width = unit(9, "line"), alpha = 0.9, box.colour = NA)

#4 - Highlight important patterns | points

Let’s add the annotations…

penguins_themed_plot +
  ggtext::geom_textbox(data = penguin_summaries,
               aes(label = paste0("**Team ", species, "**", "<br><span style = \"color:", banana_colours$light_text, "\">", commentary, "</span>")),
               family = "DM Sans", size = 3.5, width = unit(9, "line"), alpha = 0.9, box.colour = NA) +
  ggtext::geom_textbox(data = penguin_highlights,
               aes(label = commentary,
                   x = label_x,
                   y = label_y,
                   hjust = left_to_right),
               family = "DM Sans", 
               size = 3,
               fill = NA,
               box.colour = NA)

#4 - Highlight important patterns | points

… using arrows and alignments to emphasise the story

penguins_themed_plot +
  ggtext::geom_textbox(data = penguin_summaries,
               aes(label = paste0("**Team ", species, "**", "<br><span style = \"color:", banana_colours$light_text, "\">", commentary, "</span>")),
               family = "DM Sans", size = 3.5, width = unit(9, "line"), alpha = 0.9, box.colour = NA) +
  ggtext::geom_textbox(data = penguin_highlights,
               aes(label = commentary, x = label_x, y = label_y, hjust = left_to_right),
               family = "DM Sans", size = 3, fill = NA, box.colour = NA) +
  geom_curve(data = penguin_highlights,
             aes(x = label_x, xend = arrow_x_end,
                 y = label_y, yend = arrow_y_end,
                 hjust = left_to_right),
             arrow = arrow(length = unit(0.1, "cm")),
             curvature = list(0.15),
             alpha = 0.5)

#4 - Highlight important patterns | points

… using arrows and alignments to emphasise the story

penguins_themed_plot +
  ggtext::geom_textbox(data = penguin_summaries,
               aes(label = paste0("**Team ", species, "**", "<br><span style = \"color:", banana_colours$light_text, "\">", commentary, "</span>")),
               family = "DM Sans", size = 3.5, width = unit(9, "line"), alpha = 0.9, box.colour = NA) +
  ggtext::geom_textbox(data = penguin_highlights,
               aes(label = commentary, x = label_x, y = label_y, hjust = left_to_right,
                   halign = left_to_right),
               family = "DM Sans", size = 3, fill = NA, box.colour = NA) +
  geom_curve(data = penguin_highlights,
             aes(x = label_x, xend = arrow_x_end,
                 y = label_y, yend = arrow_y_end),
             arrow = arrow(length = unit(0.1, "cm")),
             curvature = list(0.15),
             alpha = 0.5)

Wait, but why again?

new_data %>% penguin_plot()

Level up your plots

The possibilities are endless!

Level up your plots

The possibilities are endless!

Level up your plots

The possibilities are endless!

Level up your plots

The possibilities are endless!

We made it!

Over to you!


hello@cararthompson.com

Tw/Li: cararthompson


Dataviz Design Systems | Dataviz Commissions | Training & Consultations