├── .DS_Store ├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── NAMESPACE ├── R ├── a_stat_calendar.R ├── compute_group_calendar.R ├── defaults_calendar.R ├── df_functions.R ├── geom_text_calendar.R ├── get_week_of_month.R ├── ggcalendar.R └── theme_grey_calendar.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── docs ├── 404.html ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── docsearch.css ├── docsearch.js ├── index.html ├── link.svg ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── figures │ │ ├── README-example-1.png │ │ ├── README-example2-1.png │ │ ├── README-unnamed-chunk-10-1.png │ │ ├── README-unnamed-chunk-12-1.png │ │ ├── README-unnamed-chunk-14-1.png │ │ ├── README-unnamed-chunk-14-2.png │ │ ├── README-unnamed-chunk-15-1.png │ │ ├── README-unnamed-chunk-16-1.png │ │ ├── README-unnamed-chunk-16-2.png │ │ ├── README-unnamed-chunk-16-3.png │ │ ├── README-unnamed-chunk-16-4.png │ │ ├── README-unnamed-chunk-16-5.png │ │ ├── README-unnamed-chunk-17-1.png │ │ ├── README-unnamed-chunk-18-1.png │ │ ├── README-unnamed-chunk-19-1.png │ │ ├── README-unnamed-chunk-20-1.png │ │ ├── README-unnamed-chunk-22-1.png │ │ ├── README-unnamed-chunk-22-2.png │ │ ├── README-unnamed-chunk-22-3.png │ │ ├── README-unnamed-chunk-6-1.png │ │ └── README-unnamed-chunk-8-1.png │ └── index.html └── sitemap.xml ├── ggcalendar.Rproj └── man ├── defaults_calendar.Rd ├── df_dates_interval.Rd ├── df_day.Rd ├── df_month.Rd ├── df_today.Rd ├── df_week.Rd ├── df_year.Rd ├── figures ├── README-example-1.png ├── README-example2-1.png ├── README-unnamed-chunk-10-1.png ├── README-unnamed-chunk-12-1.png ├── README-unnamed-chunk-13-1.png ├── README-unnamed-chunk-13-2.png ├── README-unnamed-chunk-14-1.png ├── README-unnamed-chunk-14-2.png ├── README-unnamed-chunk-14-3.png ├── README-unnamed-chunk-15-1.png ├── README-unnamed-chunk-15-2.png ├── README-unnamed-chunk-15-3.png ├── README-unnamed-chunk-16-1.png ├── README-unnamed-chunk-16-2.png ├── README-unnamed-chunk-16-3.png ├── README-unnamed-chunk-16-4.png ├── README-unnamed-chunk-16-5.png ├── README-unnamed-chunk-17-1.png ├── README-unnamed-chunk-17-2.png ├── README-unnamed-chunk-17-3.png ├── README-unnamed-chunk-17-4.png ├── README-unnamed-chunk-17-5.png ├── README-unnamed-chunk-18-1.png ├── README-unnamed-chunk-19-1.png ├── README-unnamed-chunk-20-1.png ├── README-unnamed-chunk-22-1.png ├── README-unnamed-chunk-22-2.png ├── README-unnamed-chunk-22-3.png ├── README-unnamed-chunk-23-1.png ├── README-unnamed-chunk-23-2.png ├── README-unnamed-chunk-23-3.png ├── README-unnamed-chunk-24-1.png ├── README-unnamed-chunk-24-2.png ├── README-unnamed-chunk-24-3.png ├── README-unnamed-chunk-6-1.png └── README-unnamed-chunk-8-1.png ├── geom_point_calendar.Rd ├── geom_text_calendar.Rd ├── geom_tile_calendar.Rd ├── ggcalendar.Rd ├── return_df_hours_week.Rd ├── stat_calendar.Rd └── theme_grey_calendar.Rd /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/.DS_Store -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^ggcalendar\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | 6 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ggcalendar 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person("First", "Last", , "first.last@example.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "YOUR-ORCID-ID")) 7 | Description: What the package does (one paragraph). 8 | License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a 9 | license 10 | Encoding: UTF-8 11 | Roxygen: list(markdown = TRUE) 12 | RoxygenNote: 7.3.1 13 | Imports: 14 | dplyr, 15 | ggplot2, 16 | lubridate, 17 | stringr 18 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(defaults_calendar) 4 | export(df_dates_interval) 5 | export(df_day) 6 | export(df_month) 7 | export(df_today) 8 | export(df_week) 9 | export(df_year) 10 | export(geom_point_calendar) 11 | export(geom_text_calendar) 12 | export(geom_tile_calendar) 13 | export(ggcalendar) 14 | export(return_df_hours_week) 15 | export(stat_calendar) 16 | export(theme_grey_calendar) 17 | -------------------------------------------------------------------------------- /R/a_stat_calendar.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' @param mapping 4 | #' @param data 5 | #' @param geom 6 | #' @param position 7 | #' @param na.rm 8 | #' @param show.legend 9 | #' @param inherit.aes 10 | #' @param ... 11 | #' 12 | #' @return 13 | #' @export 14 | #' 15 | #' @examples 16 | stat_calendar <- function(mapping = NULL, 17 | data = NULL, 18 | geom = "text", 19 | position = "identity", 20 | na.rm = FALSE, 21 | show.legend = NA, 22 | inherit.aes = TRUE, ...) { 23 | ggplot2::layer( 24 | stat = StatCalendar, # proto object from Step 2 25 | geom = geom, # inherit other behavior 26 | data = data, 27 | mapping = mapping, 28 | position = position, 29 | show.legend = show.legend, 30 | inherit.aes = inherit.aes, 31 | params = list(na.rm = na.rm, ...) 32 | ) 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /R/compute_group_calendar.R: -------------------------------------------------------------------------------- 1 | compute_group_calendar <- function(data, scales){ 2 | 3 | data |> 4 | dplyr::mutate(wday = lubridate::wday(.data$date)) |> 5 | dplyr::mutate(wday_abbr = lubridate::wday(.data$date, label = TRUE, abbr = TRUE)) |> 6 | dplyr::mutate(week_of_month = get_week_of_month(.data$date)) |> 7 | dplyr::mutate(day = lubridate::day(.data$date)) |> 8 | dplyr::mutate(year = lubridate::year(.data$date) - 2018) |> 9 | dplyr::mutate(month_abbr = lubridate::month(.data$date, abbr = TRUE, label = TRUE)) |> 10 | dplyr::mutate(hour = lubridate::hour(.data$date)) |> 11 | dplyr::mutate(year_academic = lubridate::year(.data$date) + 12 | ifelse(lubridate::month(date) > 13 | 6, 1, 0)) |> 14 | dplyr::mutate(month_academic_abbr = .data$month_abbr |> 15 | factor(levels = c("Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 16 | "Jan", "Feb", "Mar", "Apr", "May", "Jun"))) 17 | 18 | } 19 | 20 | StatCalendar <- ggplot2::ggproto(`_class` = "StatCalendar", 21 | `_inherit` = ggplot2::Stat, 22 | required_aes = c("date"), 23 | compute_group = compute_group_calendar, 24 | default_aes = ggplot2::aes(x = ggplot2::after_stat(wday), 25 | y = ggplot2::after_stat(week_of_month), 26 | label = ggplot2::after_stat(day))) 27 | 28 | 29 | StatWeekly <- ggplot2::ggproto(`_class` = "StatCalendar", 30 | `_inherit` = ggplot2::Stat, 31 | required_aes = c("date"), 32 | compute_group = compute_group_calendar, 33 | default_aes = ggplot2::aes(x = ggplot2::after_stat(wday), 34 | y = ggplot2::after_stat(hour), 35 | label = ggplot2::after_stat(hour))) 36 | -------------------------------------------------------------------------------- /R/defaults_calendar.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' @param day_labels 4 | #' 5 | #' @return 6 | #' @export 7 | #' 8 | #' @examples 9 | defaults_calendar <- function(day_labels = c("M", "T", "W", "T", "F", "S", "S")){ 10 | 11 | week_start <- getOption("lubridate.week.start", 7) 12 | 13 | if(week_start != 1){day_labels <- day_labels[c(week_start:7, 1:(week_start-1))]} 14 | 15 | list(scale_y_calendar(), 16 | scale_x_calendar(day_labels = day_labels), 17 | facet_calendar(), 18 | theme_grey_calendar(), 19 | stat_calendar(geom = "blank") 20 | ) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /R/df_functions.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' @return 4 | #' @export 5 | #' 6 | #' @examples 7 | df_today <- function(){ 8 | 9 | data.frame(date = Sys.Date()) 10 | 11 | } 12 | 13 | #' Title 14 | #' 15 | #' @param date 16 | #' 17 | #' @return 18 | #' @export 19 | #' 20 | #' @examples 21 | df_day <- function(date = NULL){ 22 | 23 | if(is.null(date)){date <- Sys.Date()} 24 | 25 | data.frame(date = date) 26 | 27 | } 28 | 29 | 30 | #' Title 31 | #' 32 | #' @param start_date 33 | #' @param end_date 34 | #' 35 | #' @return 36 | #' @export 37 | #' 38 | #' @examples 39 | df_dates_interval <- function(start_date, end_date){ 40 | 41 | data.frame(date = as.Date(start_date):as.Date(end_date) |> 42 | as.Date()) 43 | 44 | } 45 | 46 | #' Title 47 | #' 48 | #' @param month 49 | #' @param year 50 | #' 51 | #' @return 52 | #' @export 53 | #' 54 | #' @examples 55 | df_month <- function(month = NULL, year = NULL){ 56 | 57 | if(is.null(month)){ 58 | 59 | date <- Sys.Date() 60 | month <- lubridate::month(date) 61 | } 62 | 63 | if(is.numeric(month)){ 64 | 65 | month <- stringr::str_pad(month, width = 2, pad = "0") 66 | 67 | } 68 | 69 | if(is.null(year)){ 70 | 71 | date <- Sys.Date() 72 | year <- lubridate::year(date) 73 | } 74 | 75 | 76 | paste0(year,"-", month, "-01") |> 77 | lubridate::as_date() -> 78 | start_date 79 | 80 | start_date |> lubridate::ceiling_date(unit = "month") -> 81 | end_date 82 | 83 | data.frame(date = 84 | df_dates_interval(start_date, 85 | end_date - lubridate::days(1))) 86 | 87 | } 88 | 89 | #' Title 90 | #' 91 | #' @param date 92 | #' 93 | #' @return 94 | #' @export 95 | #' 96 | #' @examples 97 | df_week <- function(date = NULL){ 98 | 99 | if(is.null(date)){date <- Sys.Date()} 100 | 101 | start_date <- lubridate::floor_date(date, unit = "week") 102 | end_date <- lubridate::ceiling_date(date, unit = "week") 103 | 104 | data.frame(date = df_dates_interval(start_date, 105 | end_date - lubridate::days(1)) ) 106 | 107 | } 108 | 109 | #' Title 110 | #' 111 | #' @param date 112 | #' 113 | #' @return 114 | #' @export 115 | #' 116 | #' @examples 117 | return_df_hours_week <- function(date = NULL){ 118 | 119 | if(is.null(date)){date <- Sys.Date()} 120 | 121 | start_date <- lubridate::floor_date(date, unit = "week") 122 | 123 | data.frame(date = (start_date + lubridate::hours(1:(24*7-1)))) 124 | 125 | } 126 | 127 | #' Title 128 | #' 129 | #' @param year 130 | #' 131 | #' @return 132 | #' @export 133 | #' 134 | #' @examples 135 | df_year <- function(year = NULL){ 136 | 137 | if(is.null(year)){year <- lubridate::year(Sys.Date())} 138 | 139 | paste0(year, "-01-01") |> 140 | lubridate::as_date() -> 141 | start_date 142 | 143 | start_date |> lubridate::ceiling_date(unit = "year") -> 144 | end_date 145 | 146 | data.frame(date = 147 | df_dates_interval(start_date, 148 | end_date - lubridate::days(1))) 149 | 150 | } 151 | -------------------------------------------------------------------------------- /R/geom_text_calendar.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' @param ... 4 | #' 5 | #' @return 6 | #' @export 7 | #' 8 | #' @examples 9 | geom_text_calendar <- function(...){stat_calendar(geom = "text", ...)} 10 | 11 | #' Title 12 | #' 13 | #' @param ... 14 | #' 15 | #' @return 16 | #' @export 17 | #' 18 | #' @examples 19 | geom_point_calendar <- function(...){stat_calendar(geom = "point", ...)} 20 | 21 | #' Title 22 | #' 23 | #' @param ... 24 | #' 25 | #' @return 26 | #' @export 27 | #' 28 | #' @examples 29 | geom_tile_calendar <- function(...){stat_calendar(geom = "tile", ...)} 30 | -------------------------------------------------------------------------------- /R/get_week_of_month.R: -------------------------------------------------------------------------------- 1 | get_week_of_month <- function(x){ 2 | 3 | (- lubridate::wday(x) + lubridate::day(x)) %/% 4 | 7 + 1 + 5 | ifelse(lubridate::wday(lubridate::floor_date(lubridate::as_date(x), "month")) == 1, 0, 1) 6 | 7 | } 8 | -------------------------------------------------------------------------------- /R/ggcalendar.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' @param dates_df 4 | #' @param day_labels 5 | #' @param geom 6 | #' @param color 7 | #' @param size 8 | #' @param alpha 9 | #' 10 | #' @return 11 | #' @export 12 | #' 13 | #' @examples 14 | ggcalendar <- function(dates_df = df_year(), 15 | day_labels = c("M", "T", "W", "T", "F", "S", "S"), 16 | geom = "text", 17 | color = "grey35", 18 | size = 3, 19 | alpha = 1){ 20 | 21 | my_layer <- stat_calendar(geom = geom, 22 | color = color, 23 | ggplot2::aes(date = date), 24 | size = size, 25 | alpha = alpha, 26 | show.legend = F) 27 | 28 | ggplot2::ggplot(data = dates_df) + 29 | defaults_calendar(day_labels = day_labels) + 30 | ggplot2::aes(date = date) + 31 | my_layer 32 | 33 | } 34 | -------------------------------------------------------------------------------- /R/theme_grey_calendar.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' @param ... 4 | #' 5 | #' @return 6 | #' @export 7 | #' 8 | #' @examples 9 | theme_grey_calendar <- function(...){ 10 | 11 | theme_grey(...) %+replace% 12 | ggplot2::theme( 13 | axis.text.y = ggplot2::element_blank(), 14 | axis.ticks.y = ggplot2::element_blank(), 15 | axis.ticks.x = ggplot2::element_blank(), 16 | axis.title = element_blank()) + 17 | ggplot2::theme( 18 | panel.grid.major = ggplot2::element_blank()) 19 | 20 | } 21 | 22 | 23 | scale_y_calendar <- function(...){ggplot2::scale_y_reverse(breaks = 6:0, 24 | expand = c(0,0), 25 | limits = c(6.5, 0.5), ...)} 26 | 27 | scale_x_calendar <- function(day_labels = c("M", "T", "W", "T", "F", "S", "S"), ...){ 28 | 29 | ggplot2::scale_x_continuous(breaks = 1:7, 30 | labels = day_labels, 31 | limits = c(.5, 7.5), 32 | expand = c(0,0), ...)} 33 | 34 | facet_calendar <- function(...){ 35 | 36 | ggplot2::facet_wrap(~lubridate::month(date, abbr = T, label = T), 37 | scales = "free",...) 38 | 39 | } 40 | 41 | 42 | geom_calendar_blank <- function(...){ 43 | 44 | stat_calendar(geom = "blank", ...) 45 | 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | github_document: 4 | toc: TRUE 5 | --- 6 | 7 | 8 | 9 | ```{r, include = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | fig.path = "man/figures/README-", 14 | out.width = "100%", 15 | warning = F, 16 | message = F 17 | ) 18 | ``` 19 | 20 | 21 | 22 | # ggcalendar 23 | 24 | 25 | [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 26 | 27 | 28 | *Note: This README walks through package rational and contains the code that defines proposed package functions and in addition to first-cut testing. [TLDR - Jump to traditional readme content](#traditional-readme)* 29 | 30 | Here's a proposal for creating calendars with ggplot2 via Stat extension. 31 | 32 | When using calendars, 'when?' and 'where?' are the same question! So, ggcalendar introduces a new positional aesthetic: 'date'. Let's *put* things on the calendar! 33 | 34 | In this proposed package, we'll specify the position of a calendar event calendar using dates as the required aesthetic: `aes(date = my_variable_of_dates)`! Then you can use layers function `stat_calendar()` and derivative geom functions `geom_text_calendar`, `geom_tile_calendar` and `geom_point_calendar` to place specific grobs/mark in the plot space. 35 | 36 | Under the hood, the compute_group functions finds the x and y position for the date in the month (x is day in week and y is week in month). Faceting by month is used to prevent over-plotting. Note: automatic faceting by month via ggcalendar() function presupposes that your variable is also named 'date'. 37 | 38 | Other possible directions would be to calculate x and y based on date in month *and* on month - instead of relying on faceting by month. Furthermore, a dedicated Coord could be created (Teun's thought). Then maybe dates would just feed generically in as the 'x' aes - this sounds cool! 39 | 40 | 41 | ```{r setup} 42 | # library(ggcalendar) 43 | library(ggplot2) 44 | library(lubridate) 45 | library(tidyverse) 46 | ``` 47 | 48 | 49 | ``` r 50 | # install.packages("devtools") 51 | devtools::install_github("EvaMaeRey/ggcalendar") 52 | ``` 53 | 54 | 55 | # Step 00. Convenience functions, dates vectors to data frames. 56 | 57 | Because ggplot2's diet is consists solely of dataframes, we create a number of convenience functions that will help us produce dataframes with column 'date' we can feed into ggplot2. 58 | 59 | ```{r} 60 | knitrExtra:::chunk_to_r("df_functions") 61 | ``` 62 | 63 | ```{r df_functions} 64 | #' Title 65 | #' 66 | #' @return 67 | #' @export 68 | #' 69 | #' @examples 70 | df_today <- function(){ 71 | 72 | data.frame(date = Sys.Date()) 73 | 74 | } 75 | 76 | #' Title 77 | #' 78 | #' @param date 79 | #' 80 | #' @return 81 | #' @export 82 | #' 83 | #' @examples 84 | df_day <- function(date = NULL){ 85 | 86 | if(is.null(date)){date <- Sys.Date()} 87 | 88 | data.frame(date = date) 89 | 90 | } 91 | 92 | 93 | #' Title 94 | #' 95 | #' @param start_date 96 | #' @param end_date 97 | #' 98 | #' @return 99 | #' @export 100 | #' 101 | #' @examples 102 | df_dates_interval <- function(start_date, end_date){ 103 | 104 | data.frame(date = as.Date(start_date):as.Date(end_date) |> 105 | as.Date()) 106 | 107 | } 108 | 109 | #' Title 110 | #' 111 | #' @param month 112 | #' @param year 113 | #' 114 | #' @return 115 | #' @export 116 | #' 117 | #' @examples 118 | df_month <- function(month = NULL, year = NULL){ 119 | 120 | if(is.null(month)){ 121 | 122 | date <- Sys.Date() 123 | month <- lubridate::month(date) 124 | } 125 | 126 | if(is.numeric(month)){ 127 | 128 | month <- stringr::str_pad(month, width = 2, pad = "0") 129 | 130 | } 131 | 132 | if(is.null(year)){ 133 | 134 | date <- Sys.Date() 135 | year <- lubridate::year(date) 136 | } 137 | 138 | 139 | paste0(year,"-", month, "-01") |> 140 | lubridate::as_date() -> 141 | start_date 142 | 143 | start_date |> lubridate::ceiling_date(unit = "month") -> 144 | end_date 145 | 146 | data.frame(date = 147 | df_dates_interval(start_date, 148 | end_date - lubridate::days(1))) 149 | 150 | } 151 | 152 | #' Title 153 | #' 154 | #' @param date 155 | #' 156 | #' @return 157 | #' @export 158 | #' 159 | #' @examples 160 | df_week <- function(date = NULL){ 161 | 162 | if(is.null(date)){date <- Sys.Date()} 163 | 164 | start_date <- lubridate::floor_date(date, unit = "week") 165 | end_date <- lubridate::ceiling_date(date, unit = "week") 166 | 167 | data.frame(date = df_dates_interval(start_date, 168 | end_date - lubridate::days(1)) ) 169 | 170 | } 171 | 172 | #' Title 173 | #' 174 | #' @param date 175 | #' 176 | #' @return 177 | #' @export 178 | #' 179 | #' @examples 180 | return_df_hours_week <- function(date = NULL){ 181 | 182 | if(is.null(date)){date <- Sys.Date()} 183 | 184 | start_date <- lubridate::floor_date(date, unit = "week") 185 | 186 | data.frame(date = (start_date + lubridate::hours(1:(24*7-1)))) 187 | 188 | } 189 | 190 | #' Title 191 | #' 192 | #' @param year 193 | #' 194 | #' @return 195 | #' @export 196 | #' 197 | #' @examples 198 | df_year <- function(year = NULL){ 199 | 200 | if(is.null(year)){year <- lubridate::year(Sys.Date())} 201 | 202 | paste0(year, "-01-01") |> 203 | lubridate::as_date() -> 204 | start_date 205 | 206 | start_date |> lubridate::ceiling_date(unit = "year") -> 207 | end_date 208 | 209 | data.frame(date = 210 | df_dates_interval(start_date, 211 | end_date - lubridate::days(1))) 212 | 213 | } 214 | ``` 215 | 216 | 217 | ## Examples 218 | 219 | Let's have a look at some of these. 220 | 221 | ```{r} 222 | df_today() 223 | 224 | df_day() 225 | 226 | df_dates_interval(start_date = "2024-10-02", end_date = "2024-10-04") 227 | 228 | df_week() 229 | 230 | df_year() |> head() 231 | 232 | df_month() |> head() 233 | 234 | return_df_hours_week() |> head() 235 | ``` 236 | 237 | # Step 1 & 2. Compute: from date to x/y, & define StatCalendar 238 | 239 | The computation that we want to be done under the hood relates to translating the here-to-fore unknown positional aesthetic 'date' to the first-class 'x' and 'y' positional aesthetic mappings, as well as variables that can be used in faceting (month). 240 | 241 | 242 | ```{r} 243 | knitrExtra:::chunk_to_r("get_week_of_month") 244 | ``` 245 | 246 | As a pre-step to computing many useful variables from our date variable, we focus on this (currently messy) conversion of vectors of dates to week of the month. 247 | 248 | ```{r get_week_of_month} 249 | get_week_of_month <- function(x){ 250 | 251 | (- lubridate::wday(x) + lubridate::day(x)) %/% 252 | 7 + 1 + 253 | ifelse(lubridate::wday(lubridate::floor_date(lubridate::as_date(x), "month")) == 1, 0, 1) 254 | 255 | } 256 | ``` 257 | 258 | Next, we'll define a compute group function. A number of variables are created by parsing our date variable. 259 | 260 | Then, we'll pass all this computation to define a new ggproto object StatCalendar. For maximum flexibility, our compute function doesn't create ggplot2 core aesthetic channels 'x', 'y', and 'label' variables, but instead uses the default_aes field to state what should be first interpreted as x, y and label (thoughts? Maybe only 'label' should be managed like this). 261 | 262 | 263 | ```{r} 264 | knitrExtra:::chunk_to_r("compute_group_calendar") 265 | ``` 266 | 267 | ```{r compute_group_calendar} 268 | compute_group_calendar <- function(data, scales){ 269 | 270 | data |> 271 | dplyr::mutate(wday = lubridate::wday(.data$date)) |> 272 | dplyr::mutate(wday_abbr = lubridate::wday(.data$date, label = TRUE, abbr = TRUE)) |> 273 | dplyr::mutate(week_of_month = get_week_of_month(.data$date)) |> 274 | dplyr::mutate(day = lubridate::day(.data$date)) |> 275 | dplyr::mutate(year = lubridate::year(.data$date) - 2018) |> 276 | dplyr::mutate(month_abbr = lubridate::month(.data$date, abbr = TRUE, label = TRUE)) |> 277 | dplyr::mutate(hour = lubridate::hour(.data$date)) |> 278 | dplyr::mutate(year_academic = lubridate::year(.data$date) + 279 | ifelse(lubridate::month(date) > 280 | 6, 1, 0)) |> 281 | dplyr::mutate(month_academic_abbr = .data$month_abbr |> 282 | factor(levels = c("Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 283 | "Jan", "Feb", "Mar", "Apr", "May", "Jun"))) 284 | 285 | } 286 | 287 | StatCalendar <- ggplot2::ggproto(`_class` = "StatCalendar", 288 | `_inherit` = ggplot2::Stat, 289 | required_aes = c("date"), 290 | compute_group = compute_group_calendar, 291 | default_aes = ggplot2::aes(x = ggplot2::after_stat(wday), 292 | y = ggplot2::after_stat(week_of_month), 293 | label = ggplot2::after_stat(day))) 294 | 295 | 296 | StatWeekly <- ggplot2::ggproto(`_class` = "StatCalendar", 297 | `_inherit` = ggplot2::Stat, 298 | required_aes = c("date"), 299 | compute_group = compute_group_calendar, 300 | default_aes = ggplot2::aes(x = ggplot2::after_stat(wday), 301 | y = ggplot2::after_stat(hour), 302 | label = ggplot2::after_stat(hour))) 303 | ``` 304 | 305 | ## Test it out 306 | 307 | Okay, let's see how our compute and Stat work in action! 308 | 309 | ```{r} 310 | df_week() |> 311 | compute_group_calendar() 312 | 313 | df_month() |> 314 | ggplot() + 315 | aes(date = date) + 316 | geom_text(stat = StatCalendar) 317 | ``` 318 | 319 | # Step 3. Let's write a user-facing function `stat_calendar()` 320 | 321 | ```{r} 322 | knitrExtra:::chunk_to_r("a_stat_calendar") 323 | ``` 324 | 325 | 326 | ```{r a_stat_calendar} 327 | #' Title 328 | #' 329 | #' @param mapping 330 | #' @param data 331 | #' @param geom 332 | #' @param position 333 | #' @param na.rm 334 | #' @param show.legend 335 | #' @param inherit.aes 336 | #' @param ... 337 | #' 338 | #' @return 339 | #' @export 340 | #' 341 | #' @examples 342 | stat_calendar <- function(mapping = NULL, 343 | data = NULL, 344 | geom = "text", 345 | position = "identity", 346 | na.rm = FALSE, 347 | show.legend = NA, 348 | inherit.aes = TRUE, ...) { 349 | ggplot2::layer( 350 | stat = StatCalendar, # proto object from Step 2 351 | geom = geom, # inherit other behavior 352 | data = data, 353 | mapping = mapping, 354 | position = position, 355 | show.legend = show.legend, 356 | inherit.aes = inherit.aes, 357 | params = list(na.rm = na.rm, ...) 358 | ) 359 | } 360 | 361 | 362 | ``` 363 | 364 | ### Test `stat_calendar` 365 | 366 | ```{r} 367 | df_year() |> 368 | ggplot() + 369 | aes(date = date) + 370 | stat_calendar(color = "grey") + 371 | facet_wrap(~month(date, label = T, abbr = T)) + 372 | scale_y_reverse() 373 | ``` 374 | 375 | # aliasing and convenience 376 | 377 | To give the user a better sense of what they'll see when using stat_calendar we create the alias, 'geom_text_calendar()'. 378 | 379 | ```{r} 380 | knitrExtra:::chunk_to_r("geom_text_calendar") 381 | ``` 382 | 383 | ```{r geom_text_calendar} 384 | #' Title 385 | #' 386 | #' @param ... 387 | #' 388 | #' @return 389 | #' @export 390 | #' 391 | #' @examples 392 | geom_text_calendar <- function(...){stat_calendar(geom = "text", ...)} 393 | 394 | #' Title 395 | #' 396 | #' @param ... 397 | #' 398 | #' @return 399 | #' @export 400 | #' 401 | #' @examples 402 | geom_point_calendar <- function(...){stat_calendar(geom = "point", ...)} 403 | 404 | #' Title 405 | #' 406 | #' @param ... 407 | #' 408 | #' @return 409 | #' @export 410 | #' 411 | #' @examples 412 | geom_tile_calendar <- function(...){stat_calendar(geom = "tile", ...)} 413 | ``` 414 | 415 | 416 | # `defaults_calendar` & `ggcalendar()` Thinking about set of scales/coords etc, that gives you a nice calendar (to wrap up into defaults) 417 | 418 | In our test of stat_calendar, we see cumbersomeness. Below, we consider even more ggplot2 decision that would make our plot easier to consume and more beautiful. 419 | 420 | 421 | 422 | ```{r} 423 | day_labels = c("S", "M", "T", "W", "T", "F", "S") 424 | 425 | df_year() |> 426 | ggplot() + 427 | aes(date = date) + 428 | stat_calendar(color = "grey") + 429 | ggplot2::aes(date = date) + 430 | ggplot2::scale_y_reverse(breaks = 5:0, 431 | expand = c(0,0), 432 | limits = c(6.5, 0.5)) + 433 | ggplot2::scale_x_continuous(breaks = 1:7, 434 | labels = day_labels, 435 | limits = c(.5, 7.5), 436 | expand = c(0,0) 437 | ) + 438 | ggplot2::facet_wrap(~lubridate::month(date, abbr = T, label = T), scales = "free") + 439 | ggplot2::labs(x = NULL, y = NULL) + 440 | ggplot2::theme(axis.text.y = ggplot2::element_blank(), 441 | axis.ticks.y = ggplot2::element_blank(), 442 | axis.ticks.x = ggplot2::element_blank()) + 443 | ggplot2::theme(panel.grid.major = ggplot2::element_blank()) + 444 | ggplot2::geom_blank() 445 | 446 | ``` 447 | 448 | ## `theme_calendar`, `defaults_calendar` & `ggcalendar()` 449 | 450 | Then, we bundle these up into defaults_calendar, which can be quickly added for converting to a more polished and readable calendar. 451 | 452 | ```{r} 453 | knitrExtra::chunk_to_dir("theme_grey_calendar") 454 | ``` 455 | 456 | 457 | ```{r theme_grey_calendar} 458 | #' Title 459 | #' 460 | #' @param ... 461 | #' 462 | #' @return 463 | #' @export 464 | #' 465 | #' @examples 466 | theme_grey_calendar <- function(...){ 467 | 468 | theme_grey(...) %+replace% 469 | ggplot2::theme( 470 | axis.text.y = ggplot2::element_blank(), 471 | axis.ticks.y = ggplot2::element_blank(), 472 | axis.ticks.x = ggplot2::element_blank(), 473 | axis.title = element_blank()) + 474 | ggplot2::theme( 475 | panel.grid.major = ggplot2::element_blank()) 476 | 477 | } 478 | 479 | 480 | scale_y_calendar <- function(...){ggplot2::scale_y_reverse(breaks = 6:0, 481 | expand = c(0,0), 482 | limits = c(6.5, 0.5), ...)} 483 | 484 | scale_x_calendar <- function(day_labels = c("M", "T", "W", "T", "F", "S", "S"), ...){ 485 | 486 | ggplot2::scale_x_continuous(breaks = 1:7, 487 | labels = day_labels, 488 | limits = c(.5, 7.5), 489 | expand = c(0,0), ...)} 490 | 491 | facet_calendar <- function(...){ 492 | 493 | ggplot2::facet_wrap(~lubridate::month(date, abbr = T, label = T), 494 | scales = "free",...) 495 | 496 | } 497 | 498 | 499 | geom_calendar_blank <- function(...){ 500 | 501 | stat_calendar(geom = "blank", ...) 502 | 503 | } 504 | 505 | 506 | ``` 507 | 508 | ```{r} 509 | knitrExtra:::chunk_to_r("defaults_calendar") 510 | ``` 511 | 512 | ```{r defaults_calendar} 513 | #' Title 514 | #' 515 | #' @param day_labels 516 | #' 517 | #' @return 518 | #' @export 519 | #' 520 | #' @examples 521 | defaults_calendar <- function(day_labels = c("M", "T", "W", "T", "F", "S", "S")){ 522 | 523 | week_start <- getOption("lubridate.week.start", 7) 524 | 525 | if(week_start != 1){day_labels <- day_labels[c(week_start:7, 1:(week_start-1))]} 526 | 527 | list(scale_y_calendar(), 528 | scale_x_calendar(day_labels = day_labels), 529 | facet_calendar(), 530 | theme_grey_calendar(), 531 | stat_calendar(geom = "blank") 532 | ) 533 | 534 | } 535 | ``` 536 | 537 | 538 | Let's check it out... 539 | 540 | 541 | ```{r} 542 | df_week() |> 543 | ggplot() + 544 | aes(date = date) + 545 | stat_calendar() + 546 | defaults_calendar() 547 | 548 | df_year() |> 549 | ggplot() + 550 | aes(date = date) + 551 | stat_calendar() + 552 | defaults_calendar() 553 | 554 | ``` 555 | 556 | Furthermore, we provide ggcalendar as an alternative point of entry into the ggplot framework. The default data frame is even included (the current calendar year), so a full calendar will print with no additional specification. 557 | 558 | ```{r} 559 | knitrExtra:::chunk_to_r("ggcalendar") 560 | ``` 561 | 562 | ```{r ggcalendar} 563 | #' Title 564 | #' 565 | #' @param dates_df 566 | #' @param day_labels 567 | #' @param geom 568 | #' @param color 569 | #' @param size 570 | #' @param alpha 571 | #' 572 | #' @return 573 | #' @export 574 | #' 575 | #' @examples 576 | ggcalendar <- function(dates_df = df_year(), 577 | day_labels = c("M", "T", "W", "T", "F", "S", "S"), 578 | geom = "text", 579 | color = "grey35", 580 | size = 3, 581 | alpha = 1){ 582 | 583 | my_layer <- stat_calendar(geom = geom, 584 | color = color, 585 | ggplot2::aes(date = date), 586 | size = size, 587 | alpha = alpha, 588 | show.legend = F) 589 | 590 | ggplot2::ggplot(data = dates_df) + 591 | defaults_calendar(day_labels = day_labels) + 592 | ggplot2::aes(date = date) + 593 | my_layer 594 | 595 | } 596 | ``` 597 | 598 | Let's check it out! 599 | 600 | ```{r} 601 | ggcalendar() 602 | 603 | ggcalendar() + 604 | stat_calendar(geom = "point", 605 | data = df_week(), 606 | color = "darkred", 607 | size = 5, 608 | alpha = .5) 609 | 610 | options(lubridate.week.start = 1) 611 | 612 | ggcalendar() + 613 | stat_calendar(geom = "point", 614 | data = df_week(), 615 | color = "darkred", 616 | size = 5, 617 | alpha = .5) 618 | ``` 619 | 620 | ## More 621 | 622 | ```{r} 623 | library(magrittr) 624 | ggcalendar() + 625 | # remember default data in ggcalendar() is current year of dates 626 | aes(date = date) + 627 | geom_tile_calendar(data = . %>% 628 | filter(wday(date) == 3), 629 | fill = "blue", 630 | alpha = .2) + 631 | labs(title = "When to do #TidyTuesday in 2024") + 632 | stat_calendar(label = "X", 633 | color = "darkred", 634 | size = 5, 635 | data = df_dates_interval( 636 | "2024/01/01", Sys.Date() - days(1)), 637 | alpha = .35) 638 | ``` 639 | 640 | 641 | 642 | ```{r} 643 | df_month(year = 2023, month = 2) |> 644 | ggcalendar() 645 | 646 | df_month(year = 2023, month = 2) |> 647 | ggcalendar() 648 | 649 | df_month(year = 2023, month = 2) |> 650 | ggcalendar(geom = "blank") + 651 | aes(date = date) + 652 | geom_text_calendar(label = "Another\nday...", # override default 653 | size = 4) 654 | 655 | df_month(year = 2023, month = 2) |> 656 | ggcalendar() + 657 | aes(date = date) + 658 | geom_text_calendar() + 659 | geom_point_calendar(data = . %>% filter(wday(date) %in% 2:6), 660 | alpha = .2, 661 | size = 5, 662 | color = "cadetblue") + 663 | theme(panel.background = element_rect(fill = "beige")) 664 | 665 | library(ggplot2) 666 | 667 | df_dates_interval("2023-09-01", "2023-12-31") |> 668 | ggcalendar() 669 | ``` 670 | 671 | ```{r example} 672 | ## basic example code 673 | c("2022-03-19", "2022-04-09", "2022-05-07", 674 | "2022-06-11", "2022-07-16") %>% 675 | tibble(date = .) |> 676 | mutate(date = date %>% as_date) |> 677 | mutate(future = Sys.Date() < date) -> 678 | events 679 | 680 | df_year(2022) |> 681 | ggcalendar() + 682 | aes(date = date) + 683 | geom_text_calendar() + 684 | geom_point_calendar(data = events, 685 | aes(color = future), 686 | size = 8, 687 | alpha = .5, 688 | show.legend = F) + 689 | labs(title = "nu2ggplot2X^2sion, 2022") 690 | ``` 691 | 692 | 693 | 694 | 695 | 696 | # NYC flights Example 697 | 698 | > Airline on-time data for all flights departing NYC in 2013. Also includes useful 'metadata' on airlines, airports, weather, and planes. 699 | 700 | 701 | 702 | Data inspiration: https://twitter.com/rappa753/status/1545729747774308354 @rappa753 703 | 704 | ```{r} 705 | 706 | # example 707 | nycflights13::flights |> 708 | ungroup() |> 709 | mutate(date = as.Date(time_hour)) |> 710 | filter(year(date) == 2013) |> 711 | count(date) |> 712 | ggcalendar() + 713 | aes(date = date) + 714 | geom_point_calendar(data = . %>% tibble(), aes(size = n, 715 | color = n), 716 | alpha = .7, show.legend = F) + 717 | scale_color_viridis_c(option = "inferno", direction = 1) + 718 | scale_size(range = c(3,8)) + 719 | geom_text_calendar(aes(label = n), size = 2) + 720 | NULL 721 | ``` 722 | 723 | 724 | --- 725 | 726 | 727 | # Births example 728 | 729 | ```{r} 730 | births <- "https://raw.githubusercontent.com/EvaMaeRey/tableau/9e91c2b5ee803bfef10d35646cf4ce6675b92b55/tidytuesday_data/2018-10-02-us_births_2000-2014.csv" 731 | 732 | readr::read_csv(births) |> 733 | mutate(month = stringr::str_pad(month, 2, pad = "0"), 734 | date_of_month = str_pad(date_of_month, 2, pad = "0")) |> 735 | mutate(date = paste(year, month, date_of_month, sep = "-") |> as_date()) |> 736 | filter(year == 2012) |> 737 | ggcalendar() + 738 | aes(date = date) + 739 | geom_point_calendar(alpha = .4) + 740 | aes(size = births) + 741 | aes(color = births) + 742 | scale_color_viridis_c() + 743 | guides( 744 | colour = guide_legend("Births"), 745 | size = guide_legend("Births") 746 | ) + 747 | geom_point_calendar(data = data.frame(date = 748 | as_date("2012-12-25")), 749 | size = 5, color = "red", shape = 21) 750 | 751 | ``` 752 | 753 | --- 754 | 755 | # data defaults to calendar year and aes(date = date) 756 | 757 | The following feels a little weird to me, but is allowed. 758 | 759 | A grammar of graphics fundamental is that a statistical graphic are composed of geometries/marks that take on aesthetics (color, position, size), to represent a variable. 760 | 761 | Below we aren't aren't fully stating these specifications; which feels a bit funny; I would not recommend this as a starting point. 762 | 763 | ```{r} 764 | ggcalendar() + 765 | geom_text_calendar() 766 | ``` 767 | 768 | 769 | --- 770 | 771 | 772 | 773 | 774 | 775 | --- 776 | 777 | 778 | 779 | 780 | # Minimal Viable Packaging 781 | 782 | ```{r, eval = T} 783 | usethis::use_package("lubridate") 784 | usethis::use_package("ggplot2") 785 | usethis::use_package("dplyr") 786 | usethis::use_package("stringr") 787 | ``` 788 | 789 | ```{r, eval = F} 790 | devtools::check() 791 | devtools::install(pkg = ".", upgrade = "never") 792 | ``` 793 | 794 | 795 | 796 | # Traditional README 797 | 798 | 799 | ```{r, eval = T} 800 | rm(list = ls()) 801 | library(ggcalendar) 802 | library(tidyverse) 803 | options(lubridate.week.start = 1) # start on Monday 804 | 805 | ggcalendar() + 806 | labs(title = "Calendar: 2024") 807 | 808 | ggcalendar() + 809 | geom_tile_calendar( 810 | data = df_week(), 811 | fill = "red", 812 | alpha = .25) + 813 | geom_point_calendar( 814 | data = df_today(), 815 | color = "goldenrod3", shape = 21, 816 | size = 8, stroke = 1.5 817 | ) 818 | 819 | 820 | # example 821 | nycflights13::flights |> 822 | ungroup() |> 823 | mutate(date = as.Date(time_hour)) |> 824 | filter(year(date) == 2013) |> 825 | count(date) |> 826 | ggcalendar(geom = "blank") + 827 | aes(date = date) + 828 | geom_tile_calendar( 829 | aes(fill = n), 830 | alpha = .7, show.legend = F) + 831 | scale_fill_viridis_c(option = "inferno", 832 | direction = 1) + 833 | scale_size(range = c(3,8)) + 834 | geom_text_calendar(aes(label = n), 835 | size = 2) + 836 | NULL 837 | 838 | ``` 839 | 840 | ```{r} 841 | contrast <- function(colour) { 842 | out <- rep("grey35", length(colour)) 843 | light <- farver::get_channel(colour, "l", space = "hcl") 844 | out[light < 50] <- "grey80" 845 | out 846 | } 847 | 848 | aes_autocontrast_color_on_fill <- aes(colour = after_scale(contrast(fill))) 849 | 850 | library(ggcalendar) 851 | 852 | nhl_player_births <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2024/2024-01-09/nhl_player_births.csv') 853 | 854 | 855 | nhl_player_births |> 856 | mutate(date = birth_date %>% 857 | str_replace("....", "2024") %>% 858 | as_date()) %>% 859 | count(date) %>% 860 | ggcalendar(geom = "blank") + 861 | aes(date = date, # date is positional aes 862 | fill = n) + 863 | labs(title = "Number of NHL Player Birthdays by day 1879-2005\nas celebrated in 2024") + 864 | geom_tile_calendar(alpha = .85, linewidth = 0) + 865 | scale_fill_viridis_c() 866 | 867 | last_plot() + 868 | aes(label = n) + 869 | geom_text_calendar(size = 3) + 870 | aes_autocontrast_color_on_fill + 871 | guides(fill = "none") 872 | 873 | 874 | ``` 875 | 876 | 877 | ```{r example2, eval = F} 878 | ## basic example code 879 | df_month(month = "2022-07") |> 880 | head() 881 | 882 | return_dates_interval(start_date = "2022-07-01", 883 | end_date = "2022-08-31") |> 884 | ggcalendar() + 885 | aes(date = date) + 886 | geom_text_calendar(size = 8) + 887 | geom_point_calendar(data = . %>% filter(date == "2022-07-04"), 888 | size = 8, 889 | alpha = .5) + 890 | geom_point_calendar(data = . %>% filter(date < Sys.Date()), 891 | size = 10, shape = "x") 892 | ``` 893 | 894 | 895 | --- 896 | 897 | ```{r} 898 | knitr::knit_exit() 899 | 900 | ``` 901 | 902 | # more ideas 903 | 904 | ```{r} 905 | #' #' Title 906 | #' #' 907 | #' #' @param data 908 | #' #' @param scales 909 | #' #' 910 | #' #' @return 911 | #' #' @export 912 | #' #' 913 | #' #' @examples 914 | #' #' return_dates_year(1999) %>% 915 | #' #' head() %>% 916 | #' #' compute_group_calendar() 917 | #' #' 918 | #' compute_group_weekly <- function(data, scales){ 919 | #' 920 | #' data %>% 921 | #' dplyr::mutate(num_day_of_week = lubridate::wday(.data$date)) %>% 922 | #' dplyr::mutate(day_of_week = lubridate::wday(.data$date, label = TRUE, abbr = TRUE)) %>% 923 | #' dplyr::mutate(week_of_month = (- lubridate::wday(.data$date) + lubridate::day(.data$date)) %/% 7 + 1 + 924 | #' ifelse(lubridate::wday(lubridate::floor_date(lubridate::as_date(.data$date), "month")) == 1, -1, 0) 925 | #' ) %>% 926 | #' dplyr::mutate(date_of_month = lubridate::day(.data$date)) %>% 927 | #' dplyr::mutate(which_year = lubridate::year(.data$date) - 2018) %>% 928 | #' dplyr::mutate(month = lubridate::month(.data$date, abbr = TRUE, label = TRUE)) %>% 929 | #' dplyr::mutate(hour = lubridate::hour(.data$date)) %>% 930 | #' dplyr::mutate(academic_year = lubridate::year(.data$date) + 931 | #' ifelse(lubridate::month(date) > 932 | #' 6, 1, 0)) %>% 933 | #' dplyr::mutate(academic_month = .data$month %>% 934 | #' factor(levels = c("Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 935 | #' "Jan", "Feb", "Mar", "Apr", "May", "Jun"))) 936 | #' 937 | #' } 938 | #' 939 | #' StatWeekly <- ggplot2::ggproto(`_class` = "StatWeekly", 940 | #' `_inherit` = ggplot2::Stat, 941 | #' required_aes = c("date"), 942 | #' compute_group = compute_group_weekly, 943 | #' default_aes = ggplot2::aes(x = ggplot2::after_stat(day_of_week %>% as.numeric()), 944 | #' y = ggplot2::after_stat(hour), 945 | #' label = ggplot2::after_stat(date_of_month))) 946 | #' 947 | ``` 948 | 949 | 950 | ```{r} 951 | #' Title 952 | #' 953 | #' @param dates_df 954 | #' 955 | #' @return 956 | #' @export 957 | #' 958 | #' @examples 959 | #' library(lubridate) 960 | #' library(dplyr) 961 | #' library(ggplot2) 962 | #' library(magrittr) 963 | #' 964 | #' ggweekly() + 965 | #' geom_text_weekly() 966 | #' 967 | #' ggweekly() + 968 | #' geom_text_weekly(color = "grey35") + 969 | #' labs(title = "When to do #TidyTuesday in 2022") + 970 | #' geom_text_weekly(label = "X", 971 | #' data = data.frame(date = seq(as.Date("2022/01/01"), 972 | #' as.Date("2022/04/18"), "days"))) 973 | #' 974 | ggweekly <- function(dates_df = return_hours_week(), day_labels = c("M", "T", "W", "T", "F", "S", "S")){ 975 | 976 | week_start <- getOption("lubridate.week.start", 7) 977 | 978 | if(week_start != 1){day_labels <- day_labels[c(week_start:7, 1:(week_start-1))]} 979 | 980 | ggplot2::ggplot(data = dates_df) + 981 | ggplot2::aes(date = date) + 982 | ggplot2::scale_y_reverse( 983 | breaks = 7:21, 984 | expand = c(0,0), 985 | limits = c(21 + .5, 986 | 7 - .5), 987 | 988 | ) + 989 | ggplot2::scale_x_continuous(breaks = 1:7, labels = day_labels, 990 | limits = c(.5, 7.5),expand = c(0,0) 991 | 992 | #position = "top" 993 | ) + 994 | ggplot2::facet_wrap(~epiweek(date), scales = "free") + 995 | ggplot2::labs(x = NULL, y = NULL) + 996 | ggplot2::theme(#axis.text.y = ggplot2::element_blank(), 997 | axis.ticks.y = ggplot2::element_blank(), 998 | axis.ticks.x = ggplot2::element_blank()) + 999 | ggplot2::theme(panel.grid.major = ggplot2::element_blank()) + 1000 | ggplot2::geom_blank() + 1001 | # theme(strip.placement = "outside") + 1002 | NULL 1003 | 1004 | } 1005 | ``` 1006 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | - [ggcalendar](#ggcalendar) 3 | - [Step 00. Convenience functions, dates vectors to data 4 | frames.](#step-00-convenience-functions-dates-vectors-to-data-frames) 5 | - [Examples](#examples) 6 | - [Step 1 & 2. Compute: from date to x/y, & define 7 | StatCalendar](#step-1--2-compute-from-date-to-xy--define-statcalendar) 8 | - [Test it out](#test-it-out) 9 | - [Step 3. Let’s write a user-facing function 10 | `stat_calendar()`](#step-3-lets-write-a-user-facing-function-stat_calendar) 11 | - [Test `stat_calendar`](#test-stat_calendar) 12 | - [aliasing and convenience](#aliasing-and-convenience) 13 | - [`defaults_calendar` & `ggcalendar()` Thinking about set of 14 | scales/coords etc, that gives you a nice calendar (to wrap up into 15 | defaults)](#defaults_calendar--ggcalendar-thinking-about-set-of-scalescoords-etc-that-gives-you-a-nice-calendar-to-wrap-up-into-defaults) 16 | - [`theme_calendar`, `defaults_calendar` & 17 | `ggcalendar()`](#theme_calendar-defaults_calendar--ggcalendar) 18 | - [More](#more) 19 | - [NYC flights Example](#nyc-flights-example) 20 | - [Births example](#births-example) 21 | - [data defaults to calendar year and aes(date = 22 | date)](#data-defaults-to-calendar-year-and-aesdate--date) 23 | - [Minimal Viable Packaging](#minimal-viable-packaging) 24 | - [Traditional README](#traditional-readme) 25 | 26 | 27 | 28 | # ggcalendar 29 | 30 | 31 | 32 | [![Lifecycle: 33 | experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) 34 | 35 | 36 | *Note: This README walks through package rational and contains the code 37 | that defines proposed package functions and in addition to first-cut 38 | testing. [TLDR - Jump to traditional readme 39 | content](#traditional-readme)* 40 | 41 | Here’s a proposal for creating calendars with ggplot2 via Stat 42 | extension. 43 | 44 | When using calendars, ‘when?’ and ‘where?’ are the same question! So, 45 | ggcalendar introduces a new positional aesthetic: ‘date’. Let’s *put* 46 | things on the calendar! 47 | 48 | In this proposed package, we’ll specify the position of a calendar event 49 | calendar using dates as the required aesthetic: 50 | `aes(date = my_variable_of_dates)`! Then you can use layers function 51 | `stat_calendar()` and derivative geom functions `geom_text_calendar`, 52 | `geom_tile_calendar` and `geom_point_calendar` to place specific 53 | grobs/mark in the plot space. 54 | 55 | Under the hood, the compute_group functions finds the x and y position 56 | for the date in the month (x is day in week and y is week in month). 57 | Faceting by month is used to prevent over-plotting. Note: automatic 58 | faceting by month via ggcalendar() function presupposes that your 59 | variable is also named ‘date’. 60 | 61 | Other possible directions would be to calculate x and y based on date in 62 | month *and* on month - instead of relying on faceting by month. 63 | Furthermore, a dedicated Coord could be created (Teun’s thought). Then 64 | maybe dates would just feed generically in as the ‘x’ aes - this sounds 65 | cool! 66 | 67 | ``` r 68 | # library(ggcalendar) 69 | library(ggplot2) 70 | library(lubridate) 71 | library(tidyverse) 72 | ``` 73 | 74 | ``` r 75 | # install.packages("devtools") 76 | devtools::install_github("EvaMaeRey/ggcalendar") 77 | ``` 78 | 79 | # Step 00. Convenience functions, dates vectors to data frames. 80 | 81 | Because ggplot2’s diet is consists solely of dataframes, we create a 82 | number of convenience functions that will help us produce dataframes 83 | with column ‘date’ we can feed into ggplot2. 84 | 85 | ``` r 86 | knitrExtra:::chunk_to_r("df_functions") 87 | ``` 88 | 89 | ``` r 90 | #' Title 91 | #' 92 | #' @return 93 | #' @export 94 | #' 95 | #' @examples 96 | df_today <- function(){ 97 | 98 | data.frame(date = Sys.Date()) 99 | 100 | } 101 | 102 | #' Title 103 | #' 104 | #' @param date 105 | #' 106 | #' @return 107 | #' @export 108 | #' 109 | #' @examples 110 | df_day <- function(date = NULL){ 111 | 112 | if(is.null(date)){date <- Sys.Date()} 113 | 114 | data.frame(date = date) 115 | 116 | } 117 | 118 | 119 | #' Title 120 | #' 121 | #' @param start_date 122 | #' @param end_date 123 | #' 124 | #' @return 125 | #' @export 126 | #' 127 | #' @examples 128 | df_dates_interval <- function(start_date, end_date){ 129 | 130 | data.frame(date = as.Date(start_date):as.Date(end_date) |> 131 | as.Date()) 132 | 133 | } 134 | 135 | #' Title 136 | #' 137 | #' @param month 138 | #' @param year 139 | #' 140 | #' @return 141 | #' @export 142 | #' 143 | #' @examples 144 | df_month <- function(month = NULL, year = NULL){ 145 | 146 | if(is.null(month)){ 147 | 148 | date <- Sys.Date() 149 | month <- lubridate::month(date) 150 | } 151 | 152 | if(is.numeric(month)){ 153 | 154 | month <- stringr::str_pad(month, width = 2, pad = "0") 155 | 156 | } 157 | 158 | if(is.null(year)){ 159 | 160 | date <- Sys.Date() 161 | year <- lubridate::year(date) 162 | } 163 | 164 | 165 | paste0(year,"-", month, "-01") |> 166 | lubridate::as_date() -> 167 | start_date 168 | 169 | start_date |> lubridate::ceiling_date(unit = "month") -> 170 | end_date 171 | 172 | data.frame(date = 173 | df_dates_interval(start_date, 174 | end_date - lubridate::days(1))) 175 | 176 | } 177 | 178 | #' Title 179 | #' 180 | #' @param date 181 | #' 182 | #' @return 183 | #' @export 184 | #' 185 | #' @examples 186 | df_week <- function(date = NULL){ 187 | 188 | if(is.null(date)){date <- Sys.Date()} 189 | 190 | start_date <- lubridate::floor_date(date, unit = "week") 191 | end_date <- lubridate::ceiling_date(date, unit = "week") 192 | 193 | data.frame(date = df_dates_interval(start_date, 194 | end_date - lubridate::days(1)) ) 195 | 196 | } 197 | 198 | #' Title 199 | #' 200 | #' @param date 201 | #' 202 | #' @return 203 | #' @export 204 | #' 205 | #' @examples 206 | return_df_hours_week <- function(date = NULL){ 207 | 208 | if(is.null(date)){date <- Sys.Date()} 209 | 210 | start_date <- lubridate::floor_date(date, unit = "week") 211 | 212 | data.frame(date = (start_date + lubridate::hours(1:(24*7-1)))) 213 | 214 | } 215 | 216 | #' Title 217 | #' 218 | #' @param year 219 | #' 220 | #' @return 221 | #' @export 222 | #' 223 | #' @examples 224 | df_year <- function(year = NULL){ 225 | 226 | if(is.null(year)){year <- lubridate::year(Sys.Date())} 227 | 228 | paste0(year, "-01-01") |> 229 | lubridate::as_date() -> 230 | start_date 231 | 232 | start_date |> lubridate::ceiling_date(unit = "year") -> 233 | end_date 234 | 235 | data.frame(date = 236 | df_dates_interval(start_date, 237 | end_date - lubridate::days(1))) 238 | 239 | } 240 | ``` 241 | 242 | ## Examples 243 | 244 | Let’s have a look at some of these. 245 | 246 | ``` r 247 | df_today() 248 | #> date 249 | #> 1 2024-09-03 250 | ``` 251 | 252 | ``` r 253 | 254 | df_day() 255 | #> date 256 | #> 1 2024-09-03 257 | ``` 258 | 259 | ``` r 260 | 261 | df_dates_interval(start_date = "2024-10-02", end_date = "2024-10-04") 262 | #> date 263 | #> 1 2024-10-02 264 | #> 2 2024-10-03 265 | #> 3 2024-10-04 266 | ``` 267 | 268 | ``` r 269 | 270 | df_week() 271 | #> date 272 | #> 1 2024-09-01 273 | #> 2 2024-09-02 274 | #> 3 2024-09-03 275 | #> 4 2024-09-04 276 | #> 5 2024-09-05 277 | #> 6 2024-09-06 278 | #> 7 2024-09-07 279 | ``` 280 | 281 | ``` r 282 | 283 | df_year() |> head() 284 | #> date 285 | #> 1 2024-01-01 286 | #> 2 2024-01-02 287 | #> 3 2024-01-03 288 | #> 4 2024-01-04 289 | #> 5 2024-01-05 290 | #> 6 2024-01-06 291 | ``` 292 | 293 | ``` r 294 | 295 | df_month() |> head() 296 | #> date 297 | #> 1 2024-09-01 298 | #> 2 2024-09-02 299 | #> 3 2024-09-03 300 | #> 4 2024-09-04 301 | #> 5 2024-09-05 302 | #> 6 2024-09-06 303 | ``` 304 | 305 | ``` r 306 | 307 | return_df_hours_week() |> head() 308 | #> date 309 | #> 1 2024-09-01 01:00:00 310 | #> 2 2024-09-01 02:00:00 311 | #> 3 2024-09-01 03:00:00 312 | #> 4 2024-09-01 04:00:00 313 | #> 5 2024-09-01 05:00:00 314 | #> 6 2024-09-01 06:00:00 315 | ``` 316 | 317 | # Step 1 & 2. Compute: from date to x/y, & define StatCalendar 318 | 319 | The computation that we want to be done under the hood relates to 320 | translating the here-to-fore unknown positional aesthetic ‘date’ to the 321 | first-class ‘x’ and ‘y’ positional aesthetic mappings, as well as 322 | variables that can be used in faceting (month). 323 | 324 | ``` r 325 | knitrExtra:::chunk_to_r("get_week_of_month") 326 | ``` 327 | 328 | As a pre-step to computing many useful variables from our date variable, 329 | we focus on this (currently messy) conversion of vectors of dates to 330 | week of the month. 331 | 332 | ``` r 333 | get_week_of_month <- function(x){ 334 | 335 | (- lubridate::wday(x) + lubridate::day(x)) %/% 336 | 7 + 1 + 337 | ifelse(lubridate::wday(lubridate::floor_date(lubridate::as_date(x), "month")) == 1, 0, 1) 338 | 339 | } 340 | ``` 341 | 342 | Next, we’ll define a compute group function. A number of variables are 343 | created by parsing our date variable. 344 | 345 | Then, we’ll pass all this computation to define a new ggproto object 346 | StatCalendar. For maximum flexibility, our compute function doesn’t 347 | create ggplot2 core aesthetic channels ‘x’, ‘y’, and ‘label’ variables, 348 | but instead uses the default_aes field to state what should be first 349 | interpreted as x, y and label (thoughts? Maybe only ‘label’ should be 350 | managed like this). 351 | 352 | ``` r 353 | knitrExtra:::chunk_to_r("compute_group_calendar") 354 | ``` 355 | 356 | ``` r 357 | compute_group_calendar <- function(data, scales){ 358 | 359 | data |> 360 | dplyr::mutate(wday = lubridate::wday(.data$date)) |> 361 | dplyr::mutate(wday_abbr = lubridate::wday(.data$date, label = TRUE, abbr = TRUE)) |> 362 | dplyr::mutate(week_of_month = get_week_of_month(.data$date)) |> 363 | dplyr::mutate(day = lubridate::day(.data$date)) |> 364 | dplyr::mutate(year = lubridate::year(.data$date) - 2018) |> 365 | dplyr::mutate(month_abbr = lubridate::month(.data$date, abbr = TRUE, label = TRUE)) |> 366 | dplyr::mutate(hour = lubridate::hour(.data$date)) |> 367 | dplyr::mutate(year_academic = lubridate::year(.data$date) + 368 | ifelse(lubridate::month(date) > 369 | 6, 1, 0)) |> 370 | dplyr::mutate(month_academic_abbr = .data$month_abbr |> 371 | factor(levels = c("Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 372 | "Jan", "Feb", "Mar", "Apr", "May", "Jun"))) 373 | 374 | } 375 | 376 | StatCalendar <- ggplot2::ggproto(`_class` = "StatCalendar", 377 | `_inherit` = ggplot2::Stat, 378 | required_aes = c("date"), 379 | compute_group = compute_group_calendar, 380 | default_aes = ggplot2::aes(x = ggplot2::after_stat(wday), 381 | y = ggplot2::after_stat(week_of_month), 382 | label = ggplot2::after_stat(day))) 383 | 384 | 385 | StatWeekly <- ggplot2::ggproto(`_class` = "StatCalendar", 386 | `_inherit` = ggplot2::Stat, 387 | required_aes = c("date"), 388 | compute_group = compute_group_calendar, 389 | default_aes = ggplot2::aes(x = ggplot2::after_stat(wday), 390 | y = ggplot2::after_stat(hour), 391 | label = ggplot2::after_stat(hour))) 392 | ``` 393 | 394 | ## Test it out 395 | 396 | Okay, let’s see how our compute and Stat work in action! 397 | 398 | ``` r 399 | df_week() |> 400 | compute_group_calendar() 401 | #> date wday wday_abbr week_of_month day year month_abbr hour 402 | #> 1 2024-09-01 1 Sun 1 1 6 Sep 0 403 | #> 2 2024-09-02 2 Mon 1 2 6 Sep 0 404 | #> 3 2024-09-03 3 Tue 1 3 6 Sep 0 405 | #> 4 2024-09-04 4 Wed 1 4 6 Sep 0 406 | #> 5 2024-09-05 5 Thu 1 5 6 Sep 0 407 | #> 6 2024-09-06 6 Fri 1 6 6 Sep 0 408 | #> 7 2024-09-07 7 Sat 1 7 6 Sep 0 409 | #> year_academic month_academic_abbr 410 | #> 1 2025 Sep 411 | #> 2 2025 Sep 412 | #> 3 2025 Sep 413 | #> 4 2025 Sep 414 | #> 5 2025 Sep 415 | #> 6 2025 Sep 416 | #> 7 2025 Sep 417 | ``` 418 | 419 | ``` r 420 | 421 | df_month() |> 422 | ggplot() + 423 | aes(date = date) + 424 | geom_text(stat = StatCalendar) 425 | ``` 426 | 427 | 428 | 429 | # Step 3. Let’s write a user-facing function `stat_calendar()` 430 | 431 | ``` r 432 | knitrExtra:::chunk_to_r("a_stat_calendar") 433 | ``` 434 | 435 | ``` r 436 | #' Title 437 | #' 438 | #' @param mapping 439 | #' @param data 440 | #' @param geom 441 | #' @param position 442 | #' @param na.rm 443 | #' @param show.legend 444 | #' @param inherit.aes 445 | #' @param ... 446 | #' 447 | #' @return 448 | #' @export 449 | #' 450 | #' @examples 451 | stat_calendar <- function(mapping = NULL, 452 | data = NULL, 453 | geom = "text", 454 | position = "identity", 455 | na.rm = FALSE, 456 | show.legend = NA, 457 | inherit.aes = TRUE, ...) { 458 | ggplot2::layer( 459 | stat = StatCalendar, # proto object from Step 2 460 | geom = geom, # inherit other behavior 461 | data = data, 462 | mapping = mapping, 463 | position = position, 464 | show.legend = show.legend, 465 | inherit.aes = inherit.aes, 466 | params = list(na.rm = na.rm, ...) 467 | ) 468 | } 469 | ``` 470 | 471 | ### Test `stat_calendar` 472 | 473 | ``` r 474 | df_year() |> 475 | ggplot() + 476 | aes(date = date) + 477 | stat_calendar(color = "grey") + 478 | facet_wrap(~month(date, label = T, abbr = T)) + 479 | scale_y_reverse() 480 | ``` 481 | 482 | 483 | 484 | # aliasing and convenience 485 | 486 | To give the user a better sense of what they’ll see when using 487 | stat_calendar we create the alias, ‘geom_text_calendar()’. 488 | 489 | ``` r 490 | knitrExtra:::chunk_to_r("geom_text_calendar") 491 | ``` 492 | 493 | ``` r 494 | #' Title 495 | #' 496 | #' @param ... 497 | #' 498 | #' @return 499 | #' @export 500 | #' 501 | #' @examples 502 | geom_text_calendar <- function(...){stat_calendar(geom = "text", ...)} 503 | 504 | #' Title 505 | #' 506 | #' @param ... 507 | #' 508 | #' @return 509 | #' @export 510 | #' 511 | #' @examples 512 | geom_point_calendar <- function(...){stat_calendar(geom = "point", ...)} 513 | 514 | #' Title 515 | #' 516 | #' @param ... 517 | #' 518 | #' @return 519 | #' @export 520 | #' 521 | #' @examples 522 | geom_tile_calendar <- function(...){stat_calendar(geom = "tile", ...)} 523 | ``` 524 | 525 | # `defaults_calendar` & `ggcalendar()` Thinking about set of scales/coords etc, that gives you a nice calendar (to wrap up into defaults) 526 | 527 | In our test of stat_calendar, we see cumbersomeness. Below, we consider 528 | even more ggplot2 decision that would make our plot easier to consume 529 | and more beautiful. 530 | 531 | ``` r 532 | day_labels = c("S", "M", "T", "W", "T", "F", "S") 533 | 534 | df_year() |> 535 | ggplot() + 536 | aes(date = date) + 537 | stat_calendar(color = "grey") + 538 | ggplot2::aes(date = date) + 539 | ggplot2::scale_y_reverse(breaks = 5:0, 540 | expand = c(0,0), 541 | limits = c(6.5, 0.5)) + 542 | ggplot2::scale_x_continuous(breaks = 1:7, 543 | labels = day_labels, 544 | limits = c(.5, 7.5), 545 | expand = c(0,0) 546 | ) + 547 | ggplot2::facet_wrap(~lubridate::month(date, abbr = T, label = T), scales = "free") + 548 | ggplot2::labs(x = NULL, y = NULL) + 549 | ggplot2::theme(axis.text.y = ggplot2::element_blank(), 550 | axis.ticks.y = ggplot2::element_blank(), 551 | axis.ticks.x = ggplot2::element_blank()) + 552 | ggplot2::theme(panel.grid.major = ggplot2::element_blank()) + 553 | ggplot2::geom_blank() 554 | ``` 555 | 556 | 557 | 558 | ## `theme_calendar`, `defaults_calendar` & `ggcalendar()` 559 | 560 | Then, we bundle these up into defaults_calendar, which can be quickly 561 | added for converting to a more polished and readable calendar. 562 | 563 | ``` r 564 | knitrExtra::chunk_to_dir("theme_grey_calendar") 565 | ``` 566 | 567 | ``` r 568 | #' Title 569 | #' 570 | #' @param ... 571 | #' 572 | #' @return 573 | #' @export 574 | #' 575 | #' @examples 576 | theme_grey_calendar <- function(...){ 577 | 578 | theme_grey(...) %+replace% 579 | ggplot2::theme( 580 | axis.text.y = ggplot2::element_blank(), 581 | axis.ticks.y = ggplot2::element_blank(), 582 | axis.ticks.x = ggplot2::element_blank(), 583 | axis.title = element_blank()) + 584 | ggplot2::theme( 585 | panel.grid.major = ggplot2::element_blank()) 586 | 587 | } 588 | 589 | 590 | scale_y_calendar <- function(...){ggplot2::scale_y_reverse(breaks = 6:0, 591 | expand = c(0,0), 592 | limits = c(6.5, 0.5), ...)} 593 | 594 | scale_x_calendar <- function(day_labels = c("M", "T", "W", "T", "F", "S", "S"), ...){ 595 | 596 | ggplot2::scale_x_continuous(breaks = 1:7, 597 | labels = day_labels, 598 | limits = c(.5, 7.5), 599 | expand = c(0,0), ...)} 600 | 601 | facet_calendar <- function(...){ 602 | 603 | ggplot2::facet_wrap(~lubridate::month(date, abbr = T, label = T), 604 | scales = "free",...) 605 | 606 | } 607 | 608 | 609 | geom_calendar_blank <- function(...){ 610 | 611 | stat_calendar(geom = "blank", ...) 612 | 613 | } 614 | ``` 615 | 616 | ``` r 617 | knitrExtra:::chunk_to_r("defaults_calendar") 618 | ``` 619 | 620 | ``` r 621 | #' Title 622 | #' 623 | #' @param day_labels 624 | #' 625 | #' @return 626 | #' @export 627 | #' 628 | #' @examples 629 | defaults_calendar <- function(day_labels = c("M", "T", "W", "T", "F", "S", "S")){ 630 | 631 | week_start <- getOption("lubridate.week.start", 7) 632 | 633 | if(week_start != 1){day_labels <- day_labels[c(week_start:7, 1:(week_start-1))]} 634 | 635 | list(scale_y_calendar(), 636 | scale_x_calendar(day_labels = day_labels), 637 | facet_calendar(), 638 | theme_grey_calendar(), 639 | stat_calendar(geom = "blank") 640 | ) 641 | 642 | } 643 | ``` 644 | 645 | Let’s check it out… 646 | 647 | ``` r 648 | df_week() |> 649 | ggplot() + 650 | aes(date = date) + 651 | stat_calendar() + 652 | defaults_calendar() 653 | ``` 654 | 655 | 656 | 657 | ``` r 658 | 659 | df_year() |> 660 | ggplot() + 661 | aes(date = date) + 662 | stat_calendar() + 663 | defaults_calendar() 664 | ``` 665 | 666 | 667 | 668 | Furthermore, we provide ggcalendar as an alternative point of entry into 669 | the ggplot framework. The default data frame is even included (the 670 | current calendar year), so a full calendar will print with no additional 671 | specification. 672 | 673 | ``` r 674 | knitrExtra:::chunk_to_r("ggcalendar") 675 | ``` 676 | 677 | ``` r 678 | #' Title 679 | #' 680 | #' @param dates_df 681 | #' @param day_labels 682 | #' @param geom 683 | #' @param color 684 | #' @param size 685 | #' @param alpha 686 | #' 687 | #' @return 688 | #' @export 689 | #' 690 | #' @examples 691 | ggcalendar <- function(dates_df = df_year(), 692 | day_labels = c("M", "T", "W", "T", "F", "S", "S"), 693 | geom = "text", 694 | color = "grey35", 695 | size = 3, 696 | alpha = 1){ 697 | 698 | my_layer <- stat_calendar(geom = geom, 699 | color = color, 700 | ggplot2::aes(date = date), 701 | size = size, 702 | alpha = alpha, 703 | show.legend = F) 704 | 705 | ggplot2::ggplot(data = dates_df) + 706 | defaults_calendar(day_labels = day_labels) + 707 | ggplot2::aes(date = date) + 708 | my_layer 709 | 710 | } 711 | ``` 712 | 713 | Let’s check it out! 714 | 715 | ``` r 716 | ggcalendar() 717 | ``` 718 | 719 | 720 | 721 | ``` r 722 | 723 | ggcalendar() + 724 | stat_calendar(geom = "point", 725 | data = df_week(), 726 | color = "darkred", 727 | size = 5, 728 | alpha = .5) 729 | ``` 730 | 731 | 732 | 733 | ``` r 734 | 735 | options(lubridate.week.start = 1) 736 | 737 | ggcalendar() + 738 | stat_calendar(geom = "point", 739 | data = df_week(), 740 | color = "darkred", 741 | size = 5, 742 | alpha = .5) 743 | ``` 744 | 745 | 746 | 747 | ## More 748 | 749 | ``` r 750 | library(magrittr) 751 | ggcalendar() + 752 | # remember default data in ggcalendar() is current year of dates 753 | aes(date = date) + 754 | geom_tile_calendar(data = . %>% 755 | filter(wday(date) == 3), 756 | fill = "blue", 757 | alpha = .2) + 758 | labs(title = "When to do #TidyTuesday in 2024") + 759 | stat_calendar(label = "X", 760 | color = "darkred", 761 | size = 5, 762 | data = df_dates_interval( 763 | "2024/01/01", Sys.Date() - days(1)), 764 | alpha = .35) 765 | ``` 766 | 767 | 768 | 769 | ``` r 770 | df_month(year = 2023, month = 2) |> 771 | ggcalendar() 772 | ``` 773 | 774 | 775 | 776 | ``` r 777 | 778 | df_month(year = 2023, month = 2) |> 779 | ggcalendar() 780 | ``` 781 | 782 | 783 | 784 | ``` r 785 | 786 | df_month(year = 2023, month = 2) |> 787 | ggcalendar(geom = "blank") + 788 | aes(date = date) + 789 | geom_text_calendar(label = "Another\nday...", # override default 790 | size = 4) 791 | ``` 792 | 793 | 794 | 795 | ``` r 796 | 797 | df_month(year = 2023, month = 2) |> 798 | ggcalendar() + 799 | aes(date = date) + 800 | geom_text_calendar() + 801 | geom_point_calendar(data = . %>% filter(wday(date) %in% 2:6), 802 | alpha = .2, 803 | size = 5, 804 | color = "cadetblue") + 805 | theme(panel.background = element_rect(fill = "beige")) 806 | ``` 807 | 808 | 809 | 810 | ``` r 811 | 812 | library(ggplot2) 813 | 814 | df_dates_interval("2023-09-01", "2023-12-31") |> 815 | ggcalendar() 816 | ``` 817 | 818 | 819 | 820 | ``` r 821 | ## basic example code 822 | c("2022-03-19", "2022-04-09", "2022-05-07", 823 | "2022-06-11", "2022-07-16") %>% 824 | tibble(date = .) |> 825 | mutate(date = date %>% as_date) |> 826 | mutate(future = Sys.Date() < date) -> 827 | events 828 | 829 | df_year(2022) |> 830 | ggcalendar() + 831 | aes(date = date) + 832 | geom_text_calendar() + 833 | geom_point_calendar(data = events, 834 | aes(color = future), 835 | size = 8, 836 | alpha = .5, 837 | show.legend = F) + 838 | labs(title = "nu2ggplot2X^2sion, 2022") 839 | ``` 840 | 841 | 842 | 843 | # NYC flights Example 844 | 845 | > Airline on-time data for all flights departing NYC in 2013. Also 846 | > includes useful ‘metadata’ on airlines, airports, weather, and planes. 847 | 848 | Data inspiration: 849 | @rappa753 850 | 851 | ``` r 852 | 853 | # example 854 | nycflights13::flights |> 855 | ungroup() |> 856 | mutate(date = as.Date(time_hour)) |> 857 | filter(year(date) == 2013) |> 858 | count(date) |> 859 | ggcalendar() + 860 | aes(date = date) + 861 | geom_point_calendar(data = . %>% tibble(), aes(size = n, 862 | color = n), 863 | alpha = .7, show.legend = F) + 864 | scale_color_viridis_c(option = "inferno", direction = 1) + 865 | scale_size(range = c(3,8)) + 866 | geom_text_calendar(aes(label = n), size = 2) + 867 | NULL 868 | ``` 869 | 870 | 871 | 872 | ------------------------------------------------------------------------ 873 | 874 | # Births example 875 | 876 | ``` r 877 | births <- "https://raw.githubusercontent.com/EvaMaeRey/tableau/9e91c2b5ee803bfef10d35646cf4ce6675b92b55/tidytuesday_data/2018-10-02-us_births_2000-2014.csv" 878 | 879 | readr::read_csv(births) |> 880 | mutate(month = stringr::str_pad(month, 2, pad = "0"), 881 | date_of_month = str_pad(date_of_month, 2, pad = "0")) |> 882 | mutate(date = paste(year, month, date_of_month, sep = "-") |> as_date()) |> 883 | filter(year == 2012) |> 884 | ggcalendar() + 885 | aes(date = date) + 886 | geom_point_calendar(alpha = .4) + 887 | aes(size = births) + 888 | aes(color = births) + 889 | scale_color_viridis_c() + 890 | guides( 891 | colour = guide_legend("Births"), 892 | size = guide_legend("Births") 893 | ) + 894 | geom_point_calendar(data = data.frame(date = 895 | as_date("2012-12-25")), 896 | size = 5, color = "red", shape = 21) 897 | ``` 898 | 899 | 900 | 901 | ------------------------------------------------------------------------ 902 | 903 | # data defaults to calendar year and aes(date = date) 904 | 905 | The following feels a little weird to me, but is allowed. 906 | 907 | A grammar of graphics fundamental is that a statistical graphic are 908 | composed of geometries/marks that take on aesthetics (color, position, 909 | size), to represent a variable. 910 | 911 | Below we aren’t aren’t fully stating these specifications; which feels a 912 | bit funny; I would not recommend this as a starting point. 913 | 914 | ``` r 915 | ggcalendar() + 916 | geom_text_calendar() 917 | ``` 918 | 919 | 920 | 921 | ------------------------------------------------------------------------ 922 | 923 | ------------------------------------------------------------------------ 924 | 925 | # Minimal Viable Packaging 926 | 927 | ``` r 928 | usethis::use_package("lubridate") 929 | usethis::use_package("ggplot2") 930 | usethis::use_package("dplyr") 931 | usethis::use_package("stringr") 932 | ``` 933 | 934 | ``` r 935 | devtools::check() 936 | devtools::install(pkg = ".", upgrade = "never") 937 | ``` 938 | 939 | # Traditional README 940 | 941 | ``` r 942 | rm(list = ls()) 943 | library(ggcalendar) 944 | library(tidyverse) 945 | options(lubridate.week.start = 1) # start on Monday 946 | 947 | ggcalendar() + 948 | labs(title = "Calendar: 2024") 949 | ``` 950 | 951 | 952 | 953 | ``` r 954 | 955 | ggcalendar() + 956 | geom_tile_calendar( 957 | data = df_week(), 958 | fill = "red", 959 | alpha = .25) + 960 | geom_point_calendar( 961 | data = df_today(), 962 | color = "goldenrod3", shape = 21, 963 | size = 8, stroke = 1.5 964 | ) 965 | ``` 966 | 967 | 968 | 969 | ``` r 970 | 971 | 972 | # example 973 | nycflights13::flights |> 974 | ungroup() |> 975 | mutate(date = as.Date(time_hour)) |> 976 | filter(year(date) == 2013) |> 977 | count(date) |> 978 | ggcalendar(geom = "blank") + 979 | aes(date = date) + 980 | geom_tile_calendar( 981 | aes(fill = n), 982 | alpha = .7, show.legend = F) + 983 | scale_fill_viridis_c(option = "inferno", 984 | direction = 1) + 985 | scale_size(range = c(3,8)) + 986 | geom_text_calendar(aes(label = n), 987 | size = 2) + 988 | NULL 989 | ``` 990 | 991 | 992 | 993 | ``` r 994 | contrast <- function(colour) { 995 | out <- rep("grey35", length(colour)) 996 | light <- farver::get_channel(colour, "l", space = "hcl") 997 | out[light < 50] <- "grey80" 998 | out 999 | } 1000 | 1001 | aes_autocontrast_color_on_fill <- aes(colour = after_scale(contrast(fill))) 1002 | 1003 | library(ggcalendar) 1004 | 1005 | nhl_player_births <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2024/2024-01-09/nhl_player_births.csv') 1006 | 1007 | 1008 | nhl_player_births |> 1009 | mutate(date = birth_date %>% 1010 | str_replace("....", "2024") %>% 1011 | as_date()) %>% 1012 | count(date) %>% 1013 | ggcalendar(geom = "blank") + 1014 | aes(date = date, # date is positional aes 1015 | fill = n) + 1016 | labs(title = "Number of NHL Player Birthdays by day 1879-2005\nas celebrated in 2024") + 1017 | geom_tile_calendar(alpha = .85, linewidth = 0) + 1018 | scale_fill_viridis_c() 1019 | ``` 1020 | 1021 | 1022 | 1023 | ``` r 1024 | 1025 | last_plot() + 1026 | aes(label = n) + 1027 | geom_text_calendar(size = 3) + 1028 | aes_autocontrast_color_on_fill + 1029 | guides(fill = "none") 1030 | ``` 1031 | 1032 | 1033 | 1034 | ``` r 1035 | ## basic example code 1036 | df_month(month = "2022-07") |> 1037 | head() 1038 | 1039 | return_dates_interval(start_date = "2022-07-01", 1040 | end_date = "2022-08-31") |> 1041 | ggcalendar() + 1042 | aes(date = date) + 1043 | geom_text_calendar(size = 8) + 1044 | geom_point_calendar(data = . %>% filter(date == "2022-07-04"), 1045 | size = 8, 1046 | alpha = .5) + 1047 | geom_point_calendar(data = . %>% filter(date < Sys.Date()), 1048 | size = 10, shape = "x") 1049 | ``` 1050 | 1051 | ------------------------------------------------------------------------ 1052 | 1053 | ``` r 1054 | knitr::knit_exit() 1055 | ``` 1056 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/_pkgdown.yml -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Page not found (404) • ggcalendar 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 |
24 |
51 | 52 | 53 | 54 | 55 |
56 |
57 | 60 | 61 | Content not found. Please use links in the navbar. 62 | 63 |
64 | 65 | 69 | 70 |
71 | 72 | 73 | 74 |
78 | 79 |
80 |

