This Christmas it suddenly hit me. There are probably enough festive coffees at Starbucks etc that you could use them to populate a very tasty advent calendar.
In the interests of helping everyone explore the range of Christmassy drinks on offer, I’ll be updating my deposit 24 days of Christmassy drinks on Figshare every year from now on. This blogpost takes you through how you can create the advent calendar below using ggplot2.
ggplot2 advent calendar doors
In my opinion, there are only really two true advent calendar layouts:
set.seed (1 )
portrait_door_centers <- tibble (
x = rep (seq (1 , 10 , 3 ), 6 ),
y = rep (seq (1 , 16 , 3 ), times = 1 , each = 4 ),
door_number = sample (1 : 24 )
)
set.seed (1 )
landscape_door_centers <- tibble (
y = rep (seq (1 , 10 , 3 ), 6 ),
x = rep (seq (1 , 16 , 3 ), times = 1 , each = 4 ),
door_number = sample (1 : 24 )
)
gg_advent_portrait <- portrait_door_centers |>
ggplot (aes (x, y)) +
geom_tile (color = "black" , linewidth = 0.6 , linetype = "dotted" , alpha = 0 ) +
geom_label (aes (label = door_number)) +
coord_fixed () +
theme_void ()
gg_advent_landscape <- landscape_door_centers |>
ggplot (aes (x, y)) +
geom_tile (color = "black" , linewidth = 0.6 , linetype = "dotted" , alpha = 0 ) +
geom_label (aes (label = door_number)) +
coord_fixed () +
theme_void ()
gg_advent_landscape + gg_advent_portrait
We compute the centres of all the doors with tibble() and seq():
portrait_door_centers <- tibble (
x = rep (seq (1 , 10 , 3 ), 6 ),
y = rep (seq (1 , 16 , 3 ), times = 1 , each = 4 )
)
landscape_door_centers <- tibble (
y = rep (seq (1 , 10 , 3 ), 6 ),
x = rep (seq (1 , 16 , 3 ), times = 1 , each = 4 )
)
geom_tile() requires only the centre of our doors. theme_void() throws away unnecessary axis labels, leaving us with our idealised advent calendar:
gg_advent_landscape <- landscape_door_centers |>
ggplot (aes (x, y)) +
geom_tile (color = "black" , linewidth = 0.6 , linetype = "dotted" , alpha = 0 ) +
theme_void () +
coord_fixed ()
gg_advent_landscape
Add randomised door numbers with mutate() and geom_label():
set.seed (1 )
landscape_door_centers <- landscape_door_centers |>
mutate (door_number = sample (1 : 24 ))
gg_advent_landscape <- landscape_door_centers |>
ggplot (aes (x, y)) +
geom_tile (color = "black" , linewidth = 0.6 , linetype = "dotted" , alpha = 0 ) +
geom_label (aes (label = door_number)) +
theme_void () +
coord_fixed ()
gg_advent_landscape
Make your own ggplot2 advent
To follow along, create a new RStudio project and download the data:
library ("tidyverse" )
library ("grid" )
library ("ggpubr" )
library ("magick" )
library ("here" )
dir.create ("data" )
download.file (
"https://ndownloader.figshare.com/articles/7376228/versions/3" ,
"data/christmassy-drinks-2018.zip"
)
unzip ("data/christmassy-drinks-2018.zip" , exdir = "data" )
unlink ("data/christmassy-drinks-2018.zip" )
dir.create ("images" )
retailer_logos <- list.files ("data" , "*.png|.jpg" , full.names = TRUE )
file.copy (retailer_logos, "images" )
file.remove (retailer_logos)
Import the drinks data and join with door positions:
set.seed (1 )
christmassy_drinks_2018 <- read_csv ("data/24-days-of-christmassy-drinks.csv" ) |>
filter (year == 2018 ) |>
slice (landscape_door_centers$ door_number) |>
mutate (door_number = 1 : 24 )
landscape_door_treats <- christmassy_drinks_2018 |>
left_join (landscape_door_centers)
landscape_door_treats <- landscape_door_treats |>
mutate (drink.name = gsub ("(.{10,}?) \\ s" , " \\ 1 \n " , drink.name))
Import retailer logos as rasterGrob objects:
starbucks_logo <- image_read (here ("images" , "starbucks_logo_2018.png" )) |>
rasterGrob (interpolate = TRUE )
costa_logo <- image_read (here ("images" , "costa_logo_2018.png" )) |>
rasterGrob (interpolate = TRUE )
cafe_nero_logo <- image_read (here ("images" , "cafe-nero_logo_2018.jpg" )) |>
rasterGrob (interpolate = TRUE )
greggs_logo <- image_read (here ("images" , "greggs_logo_2018.png" )) |>
rasterGrob (interpolate = TRUE )
pret_logo <- image_read (here ("images" , "pret_logo_2018.jpg" )) |>
rasterGrob (interpolate = TRUE )
mc_cafe_logo <- image_read (here ("images" , "mc-café_logo_2018.png" )) |>
rasterGrob (interpolate = TRUE )
Use pwalk() to iteratively add logos via annotation_custom():
gg_logoed_treats <- gg_landscape_door_treats
landscape_door_treats |>
pwalk (function (x, y, retailer, ...) {
logo <- switch (
retailer,
"Starbucks" = starbucks_logo,
"Costa" = costa_logo,
"Café Nero" = cafe_nero_logo,
"Greggs" = greggs_logo,
"McCafé" = mc_cafe_logo,
"Pret" = pret_logo
)
gg_logoed_treats <<- gg_logoed_treats +
annotation_custom (
logo,
ymin = y - 1.25 ,
ymax = y - 0.25 ,
xmin = x - 0.5 ,
xmax = x + 0.5
)
})
Background images
Add a Christmassy background using ggpubr::background_image():
download.file (
"https://cdn.pixabay.com/photo/2017/12/13/14/48/santa-3016939_640.jpg" ,
here ("images" , "santa-3016939_640.jpg" )
)
christmassy_background <- image_read (here ("images" , "santa-3016939_640.jpg" ))
gg_advent_landscape <- landscape_door_centers |>
ggplot (aes (x, y)) +
background_image (christmassy_background) +
geom_tile (color = "black" , linewidth = 0.6 , linetype = "dotted" , alpha = 0 ) +
geom_label (aes (label = door_number)) +
theme_void () +
coord_fixed ()
Iteratively opening the advent calendar
Here’s a function for opening the advent doors:
open_advent_doors <- function (gg_advent, advent_treats, open_doors) {
gg_local <- gg_advent
advent_treats |>
filter (door_number %in% open_doors) |>
pwalk (function (x, y, retailer, drink.name, ...) {
logo <- switch (
retailer,
"Starbucks" = starbucks_logo,
"Costa" = costa_logo,
"Café Nero" = cafe_nero_logo,
"Greggs" = greggs_logo,
"McCafé" = mc_cafe_logo,
"Pret" = pret_logo
)
gg_local <<- gg_local +
annotation_custom (
cardboard_background |>
image_crop ("-200x400" ) |>
rasterGrob (interpolate = TRUE ),
ymin = y - 1.5 ,
ymax = y + 1.5 ,
xmin = x - 2 ,
xmax = x + 2
) +
annotation_custom (
logo,
ymin = y - 1.25 ,
ymax = y - 0.25 ,
xmin = x - 0.5 ,
xmax = x + 0.5
) +
geom_label (
data = tibble (x, y, drink.name),
aes (label = drink.name, x = x, y = y),
nudge_y = 0.5 ,
size = 5
)
})
gg_local
}
Export a GIF by generating each frame and stitching with magick:
tibble (remaining_days = 1 : 24 ) |>
rowwise () |>
mutate (doors = list (1 : remaining_days)) |>
pwalk (function (doors, ...) {
opened_door <- open_advent_doors (
gg_advent_landscape,
landscape_door_treats,
doors
)
img_path <- here (
"data" ,
paste0 (
"gg_iterative_opened_door_" ,
formatC (length (doors), width = 2 , format = "d" , flag = "0" ),
".png"
)
)
ggsave (img_path, opened_door)
image_read (img_path) |>
image_trim () |>
image_resize ("1400x" ) |>
image_write (img_path)
})
list.files ("images" , "gg_iterative_opened_door" , full.names = TRUE ) |>
map (image_read) |>
image_join () |>
image_animate (fps = 0.5 ) |>
image_write (here ("images" , "gg_animated_door_openings.gif" ))