NHS-R Conference 2023 | 4th October 2023
👩 Cara Thompson
👩💻 Love for patterns in music & language, and a fascination with the human brain |>
Psychology PhD |>
Analysis of postgraduate medical examinations |>
Data Visualisation Consultant
💙 Helping others maximise the impact of their expertise
Find out more: cararthompson.com/about
To equip you with some design tips and coding tricks to make the most of colour and text to create compelling and memorable visualisations.
R
code{ggtext}
and {geomtextpath}
hello@cararthompson.com
package::function("blah")
)Find out more: en.wikipedia.org/wiki/Bouba/kiki_effect
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 unripe banana in their mix. Each island chose a different quantity.
They decided to go on a retreat to plan their bakes in different locations
Each species was allowed to invite a different mentor…
… and to choose a type of snack between practice bakes
The penguins also baked their cakes for different amounts of time. Here are the mean durations per species.
The penguins also baked their cakes for different amounts of time. Here are the mean durations per species.
Using the ToothGrowth dataset
?ToothGrowth
)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",
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",
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
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()
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()
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()
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()
Add in our colours - named vector to the rescue!
Key advantages
ggtext::element_markdown()
later in this workshopGet 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()
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()
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()
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()
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()
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")
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")
So much clearer, and we haven’t even done any annotating!
It’s all about making it easier to remember what is what
Picking colours is hard! Let others help you!
Source: The Colour Palette Company
Find out more: blog.datawrapper.de/colors-for-data-vis-style-guides/
Quick tip: Viewing your colours
Quick tip: Naming and viewing your colours
Back to the bake-off… Value / transparency (blank background!)
Transparency with outlines
Which line shows the most recent data?
Playing with value (a “lighter” green)
Playing with saturation (a “less green” green)
Combining the two
[1] "#3D6946" "#597C60" "#76907B" "#92A496" "#AFB8B1"
Now also a shiny app! cararthompson.shinyapps.io/monochromeR
Final colour hack: Test it out with text and background
Final colour hack: Test it out with text and background
Final colour hack: Test it out with text and background
{monochromeR}
can help!
imagecolorpicker.com
monochromeR::view_palette()
You have 10 minutes! 📊 🎨 ☕
10:00
A few extra things to bear in mind
colorblindr::cvd_grid()
remotes::install_github("clauswilke/colorblindr")
https://github.com/clauswilke/colorblindr
A few things to bear in mind
colorblindr::cvd_grid()
A few things to bear in mind
https://enchroma.com/pages/color-blindness-test
Source: Nightingale magazine - Issue 03
Source: Nightingale magazine - Issue 03
Source: Nightingale magazine - Issue 03
“Web Content Accessibility Guidelines (WACG) 2.1 standards call for a contrast ratio of at least 3:1 for ‘meaningful graphics’. Achieving this is a mathematical impossibility for any palette containing just a small handful of colours, let alone one as a comprehensive as a data design system requires.”
So we do the best we can to make sure colours, chosen with additional accessibility criteria in mind, are visibly distinct from each other for a range of audiences.
R: {monochromeR} + {colorblindr}
Source: pimpmytype.com/color-contrast/
colorblindr::cvd_grid()
Links to resources: cararthompson.com/talks - first one
You have 5 minutes! 📊 🎨
05:00
Find 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", 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
Time to start playing with theme()
!
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, restart R Studio + 📦
{systemfonts}
({ragg}
+{textshaping}
) + Set graphics device to “AGG” + 🤞
A Dataviz Design System: A simple set of rules to follow, to effortlessly make your visualisations look on brand, every time you go to create a new plot.
Find out more: cararthompson.com/talks/rmedicine2023-dataviz-design-system/
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"]))
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"]))
Wait, that yellow… #fab909
Wait, that yellow…
basic_plot +
labs(title =
paste0("In smaller doses, **<span style='color:",
vit_c_palette["Orange Juice text"], "'>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!
Source: https://learn.microsoft.com/en-us/power-bi/visuals/service-reports-show-data
Source: https://learn.microsoft.com/en-us/power-bi/visuals/service-reports-show-data
Source: https://www.canva.com/p/templates/EAFYZYCiOWE-blue-modern-line-bar-chart-graph/
Source: https://www.canva.com/p/templates/EAFYZYCiOWE-blue-modern-line-bar-chart-graph/
systemfonts::systemfonts() |> View()
theme()
monochromeR::generate_palette()
You have 10 minutes! 📊 🎨 ☕
10:00
{usethis}
Find out more: Shannon Pileggi - Your first R package in 1 hour
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"]),
plot.margin = margin(rep(base_text_size, 4)),
plot.title.position = "plot")
}
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"]))
}
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.
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!
Time to add some text boxes!
Find out more: Alignment Cheatsheet
Time to add some text boxes!
Time to add some text boxes!
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")
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")
??????
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")
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")
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")
We might as well add a bit of extra info (with text hierarchy!) to our labels…
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
theme(strip.text = element_text(hjust = 0.03)) +
scale_colour_identity() +
ggtext::geom_textbox(aes(
label = paste0("<span style=font-size:12pt>",
dose, "mg/day</span><br>",
janitor::round_half_up(mean_length),
"mm"),
hjust = case_when(mean_length < 15 ~ 0,
TRUE ~ 1),
halign = case_when(mean_length < 15 ~ 0,
TRUE ~ 1),
colour = case_when(mean_length > 15 ~ "#FFFFFF",
TRUE ~ vit_c_palette[supplement])),
size = 6,
fill = NA,
box.colour = NA,
family = "Cabin",
fontface = "bold")
new_data %>% guinea_pig_plot()
Let’s fix that alignment…
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
scale_colour_identity() +
ggtext::geom_textbox(aes(
label = paste0("<span style=font-size:12pt>",
dose, "mg/day</span><br>",
janitor::round_half_up(mean_length),
"mm"),
hjust = case_when(mean_length < 15 ~ 0,
TRUE ~ 1),
halign = case_when(mean_length < 15 ~ 0,
TRUE ~ 1),
colour = case_when(mean_length > 15 ~ "#FFFFFF",
TRUE ~ vit_c_palette[supplement])),
size = 6,
fill = NA,
box.colour = NA,
family = "Cabin",
fontface = "bold") +
theme(strip.text = element_text(hjust = 0.03),
plot.title.position = "plot")
And add some breathing space around the plot…
themed_plot +
scale_y_continuous(expand = c(0, 0.5)) +
scale_colour_identity() +
ggtext::geom_textbox(aes(
label = paste0("<span style=font-size:12pt>",
dose, "mg/day</span><br>",
janitor::round_half_up(mean_length),
"mm"),
hjust = case_when(mean_length < 15 ~ 0,
TRUE ~ 1),
halign = case_when(mean_length < 15 ~ 0,
TRUE ~ 1),
colour = case_when(mean_length > 15 ~ "#FFFFFF",
TRUE ~ vit_c_palette[supplement])),
size = 6,
fill = NA,
box.colour = NA,
family = "Cabin",
fontface = "bold") +
theme(strip.text = element_text(hjust = 0.03),
plot.title.position = "plot",
plot.margin = margin(rep(15, 4)))
Easier than you think and makes a big difference! 🦸
ggtext::geom_textbox()
You have 10 minutes! 📊 🎨 ☕
10:00
How did those penguins get on anyway…?
Find out more: cararthompson.com/talks/hdsi_rug/
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."))
Consider text boxes instead of a legend…
Consider text boxes instead of a legend…
Consider text boxes instead of a legend…
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
I ❤️ 📦 {geomtextpath}
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)))
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))
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!")))
First, a bit of text manipulation!
# A tibble: 3 x 4
individual_id species island commentary
<chr> <chr> <chr> <chr>
1 N81A1 Adelie Dream **N81A1**, did not have a good baking day. The~
2 N56A2 Gentoo Biscoe Our star baker is **N56A2**, a Gentoo from Bis~
3 N71A2 Chinstrap Dream Our runner up is a Chinstrap from Dream: **N71~
Next, let’s work out where we want our labels…
… 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))
Find out more: cararthompson.com/posts/2021-09-02-alignment-cheat-sheet/
Let’s add the annotations…
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)
… 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)
… 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)
new_data %>% penguin_plot()
new_data %>% time_comparisons_plot()
The possibilities are endless!
The possibilities are endless!
The possibilities are endless!
The possibilities are endless!
hello@cararthompson.com
Tw/Li: cararthompson