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.
library(tidyverse)
library(lubridate)
rm(list=ls())
tibble(date = seq(as.Date('2023-03-01'), as.Date('2023-03-31'), 'days')) %>%
# manually setting the weekday using factor with the order of weekdays,
# make monday first in levels if so desired. Use abbreviations if
# desired, but also you'll need to rename the levels
mutate(wkdy = factor(weekdays(date, abbreviate=T), levels=c('Sun','Mon','Tue','Wed','Thu','Fri','Sat')),
wkn = week(date),
mo = month(date, label=T, abbr=F),
day = day(date)) %>%
# the group_by and dense rank creates
# a column which represents week of the month
group_by(mo) %>%
mutate(wkn.mo = dense_rank(wkn)) %>%
ungroup() %>%
# basically each calendar day is ploted as a geom tile
# the weekday number and week month number define the location
ggplot(aes(x=wkdy, y=wkn.mo)) +
# you can use aesthetics to colorize the tiles
geom_tile(alpha=0.8, fill='gray', width=0.9, height=0.9) +
# use additional geom text to add text of different sizes
geom_text(aes(label=day), size=20, hjust=0.5) +
# for multiple months you will want to use facet_wrap
# set for each month. Scales = "free" ensures the
# day of week labels are repeated for each month's calendar
# but if you use scales = "free" below you have to switch the
# coord_fixed to coord_cartersian which will make the grids on the
# calendar non-square.
# facet_wrap(~mo) +
# puts the day of week labels up top
scale_x_discrete(position = "top") +
# remove any default theme position
theme_void() +
# This reverses the axis and adds margin. Need to
# change for months with six weeks like April 2023 to
# c(6.6, 0.4). You could also use scale_y_reverse but
# I'd rather set manual limits so all months have the
# same height when using facet wrap
coord_fixed(expand=F, ylim=c(5.6, 0.4)) +
# set labels
labs(title="March 2023",
x="", y=""
) +
theme(
plot.background = element_rect(fill='white', color='white'),
# strip.placement outside puts month name above the x axis
# date lables
strip.placement = 'outside',
text = element_text(family = 'Arial', size=40),
plot.title = element_text(face='bold', size=60, hjust=0.5, margin=margin(0,0,10,0)),
axis.text.x = element_text(size=20),
plot.margin = margin(30,30,30,30),
legend.position = 'none'
)