Making a calendar using R

I started writing this code as a part of my bigger project of creating bullet journals using R. Therefore, my goal is create viually appealing calendar outputs, which can even incorporte .png and .jpg images.

Here I will show how to create a monthly calendar (with no task entries, it is only for tracking what day it is, which calendar week we are in, etc.); yet I am sure it is also possible to create yearly calendars by tweaking the code a tiny bit.

First, I start with choosing the month I want to create a calendar for. Say, September 2020.

month.number <- 9
year <- 2020

Then, I create a sequence of dates that covers the full month.

  month <- as.numeric(month.number)
  year <- as.numeric(year)
  if(month!=12){ #if not december
    date <- seq(as.Date(paste0(year,"/",month,"/",1)), #creates a string as in 2020-09-01
                as.Date(paste0(year,"/",month+1,"/",1)), #creates a string as in 2020-10-01
                "days") #creates a vector with the selected days and all the days in between
  }else{  #if december
    date <- seq(as.Date(paste0(year,"/",month,"/",1)), #creates a string as in 2020-12-01
                as.Date(paste0(year+1,"/",1,"/",1)), #creates a string as in 2021-01-01
                "days") #creates a vector with the selected days and all the days in between
  }
  date <- date[-length(date)] #delete the last date from the string, which is in October and not in September
  date
##  [1] "2020-09-01" "2020-09-02" "2020-09-03" "2020-09-04" "2020-09-05"
##  [6] "2020-09-06" "2020-09-07" "2020-09-08" "2020-09-09" "2020-09-10"
## [11] "2020-09-11" "2020-09-12" "2020-09-13" "2020-09-14" "2020-09-15"
## [16] "2020-09-16" "2020-09-17" "2020-09-18" "2020-09-19" "2020-09-20"
## [21] "2020-09-21" "2020-09-22" "2020-09-23" "2020-09-24" "2020-09-25"
## [26] "2020-09-26" "2020-09-27" "2020-09-28" "2020-09-29" "2020-09-30"

Once I have this layout, then I go ahead and create the variables I need to create a ggplot. My goals is to put the days of the week (Mon, Tue, etc.) on the x axis, and the week number on the y axis. For getting these date variables, I am using the lubridate package. For table data manipulation, I use tidyverse.

library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(tidyverse)
## -- Attaching packages --------------------------------------------------------------------------- tidyverse 1.3.0 --
## v ggplot2 3.3.1     v purrr   0.3.4
## v tibble  3.0.1     v dplyr   1.0.0
## v tidyr   1.1.0     v stringr 1.4.0
## v readr   1.3.1     v forcats 0.5.0
## -- Conflicts ------------------------------------------------------------------------------ tidyverse_conflicts() --
## x lubridate::as.difftime() masks base::as.difftime()
## x lubridate::date()        masks base::date()
## x dplyr::filter()          masks stats::filter()
## x lubridate::intersect()   masks base::intersect()
## x dplyr::lag()             masks stats::lag()
## x lubridate::setdiff()     masks base::setdiff()
## x lubridate::union()       masks base::union()
df <- tibble(date= date) ## create a tibble with column date
df <- df %>%
  mutate(wkdy = fct_inorder(weekdays(date, abbreviate=T)), ##day of the week
         wkn = lubridate::isoweek(date), ## Week Number. Starts on Monday.
         mo = month(date, label=T, abbr=T), ## Month (useful if you are creating a calendar for the whole year)
         day = day(date)) %>% #day number (1,29,31, etc.)
  mutate(wkdy = fct_relevel(wkdy,                ##reorder the factor level for plotting, starting on Monday.
                            "Mon", "Tue", "Wed", 
                            "Thu", "Fri", "Sat", 
                            "Sun"))
