How to make calendars with ggplot and lubridate

You don’t need any additional packages to make beautiful calendars beyond the tidyverse and lubridate. These calendars can be faceted by month using facet_wrap(~mo) for multiple months. You can set aesthetics to make the dates different colors to represent data or multiple geom_text to plot various text information on each box. This is a basic example to get you started, but you can build upon it to make advanced calendars as shown on my blog and elsewhere.

01library(tidyverse)
02library(lubridate)
03rm(list=ls())
04tibble(date = seq(as.Date('2023-03-01'), as.Date('2023-03-31'), 'days')) %>%
05  # manually setting the weekday using factor with the order of weekdays,
06  # make monday first in levels if so desired. Use abbreviations if
07  # desired, but also you'll need to rename the levels
08 mutate(wkdy = factor(weekdays(date, abbreviate=T), levels=c('Sun','Mon','Tue','Wed','Thu','Fri','Sat')),
09         wkn = week(date),
10         mo = month(date, label=T, abbr=F),
11         day = day(date)) %>%
12  # the group_by and dense rank creates
13  # a column which represents week of the month
14 group_by(mo) %>%
15  mutate(wkn.mo = dense_rank(wkn)) %>%
16  ungroup() %>%
17  # basically each calendar day is ploted as a geom tile
18  # the weekday number and week month number define the location
19  ggplot(aes(x=wkdy, y=wkn.mo)) +
20  # you can use aesthetics to colorize the tiles
21  geom_tile(alpha=0.8, fill='gray', width=0.9, height=0.9) +
22  # use additional geom text to add text of different sizes
23  geom_text(aes(label=day), size=20, hjust=0.5) +
24  # for multiple months you will want to use facet_wrap
25  # set for each month. Scales = "free" ensures the
26  # day of week labels are repeated for each month's calendar
27 # but if you use scales = "free" below you have to switch the
28 # coord_fixed to coord_cartersian which will make the grids on the
29 # calendar non-square.
30  #   facet_wrap(~mo) +
31  # puts the day of week labels up top
32  scale_x_discrete(position = "top") +
33  # remove any default theme position
34  theme_void() +
35  # This reverses the axis and adds margin. Need to
36  # change for months with six weeks like April 2023 to
37  # c(6.6, 0.4). You could also use scale_y_reverse but
38  # I'd rather set manual limits so all months have the
39  # same height when using facet wrap
40  coord_fixed(expand=F, ylim=c(5.6, 0.4)) +
41  # set labels
42  labs(title="March 2023",
43       x="", y=""
44  ) +
45  theme(
46    plot.background = element_rect(fill='white', color='white'),
47    # strip.placement outside puts month name above the x axis
48    # date lables
49    strip.placement = 'outside',
50    text = element_text(family = 'Arial', size=40),
51    plot.title = element_text(face='bold', size=60, hjust=0.5, margin=margin(0,0,10,0)),
52    axis.text.x = element_text(size=20),
53    plot.margin = margin(30,30,30,30),
54    legend.position = 'none'
55  )

Leave a Reply

Your email address will not be published. Required fields are marked *