├── .Rproj.user ├── shared │ └── notebooks │ │ ├── patch-chunk-names │ │ └── paths └── FFB26621 │ └── sources │ └── prop │ ├── 2282DB4F │ ├── 17B6932C │ ├── 2045AE90 │ ├── 2D0998D8 │ ├── 2D892F76 │ ├── A63EDE6C │ ├── C64AF870 │ ├── D3F50A69 │ ├── 32D0FEEB │ ├── AB8E0B8 │ └── INDEX ├── LICENSE ├── .Rbuildignore ├── .gitignore ├── .DS_Store ├── inst ├── htmlwidgets │ ├── css │ │ └── styles.css │ ├── .DS_Store │ ├── githubCalendar.yaml │ ├── githubCalendar.js │ └── js │ │ └── makeChart.js ├── .DS_Store ├── img │ ├── readme.gif │ └── readme.png └── example │ ├── index.Rmd │ └── app.R ├── NAMESPACE ├── R ├── notes.R ├── utils-pipe.R ├── interpolate_dates.R └── githubCalendar.R ├── man ├── pipe.Rd ├── interpolate_dates.Rd └── githubCalendar-shiny.Rd ├── githubCalendar.Rproj ├── DESCRIPTION ├── LICENSE.md └── README.md /.Rproj.user/shared/notebooks/patch-chunk-names: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/2282DB4F: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: Maya Gans 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaGans/githubCalendar/HEAD/.DS_Store -------------------------------------------------------------------------------- /inst/htmlwidgets/css/styles.css: -------------------------------------------------------------------------------- 1 | .calendar { 2 | font-style: inherit; 3 | } 4 | -------------------------------------------------------------------------------- /inst/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaGans/githubCalendar/HEAD/inst/.DS_Store -------------------------------------------------------------------------------- /inst/img/readme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaGans/githubCalendar/HEAD/inst/img/readme.gif -------------------------------------------------------------------------------- /inst/img/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaGans/githubCalendar/HEAD/inst/img/readme.png -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/17B6932C: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "5,0", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/2045AE90: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "0,30", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/2D0998D8: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "18,0", 3 | "scrollLine" : "17" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/2D892F76: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "3,0", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/A63EDE6C: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "75,16", 3 | "scrollLine" : "74" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/C64AF870: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "22,0", 3 | "scrollLine" : "12" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/D3F50A69: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "3,23", 3 | "scrollLine" : "0" 4 | } -------------------------------------------------------------------------------- /inst/htmlwidgets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MayaGans/githubCalendar/HEAD/inst/htmlwidgets/.DS_Store -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/32D0FEEB: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "1493,1", 3 | "scrollLine" : "1488" 4 | } -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/AB8E0B8: -------------------------------------------------------------------------------- 1 | { 2 | "cursorPosition" : "10,4", 3 | "scrollLine" : "5", 4 | "tempName" : "Untitled1" 5 | } -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(githubCalendar) 5 | export(githubCalendarOutput) 6 | export(renderGithubCalendar) 7 | importFrom(magrittr,"%>%") 8 | -------------------------------------------------------------------------------- /R/notes.R: -------------------------------------------------------------------------------- 1 | # Chapter 6: Dependencies 2 | # dir.create("./inst/htmlwidgets/d3", recursive = TRUE) 3 | # d3 <- paste0("https://d3js.org/d3.v6.min.js") 4 | # download.file(d3, "./inst/htmlwidgets/d3/d3.min.js") 5 | # After downloading add dependency to yaml! 6 | -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | NULL 12 | -------------------------------------------------------------------------------- /inst/htmlwidgets/githubCalendar.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: d3 3 | version: 6.1.1 4 | src: htmlwidgets/d3 5 | script: d3.min.js 6 | - name: githubCalendar 7 | version: 1.0.0 8 | src: htmlwidgets 9 | script: js/makeChart.js 10 | stylesheet: css/styles.css 11 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \description{ 10 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /inst/example/index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Report" 3 | author: "Maya" 4 | date: "9/20/2020" 5 | output: html_document 6 | --- 7 | 8 | ```{r} 9 | library(githubCalendar) 10 | dates <- sample(seq(as.Date('2020-01-01'), as.Date('2020-12-31'), by="day"), 50) 11 | values <- abs(round(rnorm(50, 100, 100), 0)) 12 | githubCalendar(dates = dates, values = values) 13 | ``` 14 | 15 | -------------------------------------------------------------------------------- /githubCalendar.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: githubCalendar 2 | Type: Package 3 | Title: What the Package Does (Title Case) 4 | Version: 0.1.0 5 | Author: Who wrote it 6 | Maintainer: The package maintainer 7 | Description: More about what it does (maybe more than one line) 8 | Use four spaces when indenting paragraphs within the Description. 9 | License: MIT + file LICENSE 10 | Encoding: UTF-8 11 | LazyData: true 12 | RoxygenNote: 7.1.1 13 | Imports: 14 | magrittr, 15 | dplyr, 16 | htmltools, 17 | htmlwidgets, 18 | lubridate, 19 | purrr, 20 | tidyr 21 | -------------------------------------------------------------------------------- /man/interpolate_dates.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/interpolate_dates.R 3 | \name{interpolate_dates} 4 | \alias{interpolate_dates} 5 | \title{Interpolate Missing Dates} 6 | \usage{ 7 | interpolate_dates(date, value) 8 | } 9 | \arguments{ 10 | \item{date}{a vector of dates, must be only one year} 11 | 12 | \item{value}{a vector of values for dates} 13 | } 14 | \value{ 15 | a dataframe with dates and values 16 | } 17 | \description{ 18 | Create a table of dates and associated values 19 | then add missing dates to table with a value of 0 20 | } 21 | -------------------------------------------------------------------------------- /.Rproj.user/shared/notebooks/paths: -------------------------------------------------------------------------------- 1 | /Users/mayagans/Documents/githubCalendar/LICENSE="4A60E1A8" 2 | /Users/mayagans/Documents/githubCalendar/LICENSE.md="DBB85DBE" 3 | /Users/mayagans/Documents/githubCalendar/R/interpolate_dates.R="72F833AD" 4 | /Users/mayagans/Documents/githubCalendar/R/notes.R="DD301F22" 5 | /Users/mayagans/Documents/githubCalendar/README.md="49237A7E" 6 | /Users/mayagans/Documents/githubCalendar/inst/example/index.Rmd="513EC054" 7 | /Users/mayagans/Documents/githubCalendar/inst/htmlwidgets/css/styles.css="B02386E5" 8 | /Users/mayagans/Documents/githubCalendar/inst/htmlwidgets/githubCalendar.js="B34F53CC" 9 | /Users/mayagans/Documents/githubCalendar/inst/htmlwidgets/githubCalendar.yaml="B5670510" 10 | /Users/mayagans/Documents/githubCalendar/inst/htmlwidgets/js/makeChart.js="E6C0924" 11 | -------------------------------------------------------------------------------- /R/interpolate_dates.R: -------------------------------------------------------------------------------- 1 | #' Interpolate Missing Dates 2 | #' 3 | #' Create a table of dates and associated values 4 | #' then add missing dates to table with a value of 0 5 | #' 6 | #' @param date a vector of dates, must be only one year 7 | #' @param value a vector of values for dates 8 | #' 9 | #' @return a dataframe with dates and values 10 | #' 11 | interpolate_dates <- function(date, value) { 12 | missingDates <- unique(lubridate::year(date)) %>% 13 | purrr::map(function(d) { 14 | seq(lubridate::make_date(d, 1, 1), lubridate::make_date(d, 12, 31), by='day') 15 | }) %>% unlist() %>% 16 | lubridate::as_date() %>% 17 | setdiff(date) %>% 18 | lubridate::as_date() 19 | 20 | dplyr::bind_rows(tidyr::tibble(date, value), 21 | tidyr::tibble(date=missingDates, value=0)) %>% 22 | dplyr::arrange(date) %>% 23 | dplyr::mutate(date = as.character(date)) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /inst/htmlwidgets/githubCalendar.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'githubCalendar', 4 | 5 | type: 'output', 6 | 7 | factory: function(el, width, height) { 8 | 9 | return { 10 | 11 | // attach empty svg that should get populated on render 12 | svg: null, 13 | 14 | renderValue: function(x) { 15 | 16 | const stringToDate = d3.timeParse("%Y-%m-%d"); 17 | const rvalues = x.data.value; 18 | const rdates = x.data.date; 19 | const rdataarray = rdates.map( 20 | function (x, i) { 21 | return { date: stringToDate(x), value: rvalues[i] } 22 | } 23 | ); 24 | 25 | // need to get the year from x.data.value string 26 | var chosen_year = x.data.date[0].substring(0,4); 27 | 28 | // attach our svg container selection to the widget 29 | // then in updates post-render we know that we don't need to recreate 30 | this.svg = makeChart(rdataarray, el, chosen_year, height, this.svg); 31 | }, 32 | 33 | resize: function(width, height) { 34 | 35 | } 36 | 37 | }; 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 Maya Gans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /inst/example/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | githubData <- tidyr::tibble( 4 | all_dates = c(sample(seq(as.Date('2018/01/01'), as.Date('2019/01/01'), by="day"), 50), 5 | sample(seq(as.Date('2019/01/01'), as.Date('2020/01/01'), by="day"), 100), 6 | sample(seq(as.Date('2020/01/01'), as.Date('2021/01/01'), by="day"), 200) 7 | ), 8 | all_values = abs(round(rnorm(350, 100, 100), 0)) 9 | ) 10 | 11 | ui <- fluidPage( 12 | fluidPage( 13 | sliderInput("year", "Select Year", min = 2018, max = 2020, value = 2019), 14 | githubCalendarOutput("calendar") 15 | ) 16 | ) 17 | 18 | 19 | server <- function(input, output) { 20 | data <- reactive({ 21 | githubData %>% 22 | dplyr::filter(lubridate::year(all_dates) %in% input$year) 23 | }) 24 | 25 | # this renders, but when the input changes 26 | # the data doesn't update, it appends another graph 27 | output$calendar <- renderGithubCalendar({ 28 | githubCalendar(dates = data()$all_dates, values = data()$all_values) 29 | }) 30 | } 31 | 32 | # Run the application 33 | shinyApp(ui = ui, server = server) 34 | -------------------------------------------------------------------------------- /man/githubCalendar-shiny.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/githubCalendar.R 3 | \name{githubCalendar-shiny} 4 | \alias{githubCalendar-shiny} 5 | \alias{githubCalendar} 6 | \alias{githubCalendarOutput} 7 | \alias{renderGithubCalendar} 8 | \title{A github inspired calendar visualization} 9 | \usage{ 10 | githubCalendar(dates, values, width = "100\%", height = NULL, elementId = NULL) 11 | 12 | githubCalendarOutput(outputId, width = "100\%", height = "400px") 13 | 14 | renderGithubCalendar(expr, env = parent.frame(), quoted = FALSE) 15 | } 16 | \arguments{ 17 | \item{dates}{the dates to plot on the calendar, must all be in the same year} 18 | 19 | \item{values}{the values associated with each date} 20 | 21 | \item{width, height}{Must be a valid CSS unit (like \code{'100\%'}, 22 | \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 23 | string and have \code{'px'} appended.} 24 | 25 | \item{elementId}{output variable to read from} 26 | 27 | \item{outputId}{output variable to read from} 28 | 29 | \item{expr}{An expression that generates a githubCalendar} 30 | 31 | \item{env}{The environment in which to evaluate \code{expr}.} 32 | 33 | \item{quoted}{Is \code{expr} a quoted expression (with \code{quote()})? This 34 | is useful if you want to save an expression in a variable.} 35 | } 36 | \description{ 37 | Output and render functions for using githubCalendar within Shiny 38 | applications and interactive Rmd documents. 39 | } 40 | \examples{ 41 | dates <- sample(seq(as.Date('2020-01-01'), as.Date('2020-12-31'), by="day"), 50) 42 | values <- abs(round(rnorm(50, 100, 100), 0)) 43 | githubCalendar(dates = dates, values = values) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # githubCalendar 2 | Creating a custom visual in Observable, then turning it into an htmlwidget to be used in R! All the user needs to do is supply their own data! 3 | 4 | 5 | ## Install the library 6 | ``` 7 | devtools::install_github("MayaGans/githubCalendar") 8 | ``` 9 | 10 | ## Run the `githubCalendar` function on your data 11 | 12 | ``` 13 | # dates must be Date type 14 | dates <- sample(seq(as.Date('2020-01-01'), as.Date('2020-12-31'), by="day"), 50) 15 | values <- abs(round(rnorm(50, 100, 100), 0)) 16 | githubCalendar(dates = dates, values = values) 17 | 18 | ``` 19 | 20 | ![](inst/img/readme.png) 21 | 22 | ## Add a dynamic Calendar to your shiny app 23 | 24 | ``` 25 | library(shiny) 26 | 27 | githubData <- tidyr::tibble( 28 | all_dates = c(sample(seq(as.Date('2018/01/01'), as.Date('2019/01/01'), by="day"), 50), 29 | sample(seq(as.Date('2019/01/01'), as.Date('2020/01/01'), by="day"), 100), 30 | sample(seq(as.Date('2020/01/01'), as.Date('2021/01/01'), by="day"), 200) 31 | ), 32 | all_values = abs(round(rnorm(350, 100, 100), 0)) 33 | ) 34 | 35 | ui <- fluidPage( 36 | fluidPage( 37 | sliderInput("year", "Select Year", min = 2018, max = 2020, value = 2019), 38 | githubCalendarOutput("calendar") 39 | ) 40 | ) 41 | 42 | 43 | server <- function(input, output) { 44 | data <- reactive({ 45 | githubData %>% 46 | dplyr::filter(lubridate::year(all_dates) %in% input$year) 47 | }) 48 | 49 | # this renders, but when the input changes 50 | # the data doesn't update, it appends another graph 51 | output$calendar <- renderGithubCalendar({ 52 | githubCalendar(dates = data()$all_dates, values = data()$all_values) 53 | }) 54 | } 55 | 56 | # Run the application 57 | shinyApp(ui = ui, server = server) 58 | ``` 59 | 60 | ![](inst/img/readme.gif) 61 | 62 | 63 | -------------------------------------------------------------------------------- /.Rproj.user/FFB26621/sources/prop/INDEX: -------------------------------------------------------------------------------- 1 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FNAMESPACE="2045AE90" 2 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FR%2FgithubCalendar.R="A63EDE6C" 3 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FR%2Fhello.R="2D892F76" 4 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FR%2Finterpolate_dates.R="AB8E0B8" 5 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FR%2Fnotes.R="17B6932C" 6 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FR%2Futils-pipe.R="2282DB4F" 7 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2FREADME.md="7CF57BEC" 8 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fexample%2Fapp.R="6CB6197B" 9 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fhtmlwidgets%2Fd3%2Fd3.min.js="32D0FEEB" 10 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fhtmlwidgets%2FgithubCalendar.js="C64AF870" 11 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fhtmlwidgets%2FgithubCalendar.yaml="D3F50A69" 12 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fhtmlwidgets%2Fjs%2FmakeChart.js="B5DBA38C" 13 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fhtmlwidgets%2FmakeChart.js="837757A9" 14 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Finst%2Fstyles.css="B66C2990" 15 | ~%2FDocuments%2FDEMOS%2FgithubCalendar%2Ftt%2Fapp.R="2D0998D8" 16 | ~%2FDocuments%2FgithubCalendar%2FDESCRIPTION="78FFC172" 17 | ~%2FDocuments%2FgithubCalendar%2FLICENSE="C272A430" 18 | ~%2FDocuments%2FgithubCalendar%2FLICENSE.md="E26669E7" 19 | ~%2FDocuments%2FgithubCalendar%2FNAMESPACE="7E422189" 20 | ~%2FDocuments%2FgithubCalendar%2FR%2FgithubCalendar.R="E13FCA5D" 21 | ~%2FDocuments%2FgithubCalendar%2FR%2Finterpolate_dates.R="482EFC04" 22 | ~%2FDocuments%2FgithubCalendar%2FR%2Fnotes.R="D1861436" 23 | ~%2FDocuments%2FgithubCalendar%2FREADME.md="2B761D74" 24 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fexample%2Fapp.R="30E9DA50" 25 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fexample%2Findex.Rmd="3BFAAA1D" 26 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fhtmlwidgets%2Fcss%2Fstyles.css="D393B38A" 27 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fhtmlwidgets%2FgithubCalendar.js="887B2911" 28 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fhtmlwidgets%2FgithubCalendar.yaml="D76AB8F3" 29 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fhtmlwidgets%2Fjs%2FmakeChart.js="C62125F3" 30 | ~%2FDocuments%2FgithubCalendar%2Finst%2Fstyles.css="8414C665" 31 | -------------------------------------------------------------------------------- /R/githubCalendar.R: -------------------------------------------------------------------------------- 1 | #' A github inspired calendar visualization 2 | #' 3 | #' @param dates the dates to plot on the calendar, must all be in the same year 4 | #' @param values the values associated with each date 5 | #' @param elementId output variable to read from 6 | #' @param width,height Must be a valid CSS unit (like \code{'100\%'}, 7 | #' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 8 | #' string and have \code{'px'} appended. 9 | #' 10 | #' @name githubCalendar-shiny 11 | #' 12 | #' @export 13 | #' 14 | #' @examples 15 | #' dates <- sample(seq(as.Date('2020-01-01'), as.Date('2020-12-31'), by="day"), 50) 16 | #' values <- abs(round(rnorm(50, 100, 100), 0)) 17 | #' githubCalendar(dates = dates, values = values) 18 | #' 19 | githubCalendar <- function(dates, values, width = "100%", height = NULL, elementId = NULL) { 20 | 21 | if (!lubridate::is.Date(dates)) { 22 | stop("dates argument must be of type Date") 23 | } 24 | 25 | if (length(unique(lubridate::year(dates))) < 1) { 26 | stop("githubCalendar cannot be created without data") 27 | } 28 | 29 | if (length(unique(lubridate::year(dates))) > 1) { 30 | stop("githubCalendar can only be create for a single year") 31 | } 32 | 33 | 34 | # forward options using x 35 | x = list( 36 | data = interpolate_dates(dates, values) 37 | ) 38 | 39 | # create widget 40 | htmlwidgets::createWidget( 41 | name = 'githubCalendar', 42 | x, 43 | width = width, 44 | height = height, 45 | package = 'githubCalendar', 46 | elementId = elementId, 47 | sizingPolicy = htmlwidgets::sizingPolicy( 48 | padding = 15, 49 | browser.fill = TRUE, 50 | defaultWidth = "100%" 51 | ) 52 | ) 53 | } 54 | 55 | 56 | 57 | #' Shiny bindings for githubCalendar 58 | #' 59 | #' Output and render functions for using githubCalendar within Shiny 60 | #' applications and interactive Rmd documents. 61 | #' 62 | #' @param outputId output variable to read from 63 | #' @param width,height Must be a valid CSS unit (like \code{'100\%'}, 64 | #' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 65 | #' string and have \code{'px'} appended. 66 | #' @param expr An expression that generates a githubCalendar 67 | #' @param env The environment in which to evaluate \code{expr}. 68 | #' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This 69 | #' is useful if you want to save an expression in a variable. 70 | #' 71 | #' @name githubCalendar-shiny 72 | #' 73 | #' @export 74 | #' 75 | githubCalendarOutput <- function(outputId, width = '100%', height = '400px'){ 76 | htmlwidgets::shinyWidgetOutput(outputId, 'githubCalendar', width, height, package = 'githubCalendar') 77 | } 78 | 79 | #' @rdname githubCalendar-shiny 80 | #' @export 81 | renderGithubCalendar <- function(expr, env = parent.frame(), quoted = FALSE) { 82 | if (!quoted) { expr <- substitute(expr) } # force quoted 83 | htmlwidgets::shinyRenderWidget(expr, githubCalendarOutput, env, quoted = TRUE) 84 | } 85 | 86 | githubCalendar_html <- function(...){ 87 | htmltools::tagList( 88 | htmltools::tags$div(..., class="calendar") 89 | ) 90 | } 91 | 92 | -------------------------------------------------------------------------------- /inst/htmlwidgets/js/makeChart.js: -------------------------------------------------------------------------------- 1 | const makeChart = (rdataarray, el, chosen_year, height, svg) => { 2 | 3 | const colorScale = d3.scaleOrdinal() 4 | .domain(d3.extent(rdataarray, d => d.value)) 5 | .range(["#9BE9A8", "#3FC463", "#2EA14E", "#1F6E39"]); 6 | 7 | const formatMonth = d3.timeFormat("%b"); 8 | const formatDay = d => "SMTWTFS"[d.getDay()]; 9 | const formatDate = d3.timeFormat("%x"); 10 | const format = d3.format("+.2%"); 11 | const countDay = d => d.getDay(); 12 | const timeWeek = d3.timeSunday; 13 | const cellSize = 17; 14 | 15 | const parseTime = d3.timeFormat("%b %d"); 16 | 17 | let dimensions = { 18 | width: 900, 19 | height: 200, 20 | margin: { 21 | top: 15, 22 | right: 15, 23 | bottom: 40, 24 | left: 50, 25 | }, 26 | }; 27 | 28 | dimensions.boundedWidth = 29 | dimensions.width - dimensions.margin.left - dimensions.margin.right; 30 | 31 | dimensions.boundedHeight = 32 | dimensions.height - dimensions.margin.top - dimensions.margin.bottom; 33 | 34 | // create the svg container for our visualization 35 | // if svg is not null then we have already created the svg 36 | // and do not need to create again 37 | if(svg === null) { 38 | svg = d3.select(el) 39 | .append("svg") 40 | } 41 | svg.attr( 42 | "viewBox", 43 | `0 0 ${ 44 | dimensions.width + dimensions.margin.left + dimensions.margin.right 45 | } ${ 46 | dimensions.height + dimensions.margin.top + dimensions.margin.bottom 47 | }` 48 | ) 49 | 50 | var tooltip = d3.select("body") 51 | .append("div") 52 | .attr("class", "calendar") 53 | .style("position", "absolute") 54 | .style("z-index", "100") 55 | .style("visibility", "hidden") 56 | .style("color", "white") 57 | .style("background-color", "black") 58 | .style("opacity", 0.7) 59 | .style("border-radius", "5px") 60 | .style("padding", "10px") 61 | 62 | var calendarData = [{key: chosen_year, values: rdataarray}]; 63 | 64 | const year = svg.selectAll("g") 65 | .data(calendarData) 66 | .join("g") 67 | .attr("class","calendar") 68 | .attr("transform", (d, i) => `translate(40,${height * i + cellSize * 1.5})`); 69 | 70 | year.append("g") 71 | .attr("class","calendar") 72 | .selectAll("rect") 73 | .data(d => d.values) 74 | .join("rect") 75 | .attr("width", cellSize - 1) 76 | .attr("height", cellSize - 1) 77 | .attr("x", d => timeWeek.count(d3.timeYear(d.date), d.date) * cellSize + 0.5) 78 | .attr("y", d => countDay(d.date) * cellSize + 0.5) 79 | .attr('fill', d => d.value === 0 ? "#EBEDF0" : colorScale(d.value)) 80 | .attr("stroke", 'white') 81 | .attr("rx", 5) 82 | .on("mouseover", function (event, d, i) { 83 | d3.select(this) 84 | .transition() 85 | .duration("50") 86 | tooltip.html(d.value + " on " + parseTime(d.date)) 87 | return tooltip.style("visibility", "visible"); 88 | }) 89 | 90 | // Setting things back to normal after tool tip 91 | .on('mouseover', function (event, d) { 92 | tooltip.html(d.value + " on " + parseTime(d.date)) 93 | return tooltip.style("visibility", "visible") 94 | }) 95 | .on('mouseout', function (event, d) { 96 | return tooltip.style("visibility", "hidden") 97 | }) 98 | .on("mousemove", function(event){ 99 | return tooltip.style("top", (event.pageY-20)+"px") 100 | .style("left",(event.pageX+20)+"px"); 101 | }) 102 | 103 | // I want this to print only Mon, Wed, Fri 104 | year.append("g") 105 | .attr("text-anchor", "end") 106 | .attr("class","calendar") 107 | .selectAll("text") 108 | .data((d3.range(7)).map(i => new Date(chosen_year, 0, i))) 109 | .enter().append("text") 110 | .attr("x", -5) 111 | .attr("y", d => (countDay(d) + 0.5) * cellSize) 112 | .attr("dy", "0.31em") 113 | .text(formatDay) 114 | .attr("fill", d => (countDay(d) % 2 ? "black" : "white")) 115 | 116 | const month = year.append("g") 117 | .attr("class","calendar") 118 | .selectAll("g") 119 | .data(d => d3.timeMonths(d3.timeMonth(d.values[0].date), d.values[d.values.length - 1].date)) 120 | .enter().append("g") 121 | 122 | month.append("text") 123 | .attr("class","calendar") 124 | .attr("x", d => timeWeek.count(d3.timeYear(d), timeWeek.ceil(d)) * cellSize + 2) 125 | .attr("y", -5) 126 | .text(formatMonth) 127 | .attr("fill", d => (d3.timeYear(d) ? "black" : "white")) 128 | 129 | var colorLegendScale = d3.scaleOrdinal() 130 | .domain([1,2,3,4,5]) 131 | .range(["#EBEDF0", "#9BE9A8", "#3FC463", "#2EA14E", "#1F6E39"]); 132 | 133 | // Add one dot in the legend for each name. 134 | var legendsize = 16 135 | 136 | const legend = svg.append("g") 137 | .selectAll("legend") 138 | .data([1,2,3,4,5]) 139 | .enter() 140 | .append("rect") 141 | .attr("x", function(d,i){ return 750 + i*(legendsize+5)}) 142 | .attr("y", 160) 143 | .attr("width", legendsize) 144 | .attr("height", legendsize) 145 | .style("fill", function(d){ return colorLegendScale(d)}) 146 | .attr("rx", 5) 147 | 148 | svg.append("text") 149 | .attr("x", 705) 150 | .attr("y", 173) 151 | .attr("fill", "black") 152 | .html("Less") 153 | 154 | svg.append("text") 155 | .attr("x", 865) 156 | .attr("y", 173) 157 | .attr("fill", "black") 158 | .html("More") 159 | 160 | // return the svg element from this function 161 | // so we can attach to our widget to prevent re-creation 162 | return svg 163 | 164 | }; 165 | --------------------------------------------------------------------------------