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