df
## # A tibble: 30 x 5
##    date       wkdy    wkn mo      day
##    <date>     <fct> <dbl> <ord> <int>
##  1 2020-09-01 Tue      36 Sep       1
##  2 2020-09-02 Wed      36 Sep       2
##  3 2020-09-03 Thu      36 Sep       3
##  4 2020-09-04 Fri      36 Sep       4
##  5 2020-09-05 Sat      36 Sep       5
##  6 2020-09-06 Sun      36 Sep       6
##  7 2020-09-07 Mon      37 Sep       7
##  8 2020-09-08 Tue      37 Sep       8
##  9 2020-09-09 Wed      37 Sep       9
## 10 2020-09-10 Thu      37 Sep      10
## # ... with 20 more rows

Once this task is complete, we are ready to create our calendar! We will go step by step.

p <- df %>%
  ggplot(aes(x=wkdy, y=wkn)) + #create the plot table
  geom_point(alpha=0.7, aes(color=wkdy), size=10) + #add circles for each day of the month
  geom_text(aes(label=day), size=5) #add number labels for each day of the month
p

It is a promising start, but we are not there yet! All the days are there, the Mon-Sun order is correct. Yet the numbers are ordered… Upside down. Because of the default ordering of y axes in 2D plots. Let’s correct for that.

Also, in calendars we tend to put the dayes on top of the calendar, not on the bottom. So let’s also carry the x axis ticks to the top of the plot.

p <- p +
  scale_y_reverse() + # reverse the order of y axis
  scale_x_discrete(position = "top", ) # reposition the x axis (carry to the top)
p

Now let’s work on the visual appeal. Let’s change two things: * The color of the dots (let’s use a color gradient, like, viridis!) * The calendar title (Why don’t we use ‘September 2020’?)

p <- p+ 
  scale_colour_viridis_d(guide="none") + #use viridis colors
  labs(title= paste(month(ymd(080101) + months(0:11), label = TRUE, abbr=F)[month], year),  #add title
         x="", y="") + #remove x and y axis titles
  expand_limits(y=c(min(df$wkn)-.5,max(df$wkn)+.5)) # place the nodes a little closer to each other
p

Now, let’s change the theme and fonts. Let’s get rid of that gray background color by using a simple theme called ‘classic’.

When it comes to fonts, R base does not provide a lot of options, but we can use the showtext package to download fonts from google.

library(showtext)
## Warning: package 'showtext' was built under R version 4.0.2
## Loading required package: sysfonts
## Warning: package 'sysfonts' was built under R version 4.0.2
## Loading required package: showtextdb
## Warning: package 'showtextdb' was built under R version 4.0.2
font_add_google(name = "Amatic SC", family = "amatic-sc") #let's choose a fun font!

#you need to run this function first to be able to visualize the font you downloaded in plots
showtext_auto() 

p <- p  +
  theme_classic()+
  theme(text=element_text(size=40,  family="amatic-sc"),
        strip.placement = "outside") #strip.placement= "outside" parameter is useful if you are creating a calendar for a whole year, and use facet grid for dividing the months.

p

This is way better! Oh, would you like to highlight the week you are currently in? say, today is September 24, 2020:

library(viridis)
## Warning: package 'viridis' was built under R version 4.0.2
## Loading required package: viridisLite
highlight.week.with.day.number <- 24 #Current day
highlight.week.with.day.number <- day(Sys.time()) #Or, alternatively, you can get today's date using Sys.time() and select the day
boxrow <- df$wkn[df$day==highlight.week.with.day.number] #Current week
p <- p +
  annotate(geom='rect', #annotate with a rectangle
           xmin=0.5,xmax=7.5,ymin = boxrow - 0.5, ymax = boxrow + 0.5, #chooses the borders of the rectangle.
           fill = viridis(4)[3], color = "transparent",  alpha=.3)  #takes 4 viridis colors and selects the third to highlight the week
p

Ok, now, let’s add some images to this! Since it is September, Let’s use an autumn themed background.

We will use annotate function to add the image, and as you might guess, we need to first insert that image before we add all the other properties (the day labels, the day points, etc.) So let’s start over:

I use magick package to open the image, and grid package to create a grob object from that image using rasterGrob():

