# Libraries ----
library(tidyverse)
library(glue)
# Data source
library(palmerpenguins)
# Annotations
library(extrafont)
library(ggtext)
# Data ----
# |- Penguins ----
<- penguins %>%
penguins # Flipper length and Body mass are integers. We need
# to turn them into numerics to allow the case_when()
# mutations later in the code.
mutate(flipper_length_mm = as.numeric(flipper_length_mm),
body_mass_g = as.numeric(body_mass_g))
%>%
penguins ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
colour = species,
size = body_mass_g),
alpha = 0.9) +
labs(title = "Perfectly proportional penguins - with a few exceptions!",
subtitle = "Typically, the heavier the penguin, the longer its flippers and bill. The proportions within each species are also very consistent!
Adelie penguins tend to be the lightest penguins, with the shortest bills and flippers. Chinstraps have longer bills but not much longer flippers.
The largest birds in this dataset, with the longest flippers, are the Gentoo. The proportions within each species are also very consistent!",
x = "Bill length (mm)",
y = "Flipper length (mm)") -> basic_plot
# |- Summary data ----
<- penguins %>%
p_summaries group_by(species) %>%
summarise(mean_mass_kg = round(mean(body_mass_g, na.rm = T)/1000, 2),
mean_x = mean(bill_length_mm, na.rm = T),
mean_y = mean(flipper_length_mm, na.rm = T))
# |- Exceptions ----
<- penguins %>%
p_exceptions filter(bill_length_mm == 48.7 & flipper_length_mm == 222 |
== 46.9 & flipper_length_mm == 192 |
bill_length_mm == 58.0 & flipper_length_mm == 181 |
bill_length_mm == 44.1 & flipper_length_mm == 210) %>%
bill_length_mm arrange(bill_length_mm) %>%
mutate(nickname = c( "The BFG", "Tinkerbell", "Average Joes", "Cyrano"),
description = c("Sitting comfortably within the cluster of Gentoos, this Adelie penguin defies the predictions of his species on both flipper length and bill length.",
"The smallest penguin in the set defies the odds on weight for her species and flipper and bill length.",
"The Gentoos tend to stick to their trend, with very few outliers. Heavier penguins have longer flippers and longer bills.",
"With an exceptionally long bill and the shortest flippers bar one among the Chinstraps, this little guy is sure to stand out!")) %>%
mutate(label_x = c(39, 49, 44, 58),
label_y = c(210, 185, 229,187)) %>%
mutate(label_hjust = case_when(label_x < bill_length_mm ~ 1,
== bill_length_mm ~ 0.5,
label_x TRUE ~ 0),
label_vjust = case_when(label_y < flipper_length_mm ~ 0.85,
< flipper_length_mm &
label_y == bill_length_mm ~ 1,
label_x == flipper_length_mm ~ 0.5,
label_y > flipper_length_mm &
label_y == bill_length_mm ~ 0,
label_x TRUE ~ 0.15)) %>%
mutate(arrow_end_x = case_when(label_x < bill_length_mm ~ bill_length_mm - 0.3,
== bill_length_mm ~ bill_length_mm,
label_x TRUE ~ bill_length_mm + 0.3),
arrow_end_y = case_when(label_y < flipper_length_mm ~ flipper_length_mm - 1.5,
== flipper_length_mm ~ flipper_length_mm,
label_y TRUE ~ flipper_length_mm + 1.5)) %>%
mutate(curvature = c(0, -0.15, -0.1, 0))
# Plot ----
# |- Palette ----
<- list("Adelie" = "#fd7901",
penguin_palette "Chinstrap" = "#c35bca",
"Gentoo" = "#0e7175",
"dark_text" = "#1A242F",
"light_text" = "#94989D")
# To get text colour variants
::generate_palette("#1A242F",
monochromeR"go_lighter",
n_colours = 4)
# |- Main plot ----
<- basic_plot +
labelled_plot scale_colour_manual(values = penguin_palette) +
theme_minimal() +
labs(subtitle = glue("Typically, the heavier the penguin, the longer its flippers and bill.
The proportions within each species are also very consistent.<br>
<span style='color:{penguin_palette$Adelie}'>**Adelie**</span>
penguins tend to be the smallest penguins, with the shortest bills
and flippers. <span style='color:{penguin_palette$Chinstrap}'>**Chinstraps**</span>
have longer bills<br>but not much longer flippers. The largest birds in this dataset,
with the longest flippers, are the
<span style='color:{penguin_palette$Gentoo}'>**Gentoo**</span>.")) +
geom_textbox(data = p_summaries,
aes(x = mean_x, y = mean_y, colour = species,
label = glue("{species}<span style='color:{penguin_palette$light_text};
font-size:9pt'><br>Mean Body Mass<br></span>
{mean_mass_kg} Kg")),
halign = 0.5, hjust = 0.5,
size = 6.5,
family = "Segoe UI",
box.color = NA,
alpha = 0.8,
maxwidth = unit(6.5, "lines")) +
geom_textbox(data = p_exceptions,
aes(x = label_x, y = label_y,
colour = species,
vjust = label_vjust, valign = label_vjust,
hjust = label_hjust, halign = label_hjust,
label = glue("{nickname}<br>
<span style='color:{penguin_palette$dark_text};font-size:9pt'>
{description}</span>")),
width = unit(12, "lines"),
size = 5,
lineheight = 0.9,
fill = NA,
family = "Segoe UI",
box.colour = NA) +
scale_y_continuous(expand = expansion(c(.2, .2))) +
guides(colour = "none",
size = guide_legend(title = "Body mass (g)", reverse = T,
override.aes = list(colour = penguin_palette$dark_text))) +
theme(text = element_text(family = "Segoe UI", colour = penguin_palette$light_text),
plot.subtitle = element_markdown(size = 14,
lineheight = 1.3,
margin = unit(c(0, 0, 0.5, 0), "cm")),
plot.title = element_markdown(family = "Arvo",
size = 22, colour = penguin_palette$dark_text,
margin = unit(c(1, 0, 0.5, 0), "cm")),
axis.text = element_text(colour = penguin_palette$light_text, size = 8),
axis.title = element_text(family = "Arvo",
colour = penguin_palette$dark_text, size = 10),
legend.position = "top",
legend.justification = "left",
legend.margin = margin(unit(c(0,-0.45,0,0), "cm")),
legend.title = element_text(family = "Arvo", colour = penguin_palette$dark_text),
panel.grid.minor = element_blank())
# |- Arrows ----
for(curv in unique(p_exceptions$curvature)) {
<- filter(p_exceptions,
filtered_data == curv)
curvature
<- labelled_plot +
labelled_plot annotate(geom = "curve",
x = filtered_data$label_x,
y = filtered_data$label_y,
xend = filtered_data$arrow_end_x,
yend = filtered_data$arrow_end_y,
size = 0.3,
colour = case_when(filtered_data$species == "Adelie" ~ penguin_palette$Adelie,
$species == "Chinstrap" ~ penguin_palette$Chinstrap,
filtered_data$species == "Gentoo" ~ penguin_palette$Gentoo),
filtered_datacurvature = curv,
arrow = arrow(length = unit(1.5, "mm")))
}
# Export ----
ggsave(labelled_plot, filename = here("annotated_penguins.png"),
dpi = 400, width = 12, height = 9,
bg = "#ffffff")
Level Up Your Labels: Tips and Tricks for Annotating Plots
user!2022 - Poster and Elevator Pitch
Recording
Plot code
Final plot
Making of
Reuse
Citation
For attribution, please cite this work as:
Thompson, Cara. 2022. “Level Up Your Labels: Tips and Tricks for
Annotating Plots.” June 22, 2022. https://www.cararthompson.com/talks/user2022.