RLadies Cambridge | 23rd February 2023
đź‘© Cara Thompson
👩‍💻 Psychology PhD |>
Analysis of postgraduate medical examinations |>
Freelance data consultant specialising in dataviz and “enhanced” reproducible outputs
đź’™ Helping others maximise the impact of their expertise
Find out more: cararthompson.com/about
To equip you with some design tips and coding tricks to make the most of text in your plots.
{ggtext}
, {geomtextpath}
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 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 Adelie penguins decided to experiment with different quantities of banana in their mix. Each island chose a different quantity.
The Adelie penguins decided to experiment with different quantities of banana in their mix. Each island chose a different quantity.
The penguins also baked their cakes for different amounts of time. Here are the mean durations per species. Which species left their cakes in the oven for longest?
The penguins also baked their cakes for different amounts of time. Here are the mean durations per species. Which species left their cakes in the oven for longest?
{gghighlight}
, Tee pipe and curved arrowsUsing the ToothGrowth dataset
package::function()
🕵️?ToothGrowth
)(Feel free to munch along!)
With a few tips along the way
With a few tips along the way
With a few tips along the way
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)
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()
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()
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",
position = "dodge",
colour = "#FFFFFF",
size = 2) +
facet_wrap(supplement ~ ., ncol = 1) +
theme_minimal()
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",
position = "dodge",
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()
Legend + facet strip + colour + title… Wait, which one is which?
Generating a colour palette, starting with orange juice! #fab909
[1] "#DB5A05" "#E93603" "#F71201"
[1] "#3C6B30" "#0C1509"
[1] "#0C1509" "#323A30" "#595F57" "#80857F" "#A7AAA6" "#CED0CD"
Creating a named vector, for ease later
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",
position = "dodge",
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()
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",
position = "dodge",
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()
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",
position = "dodge",
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()
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",
position = "dodge",
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) +
scale_alpha(range = c(0.33, 1)) +
facet_wrap(supplement ~ ., ncol = 1) +
theme_minimal()
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",
position = "dodge",
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) +
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()
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",
position = "dodge",
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) +
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")
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",
position = "dodge",
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) +
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")
So much clearer, and we haven’t even done any annotating!
{gghighlight}
, Tee pipe and curved arrowsFind out more: https://www.interaction-design.org/
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",
position = "dodge",
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) +
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) +
theme(legend.position = "none")
basic_plot
Time to start playing with theme()
!
Time to start playing with theme()
!
Time to start playing with theme()
!
Move away from the default fonts
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"]))
Choosing fonts can be tricky!
Find out more: pimpmytype.com/font-matrix/
Getting custom fonts to work can be frustrating!
Install fonts locally +
{ragg}
+{systemfonts}
+{textshaping}
+ Set graphics device to “AGG” + 🤞
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"]))
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"]))
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"]))
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"]))
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"]))
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! 🤯
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"]))
See for yourselves!
{gghighlight}
& curved arrowsWe’ve made it easy to see what’s what. Now, let’s make it even easier to compare values.
We’ve made it easy to see what’s what. Now, let’s make it even easier to compare values.
We’ve made it easy to see what’s what. Now, let’s make it even easier to compare values.
Time to add some text boxes!
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
# x (dose) and y (mean_length) are already
# set in the global ggplot() call!
ggtext::geom_textbox(aes(label = mean_length))
Time to add some text boxes!
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
ggtext::geom_textbox(aes(label = mean_length),
size = 6,
halign = 1,
hjust = 1)
Find out more: Alignment Cheatsheet
Time to add some text boxes!
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
ggtext::geom_textbox(aes(label = mean_length),
size = 6,
halign = 1,
hjust = 1,
fill = NA,
box.colour = NA)
Time to add some text boxes!
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
ggtext::geom_textbox(aes(label = mean_length),
size = 6,
halign = 1,
hjust = 1,
fill = NA,
box.colour = NA,
family = "Cabin",
colour = "#FFFFFF",
fontface = "bold")
Now for the fun stuff…
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
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")
Now for the fun stuff…
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
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")
??????
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
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")
scale_colour_identity()
required!
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
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")
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(family = "Enriqueta", colour = vit_c_palette["light_text"], size = rel(1.1), face = "bold", hjust = 0.03, margin = margin(2, 0, 0.5, 0, "lines"))) +
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")
Easier than you think and makes a big difference! 🦸
{gghighlight}
, Tee pipe and curved arrows“That’s all well and good, but we all know summary data can be misleading…”
Find out more: cararthompson.com/talks/nhsr2022-ggplot-themes
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
More textboxes with markdown and conditional alignment (horizontal and vertical!)
themed_scatter_plot +
geomtextpath::geom_textline(stat = "smooth", aes(label = supplement),
hjust = 0.1,
vjust = 0.3,
fontface = "bold",
family = "Cabin") +
ggtext::geom_textbox(data = filter(min_max_gps,
dose %in% c(1, 2)),
aes(x = case_when(dose < 1.5 ~ dose + 0.05,
TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1,
TRUE ~ len * 0.9),
label = paste0("**<span style='font-family:Enriqueta'>",
guinea_pig_name,
"</span>** - ", len, " mm"),
hjust = case_when(dose < 1.5 ~ 0,
TRUE ~ 1),
halign = case_when(dose < 1.5 ~ 0,
TRUE ~ 1)),
family = "Cabin",
size = 4,
fill = NA,
box.colour = NA) +
scale_colour_manual(values = vit_c_palette)
Sometimes less is more!
themed_scatter_plot +
geomtextpath::geom_textline(stat = "smooth", aes(label = supplement),
hjust = 0.1,
vjust = 0.3,
fontface = "bold",
family = "Cabin") +
ggtext::geom_textbox(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05,
TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1,
TRUE ~ len * 0.9),
label = paste0("**<span style='font-family:Enriqueta'>",
guinea_pig_name,
"</span>** - ", len, " mm"),
hjust = case_when(dose < 1.5 ~ 0,
TRUE ~ 1),
halign = case_when(dose < 1.5 ~ 0,
TRUE ~ 1)),
family = "Cabin",
size = 4,
fill = NA,
box.colour = NA) +
scale_colour_manual(values = vit_c_palette)
Same principle, let’s add in some arrows!
themed_scatter_plot +
geomtextpath::geom_textline(stat = "smooth", aes(label = supplement),
hjust = 0.1,
vjust = 0.3,
fontface = "bold",
family = "Cabin") +
ggtext::geom_textbox(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05, TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1, TRUE ~ len * 0.9),
label = paste0("**<span style='font-family:Enriqueta'>", guinea_pig_name,"</span>** - ", len, " mm"),
hjust = case_when(dose < 1.5 ~ 0,TRUE ~ 1),
halign = case_when(dose < 1.5 ~ 0, TRUE ~ 1)),
family = "Cabin", size = 4, fill = NA, box.colour = NA) +
geom_curve(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05,
TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1,
TRUE ~ len * 0.9),
xend = case_when(dose < 1.5 ~ dose + 0.02,
TRUE ~ dose - 0.02),
yend = case_when(min_or_max == "max"~ len + 0.5,
TRUE ~ len - 0.5)),
arrow = arrow(length = unit(0.1, "cm")),
alpha = 0.5) +
scale_colour_manual(values = vit_c_palette)
Same principle, let’s add in some arrows!
themed_scatter_plot +
geomtextpath::geom_textline(stat = "smooth", aes(label = supplement),
hjust = 0.1,
vjust = 0.3,
fontface = "bold",
family = "Cabin") +
ggtext::geom_textbox(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05, TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1, TRUE ~ len * 0.9),
label = paste0("**<span style='font-family:Enriqueta'>", guinea_pig_name,"</span>** - ", len, " mm"),
hjust = case_when(dose < 1.5 ~ 0,TRUE ~ 1),
halign = case_when(dose < 1.5 ~ 0, TRUE ~ 1)),
family = "Cabin", size = 4, fill = NA, box.colour = NA) +
geom_curve(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05,
TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1,
TRUE ~ len * 0.9),
xend = case_when(dose < 1.5 ~ dose + 0.02,
TRUE ~ dose - 0.02),
yend = case_when(min_or_max == "max"~ len + 0.5,
TRUE ~ len - 0.5)),
curvature = 0.1,
arrow = arrow(length = unit(0.1, "cm")),
alpha = 0.5) +
scale_colour_manual(values = vit_c_palette)
Same principle, let’s add in some arrows!
themed_scatter_plot +
geomtextpath::geom_textline(stat = "smooth", aes(label = supplement),
hjust = 0.1,
vjust = 0.3,
fontface = "bold",
family = "Cabin") +
ggtext::geom_textbox(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05, TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1, TRUE ~ len * 0.9),
label = paste0("**<span style='font-family:Enriqueta'>", guinea_pig_name,"</span>** - ", len, " mm"),
hjust = case_when(dose < 1.5 ~ 0,TRUE ~ 1),
halign = case_when(dose < 1.5 ~ 0, TRUE ~ 1)),
family = "Cabin", size = 4, fill = NA, box.colour = NA) +
geom_curve(data = filter(min_max_gps,
dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05,
TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1,
TRUE ~ len * 0.9),
xend = case_when(dose < 1.5 ~ dose + 0.02,
TRUE ~ dose - 0.02),
yend = case_when(min_or_max == "max"~ len + 0.5,
TRUE ~ len - 0.5)),
curvature = 0,
arrow = arrow(length = unit(0.1, "cm")),
alpha = 0.5) +
scale_colour_manual(values = vit_c_palette)
Nearly there, folks! Look how far we’ve come!
{gghighlight}
, Tee pipe and curved arrowsBe brave - try theme_void()
Tweak the grid lines, using a matching colour - {monochromeR}
And add a bit of white space
{gghighlight}
, Tee pipe and curved arrows{gghighlight}
, Tee pipe and curved arrows“But I have way more conditions than this, how can I highlight a more subtle pattern?”
Suppose we have data that has so many series that it is hard to identify them by their colours as the differences are so subtle…
Find out more: yutannihilation.github.io/gghighlight/
Nicola Rennie’s Hollywood Age Gaps plot
Find out more: twitter.com/nrennie35
Creating label content on the fly
ToothGrowth %>%
mutate(guinea_pig_name = sample(unique(bakeoff::bakers$baker), 60),
supplement = case_when(supp == "OJ" ~ "Orange Juice",
supp == "VC" ~ "Vitamin C",
TRUE ~ as.character(supp))) %T>%
{
{
# Double assign to jump out of the pipe!
min_max_gps <<- group_by(., supplement, dose) %>%
filter(., len == min(len) | len == max(len)) %>%
mutate(min_or_max = case_when(len == max(len) ~ "max",
TRUE ~ "min"))
}
} %>%
ggplot(aes(x = dose, y = len, fill = supplement,
colour = supplement)) +
...
Conditional curvatures
?
themed_scatter_plot +
geomtextpath::geom_textline(stat = "smooth", aes(label = supplement), hjust = 0.1, vjust = 0.3, fontface = "bold", family = "Cabin") +
ggtext::geom_textbox(data = filter(min_max_gps, dose == 2),
aes(x = case_when(dose < 1.5 ~ dose + 0.05, TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1, TRUE ~ len * 0.9),
label = paste0("**<span style='font-family:Enriqueta'>", guinea_pig_name,"</span>** - ", len, " mm"),
hjust = case_when(dose < 1.5 ~ 0,TRUE ~ 1),
halign = case_when(dose < 1.5 ~ 0, TRUE ~ 1)),
family = "Cabin", size = 4, fill = NA, box.colour = NA) +
geom_curve(data = filter(min_max_gps,
dose == 2 &
min_or_max == "max"),
aes(x = case_when(dose < 1.5 ~ dose + 0.05, TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1, TRUE ~ len * 0.9),
xend = case_when(dose < 1.5 ~ dose + 0.02, TRUE ~ dose - 0.02),
yend = case_when(min_or_max == "max"~ len + 0.5,TRUE ~ len - 0.5)),
curvature = -0.1,
arrow = arrow(length = unit(0.1, "cm")),
alpha = 0.5) +
geom_curve(data = filter(min_max_gps,
dose == 2 &
min_or_max == "min"),
aes(x = case_when(dose < 1.5 ~ dose + 0.05, TRUE ~ dose - 0.05),
y = case_when(min_or_max == "max"~ len * 1.1, TRUE ~ len * 0.9),
xend = case_when(dose < 1.5 ~ dose + 0.02, TRUE ~ dose - 0.02),
yend = case_when(min_or_max == "max"~ len + 0.5, TRUE ~ len - 0.5)),
curvature = 0.1,
arrow = arrow(length = unit(0.1, "cm")),
alpha = 0.5) +
scale_colour_manual(values = vit_c_palette)
To avoid it getting too unwieldy, add the curvatures to your data and iterate.
labelled_plot # plot with everything but the arrows
for(curv in unique(my_data$curvature)) {
filtered_data <- filter(my_data,
curvature == curv)
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,
filtered_data$species == "Chinstrap" ~ penguin_palette$Chinstrap,
filtered_data$species == "Gentoo" ~ penguin_palette$Gentoo),
curvature = curv,
arrow = arrow(length = unit(1.5, "mm")))
}
labelled_plot # plot with as many different curvatures as you like!
Find out more: cararthompson.com/talks/user2022
Slides and recording: cararthompson.com/talks/rl-cambridge-beautifully-annotated/