Christmas Drinks ggplot2 Advent Calendar 2018

There are just about 24 Christmassy hot drinks available in the big UK cafes. I’ve made an advent calendar of them in ggplot2 — read this post to find out how to do the same.
R
dataviz
Published

November 28, 2018

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"))