The value of creating and implementing a dataviz design system in R
NHSR 2023 | 11th 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
Just in case…
@cararthompson
Just in case…
@cararthompson
Find out more: cararthompson.com/about
Dataviz Design System, implemented as an R package
Dataviz Design System, implemented as an R package
Dataviz-friendly brand guidelines
Dataviz-friendly brand guidelines
geom
s🎨 Graphs, tables, presentations, documents, Quarto slides…
“So much more than pretty graphs”
#rstats
💙 Automate the “boring stuff” to focus on the stuff that needs your expertiseMore than green and red!
Source: Nightingale magazine - Issue 03
Top tips
Source: Nightingale magazine - Issue 03
R: {monochromeR} + {colorblindr}
R: {monochromeR} + {colorblindr}
R: {monochromeR} + {colorblindr}
library(tidyverse)
palmerpenguins::penguins |>
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
size = body_mass_g,
color = species),
alpha = 0.8) +
labs(title = "Perfectly proportional penguins",
subtitle = "Look at them go!",
x = "Bill length (mm)",
y = "Flipper length (mm)",
caption = "Demo plot, built with {palmerpenguins}") +
ophelia::scale_colour_ophelia(palette = "warm_colours",
continuous = FALSE) +
ophelia::theme_ophelia(background = FALSE) +
theme(legend.position = "none")
R: {monochromeR} + {colorblindr}
1Il
testceo
db
qp
.ttf
.ttf
Here is some text
in Nunito Sans, 35pt!
Here is some text
in Crimson Pro, 35pt!
Find out more: Oliver Schöndorfer’s Font Matrix
Getting custom fonts to work can be frustrating!
Install fonts locally, restart R Studio + 📦
{systemfonts}
({ragg}
+{textshaping}
) + Set graphics device to “AGG” + 🤞
knitr::opts_chunk$set(dev = “ragg_png”)
Anchor colours
Several palettes, derived from the anchor colours
ophelia_palettes <- list(
default = c(ophelia_colours$deep_purple,
ophelia_colours$dark_red,
ophelia_colours$purple,
ophelia_colours$gold,
ophelia_colours$pink,
ophelia_colours$light_blue,
ophelia_colours$dark_green),
cool_colours = c(ophelia_colours$dark_green,
ophelia_colours$light_blue,
ophelia_colours$pale_green),
warm_colours = c(ophelia_colours$deep_purple,
ophelia_colours$pink,
ophelia_colours$gold),
neg_to_pos = c(ophelia_colours$deep_purple,
ophelia_colours$pale_pink,
ophelia_colours$bright_gold),
greens = c(ophelia_colours$dark_green,
ophelia_colours$pale_green),
purples = c(ophelia_colours$deep_purple,
ophelia_colours$pale_purple)
)
Feed the palettes into a bespoke function that uses ggplot scale functions
scale_colour_ophelia <- function(palette = "default",
continuous = FALSE,
.colours = ophelia_colours,
.palettes = ophelia_palettes,
...) {
if(continuous == FALSE) {
ggplot2::discrete_scale(palette = grDevices::colorRampPalette(.palettes[[palette]]),
aesthetics = "colour",
scale_name = .palettes[[palette]],
na.value = .colours$na_value,
...)
} else {
ggplot2::scale_colour_gradientn(colours = .palettes[[palette]],
na.value = .colours$na_value,
...)
}
}
… with a few extra touches
Text colours: find a “starting” colour the ties in with all the palettes
Text colours: feed it into {monochromeR}
to generate a dark text colour
Text colours: feed that dark text colour into {monochromeR}
again to generate a light text colour
theme_ophelia()
ggtext::element_markdown
or ggtext::element_textbox_simple
, …Find out more: Variation on a ggtheme
… with a few option toggles
{ophelia}
palmerpenguins::penguins %>%
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
colour = species,
size = body_mass_g)) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Let's try some *italics* in the title",
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
caption = "Data from {palmerpenguins}") +
guides(size = "none")
palmerpenguins::penguins %>%
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
colour = species,
size = body_mass_g)) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Let's try some *italics* in the title",
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
caption = "Data from {palmerpenguins}") +
guides(size = "none") +
scale_colour_ophelia() +
theme_ophelia()
palmerpenguins::penguins %>%
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g),
shape = 21,
colour = "white",
alpha = 0.8) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Let's try some *italics* in the title",
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
caption = "Data from {palmerpenguins}") +
guides(size = "none")
palmerpenguins::penguins %>%
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g),
shape = 21,
colour = "white",
alpha = 0.8) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Let's try some *italics* in the title",
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
caption = "Data from {palmerpenguins}") +
guides(size = "none") +
scale_fill_ophelia(continuous = TRUE) +
theme_ophelia()
palmerpenguins::penguins %>%
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g),
shape = 21,
colour = "white",
alpha = 0.8) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Let's try some *italics* in the title",
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
caption = "Data from {palmerpenguins}") +
guides(size = "none") +
scale_fill_ophelia(continuous = TRUE) +
theme_ophelia(base_text_size = 18)
palmerpenguins::penguins %>%
filter(!is.na(sex)) %>%
ggplot(aes(x = species,
fill = island),
stat = "count") +
geom_bar() +
labs(title = "Perfectly proportional penguins",
subtitle = "Where do they all live?",
caption = "Data from {palmerpenguins}") +
facet_grid(. ~ sex) +
scale_fill_ophelia(palette = "cool_colours") +
theme_ophelia(background_colour = FALSE,
base_text_size = 14)
palmerpenguins::penguins %>%
ggplot(aes(x = 1,
fill = species),
stat = "count") +
geom_bar() +
xlim(c(-0.5, 2)) +
coord_polar(theta = "y") +
labs(title = "Does anyone know if penguins like donuts?",
subtitle = "Not sure, but we know there are three species in the dataset",
caption = "Data from {palmerpenguins}")
palmerpenguins::penguins %>%
ggplot(aes(x = 1,
fill = species),
stat = "count") +
geom_bar() +
xlim(c(-0.5, 2)) +
coord_polar(theta = "y") +
labs(title = "Does anyone know if penguins like donuts?",
subtitle = "Not sure, but we know there are three species in the dataset",
caption = "Data from {palmerpenguins}") +
scale_fill_ophelia("warm_colours") +
theme_ophelia(void = TRUE,
base_text_size = 14,
background_colour = FALSE)
tibble(answer = factor(rep(c("Strongly Disagree", "Disagree",
"Agree", "Strongly Agree"), 2),
levels = c("Strongly Disagree", "Disagree",
"Strongly Agree", "Agree")),
percent = c(8, 12, 40, 10,
6, 18, 34, 18),
group = sort(rep(c("Male", "Female"), 4))) %>%
mutate(display_percent = case_when(grepl("Dis|Neutral", answer) ~ -percent,
TRUE ~ percent)) %>%
ggplot() +
geom_col(aes(x = group,
fill = answer,
y = display_percent),
width = 0.8) +
labs(title = "Let's find out!",
subtitle = "How much do they agree with the statement \"Donuts are delicious\"?",
caption = "Totally made up data!",
x = "Percent",
fill = "Answer") +
scale_y_continuous(labels = function(x) paste0(abs(x), "%")) +
coord_flip()
tibble(answer = factor(rep(c("Strongly Disagree", "Disagree",
"Agree", "Strongly Agree"), 2),
levels = c("Strongly Disagree", "Disagree",
"Strongly Agree", "Agree")),
percent = c(8, 12, 40, 10,
6, 18, 34, 18),
group = sort(rep(c("Male", "Female"), 4))) %>%
mutate(display_percent = case_when(grepl("Dis|Neutral", answer) ~ -percent,
TRUE ~ percent)) %>%
ggplot() +
geom_col(aes(x = group,
fill = answer,
y = display_percent),
width = 0.8) +
labs(title = "Let's find out!",
subtitle = "How much do they agree with the statement \"Donuts are delicious\"?",
caption = "Totally made up data!",
x = "Percent",
fill = "Answer") +
scale_y_continuous(labels = function(x) paste0(abs(x), "%")) +
coord_flip() +
scale_fill_ophelia(
palette = "neg_to_pos",
limits = c("Strongly Disagree", "Disagree",
"Agree", "Strongly Agree")) +
theme_ophelia(background_colour = FALSE,
base_text_size = 14) +
theme(axis.title = element_blank(),
legend.position = "bottom")
remotes::install_github("cararthompson/ophelia", "TopSecretKey")
Starting point
# A tibble: 10 x 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# i 2 more variables: sex <fct>, year <int>
{ophelia}
+ {reactable}
{ophelia}
+ {reactable}
{ophelia}
+ {reactable}
+ {reactablefmtr}
head(palmerpenguins::penguins, 10) %>%
reactable::reactable(
theme = ophelia::reactable_theme(colour = "dark_green"),
pagination = FALSE,
striped = TRUE,
columns = list(
body_mass_g = reactable::colDef(
cell = reactablefmtr::data_bars(head(palmerpenguins::penguins, 10),
text_position = "outside-base",
fill_color = ophelia::ophelia_colours$light_blue,
number_fmt = function(x) format(x, big.mark = ","))
)
)
)
{ophelia}
+ {reactable}
+ {reactablefmtr}
Using {pkgdown}
- cararthompson.github.io/ophelia/
Set of Quarto slides!
carartemplates::create_slides()
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Set of Quarto slides!
ophelia::create_slides()
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Interactive graphs with ggiraph tooltip formatting defaults
palmerpenguins::penguins %>%
ggplot() +
geom_point(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g),
shape = 21,
colour = "white",
alpha = 0.8) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Perfectly proportional penguins",
caption = "Data from {palmerpenguins}") +
guides(size = "none") +
ophelia::scale_fill_ophelia(continuous = TRUE) +
ophelia::theme_ophelia()
Interactive graphs with ggiraph tooltip formatting defaults
penguin_plot <- palmerpenguins::penguins %>%
ggplot() +
ggiraph::geom_point_interactive(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g,
tooltip = paste0("Body mass<br><b>", sprintf("%.03f", body_mass_g/1000), "kg</b>")),
shape = 21,
colour = "white",
alpha = 0.8,
show.legend = FALSE) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Perfectly proportional penguins",
caption = "Data from {palmerpenguins}") +
ophelia::scale_fill_ophelia(continuous = TRUE) +
ophelia::theme_ophelia()
ggiraph::girafe(ggobj = penguin_plot)
Interactive graphs with ggiraph tooltip formatting defaults
penguin_plot <- palmerpenguins::penguins %>%
ggplot() +
ggiraph::geom_point_interactive(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g,
tooltip = paste0("Body mass<br><b>", sprintf("%.03f", body_mass_g/1000), "kg</b>")),
shape = 21,
colour = "white",
alpha = 0.8,
show.legend = FALSE) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Perfectly proportional penguins",
caption = "Data from {palmerpenguins}") +
ophelia::scale_fill_ophelia(continuous = TRUE) +
ophelia::theme_ophelia()
ggiraph::girafe(ggobj = penguin_plot,
options = list(
ggiraph::opts_tooltip(
css = "background-color:#2B2529;color:#FEFCF7;padding:7.5px;letter-spacing:0.025em;line-height:1.3;border-radius:5px;font-family:Nunito Sans;")))
Interactive graphs with ggiraph tooltip formatting defaults
penguin_plot <- palmerpenguins::penguins %>%
ggplot() +
ggiraph::geom_point_interactive(aes(x = bill_length_mm,
y = flipper_length_mm,
fill = body_mass_g,
size = body_mass_g,
tooltip = paste0("Body mass<br><b>", sprintf("%.03f", body_mass_g/1000), "kg</b>")),
shape = 21,
colour = "white",
alpha = 0.8,
show.legend = FALSE) +
labs(x = "Bill length (mm)",
y = "Flipper length (mm)",
title = "Perfectly proportional penguins",
caption = "Data from {palmerpenguins}") +
ophelia::scale_fill_ophelia(continuous = TRUE) +
ophelia::theme_ophelia()
ggiraph::girafe(ggobj = penguin_plot,
options = list(
ggiraph::opts_tooltip(
css = ophelia::tooltip_css)))
ggplot
theme
hello@cararthompson.com
—
Data-to-viz Commissions
Dataviz Design Systems
Training & Consultations