Cara R Thompson, PhD | R!SK Conference 2026
They are arriving in batches. Help us visualise the risks in each batch so we can prepare well for their arrival.
*Everything is super important all the time
What do the users need to know?
What do the users need to know?
What do the users need to know?
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species
),
size = 4
) +
labs(x = "Beak length") +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
theme(axis.title.y = element_blank())What do the users need to know?
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(x = "Beak length") +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_colour_gradient2(
low = "#008000",
mid = "orange",
high = "red"
) +
scale_shape(guide = "none") +
theme(axis.title.y = element_blank())What do the users need to know?
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(x = "Beak length") +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_colour_gradient2(
low = "#008000",
mid = "orange",
high = "red",
midpoint = mean(penguins$flipper_len, na.rm = TRUE)
) +
scale_shape(guide = "none") +
theme(axis.title.y = element_blank())It looked nice but…
Mute your colours!
And check what it looks like to others
And check what it looks like to others
And check what it looks like to others
Let’s take a look!
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(x = "Beak length") +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#edac59",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE)
) +
theme(axis.title.y = element_blank())Let’s take a look!
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(x = "Beak length") +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE)
) +
theme(axis.title.y = element_blank())wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text oh look a squirrel wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text wall of text
Our starting point
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "*Beaks > 50 mm*"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(
x = "Beak length",
title = paste0("We are about to welcome ", nrow(penguins), " new penguins"),
subtitle = paste0(
"There are ",
verbaliseR::pluralise(
sum(penguins$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
colour = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
plot.subtitle = marquee::element_marquee(width = 1),
axis.title.y = element_blank(),
legend.position = "top",
legend.title.position = "top",
legend.justification = 0,
legend.key.width = unit(3.3, "lines")
)The magic of typography ✨
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(penguins),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(penguins$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
colour = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)Investing in a Dataviz Design System
Starting point
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("red", 0.3)
) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(penguins),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(penguins$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
colour = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)Better in several ways!
penguins |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(penguins),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(penguins$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
colour = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)🥳
make_welcome_plot <- function(df = penguins) {
df |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
colour = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape(guide = "none") +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(df$flipper_len, na.rm = TRUE),
limits = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
breaks = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}scale_shape_manual()
make_welcome_plot <- function(df = penguins) {
df |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
colour = flipper_len
),
size = 4
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
colour = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape_manual(
guide = "none",
values = c("Adelie" = 16, "Chinstrap" = 17, "Gentoo" = 15)
) +
scale_colour_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(df$flipper_len, na.rm = TRUE),
limits = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
breaks = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}Actually…
make_welcome_plot <- function(df = penguins) {
df |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
fill = flipper_len
),
size = 4,
colour = "#2c3d4f",
stroke = 0.2
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
fill = "Predicted need for support"
) +
scale_x_continuous(label = function(x) paste0(x, "mm")) +
scale_shape_manual(
guide = "none",
values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
) +
scale_fill_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(df$flipper_len, na.rm = TRUE),
limits = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
breaks = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}The x-axis needs to stay the same for comparability
make_welcome_plot <- function(df = penguins) {
df |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
fill = flipper_len
),
size = 4,
colour = "#2c3d4f",
stroke = 0.2
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
fill = "Predicted need for support"
) +
scale_x_continuous(
label = function(x) paste0(x, "mm"),
limits = c(
# Using range in the full penguin dataframe
min(penguins$bill_len, na.rm = TRUE),
max(penguins$bill_len, na.rm = TRUE)
)
) +
scale_shape_manual(
guide = "none",
values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
) +
scale_fill_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(df$flipper_len, na.rm = TRUE),
limits = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
breaks = c(
min(df$flipper_len, na.rm = TRUE),
max(df$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}Do we want to fix colour?
make_welcome_plot <- function(df = penguins) {
df |>
ggplot() +
annotate(
"rect",
xmin = 50,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = 50, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = 50, y = Inf, label = "Beaks > 50 mm"),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
fill = flipper_len
),
size = 4,
colour = "#2c3d4f",
stroke = 0.2
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > 50, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
fill = "Predicted need for support"
) +
scale_x_continuous(
label = function(x) paste0(x, "mm"),
limits = c(
# Using range in the full penguin dataframe
min(penguins$bill_len, na.rm = TRUE),
max(penguins$bill_len, na.rm = TRUE)
)
) +
scale_shape_manual(
guide = "none",
values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
) +
scale_fill_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}What does each user need?
Allowing user-specific thresholds?
make_welcome_plot <- function(df = penguins, zoo_keeper = "anyone") {
threshold <- dplyr::case_when(
zoo_keeper == "Sam" ~ 40,
zoo_keeper == "Meg" ~ 50,
zoo_keeper == "Ron" ~ 60,
.default = 50
)
df |>
ggplot() +
annotate(
"rect",
xmin = threshold,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = threshold, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = threshold, y = Inf, label = paste0("Beaks > ", threshold, "mm")),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
fill = flipper_len
),
size = 4,
colour = "#2c3d4f",
stroke = 0.2
) +
labs(
x = "Beak length",
title = paste0(
"We are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > threshold, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
fill = "Predicted need for support"
) +
scale_x_continuous(
label = function(x) paste0(x, "mm"),
limits = c(
# Using range in the full penguin dataframe
min(penguins$bill_len, na.rm = TRUE),
max(penguins$bill_len, na.rm = TRUE)
)
) +
scale_shape_manual(
guide = "none",
values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
) +
scale_fill_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = element_text(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}What if they pick up someone else’s graph?
make_welcome_plot <- function(df = penguins, zoo_keeper = "anyone") {
threshold <- dplyr::case_when(
zoo_keeper == "Sam" ~ 40,
zoo_keeper == "Meg" ~ 50,
zoo_keeper == "Ron" ~ 60,
.default = 50
)
keeper_emoji <- dplyr::case_when(
zoo_keeper == "Sam" ~ "🙀",
zoo_keeper == "Meg" ~ "🦸🏾♀️",
zoo_keeper == "Ron" ~ "👨🏻",
.default = "🧑🌾"
)
df |>
ggplot() +
annotate(
"rect",
xmin = threshold,
xmax = Inf,
ymin = -Inf,
ymax = Inf,
fill = alpha("skyblue", 0.3)
) +
geom_vline(xintercept = threshold, colour = "#2c3d4f", linewidth = 0.2) +
ggtext::geom_textbox(
data = data.frame(),
aes(x = threshold, y = Inf, label = paste0("Beaks > ", threshold, "mm")),
fill = NA,
box.colour = NA,
hjust = 0,
vjust = 1,
box.padding = unit(10, "pt"),
family = "Noah",
colour = "#3b4046",
size = 5
) +
ggbeeswarm::geom_quasirandom(
aes(
x = bill_len,
y = species,
shape = species,
fill = flipper_len
),
size = 4,
colour = "#2c3d4f",
stroke = 0.2
) +
labs(
x = "Beak length",
title = paste0(
keeper_emoji,
" ",
dplyr::case_when(
zoo_keeper %in% c("Sam", "Meg", "Ron") ~ zoo_keeper,
.default = "Keeper"
),
", you are about to welcome ",
nrow(df),
" penguins"
),
subtitle = paste0(
"There are **",
verbaliseR::pluralise(
sum(df$bill_len > threshold, na.rm = TRUE),
word = "penguin"
),
" with scary beaks**. Please look out for the ones on the red (darker) end of the spectrum; they have longer flippers so will need more support settling in."
),
fill = "Predicted need for support"
) +
scale_x_continuous(
label = function(x) paste0(x, "mm"),
limits = c(
# Using range in the full penguin dataframe
min(penguins$bill_len, na.rm = TRUE),
max(penguins$bill_len, na.rm = TRUE)
)
) +
scale_shape_manual(
guide = "none",
values = c("Adelie" = 21, "Chinstrap" = 24, "Gentoo" = 22)
) +
scale_fill_gradient2(
low = "#a5dc94",
mid = "#a7a7a7",
high = "#e0361d",
midpoint = mean(penguins$flipper_len, na.rm = TRUE),
limits = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
breaks = c(
min(penguins$flipper_len, na.rm = TRUE),
max(penguins$flipper_len, na.rm = TRUE)
),
labels = c("low", "high")
) +
theme(
text = element_text(family = "Noah", colour = "#3b4046"),
plot.subtitle = marquee::element_marquee(
width = 1,
family = "Noah",
size = rel(1.2)
),
plot.title = element_text(
face = "bold",
size = rel(1.6),
colour = "#1A242F"
),
legend.title = marquee::element_marquee(hjust = 0.5),
axis.title.y = element_blank(),
panel.grid = element_line(colour = "white"),
plot.background = element_rect(fill = "#fafafa", colour = "#fafafa"),
plot.margin = margin_auto(40),
axis.title.x = element_text(hjust = 1, margin = margin(10, 0, 0, 0)),
legend.position = "top",
plot.title.position = "plot",
legend.title.position = "top",
legend.justification = 0.5,
legend.key.width = unit(3.6, "lines")
)
}🤯 Visualising the risks in the visualisation tools you’re using
to visualise risk
📦 {pkgdepends}
local::~/work/resources/beakoff 0.1.0 ✨👷🏾
├─dplyr 1.2.0 ✨👷🏾🔧 ⬇ (922.39 kB)
│ ├─cli 3.6.5 ✨👷🏾🔧 ⬇ (640.24 kB)
│ ├─generics 0.1.4 ✨👷🏾 ⬇ (47.22 kB)
│ ├─glue 1.8.0 ✨👷🏾🔧 ⬇ (126.68 kB)
│ ├─lifecycle 1.0.5 ✨👷🏾 ⬇ (107.14 kB)
│ │ ├─cli
│ │ └─rlang 1.1.7 ✨👷🏾🔧 ⬇ (770.34 kB)
│ ├─magrittr 2.0.4 ✨👷🏾🔧 ⬇ (281.79 kB)
│ ├─pillar 1.11.1 ✨👷🏾 ⬇ (409.51 kB)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─rlang
│ │ ├─utf8 1.2.6 ✨👷🏾🔧 ⬇ (243.86 kB)
│ │ └─vctrs 0.7.1 ✨👷🏾🔧 ⬇ (1.08 MB)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ └─rlang
│ ├─R6 2.6.1 ✨👷🏾 ⬇ (64.51 kB)
│ ├─rlang
│ ├─tibble 3.3.1 ✨👷🏾🔧 ⬇ (557.13 kB)
│ │ ├─cli
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─pillar
│ │ ├─pkgconfig 2.0.3 ✨👷🏾 ⬇ (6.08 kB)
│ │ ├─rlang
│ │ └─vctrs
│ ├─tidyselect 1.2.1 ✨👷🏾🔧 ⬇ (103.59 kB)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─rlang
│ │ ├─vctrs
│ │ └─withr 3.0.2 ✨👷🏾 ⬇ (103.24 kB)
│ └─vctrs
├─janitor 2.2.1 ✨👷🏾 ⬇ (232.99 kB)
│ ├─dplyr
│ ├─hms 1.1.4 ✨👷🏾 ⬇ (44.79 kB)
│ │ ├─cli
│ │ ├─lifecycle
│ │ ├─pkgconfig
│ │ ├─rlang
│ │ └─vctrs
│ ├─lifecycle
│ ├─lubridate 1.9.5 ✨👷🏾🔧 ⬇ (429.86 kB)
│ │ ├─generics
│ │ └─timechange 0.4.0 ✨👷🏾🔧 ⬇ (103.97 kB)
│ │ └─cpp11 0.5.3 ✨👷🏾 ⬇ (302.29 kB)
│ ├─magrittr
│ ├─purrr 1.2.1 ✨👷🏾🔧 ⬇ (278.29 kB)
│ │ ├─cli
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─rlang
│ │ └─vctrs
│ ├─rlang
│ ├─snakecase 0.11.1 ✨👷🏾 ⬇ (457.82 kB)
│ │ ├─stringi 1.8.7 ✨👷🏾🔧 ⬇ (11.91 MB)
│ │ └─stringr 1.6.0 ✨👷🏾 ⬇ (195.83 kB)
│ │ ├─cli
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─rlang
│ │ ├─stringi
│ │ └─vctrs
│ ├─stringi
│ ├─stringr
│ ├─tidyr 1.3.2 ✨👷🏾🔧 ⬇ (809.03 kB)
│ │ ├─cli
│ │ ├─cpp11
│ │ ├─dplyr
│ │ ├─glue
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─purrr
│ │ ├─rlang
│ │ ├─stringr
│ │ ├─tibble
│ │ ├─tidyselect
│ │ └─vctrs
│ └─tidyselect
├─palmerpenguins 0.1.1 ✨👷🏾 ⬇ (3.00 MB)
├─vdiffr 1.0.9 ✨👷🏾🔧 ⬇ (102.43 kB)
│ ├─cpp11
│ ├─diffobj 0.3.6 ✨👷🏾🔧 ⬇ (472.96 kB)
│ │ └─crayon 1.5.3 ✨👷🏾 ⬇ (40.40 kB)
│ ├─glue
│ ├─htmltools 0.5.9 ✨👷🏾🔧 ⬇ (135.21 kB)
│ │ ├─base64enc 0.1-6 ✨👷🏾🔧 ⬇ (9.42 kB)
│ │ ├─digest 0.6.39 ✨👷🏾🔧 ⬇ (236.63 kB)
│ │ ├─fastmap 1.2.0 ✨👷🏾🔧 ⬇ (46.71 kB)
│ │ └─rlang
│ ├─lifecycle
│ ├─rlang
│ ├─testthat 3.3.2 ✨👷🏾🔧 ⬇ (834.04 kB)
│ │ ├─brio 1.1.5 ✨👷🏾🔧 ⬇ (13.17 kB)
│ │ ├─callr 3.7.6 ✨👷🏾 ⬇ (104.36 kB)
│ │ │ ├─processx 3.8.6 ✨👷🏾🔧 ⬇ (165.19 kB)
│ │ │ │ ├─ps 1.9.1 ✨👷🏾🔧 ⬇ (167.92 kB)
│ │ │ │ └─R6
│ │ │ └─R6
│ │ ├─cli
│ │ ├─desc 1.4.3 ✨👷🏾 ⬇ (80.07 kB)
│ │ │ ├─cli
│ │ │ └─R6
│ │ ├─evaluate 1.0.5 ✨👷🏾 ⬇ (39.32 kB)
│ │ ├─jsonlite 2.0.0 ✨👷🏾🔧 ⬇ (1.06 MB)
│ │ ├─lifecycle
│ │ ├─magrittr
│ │ ├─pkgload 1.5.0 ✨👷🏾 ⬇ (87.78 kB)
│ │ │ ├─cli
│ │ │ ├─desc
│ │ │ ├─fs 1.6.6 ✨👷🏾🔧 ⬇ (1.20 MB)
│ │ │ ├─glue
│ │ │ ├─lifecycle
│ │ │ ├─pkgbuild 1.4.8 ✨👷🏾 ⬇ (51.30 kB)
│ │ │ │ ├─callr
│ │ │ │ ├─cli
│ │ │ │ ├─desc
│ │ │ │ ├─processx
│ │ │ │ └─R6
│ │ │ ├─processx
│ │ │ ├─rlang
│ │ │ └─rprojroot 2.1.1 ✨👷🏾 ⬇ (59.90 kB)
│ │ ├─praise 1.0.0 ✨👷🏾 ⬇ (6.10 kB)
│ │ ├─processx
│ │ ├─ps
│ │ ├─R6
│ │ ├─rlang
│ │ ├─waldo 0.6.2 ✨👷🏾 ⬇ (45.91 kB)
│ │ │ ├─cli
│ │ │ ├─diffobj
│ │ │ ├─glue
│ │ │ └─rlang
│ │ └─withr
│ └─xml2 1.5.2 ✨👷🏾🔧 ⬇ (154.70 kB)
│ ├─cli
│ └─rlang
└─ggplot2 4.0.2 ✨👷🏾 ⬇ (6.36 MB)
├─cli
├─gtable 0.3.6 ✨👷🏾 ⬇ (148.15 kB)
│ ├─cli
│ ├─glue
│ ├─lifecycle
│ └─rlang
├─isoband 0.3.0 ✨👷🏾🔧 ⬇ (1.59 MB)
│ ├─cli
│ ├─cpp11
│ └─rlang
├─lifecycle
├─rlang
├─S7 0.2.1 ✨👷🏾🔧 ⬇ (184.14 kB)
├─scales 1.4.0 ✨👷🏾 ⬇ (328.67 kB)
│ ├─cli
│ ├─farver 2.1.2 ✨👷🏾🔧 ⬇ (1.28 MB)
│ ├─glue
│ ├─labeling 0.4.3 ✨👷🏾 ⬇ (10.17 kB)
│ ├─lifecycle
│ ├─R6
│ ├─RColorBrewer 1.1-3 ✨👷🏾 ⬇ (11.64 kB)
│ ├─rlang
│ └─viridisLite 0.4.3 ✨👷🏾 ⬇ (1.27 MB)
├─vctrs
└─withr
Key: ✨ new | ⬇ download | 👷🏾 build | 🔧 compile
📦 {vdiffr} to the rescue! beakoff/testthat/test_welcome_plot.R
set.seed(2026)
describe("welcome plot", {
it("The plot looks as expected with all penguins for unspecified zoo keeper", {
vdiffr::expect_doppelganger(
"all penguins any keeper",
make_welcome_plot()
)
})
})
describe("welcome plot", {
it("The plot looks as expected with head(penguins, 250) for unspecified zoo keeper", {
vdiffr::expect_doppelganger(
"head 250 penguins any keeper",
make_welcome_plot(df = head(penguins, 250))
)
})
})
describe("welcome plot", {
it("The plot looks as expected with head(penguins, 250) for Meg", {
vdiffr::expect_doppelganger(
"head 250 penguins Meg",
make_welcome_plot(df = head(penguins, 250), zoo_keeper = "Meg")
)
})
})📦 {vdiffr} to the rescue!
beakoff/testthat/_snaps the first time we run the testsSpot the (unanticipated) difference.
cararthompson.com/talkshello@cararthompson.comcararthompson.com/newsletter
cararthompson.com/talks