Level up your plots

RLadies Edinburgh | Dataviz workshop | 30th May 2023

Hi there 👋 !

👩 Cara Thompson

👩‍💻 A love for patterns |> Psychology PhD |> Psychometrics @ RCSEd |> Data Visualisation Consultant

💙 Helping others maximise the impact of their expertise

☕ Cafe at Modern One Art Gallery


Today’s goal

To equip you with some design tips and coding tricks to enhance the storytelling capabilities of your plots.

  • Explore how to be less dependent on annotations by using intuitive colour palettes
  • Illustrate ways in which we can use text colour and fonts to add text hierarchy
  • Add in story-enhancing annotations and data highlights to draw attention to the key data patterns
  • Package up bits of reusable R code
  • Introduce you to {ggtext}, {geomtextpath} and {gghighlight}

A bit of housekeeping

  • Please ask for help!
  • Regular breaks to apply what we’re learning to your own plots
  • Namespacing (package::function("blah"))
  • Choose your own pipe (I like the old one)
  • 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 unripe 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.

Level up your plots

#1 - Use colour (and orientation) purposefully

#2 - Add text hierarchy

#3 - Reduce unnecessary eye movement

#4 - Highlight important patterns

#1 Use colour purposefully

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

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

#1 Use colour purposefully

plots +
  scale_colour_ophelia() +
  theme_ophelia()

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

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

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


#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” - consider “dark mode”


#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” - consider “dark mode”


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
  • Check what they look like together - monochromeR::view_palette()
  • Assess using colorblindr::cvd_grid()

You have 5 minutes! 📊 🎨 ☕

05:00

Let’s get coding!

Setting up our first plot

Using the ToothGrowth dataset

  • Built into R for easy “codealongability”
  • Intriguing dataset (?ToothGrowth)
  • Research question with a pattern to visualise and annotate

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 (and orientation) 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 (and orientation) purposefully

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

Level up your plots

#1 - Use colour 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

See what fonts are available on your device

  • systemfonts::system_fonts() %>% View()

#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

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

Wait, but why?

#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 5 minutes! 📊 🎨 ☕

05: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…?

#4 - Highlight important patterns

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

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

I ❤️ 📦 {geomtextpath}

penguins_themed_plot +
  scale_alpha(range = c(0.1, 0.5)) +
  geomtextpath::geom_textpath(aes(label = paste0(
    "Team ", species)),
    stat = "smooth")

#4 - Highlight important patterns

I ❤️ 📦 {geomtextpath}

penguins_themed_plot +
  scale_alpha(range = c(0.1, 0.5)) +
  geomtextpath::geom_textpath(aes(label = paste0(
    "Team ", species)),
    stat = "smooth",
    family = "DM Sans",
    fontface = "bold",
    vjust = 0.2,
    hjust = 0.1)

#4 - Highlight important patterns

I ❤️ 📦 {geomtextpath}

#4 - Highlight important patterns

I ❤️ 📦 {geomtextpath}

penguins_themed_plot +
  scale_alpha(range = c(0.1, 0.5)) +
  geomtextpath::geom_labelpath(aes(label = paste0(
    "Team ", species)),
    stat = "smooth",
    family = "DM Sans",
    alpha = 0.8)

#4 - Highlight important patterns

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 data 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 data 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 data points

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

#4 - Highlight important data 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 data 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 data 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 data 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 data 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)

#4 - Highlight important data points

Finally, enter {gghighlight}

penguins_themed_plot +
  ggtext::geom_textbox(data = filter(penguin_summaries, species == "Gentoo"),
               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 = filter(penguin_highlights, species == "Gentoo"),
               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 = filter(penguin_highlights, species == "Gentoo"),
             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) +
  gghighlight::gghighlight(species == "Gentoo", 
                           use_direct_label = FALSE)

#4 - Highlight important data points

Finally, enter {gghighlight}

penguins_themed_plot +
  ggtext::geom_textbox(data = filter(penguin_summaries, species == "Chinstrap"),
               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) +
  gghighlight::gghighlight(species == "Chinstrap", 
                           use_direct_label = FALSE)

#4 - Highlight important data points

Finally, enter {gghighlight}

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) +
  gghighlight::gghighlight(bill_length_mm < 40)

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!

Time for snacks 😊

Over to you!


hello@cararthompson.com

Tw/Li: @cararthompson


Dataviz Commissions | Dataviz Design Systems | Training & Consultations