#open the image:
library(magick)
## Warning: package 'magick' was built under R version 4.0.2
## Linking to ImageMagick 6.9.9.14
## Enabled features: cairo, freetype, fftw, ghostscript, lcms, pango, rsvg, webp
## Disabled features: fontconfig, x11
library(grid)
background.image <- 'C:\\Users\\Meltem Odabas\\Downloads\\leaf1.png' ## image location
bg <- magick::image_read(background.image)
bg <- magick::image_scale(bg, "300")

Then, add the image to ggplot right after you defined the characteristics:

p2 <- df %>%
  ggplot(aes(x=wkdy, y=wkn)) + #create the plot table
  annotation_custom(rasterGrob(bg, 
                                      width = unit(1,"npc"),
                                      height = unit(1,"npc")),
                           -Inf, Inf, -Inf, Inf) 

And go ahead and add the rest! (note that I will alter the viridis colors by using the option ‘B’ to make it more autumny! I also altered the size of day labels and the transparency of the circles)

p2 <- p2 +
  geom_point(alpha=0.2, aes(color=wkdy), size=20) + #add circles for each day of the month
  geom_text(aes(label=day), size=14) + #add number labels for each day of the month
  scale_y_reverse() + # reverse the order of y axis
  scale_x_discrete(position = "top", ) + # reposition the x axis (carry to the top)
    scale_colour_viridis_d(guide="none", option='B', begin = 0.5, end = 0.8) + #use viridis colors, with option added
  labs(title= paste(month(ymd(080101) + months(0:11), label = TRUE, abbr=F)[month], year),  #add title
         x="", y="") + #remove x and y axis titles
  expand_limits(y=c(min(df$wkn)-.5,max(df$wkn)+.5)) + # place the nodes a little closer to each other
  theme_classic()+
  theme(text=element_text(size=40,  family="amatic-sc"), strip.placement = "outside") +
  annotate(geom='rect', #annotate with a rectangle
           xmin=0.5,xmax=7.5,ymin = boxrow - 0.5, ymax = boxrow + 0.5, #chooses the borders of the rectangle.
           fill = viridis(5, option='B')[4], color = "transparent",  alpha=.3)  #takes 5 viridis colors and selects the third to highlight the week

p2

or, maybe, you rather want to use images instead of the circles! Let’s now do that. Let’s start with opening the image and starting to create the plot:

library(ggimage)
## Warning: package 'ggimage' was built under R version 4.0.2
day.images <- 'C:\\Users\\Meltem Odabas\\Downloads\\leaf1.png' #open the image file
day.images <- rep(day.images,nrow(df)) #repeat the filename by the total number of days ni the selected month

p3 <- df %>%
  ggplot(aes(x=wkdy, y=wkn)) + #create the plot table
  geom_image(aes(image = day.images), size = .15) #add leaves for each day of the month

And then add the rest!

p3 <- p3 +
  geom_text(aes(label=day), size=14,color="white") + #add number labels for each day of the month
  scale_y_reverse() + # reverse the order of y axis
  scale_x_discrete(position = "top",) + # reposition the x axis (carry to the top)
  scale_colour_viridis_d(guide="none", option='B') + #use viridis colors, with option added
  labs(title= paste(month(ymd(080101) + months(0:11), label = TRUE, abbr=F)[month], year),  #add title
         x="", y="") + #remove x and y axis titles
  expand_limits(y=c(min(df$wkn)-.5,max(df$wkn)+.5)) + # place the nodes a little closer to each other
  theme_classic()+
  theme(text=element_text(size=40,  family="amatic-sc"), strip.placement = "outside") +
  annotate(geom='rect', #annotate with a rectangle
           xmin=0.5,xmax=7.5,ymin = boxrow - 0.5, ymax = boxrow + 0.5, #chooses the borders of the rectangle.
           fill = viridis(4, option='B')[3], color = "transparent",  alpha=.3)  #takes 4 viridis colors and selects the third to highlight the week

p3