81 |

Site built with pkgdown 2.0.9.

82 |
83 | 84 |
85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | Authors and Citation • ggcalendar 6 | 7 | 8 |
9 |
31 | 32 | 33 | 34 |
35 |
36 |
37 | 40 | 41 | 42 |
  • 43 |

    First Last. Author, maintainer. 44 |

    45 |
  • 46 |
47 |
48 |
49 |

Citation

50 | 51 |
52 |
53 | 54 | 55 |

Last F (2024). 56 | ggcalendar: What the Package Does (One Line, Title Case). 57 | R package version 0.0.0.9000. 58 |

59 |
@Manual{,
60 |   title = {ggcalendar: What the Package Does (One Line, Title Case)},
61 |   author = {First Last},
62 |   year = {2024},
63 |   note = {R package version 0.0.0.9000},
64 | }
65 | 66 |
67 | 68 |
69 | 70 | 71 | 72 |
75 | 76 |
77 |

Site built with pkgdown 2.0.9.

78 |
79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/docsearch.css: -------------------------------------------------------------------------------- 1 | /* Docsearch -------------------------------------------------------------- */ 2 | /* 3 | Source: https://github.com/algolia/docsearch/ 4 | License: MIT 5 | */ 6 | 7 | .algolia-autocomplete { 8 | display: block; 9 | -webkit-box-flex: 1; 10 | -ms-flex: 1; 11 | flex: 1 12 | } 13 | 14 | .algolia-autocomplete .ds-dropdown-menu { 15 | width: 100%; 16 | min-width: none; 17 | max-width: none; 18 | padding: .75rem 0; 19 | background-color: #fff; 20 | background-clip: padding-box; 21 | border: 1px solid rgba(0, 0, 0, .1); 22 | box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); 23 | } 24 | 25 | @media (min-width:768px) { 26 | .algolia-autocomplete .ds-dropdown-menu { 27 | width: 175% 28 | } 29 | } 30 | 31 | .algolia-autocomplete .ds-dropdown-menu::before { 32 | display: none 33 | } 34 | 35 | .algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] { 36 | padding: 0; 37 | background-color: rgb(255,255,255); 38 | border: 0; 39 | max-height: 80vh; 40 | } 41 | 42 | .algolia-autocomplete .ds-dropdown-menu .ds-suggestions { 43 | margin-top: 0 44 | } 45 | 46 | .algolia-autocomplete .algolia-docsearch-suggestion { 47 | padding: 0; 48 | overflow: visible 49 | } 50 | 51 | .algolia-autocomplete .algolia-docsearch-suggestion--category-header { 52 | padding: .125rem 1rem; 53 | margin-top: 0; 54 | font-size: 1.3em; 55 | font-weight: 500; 56 | color: #00008B; 57 | border-bottom: 0 58 | } 59 | 60 | .algolia-autocomplete .algolia-docsearch-suggestion--wrapper { 61 | float: none; 62 | padding-top: 0 63 | } 64 | 65 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { 66 | float: none; 67 | width: auto; 68 | padding: 0; 69 | text-align: left 70 | } 71 | 72 | .algolia-autocomplete .algolia-docsearch-suggestion--content { 73 | float: none; 74 | width: auto; 75 | padding: 0 76 | } 77 | 78 | .algolia-autocomplete .algolia-docsearch-suggestion--content::before { 79 | display: none 80 | } 81 | 82 | .algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { 83 | padding-top: .75rem; 84 | margin-top: .75rem; 85 | border-top: 1px solid rgba(0, 0, 0, .1) 86 | } 87 | 88 | .algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { 89 | display: block; 90 | padding: .1rem 1rem; 91 | margin-bottom: 0.1; 92 | font-size: 1.0em; 93 | font-weight: 400 94 | /* display: none */ 95 | } 96 | 97 | .algolia-autocomplete .algolia-docsearch-suggestion--title { 98 | display: block; 99 | padding: .25rem 1rem; 100 | margin-bottom: 0; 101 | font-size: 0.9em; 102 | font-weight: 400 103 | } 104 | 105 | .algolia-autocomplete .algolia-docsearch-suggestion--text { 106 | padding: 0 1rem .5rem; 107 | margin-top: -.25rem; 108 | font-size: 0.8em; 109 | font-weight: 400; 110 | line-height: 1.25 111 | } 112 | 113 | .algolia-autocomplete .algolia-docsearch-footer { 114 | width: 110px; 115 | height: 20px; 116 | z-index: 3; 117 | margin-top: 10.66667px; 118 | float: right; 119 | font-size: 0; 120 | line-height: 0; 121 | } 122 | 123 | .algolia-autocomplete .algolia-docsearch-footer--logo { 124 | background-image: url("data:image/svg+xml;utf8,"); 125 | background-repeat: no-repeat; 126 | background-position: 50%; 127 | background-size: 100%; 128 | overflow: hidden; 129 | text-indent: -9000px; 130 | width: 100%; 131 | height: 100%; 132 | display: block; 133 | transform: translate(-8px); 134 | } 135 | 136 | .algolia-autocomplete .algolia-docsearch-suggestion--highlight { 137 | color: #FF8C00; 138 | background: rgba(232, 189, 54, 0.1) 139 | } 140 | 141 | 142 | .algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { 143 | box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5) 144 | } 145 | 146 | .algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { 147 | background-color: rgba(192, 192, 192, .15) 148 | } 149 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | position: relative; 22 | } 23 | 24 | body > .container { 25 | display: flex; 26 | height: 100%; 27 | flex-direction: column; 28 | } 29 | 30 | body > .container .row { 31 | flex: 1 0 auto; 32 | } 33 | 34 | footer { 35 | margin-top: 45px; 36 | padding: 35px 0 36px; 37 | border-top: 1px solid #e5e5e5; 38 | color: #666; 39 | display: flex; 40 | flex-shrink: 0; 41 | } 42 | footer p { 43 | margin-bottom: 0; 44 | } 45 | footer div { 46 | flex: 1; 47 | } 48 | footer .pkgdown { 49 | text-align: right; 50 | } 51 | footer p { 52 | margin-bottom: 0; 53 | } 54 | 55 | img.icon { 56 | float: right; 57 | } 58 | 59 | /* Ensure in-page images don't run outside their container */ 60 | .contents img { 61 | max-width: 100%; 62 | height: auto; 63 | } 64 | 65 | /* Fix bug in bootstrap (only seen in firefox) */ 66 | summary { 67 | display: list-item; 68 | } 69 | 70 | /* Typographic tweaking ---------------------------------*/ 71 | 72 | .contents .page-header { 73 | margin-top: calc(-60px + 1em); 74 | } 75 | 76 | dd { 77 | margin-left: 3em; 78 | } 79 | 80 | /* Section anchors ---------------------------------*/ 81 | 82 | a.anchor { 83 | display: none; 84 | margin-left: 5px; 85 | width: 20px; 86 | height: 20px; 87 | 88 | background-image: url(./link.svg); 89 | background-repeat: no-repeat; 90 | background-size: 20px 20px; 91 | background-position: center center; 92 | } 93 | 94 | h1:hover .anchor, 95 | h2:hover .anchor, 96 | h3:hover .anchor, 97 | h4:hover .anchor, 98 | h5:hover .anchor, 99 | h6:hover .anchor { 100 | display: inline-block; 101 | } 102 | 103 | /* Fixes for fixed navbar --------------------------*/ 104 | 105 | .contents h1, .contents h2, .contents h3, .contents h4 { 106 | padding-top: 60px; 107 | margin-top: -40px; 108 | } 109 | 110 | /* Navbar submenu --------------------------*/ 111 | 112 | .dropdown-submenu { 113 | position: relative; 114 | } 115 | 116 | .dropdown-submenu>.dropdown-menu { 117 | top: 0; 118 | left: 100%; 119 | margin-top: -6px; 120 | margin-left: -1px; 121 | border-radius: 0 6px 6px 6px; 122 | } 123 | 124 | .dropdown-submenu:hover>.dropdown-menu { 125 | display: block; 126 | } 127 | 128 | .dropdown-submenu>a:after { 129 | display: block; 130 | content: " "; 131 | float: right; 132 | width: 0; 133 | height: 0; 134 | border-color: transparent; 135 | border-style: solid; 136 | border-width: 5px 0 5px 5px; 137 | border-left-color: #cccccc; 138 | margin-top: 5px; 139 | margin-right: -10px; 140 | } 141 | 142 | .dropdown-submenu:hover>a:after { 143 | border-left-color: #ffffff; 144 | } 145 | 146 | .dropdown-submenu.pull-left { 147 | float: none; 148 | } 149 | 150 | .dropdown-submenu.pull-left>.dropdown-menu { 151 | left: -100%; 152 | margin-left: 10px; 153 | border-radius: 6px 0 6px 6px; 154 | } 155 | 156 | /* Sidebar --------------------------*/ 157 | 158 | #pkgdown-sidebar { 159 | margin-top: 30px; 160 | position: -webkit-sticky; 161 | position: sticky; 162 | top: 70px; 163 | } 164 | 165 | #pkgdown-sidebar h2 { 166 | font-size: 1.5em; 167 | margin-top: 1em; 168 | } 169 | 170 | #pkgdown-sidebar h2:first-child { 171 | margin-top: 0; 172 | } 173 | 174 | #pkgdown-sidebar .list-unstyled li { 175 | margin-bottom: 0.5em; 176 | } 177 | 178 | /* bootstrap-toc tweaks ------------------------------------------------------*/ 179 | 180 | /* All levels of nav */ 181 | 182 | nav[data-toggle='toc'] .nav > li > a { 183 | padding: 4px 20px 4px 6px; 184 | font-size: 1.5rem; 185 | font-weight: 400; 186 | color: inherit; 187 | } 188 | 189 | nav[data-toggle='toc'] .nav > li > a:hover, 190 | nav[data-toggle='toc'] .nav > li > a:focus { 191 | padding-left: 5px; 192 | color: inherit; 193 | border-left: 1px solid #878787; 194 | } 195 | 196 | nav[data-toggle='toc'] .nav > .active > a, 197 | nav[data-toggle='toc'] .nav > .active:hover > a, 198 | nav[data-toggle='toc'] .nav > .active:focus > a { 199 | padding-left: 5px; 200 | font-size: 1.5rem; 201 | font-weight: 400; 202 | color: inherit; 203 | border-left: 2px solid #878787; 204 | } 205 | 206 | /* Nav: second level (shown on .active) */ 207 | 208 | nav[data-toggle='toc'] .nav .nav { 209 | display: none; /* Hide by default, but at >768px, show it */ 210 | padding-bottom: 10px; 211 | } 212 | 213 | nav[data-toggle='toc'] .nav .nav > li > a { 214 | padding-left: 16px; 215 | font-size: 1.35rem; 216 | } 217 | 218 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 219 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 220 | padding-left: 15px; 221 | } 222 | 223 | nav[data-toggle='toc'] .nav .nav > .active > a, 224 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 225 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 226 | padding-left: 15px; 227 | font-weight: 500; 228 | font-size: 1.35rem; 229 | } 230 | 231 | /* orcid ------------------------------------------------------------------- */ 232 | 233 | .orcid { 234 | font-size: 16px; 235 | color: #A6CE39; 236 | /* margins are required by official ORCID trademark and display guidelines */ 237 | margin-left:4px; 238 | margin-right:4px; 239 | vertical-align: middle; 240 | } 241 | 242 | /* Reference index & topics ----------------------------------------------- */ 243 | 244 | .ref-index th {font-weight: normal;} 245 | 246 | .ref-index td {vertical-align: top; min-width: 100px} 247 | .ref-index .icon {width: 40px;} 248 | .ref-index .alias {width: 40%;} 249 | .ref-index-icons .alias {width: calc(40% - 40px);} 250 | .ref-index .title {width: 60%;} 251 | 252 | .ref-arguments th {text-align: right; padding-right: 10px;} 253 | .ref-arguments th, .ref-arguments td {vertical-align: top; min-width: 100px} 254 | .ref-arguments .name {width: 20%;} 255 | .ref-arguments .desc {width: 80%;} 256 | 257 | /* Nice scrolling for wide elements --------------------------------------- */ 258 | 259 | table { 260 | display: block; 261 | overflow: auto; 262 | } 263 | 264 | /* Syntax highlighting ---------------------------------------------------- */ 265 | 266 | pre, code, pre code { 267 | background-color: #f8f8f8; 268 | color: #333; 269 | } 270 | pre, pre code { 271 | white-space: pre-wrap; 272 | word-break: break-all; 273 | overflow-wrap: break-word; 274 | } 275 | 276 | pre { 277 | border: 1px solid #eee; 278 | } 279 | 280 | pre .img, pre .r-plt { 281 | margin: 5px 0; 282 | } 283 | 284 | pre .img img, pre .r-plt img { 285 | background-color: #fff; 286 | } 287 | 288 | code a, pre a { 289 | color: #375f84; 290 | } 291 | 292 | a.sourceLine:hover { 293 | text-decoration: none; 294 | } 295 | 296 | .fl {color: #1514b5;} 297 | .fu {color: #000000;} /* function */ 298 | .ch,.st {color: #036a07;} /* string */ 299 | .kw {color: #264D66;} /* keyword */ 300 | .co {color: #888888;} /* comment */ 301 | 302 | .error {font-weight: bolder;} 303 | .warning {font-weight: bolder;} 304 | 305 | /* Clipboard --------------------------*/ 306 | 307 | .hasCopyButton { 308 | position: relative; 309 | } 310 | 311 | .btn-copy-ex { 312 | position: absolute; 313 | right: 0; 314 | top: 0; 315 | visibility: hidden; 316 | } 317 | 318 | .hasCopyButton:hover button.btn-copy-ex { 319 | visibility: visible; 320 | } 321 | 322 | /* headroom.js ------------------------ */ 323 | 324 | .headroom { 325 | will-change: transform; 326 | transition: transform 200ms linear; 327 | } 328 | .headroom--pinned { 329 | transform: translateY(0%); 330 | } 331 | .headroom--unpinned { 332 | transform: translateY(-100%); 333 | } 334 | 335 | /* mark.js ----------------------------*/ 336 | 337 | mark { 338 | background-color: rgba(255, 255, 51, 0.5); 339 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 340 | padding: 1px; 341 | } 342 | 343 | /* vertical spacing after htmlwidgets */ 344 | .html-widget { 345 | margin-bottom: 10px; 346 | } 347 | 348 | /* fontawesome ------------------------ */ 349 | 350 | .fab { 351 | font-family: "Font Awesome 5 Brands" !important; 352 | } 353 | 354 | /* don't display links in code chunks when printing */ 355 | /* source: https://stackoverflow.com/a/10781533 */ 356 | @media print { 357 | code a:link:after, code a:visited:after { 358 | content: ""; 359 | } 360 | } 361 | 362 | /* Section anchors --------------------------------- 363 | Added in pandoc 2.11: https://github.com/jgm/pandoc-templates/commit/9904bf71 364 | */ 365 | 366 | div.csl-bib-body { } 367 | div.csl-entry { 368 | clear: both; 369 | } 370 | .hanging-indent div.csl-entry { 371 | margin-left:2em; 372 | text-indent:-2em; 373 | } 374 | div.csl-left-margin { 375 | min-width:2em; 376 | float:left; 377 | } 378 | div.csl-right-inline { 379 | margin-left:2em; 380 | padding-left:1em; 381 | } 382 | div.csl-indent { 383 | margin-left: 2em; 384 | } 385 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $("div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.7.3 2 | pkgdown: 2.0.9 3 | pkgdown_sha: ~ 4 | articles: {} 5 | last_built: 2024-05-16T14:45Z 6 | 7 | -------------------------------------------------------------------------------- /docs/reference/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-example-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-example2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-example2-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-10-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-12-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-14-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-14-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-14-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-14-2.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-15-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-15-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-16-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-16-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-16-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-16-2.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-16-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-16-3.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-16-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-16-4.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-16-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-16-5.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-17-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-17-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-18-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-18-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-19-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-19-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-20-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-20-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-22-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-22-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-22-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-22-2.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-22-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-22-3.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-6-1.png -------------------------------------------------------------------------------- /docs/reference/figures/README-unnamed-chunk-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/docs/reference/figures/README-unnamed-chunk-8-1.png -------------------------------------------------------------------------------- /docs/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | • ggcalendar 6 | 7 | 8 |
    9 |
    31 | 32 | 33 | 34 |
    35 |
    36 | 39 | 40 |
    41 | 42 | 45 |
    46 | 47 | 48 |
    51 | 52 |
    53 |

    Site built with pkgdown 2.0.9.

    54 |
    55 | 56 |
    57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /404.html 5 | 6 | 7 | /authors.html 8 | 9 | 10 | /index.html 11 | 12 | 13 | /reference/index.html 14 | 15 | 16 | -------------------------------------------------------------------------------- /ggcalendar.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /man/defaults_calendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/defaults_calendar.R 3 | \name{defaults_calendar} 4 | \alias{defaults_calendar} 5 | \title{Title} 6 | \usage{ 7 | defaults_calendar(day_labels = c("M", "T", "W", "T", "F", "S", "S")) 8 | } 9 | \arguments{ 10 | \item{day_labels}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/df_dates_interval.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{df_dates_interval} 4 | \alias{df_dates_interval} 5 | \title{Title} 6 | \usage{ 7 | df_dates_interval(start_date, end_date) 8 | } 9 | \arguments{ 10 | \item{start_date}{} 11 | 12 | \item{end_date}{} 13 | } 14 | \description{ 15 | Title 16 | } 17 | -------------------------------------------------------------------------------- /man/df_day.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{df_day} 4 | \alias{df_day} 5 | \title{Title} 6 | \usage{ 7 | df_day(date = NULL) 8 | } 9 | \arguments{ 10 | \item{date}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/df_month.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{df_month} 4 | \alias{df_month} 5 | \title{Title} 6 | \usage{ 7 | df_month(month = NULL, year = NULL) 8 | } 9 | \arguments{ 10 | \item{month}{} 11 | 12 | \item{year}{} 13 | } 14 | \description{ 15 | Title 16 | } 17 | -------------------------------------------------------------------------------- /man/df_today.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{df_today} 4 | \alias{df_today} 5 | \title{Title} 6 | \usage{ 7 | df_today() 8 | } 9 | \description{ 10 | Title 11 | } 12 | -------------------------------------------------------------------------------- /man/df_week.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{df_week} 4 | \alias{df_week} 5 | \title{Title} 6 | \usage{ 7 | df_week(date = NULL) 8 | } 9 | \arguments{ 10 | \item{date}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/df_year.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{df_year} 4 | \alias{df_year} 5 | \title{Title} 6 | \usage{ 7 | df_year(year = NULL) 8 | } 9 | \arguments{ 10 | \item{year}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-example-1.png -------------------------------------------------------------------------------- /man/figures/README-example2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-example2-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-10-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-12-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-13-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-13-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-13-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-14-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-14-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-14-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-14-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-14-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-14-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-15-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-15-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-15-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-15-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-15-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-15-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-16-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-16-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-16-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-16-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-16-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-16-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-16-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-16-4.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-16-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-16-5.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-17-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-17-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-17-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-17-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-17-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-17-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-17-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-17-4.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-17-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-17-5.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-18-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-18-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-19-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-19-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-20-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-20-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-22-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-22-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-22-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-22-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-22-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-22-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-23-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-23-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-23-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-23-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-23-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-23-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-24-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-24-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-24-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-24-2.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-24-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-24-3.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-6-1.png -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EvaMaeRey/ggcalendar/3ac68e85351c25b1ab3b29b80c55ff7eb97701d8/man/figures/README-unnamed-chunk-8-1.png -------------------------------------------------------------------------------- /man/geom_point_calendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_text_calendar.R 3 | \name{geom_point_calendar} 4 | \alias{geom_point_calendar} 5 | \title{Title} 6 | \usage{ 7 | geom_point_calendar(...) 8 | } 9 | \arguments{ 10 | \item{...}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/geom_text_calendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_text_calendar.R 3 | \name{geom_text_calendar} 4 | \alias{geom_text_calendar} 5 | \title{Title} 6 | \usage{ 7 | geom_text_calendar(...) 8 | } 9 | \arguments{ 10 | \item{...}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/geom_tile_calendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_text_calendar.R 3 | \name{geom_tile_calendar} 4 | \alias{geom_tile_calendar} 5 | \title{Title} 6 | \usage{ 7 | geom_tile_calendar(...) 8 | } 9 | \arguments{ 10 | \item{...}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/ggcalendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ggcalendar.R 3 | \name{ggcalendar} 4 | \alias{ggcalendar} 5 | \title{Title} 6 | \usage{ 7 | ggcalendar( 8 | dates_df = df_year(), 9 | day_labels = c("M", "T", "W", "T", "F", "S", "S"), 10 | geom = "text", 11 | color = "grey35", 12 | size = 3, 13 | alpha = 1 14 | ) 15 | } 16 | \arguments{ 17 | \item{dates_df}{} 18 | 19 | \item{day_labels}{} 20 | 21 | \item{geom}{} 22 | 23 | \item{color}{} 24 | 25 | \item{size}{} 26 | 27 | \item{alpha}{} 28 | } 29 | \description{ 30 | Title 31 | } 32 | -------------------------------------------------------------------------------- /man/return_df_hours_week.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/df_functions.R 3 | \name{return_df_hours_week} 4 | \alias{return_df_hours_week} 5 | \title{Title} 6 | \usage{ 7 | return_df_hours_week(date = NULL) 8 | } 9 | \arguments{ 10 | \item{date}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | -------------------------------------------------------------------------------- /man/stat_calendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/a_stat_calendar.R 3 | \name{stat_calendar} 4 | \alias{stat_calendar} 5 | \title{Title} 6 | \usage{ 7 | stat_calendar( 8 | mapping = NULL, 9 | data = NULL, 10 | geom = "text", 11 | position = "identity", 12 | na.rm = FALSE, 13 | show.legend = NA, 14 | inherit.aes = TRUE, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{mapping}{} 20 | 21 | \item{data}{} 22 | 23 | \item{geom}{} 24 | 25 | \item{position}{} 26 | 27 | \item{na.rm}{} 28 | 29 | \item{show.legend}{} 30 | 31 | \item{inherit.aes}{} 32 | 33 | \item{...}{} 34 | } 35 | \description{ 36 | Title 37 | } 38 | -------------------------------------------------------------------------------- /man/theme_grey_calendar.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/theme_grey_calendar.R 3 | \name{theme_grey_calendar} 4 | \alias{theme_grey_calendar} 5 | \title{Title} 6 | \usage{ 7 | theme_grey_calendar(...) 8 | } 9 | \arguments{ 10 | \item{...}{} 11 | } 12 | \description{ 13 | Title 14 | } 15 | --------------------------------------------------------------------------------