├── .github ├── .gitignore ├── FUNDING.yml └── workflows │ └── R-CMD-check.yaml ├── LICENSE ├── .gitignore ├── tests ├── testthat.R └── testthat │ ├── setup_helpers.R │ └── test-dataframeToD3.R ├── .Rbuildignore ├── inst ├── img │ ├── app.png │ ├── basic.png │ ├── hex.png │ ├── groups.png │ ├── minimal.png │ ├── worldcup.png │ ├── customstyle.png │ └── nestedgroups.png ├── example │ ├── www │ │ ├── background.jpg │ │ └── style.css │ ├── utils.R │ ├── ui-helpers.R │ ├── sampleData.R │ ├── server.R │ └── ui.R └── htmlwidgets │ ├── timevis.yaml │ ├── timevis.css │ ├── timevis.js │ └── lib │ └── vis-7.4.9 │ └── vis-timeline-graph2d.min.css ├── R ├── timevis-package.R ├── data.R ├── runExample.R ├── utils.R ├── api.R └── timevis.R ├── data ├── timevisData.RData └── timevisDataGroups.RData ├── man ├── runExample.Rd ├── timevisData.Rd ├── timevisDataGroups.Rd ├── reexports.Rd ├── setCurrentTime.Rd ├── addItem.Rd ├── removeItem.Rd ├── addCustomTime.Rd ├── setCustomTime.Rd ├── setItems.Rd ├── addItems.Rd ├── timevis-package.Rd ├── fitWindow.Rd ├── removeCustomTime.Rd ├── centerTime.Rd ├── setWindow.Rd ├── setSelection.Rd ├── setOptions.Rd ├── setGroups.Rd ├── zoom.Rd ├── centerItem.Rd ├── timevis-shiny.Rd └── timevis.Rd ├── timevis.Rproj ├── NAMESPACE ├── DESCRIPTION ├── NEWS.md └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: Dean Attali 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc 5 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | 3 | test_check("timevis") 4 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.github$ 4 | ^LICENSE\.md$ 5 | -------------------------------------------------------------------------------- /inst/img/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/app.png -------------------------------------------------------------------------------- /inst/img/basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/basic.png -------------------------------------------------------------------------------- /inst/img/hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/hex.png -------------------------------------------------------------------------------- /R/timevis-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @aliases timevis-package 3 | "_PACKAGE" 4 | -------------------------------------------------------------------------------- /inst/img/groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/groups.png -------------------------------------------------------------------------------- /inst/img/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/minimal.png -------------------------------------------------------------------------------- /data/timevisData.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/data/timevisData.RData -------------------------------------------------------------------------------- /inst/img/worldcup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/worldcup.png -------------------------------------------------------------------------------- /inst/img/customstyle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/customstyle.png -------------------------------------------------------------------------------- /inst/img/nestedgroups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/img/nestedgroups.png -------------------------------------------------------------------------------- /data/timevisDataGroups.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/data/timevisDataGroups.RData -------------------------------------------------------------------------------- /inst/example/www/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/timevis/HEAD/inst/example/www/background.jpg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: daattali 4 | patreon: DeanAttali 5 | -------------------------------------------------------------------------------- /tests/testthat/setup_helpers.R: -------------------------------------------------------------------------------- 1 | # Produce a more informative output when failing expect_equivalent/identical 2 | # testcases for visual comparison 3 | info_comp <- function(actual, expect){ 4 | paste0( 5 | c("Expect: ", "Actual: "), 6 | c(expect, actual), 7 | collapse = "\n" 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /inst/htmlwidgets/timevis.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: visjs 3 | version: 7.4.9 4 | src: htmlwidgets/lib/vis-7.4.9 5 | script: vis-timeline-graph2d.min.js 6 | stylesheet: vis-timeline-graph2d.min.css 7 | 8 | - name: timeline 9 | version: 0.4 10 | src: htmlwidgets 11 | stylesheet: timevis.css 12 | -------------------------------------------------------------------------------- /inst/example/utils.R: -------------------------------------------------------------------------------- 1 | # generate a random string of 16 characters 2 | randomID <- function() { 3 | paste(sample(c(letters, LETTERS, 0:9), 16, replace = TRUE), collapse = "") 4 | } 5 | 6 | prettyDate <- function(d) { 7 | if (is.null(d)) return() 8 | posix <- as.POSIXct(d, format = "%Y-%m-%dT%H:%M:%OS", tz = "UTC") 9 | corrected <- lubridate::with_tz(posix, tzone = Sys.timezone()) 10 | format(corrected, "%Y-%m-%d %H:%M:%OS %Z") 11 | } 12 | -------------------------------------------------------------------------------- /man/runExample.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/runExample.R 3 | \name{runExample} 4 | \alias{runExample} 5 | \title{Run examples of using timevis in a Shiny app} 6 | \usage{ 7 | runExample() 8 | } 9 | \description{ 10 | This example is also 11 | \href{https://daattali.com/shiny/timevis-demo/}{available online}. 12 | } 13 | \examples{ 14 | if (interactive()) { 15 | runExample() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | #' Timevis sample data 2 | #' 3 | #' A dataset containing sample time schedule data for a community center 4 | #' that can be rendered by timevis. 5 | #' 6 | #' @format A data frame with 11 rows and 6 variables. 7 | "timevisData" 8 | 9 | #' Timevis sample group data 10 | #' 11 | #' A dataset containing groups data to be used with the \code{timevisData} data. 12 | #' 13 | #' @format A data frame with 3 rows and 2 variables. 14 | "timevisDataGroups" 15 | -------------------------------------------------------------------------------- /timevis.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: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageInstallArgs: --no-multiarch --with-keep.source 20 | PackageRoxygenize: rd,collate,namespace 21 | -------------------------------------------------------------------------------- /man/timevisData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{timevisData} 5 | \alias{timevisData} 6 | \title{Timevis sample data} 7 | \format{ 8 | A data frame with 11 rows and 6 variables. 9 | } 10 | \usage{ 11 | timevisData 12 | } 13 | \description{ 14 | A dataset containing sample time schedule data for a community center 15 | that can be rendered by timevis. 16 | } 17 | \keyword{datasets} 18 | -------------------------------------------------------------------------------- /man/timevisDataGroups.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{timevisDataGroups} 5 | \alias{timevisDataGroups} 6 | \title{Timevis sample group data} 7 | \format{ 8 | A data frame with 3 rows and 2 variables. 9 | } 10 | \usage{ 11 | timevisDataGroups 12 | } 13 | \description{ 14 | A dataset containing groups data to be used with the \code{timevisData} data. 15 | } 16 | \keyword{datasets} 17 | -------------------------------------------------------------------------------- /man/reexports.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \docType{import} 4 | \name{reexports} 5 | \alias{reexports} 6 | \alias{\%>\%} 7 | \title{Objects exported from other packages} 8 | \keyword{internal} 9 | \description{ 10 | These objects are imported from other packages. Follow the links 11 | below to see their documentation. 12 | 13 | \describe{ 14 | \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} 15 | }} 16 | 17 | -------------------------------------------------------------------------------- /R/runExample.R: -------------------------------------------------------------------------------- 1 | #' Run examples of using timevis in a Shiny app 2 | #' 3 | #' This example is also 4 | #' \href{https://daattali.com/shiny/timevis-demo/}{available online}. 5 | #' 6 | #' @examples 7 | #' if (interactive()) { 8 | #' runExample() 9 | #' } 10 | #' @export 11 | runExample <- function() { 12 | appDir <- system.file("example", package = "timevis") 13 | if (appDir == "") { 14 | stop("Could not find example directory. Try re-installing `timevis`.", 15 | call. = FALSE) 16 | } 17 | 18 | shiny::runApp(appDir, display.mode = "normal") 19 | } 20 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(addCustomTime) 5 | export(addItem) 6 | export(addItems) 7 | export(centerItem) 8 | export(centerTime) 9 | export(fitWindow) 10 | export(removeCustomTime) 11 | export(removeItem) 12 | export(renderTimevis) 13 | export(runExample) 14 | export(setCurrentTime) 15 | export(setCustomTime) 16 | export(setGroups) 17 | export(setItems) 18 | export(setOptions) 19 | export(setSelection) 20 | export(setWindow) 21 | export(timevis) 22 | export(timevisOutput) 23 | export(zoomIn) 24 | export(zoomOut) 25 | importFrom(magrittr,"%>%") 26 | -------------------------------------------------------------------------------- /inst/htmlwidgets/timevis.css: -------------------------------------------------------------------------------- 1 | .timevis.html-widget { 2 | margin-bottom: 20px; 3 | position: relative; 4 | } 5 | .timevis.html-widget .vis-background .vis-minor.vis-odd { 6 | background: #fdfdfd; 7 | } 8 | .timevis.html-widget .vis-major { 9 | font-weight: bold; 10 | } 11 | .timevis.html-widget .zoom-menu { 12 | display: none; 13 | position: absolute; 14 | right: 5px; 15 | top: 5px; 16 | z-index: 15; 17 | } 18 | .timevis.html-widget .zoom-menu[data-show-zoom] { 19 | display: block; 20 | } 21 | .timevis.html-widget .zoom-menu .btn { 22 | color: #333; 23 | font-family: arial; 24 | font-size: 18px; 25 | line-height: 1.3333333; 26 | text-shadow: none; 27 | border-radius: 6px; 28 | border: 1px solid #CCC; 29 | padding: 0; 30 | width: 30px; 31 | height: 30px; 32 | background: #FDFDFD; 33 | box-shadow: 0 0 2px #ddd; 34 | } 35 | -------------------------------------------------------------------------------- /man/setCurrentTime.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setCurrentTime} 4 | \alias{setCurrentTime} 5 | \title{Adjust the time of the current time bar} 6 | \usage{ 7 | setCurrentTime(id, time) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{time}{The new date/time} 13 | } 14 | \description{ 15 | Adjust the time of the current time bar 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis() \%>\% 20 | setCurrentTime(Sys.Date()) 21 | } 22 | 23 | if (interactive()) { 24 | library(shiny) 25 | shinyApp( 26 | ui = fluidPage( 27 | timevisOutput("timeline"), 28 | actionButton("btn", "Set current time to beginning of today") 29 | ), 30 | server = function(input, output) { 31 | output$timeline <- renderTimevis( 32 | timevis() 33 | ) 34 | observeEvent(input$btn, { 35 | setCurrentTime("timeline", Sys.Date()) 36 | }) 37 | } 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /man/addItem.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{addItem} 4 | \alias{addItem} 5 | \title{Add a single item to a timeline} 6 | \usage{ 7 | addItem(id, data) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{data}{A named list containing the item data to add.} 13 | } 14 | \description{ 15 | Add a single item to a timeline 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis() \%>\% 20 | addItem(list(start = Sys.Date(), content = "Today")) 21 | } 22 | 23 | if (interactive()) { 24 | library(shiny) 25 | shinyApp( 26 | ui = fluidPage( 27 | timevisOutput("timeline"), 28 | actionButton("btn", "Add item today") 29 | ), 30 | server = function(input, output) { 31 | output$timeline <- renderTimevis( 32 | timevis() 33 | ) 34 | observeEvent(input$btn, { 35 | addItem("timeline", list(start = Sys.Date(), content = "Today")) 36 | }) 37 | } 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /man/removeItem.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{removeItem} 4 | \alias{removeItem} 5 | \title{Remove an item from a timeline} 6 | \usage{ 7 | removeItem(id, itemId) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{itemId}{The id of the item to remove} 13 | } 14 | \description{ 15 | Remove an item from a timeline 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis(data.frame(start = Sys.Date(), content = c("1", "2"))) \%>\% 20 | removeItem(2) 21 | } 22 | 23 | if (interactive()) { 24 | library(shiny) 25 | shinyApp( 26 | ui = fluidPage( 27 | timevisOutput("timeline"), 28 | actionButton("btn", "Remove item 2") 29 | ), 30 | server = function(input, output) { 31 | output$timeline <- renderTimevis( 32 | timevis(data.frame( 33 | start = Sys.Date(), content = c("1", "2")) 34 | ) 35 | ) 36 | observeEvent(input$btn, { 37 | removeItem("timeline", 2) 38 | }) 39 | } 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /man/addCustomTime.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{addCustomTime} 4 | \alias{addCustomTime} 5 | \title{Add a new vertical bar at a time point that can be dragged by the user} 6 | \usage{ 7 | addCustomTime(id, time, itemId) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{time}{The date/time to add} 13 | 14 | \item{itemId}{The id of the custom time bar} 15 | } 16 | \description{ 17 | Add a new vertical bar at a time point that can be dragged by the user 18 | } 19 | \examples{ 20 | \dontrun{ 21 | timevis() \%>\% 22 | addCustomTime(Sys.Date() - 1, "yesterday") 23 | } 24 | 25 | if (interactive()) { 26 | library(shiny) 27 | shinyApp( 28 | ui = fluidPage( 29 | timevisOutput("timeline"), 30 | actionButton("btn", "Add time bar 24 hours ago") 31 | ), 32 | server = function(input, output) { 33 | output$timeline <- renderTimevis( 34 | timevis() 35 | ) 36 | observeEvent(input$btn, { 37 | addCustomTime("timeline", Sys.Date() - 1, "yesterday") 38 | }) 39 | } 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /man/setCustomTime.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setCustomTime} 4 | \alias{setCustomTime} 5 | \title{Adjust the time of a custom time bar} 6 | \usage{ 7 | setCustomTime(id, time, itemId) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{time}{The new date/time} 13 | 14 | \item{itemId}{The id of the custom time bar} 15 | } 16 | \description{ 17 | Adjust the time of a custom time bar 18 | } 19 | \examples{ 20 | \dontrun{ 21 | timevis() \%>\% 22 | addCustomTime(Sys.Date(), "yesterday") \%>\% 23 | setCustomTime(Sys.Date() - 1, "yesterday") 24 | } 25 | 26 | if (interactive()) { 27 | library(shiny) 28 | shinyApp( 29 | ui = fluidPage( 30 | timevisOutput("timeline"), 31 | actionButton("btn", "Set time bar 24 hours ago") 32 | ), 33 | server = function(input, output) { 34 | output$timeline <- renderTimevis( 35 | timevis() \%>\% addCustomTime(Sys.Date(), "yesterday") 36 | ) 37 | observeEvent(input$btn, { 38 | setCustomTime("timeline", Sys.Date() - 1, "yesterday") 39 | }) 40 | } 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /man/setItems.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setItems} 4 | \alias{setItems} 5 | \title{Set the items of a timeline} 6 | \usage{ 7 | setItems(id, data) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{data}{A dataframe containing the item data to use.} 13 | } 14 | \description{ 15 | Set the items of a timeline 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis(data.frame(start = Sys.Date(), content = "Today")) \%>\% 20 | setItems(data.frame(start = Sys.Date() - 1, content = "yesterday")) 21 | } 22 | 23 | if (interactive()) { 24 | library(shiny) 25 | shinyApp( 26 | ui = fluidPage( 27 | timevisOutput("timeline"), 28 | actionButton("btn", "Change the data to yesterday") 29 | ), 30 | server = function(input, output) { 31 | output$timeline <- renderTimevis( 32 | timevis(data.frame(start = Sys.Date(), content = "Today")) 33 | ) 34 | observeEvent(input$btn, { 35 | setItems("timeline", 36 | data.frame(start = Sys.Date() - 1, content = "yesterday")) 37 | }) 38 | } 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /man/addItems.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{addItems} 4 | \alias{addItems} 5 | \title{Add multiple items to a timeline} 6 | \usage{ 7 | addItems(id, data) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{data}{A dataframe containing the items data to add.} 13 | } 14 | \description{ 15 | Add multiple items to a timeline 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis() \%>\% 20 | addItems(data.frame(start = c(Sys.Date(), Sys.Date() - 1), 21 | content = c("Today", "Yesterday"))) 22 | } 23 | 24 | if (interactive()) { 25 | library(shiny) 26 | shinyApp( 27 | ui = fluidPage( 28 | timevisOutput("timeline"), 29 | actionButton("btn", "Add items today and yesterday") 30 | ), 31 | server = function(input, output) { 32 | output$timeline <- renderTimevis( 33 | timevis() 34 | ) 35 | observeEvent(input$btn, { 36 | addItems("timeline", 37 | data.frame(start = c(Sys.Date(), Sys.Date() - 1), 38 | content = c("Today", "Yesterday"))) 39 | }) 40 | } 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /man/timevis-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timevis-package.R 3 | \docType{package} 4 | \name{timevis-package} 5 | \alias{timevis-package} 6 | \alias{_PACKAGE} 7 | \title{timevis: Create Interactive Timeline Visualizations in R} 8 | \description{ 9 | Create rich and fully interactive timeline visualizations. Timelines can be included in Shiny apps or R markdown documents. 'timevis' includes an extensive API to manipulate a timeline after creation, and supports getting data out of the visualization into R. Based on the 'vis.js' Timeline JavaScript library. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/daattali/timevis} 15 | \item \url{https://daattali.com/shiny/timevis-demo/} 16 | \item Report bugs at \url{https://github.com/daattali/timevis/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Dean Attali \email{daattali@gmail.com} (\href{https://orcid.org/0000-0002-5645-3493}{ORCID}) (R interface) 22 | 23 | Authors: 24 | \itemize{ 25 | \item Almende B.V. (vis.js Timeline library, https://visjs.github.io/vis-timeline/docs/timeline/) [copyright holder] 26 | } 27 | 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/fitWindow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{fitWindow} 4 | \alias{fitWindow} 5 | \title{Adjust the visible window such that it fits all items} 6 | \usage{ 7 | fitWindow(id, options) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{options}{Named list of options controlling the animation. Most common 13 | option is \code{"animation" = TRUE/FALSE}. For a full list of options, see 14 | the "fit" method in the 15 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 16 | Timeline documentation}} 17 | } 18 | \description{ 19 | Adjust the visible window such that it fits all items 20 | } 21 | \examples{ 22 | if (interactive()) { 23 | library(shiny) 24 | shinyApp( 25 | ui = fluidPage( 26 | timevisOutput("timeline"), 27 | actionButton("btn", "Fit all items") 28 | ), 29 | server = function(input, output) { 30 | output$timeline <- renderTimevis( 31 | timevis(data.frame( 32 | start = c(Sys.Date(), Sys.Date() - 1), content = c("1", "2") 33 | )) 34 | ) 35 | observeEvent(input$btn, { 36 | fitWindow("timeline", list(animation = FALSE)) 37 | }) 38 | } 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /man/removeCustomTime.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{removeCustomTime} 4 | \alias{removeCustomTime} 5 | \title{Remove a custom time previously added} 6 | \usage{ 7 | removeCustomTime(id, itemId) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{itemId}{The id of the custom time bar} 13 | } 14 | \description{ 15 | Remove a custom time previously added 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis() \%>\% 20 | addCustomTime(Sys.Date() - 1, "yesterday") \%>\% 21 | addCustomTime(Sys.Date() + 1, "tomorrow") \%>\% 22 | removeCustomTime("yesterday") 23 | } 24 | 25 | if (interactive()) { 26 | library(shiny) 27 | shinyApp( 28 | ui = fluidPage( 29 | timevisOutput("timeline"), 30 | actionButton("btn0", "Add custom time"), 31 | actionButton("btn", "Remove custom time bar") 32 | ), 33 | server = function(input, output) { 34 | output$timeline <- renderTimevis( 35 | timevis() 36 | ) 37 | observeEvent(input$btn0, { 38 | addCustomTime("timeline", Sys.Date() - 1, "yesterday") 39 | }) 40 | observeEvent(input$btn, { 41 | removeCustomTime("timeline", "yesterday") 42 | }) 43 | } 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /man/centerTime.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{centerTime} 4 | \alias{centerTime} 5 | \title{Move the window such that the given time is centered} 6 | \usage{ 7 | centerTime(id, time, options) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{time}{The date/time to center around} 13 | 14 | \item{options}{Named list of options controlling the animation. Most common 15 | option is \code{"animation" = TRUE/FALSE}. For a full list of options, see 16 | the "moveTo" method in the 17 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 18 | Timeline documentation}} 19 | } 20 | \description{ 21 | Move the window such that the given time is centered 22 | } 23 | \examples{ 24 | \dontrun{ 25 | timevis() \%>\% 26 | centerTime(Sys.Date() - 1) 27 | } 28 | 29 | if (interactive()) { 30 | library(shiny) 31 | shinyApp( 32 | ui = fluidPage( 33 | timevisOutput("timeline"), 34 | actionButton("btn", "Center around 24 hours ago") 35 | ), 36 | server = function(input, output) { 37 | output$timeline <- renderTimevis( 38 | timevis() 39 | ) 40 | observeEvent(input$btn, { 41 | centerTime("timeline", Sys.Date() - 1) 42 | }) 43 | } 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | # register a handler to decode the timeline data passed from JS to R 3 | # because the default way of decoding it in shiny flattens a data.frame 4 | # to a vector 5 | shiny::registerInputHandler("timevisDF", function(data, ...) { 6 | jsonlite::fromJSON(jsonlite::toJSON(data, auto_unbox = TRUE)) 7 | }, force = TRUE) 8 | } 9 | 10 | # Check if an argument is a single boolean value 11 | is.bool <- function(x) { 12 | is.logical(x) && length(x) == 1 && !is.na(x) 13 | } 14 | 15 | # Convert a data.frame to a list of lists (the data format that D3 uses) 16 | dataframeToD3 <- function(df) { 17 | if (missing(df) || is.null(df)) { 18 | return(list()) 19 | } 20 | if (!is.data.frame(df)) { 21 | stop("timevis: the input must be a dataframe", call. = FALSE) 22 | } 23 | df <- as.data.frame(df) 24 | row.names(df) <- NULL 25 | lapply(seq_len(nrow(df)), function(row) { 26 | row <- df[row, , drop = FALSE] 27 | row_not_na <- row[, !is.na(row), drop = FALSE] 28 | lapply(row_not_na, function(x) { 29 | if (!lengths(x)) return(NA) 30 | if (is.logical(x)) return(x) 31 | if (lengths(x) > 1 | is.list(x)) return(lapply(unlist(x),as.character)) 32 | return(as.character(x)) 33 | }) 34 | }) 35 | } 36 | 37 | #' @importFrom magrittr %>% 38 | #' @export 39 | magrittr::`%>%` 40 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: timevis 2 | Title: Create Interactive Timeline Visualizations in R 3 | Version: 2.1.0.9000 4 | Authors@R: c( 5 | person("Dean", "Attali", email = "daattali@gmail.com", 6 | role = c("aut", "cre"), comment = c("R interface", ORCID="0000-0002-5645-3493")), 7 | person(family = "Almende B.V.", role = c("aut", "cph"), 8 | comment = "vis.js Timeline library, https://visjs.github.io/vis-timeline/docs/timeline/") 9 | ) 10 | Description: Create rich and fully interactive timeline visualizations. 11 | Timelines can be included in Shiny apps or R markdown documents. 12 | 'timevis' includes an extensive API to manipulate a timeline after creation, 13 | and supports getting data out of the visualization into R. Based on the 14 | 'vis.js' Timeline JavaScript library. 15 | URL: https://github.com/daattali/timevis, https://daattali.com/shiny/timevis-demo/ 16 | BugReports: https://github.com/daattali/timevis/issues 17 | Depends: 18 | R (>= 3.1.0) 19 | Imports: 20 | crosstalk, 21 | htmltools (>= 0.2.6), 22 | htmlwidgets (>= 0.6), 23 | jsonlite, 24 | magrittr, 25 | methods, 26 | rmarkdown, 27 | shiny 28 | Suggests: 29 | lubridate, 30 | testthat (>= 0.9.1), 31 | shinydisconnect 32 | License: MIT + file LICENSE 33 | Encoding: UTF-8 34 | LazyData: true 35 | RoxygenNote: 7.2.3 36 | Roxygen: list(markdown = TRUE) 37 | -------------------------------------------------------------------------------- /man/setWindow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setWindow} 4 | \alias{setWindow} 5 | \title{Set the current visible window} 6 | \usage{ 7 | setWindow(id, start, end, options) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{start}{The start date/time to show in the timeline} 13 | 14 | \item{end}{The end date/time to show in the timeline} 15 | 16 | \item{options}{Named list of options controlling mainly the animation. 17 | Most common option is \code{animation = TRUE/FALSE}. For a full list of 18 | options, see the "setWindow" method in the 19 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 20 | Timeline documentation}} 21 | } 22 | \description{ 23 | Set the current visible window 24 | } 25 | \examples{ 26 | \dontrun{ 27 | timevis() \%>\% 28 | setWindow(Sys.Date() - 1, Sys.Date() + 1) 29 | } 30 | 31 | if (interactive()) { 32 | library(shiny) 33 | shinyApp( 34 | ui = fluidPage( 35 | timevisOutput("timeline"), 36 | actionButton("btn", "Set window to show between yesterday to tomorrow") 37 | ), 38 | server = function(input, output) { 39 | output$timeline <- renderTimevis( 40 | timevis() 41 | ) 42 | observeEvent(input$btn, { 43 | setWindow("timeline", Sys.Date() - 1, Sys.Date() + 1) 44 | }) 45 | } 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /inst/example/ui-helpers.R: -------------------------------------------------------------------------------- 1 | share <- list( 2 | title = "timevis - An R package for creating timeline visualizations", 3 | url = "https://daattali.com/shiny/timevis-demo/", 4 | image = "https://daattali.com/shiny/img/timevis-demo.png", 5 | description = "Create rich and fully interactive timeline visualizations. Timelines can be included in Shiny apps and R markdown documents, or viewed from the R console and RStudio Viewer.", 6 | twitter_user = "daattali" 7 | ) 8 | 9 | codeConsole <- 10 | 'library(timevis) 11 | 12 | data <- data.frame( 13 | id = 1:4, 14 | content = c("Item one", "Item two", 15 | "Ranged item", "Item four"), 16 | start = c("2016-01-10", "2016-01-11", 17 | "2016-01-20", "2016-02-14 15:00:00"), 18 | end = c(NA, NA, "2016-02-04", NA) 19 | ) 20 | 21 | timevis(data)' 22 | 23 | codeShiny <- 24 | 'library(shiny) 25 | library(timevis) 26 | 27 | data <- data.frame( 28 | id = 1:4, 29 | content = c("Item one", "Item two", 30 | "Ranged item", "Item four"), 31 | start = c("2016-01-10", "2016-01-11", 32 | "2016-01-20", "2016-02-14 15:00:00"), 33 | end = c(NA, NA, "2016-02-04", NA) 34 | ) 35 | 36 | ui <- fluidPage( 37 | timevisOutput("timeline") 38 | ) 39 | 40 | server <- function(input, output, session) { 41 | output$timeline <- renderTimevis({ 42 | timevis(data) 43 | }) 44 | } 45 | 46 | shinyApp(ui = ui, server = server) 47 | ' 48 | -------------------------------------------------------------------------------- /man/setSelection.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setSelection} 4 | \alias{setSelection} 5 | \title{Select one or multiple items on a timeline} 6 | \usage{ 7 | setSelection(id, itemId, options) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{itemId}{A vector (or single value) of the item ids to select} 13 | 14 | \item{options}{Named list of options controlling mainly the animation. 15 | Most common options are \code{focus = TRUE/FALSE} and 16 | \code{"animation" = TRUE/FALSE}. For a full list of options, see 17 | the "setSelection" method in the 18 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 19 | Timeline documentation}} 20 | } 21 | \description{ 22 | Select one or multiple items on a timeline 23 | } 24 | \examples{ 25 | \dontrun{ 26 | timevis(data.frame(start = Sys.Date(), content = 1:3)) \%>\% 27 | setSelection(2) 28 | } 29 | 30 | if (interactive()) { 31 | library(shiny) 32 | shinyApp( 33 | ui = fluidPage( 34 | timevisOutput("timeline"), 35 | actionButton("btn", "Select item 2") 36 | ), 37 | server = function(input, output) { 38 | output$timeline <- renderTimevis( 39 | timevis( 40 | data.frame(start = Sys.Date(), content = 1:3) 41 | ) 42 | ) 43 | observeEvent(input$btn, { 44 | setSelection("timeline", 2) 45 | }) 46 | } 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /man/setOptions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setOptions} 4 | \alias{setOptions} 5 | \title{Update the configuration options of a timeline} 6 | \usage{ 7 | setOptions(id, options) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{options}{A named list containing updated configuration options to use. 13 | See the \code{options} parameter of the 14 | \code{\link[timevis]{timevis}} function to see more details.} 15 | } 16 | \description{ 17 | Update the configuration options of a timeline 18 | } 19 | \examples{ 20 | \dontrun{ 21 | timevis( 22 | data.frame(start = Sys.Date(), content = "Today"), 23 | options = list(showCurrentTime = FALSE, orientation = "top") 24 | ) \%>\% 25 | setOptions(list(editable = TRUE, showCurrentTime = TRUE)) 26 | } 27 | 28 | if (interactive()) { 29 | library(shiny) 30 | shinyApp( 31 | ui = fluidPage( 32 | timevisOutput("timeline"), 33 | actionButton("btn", "Show current time and allow items to be editable") 34 | ), 35 | server = function(input, output) { 36 | output$timeline <- renderTimevis( 37 | timevis( 38 | data.frame(start = Sys.Date(), content = "Today"), 39 | options = list(showCurrentTime = FALSE, orientation = "top") 40 | ) 41 | ) 42 | observeEvent(input$btn, { 43 | setOptions("timeline", list(editable = TRUE, showCurrentTime = TRUE)) 44 | }) 45 | } 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /man/setGroups.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{setGroups} 4 | \alias{setGroups} 5 | \title{Set the groups of a timeline} 6 | \usage{ 7 | setGroups(id, data) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{data}{A dataframe containing the groups data to use.} 13 | } 14 | \description{ 15 | Set the groups of a timeline 16 | } 17 | \examples{ 18 | \dontrun{ 19 | timevis(data = data.frame( 20 | start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2), 21 | content = c("one", "two", "three", "four"), 22 | group = c(1, 2, 1, 2)), 23 | groups = data.frame(id = 1:2, content = c("G1", "G2")) 24 | ) \%>\% 25 | setGroups(data.frame(id = 1:2, content = c("Group 1", "Group 2"))) 26 | } 27 | 28 | if (interactive()) { 29 | library(shiny) 30 | shinyApp( 31 | ui = fluidPage( 32 | timevisOutput("timeline"), 33 | actionButton("btn", "Change group names") 34 | ), 35 | server = function(input, output) { 36 | output$timeline <- renderTimevis( 37 | timevis(data = data.frame( 38 | start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2), 39 | content = c("one", "two", "three", "four"), 40 | group = c(1, 2, 1, 2)), 41 | groups = data.frame(id = 1:2, content = c("G1", "G2"))) 42 | 43 | ) 44 | observeEvent(input$btn, { 45 | setGroups("timeline", 46 | data.frame(id = 1:2, content = c("Group 1", "Group 2"))) 47 | }) 48 | } 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /man/zoom.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{zoom} 4 | \alias{zoom} 5 | \alias{zoomIn} 6 | \alias{zoomOut} 7 | \title{Zoom in/out the current visible window} 8 | \usage{ 9 | zoomIn(id, percent = 0.5, animation = TRUE) 10 | 11 | zoomOut(id, percent = 0.5, animation = TRUE) 12 | } 13 | \arguments{ 14 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 15 | 16 | \item{percent}{The amount to zoom in or out. Must be a number between 0 and 1. 17 | A value of 0.5 means that after zooming out the timeline will show 50\% more content.} 18 | 19 | \item{animation}{Whether or not to animate the zoom.} 20 | } 21 | \description{ 22 | Zoom in/out the current visible window 23 | } 24 | \examples{ 25 | \dontrun{ 26 | timevis() \%>\% 27 | zoomIn() 28 | 29 | timevis() \%>\% 30 | zoomOut(0.3) 31 | } 32 | 33 | if (interactive()) { 34 | library(shiny) 35 | shinyApp( 36 | ui = fluidPage( 37 | timevisOutput("timeline"), 38 | sliderInput("zoom", "Zoom by", min = 0, max = 1, value = 0.5, step = 0.1), 39 | checkboxInput("animate", "Animate?", TRUE), 40 | actionButton("zoomIn", "Zoom IN"), 41 | actionButton("zoomOut", "Zoom OUT") 42 | ), 43 | server = function(input, output) { 44 | output$timeline <- renderTimevis( 45 | timevis() 46 | ) 47 | observeEvent(input$zoomIn, { 48 | zoomIn("timeline", percent = input$zoom, animation = input$animate) 49 | }) 50 | observeEvent(input$zoomOut, { 51 | zoomOut("timeline", percent = input$zoom, animation = input$animate) 52 | }) 53 | } 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /man/centerItem.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/api.R 3 | \name{centerItem} 4 | \alias{centerItem} 5 | \title{Move the window such that given item or items are centered} 6 | \usage{ 7 | centerItem(id, itemId, options) 8 | } 9 | \arguments{ 10 | \item{id}{Timeline id or a \code{timevis} object (the output from \code{timevis()})} 11 | 12 | \item{itemId}{A vector (or single value) of the item ids to center} 13 | 14 | \item{options}{Named list of options controlling mainly the animation. 15 | Most common option is \code{"animation" = TRUE/FALSE}. For a full list of 16 | options, see the "focus" method in the 17 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 18 | Timeline documentation}} 19 | } 20 | \description{ 21 | Move the window such that given item or items are centered 22 | } 23 | \examples{ 24 | \dontrun{ 25 | timevis(data.frame( 26 | start = c(Sys.Date() - 1, Sys.Date(), Sys.Date() + 1), 27 | content = c("Item 1", "Item 2", "Item 3")) 28 | ) \%>\% 29 | centerItem(1) 30 | } 31 | 32 | if (interactive()) { 33 | library(shiny) 34 | shinyApp( 35 | ui = fluidPage( 36 | timevisOutput("timeline"), 37 | actionButton("btn", "Center around item 1") 38 | ), 39 | server = function(input, output) { 40 | output$timeline <- renderTimevis( 41 | timevis( 42 | data.frame( 43 | start = c(Sys.Date() - 1, Sys.Date(), Sys.Date() + 1), 44 | content = c("Item 1", "Item 2", "Item 3")) 45 | ) 46 | ) 47 | observeEvent(input$btn, { 48 | centerItem("timeline", 1) 49 | }) 50 | } 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /man/timevis-shiny.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timevis.R 3 | \name{timevis-shiny} 4 | \alias{timevis-shiny} 5 | \alias{timevisOutput} 6 | \alias{renderTimevis} 7 | \title{Shiny bindings for timevis} 8 | \usage{ 9 | timevisOutput(outputId, width = "100\%", height = "auto") 10 | 11 | renderTimevis(expr, env = parent.frame(), quoted = FALSE) 12 | } 13 | \arguments{ 14 | \item{outputId}{output variable to read from} 15 | 16 | \item{width, height}{Must be a valid CSS unit (like \code{'100\%'}, 17 | \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 18 | string and have \code{'px'} appended. \code{height} will probably not 19 | have an effect; instead, use the \code{height} parameter in 20 | \code{\link[timevis]{timevis}}.} 21 | 22 | \item{expr}{An expression that generates a timevis} 23 | 24 | \item{env}{The environment in which to evaluate \code{expr}.} 25 | 26 | \item{quoted}{Is \code{expr} a quoted expression (with \code{quote()})? This 27 | is useful if you want to save an expression in a variable.} 28 | } 29 | \description{ 30 | Output and render functions for using timevis within Shiny 31 | applications and interactive Rmd documents. 32 | } 33 | \examples{ 34 | if (interactive()) { 35 | library(shiny) 36 | 37 | #----------------------- Most basic example ----------------- 38 | shinyApp( 39 | ui = fluidPage(timevisOutput("timeline")), 40 | server = function(input, output) { 41 | output$timeline <- renderTimevis( 42 | timevis() 43 | ) 44 | } 45 | ) 46 | 47 | 48 | #----------------------- More advanced example ----------------- 49 | data <- data.frame( 50 | start = c("2015-04-04", "2015-04-05 11:00:00", "2015-04-06 15:00:00"), 51 | end = c("2015-04-08", NA, NA), 52 | content = c("

Vacation!!!

", "Acupuncture", "Massage"), 53 | style = c("color: red;", NA, NA) 54 | ) 55 | 56 | ui <- fluidPage( 57 | timevisOutput("appts"), 58 | div("Selected items:", textOutput("selected", inline = TRUE)), 59 | div("Visible window:", textOutput("window", inline = TRUE)), 60 | tableOutput("table") 61 | ) 62 | 63 | server <- function(input, output) { 64 | output$appts <- renderTimevis( 65 | timevis( 66 | data, 67 | options = list(editable = TRUE, multiselect = TRUE, align = "center") 68 | ) 69 | ) 70 | 71 | output$selected <- renderText( 72 | paste(input$appts_selected, collapse = " ") 73 | ) 74 | 75 | output$window <- renderText( 76 | paste(input$appts_window[1], "to", input$appts_window[2]) 77 | ) 78 | 79 | output$table <- renderTable( 80 | input$appts_data 81 | ) 82 | } 83 | shinyApp(ui, server) 84 | } 85 | 86 | } 87 | \seealso{ 88 | \code{\link[timevis]{timevis}}. 89 | } 90 | -------------------------------------------------------------------------------- /tests/testthat/test-dataframeToD3.R: -------------------------------------------------------------------------------- 1 | context("dataframeToD3") 2 | 3 | test_that("dataframeToD3 works with no input", { 4 | expect_identical(dataframeToD3(), list()) 5 | expect_identical(dataframeToD3(data.frame()), list()) 6 | }) 7 | 8 | test_that("dataframeToD3 works with a single row", { 9 | df <- data.frame(name = "Dean", age = 27, stringsAsFactors = FALSE) 10 | list <- list(list(name = "Dean", age = "27")) 11 | expect_identical(dataframeToD3(df), list, 12 | info = info_comp(dataframeToD3(df), list)) 13 | }) 14 | 15 | test_that("dataframeToD3 works with multiple rows", { 16 | df <- data.frame(name = c("Dean", "Ben"), age = c(27, 24)) 17 | list <- list(list(name = "Dean", age = "27"), 18 | list(name = "Ben", age = "24")) 19 | expect_identical(dataframeToD3(df), list, 20 | info = info_comp(dataframeToD3(df), list)) 21 | }) 22 | 23 | test_that("dataframeToD3 works with NA values", { 24 | df <- data.frame(name = c("Dean", "Ben"), age = c(27, 24), degree = c("MSc", NA)) 25 | list <- list(list(name = "Dean", age = "27", degree = "MSc"), 26 | list(name = "Ben", age = "24")) 27 | expect_identical(dataframeToD3(df), list, 28 | info = info_comp(dataframeToD3(df), list)) 29 | }) 30 | 31 | test_that("dataframeToD3 errors when given a non-dataframe", { 32 | expect_error(dataframeToD3(50), "input must be a dataframe") 33 | expect_error(dataframeToD3(FALSE), "input must be a dataframe") 34 | }) 35 | 36 | test_that("dataframeToD3 returns the same whether the dataframe is pure or merged", { 37 | df <- data.frame(name = c("Dean", "Ben"), age = c(27, 24)) 38 | df_rbind <- rbind(df[1, ], df[2, ]) 39 | expect_identical(dataframeToD3(df), dataframeToD3(df_rbind), 40 | info = info_comp(dataframeToD3(df), dataframeToD3(df_rbind))) 41 | }) 42 | 43 | 44 | test_that("nested columns behave the way they ought to",{ 45 | matts_hobbies <- c("Working", "thinking about work") 46 | df <- data.frame(name = c("Dean", "Matt"), 47 | age = c(27, 23), 48 | hobbies = I(list(NA, matts_hobbies))) 49 | out <- dataframeToD3(df) 50 | expect <- list(list(name = "Dean", age = "27"), 51 | list(name = "Matt", age = "23", hobbies = as.list(matts_hobbies))) 52 | expect_identical(out, expect, info = info_comp(out, expect)) 53 | 54 | 55 | deans_hobby <- "Responding to pull requests" 56 | df <- data.frame(name = c("Dean", "Matt"), 57 | age = c(27, 23), 58 | hobbies = I(list(deans_hobby, matts_hobbies))) 59 | expect <- list(list(name = "Dean", age = "27", hobbies = as.list(deans_hobby)), 60 | list(name = "Matt", age = "23", hobbies = as.list(matts_hobbies))) 61 | out <- dataframeToD3(df) 62 | expect_identical(out, expect, info = info_comp(out, expect)) 63 | 64 | df$hobbies[[1]] <- NA 65 | expect[[1]]$hobbies <- NULL 66 | out <- dataframeToD3(df) 67 | expect_identical(out, expect, info = info_comp(out, expect)) 68 | }) 69 | -------------------------------------------------------------------------------- /inst/example/sampleData.R: -------------------------------------------------------------------------------- 1 | # Simple timeline with 4 items 2 | dataBasic <- data.frame( 3 | content = c("Item one", "Item two" ,"Ranged item", "Item four"), 4 | start = c("2016-01-10", "2016-01-11", "2016-01-20", "2016-02-14"), 5 | end = c(NA, NA, "2016-02-04", NA) 6 | ) 7 | 8 | countryToCode <- list( 9 | "Argentina" = "ar", 10 | "Belgium" = "be", 11 | "Brazil" = "br", 12 | "Colombia" = "co", 13 | "Costa Rica" = "cr", 14 | "France" = "fr", 15 | "Germany" = "de", 16 | "Netherlands" = "nl" 17 | ) 18 | 19 | # Template for world cup HTML of each item 20 | templateWC <- function(stage, team1, team2, score1, score2) { 21 | sprintf( 22 | ' 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
%s
%s %s - %s %s
%s%s
', 35 | stage, team1, score1, score2, team2, countryToCode[[team1]], team1, countryToCode[[team2]], team2 36 | ) 37 | } 38 | 39 | # Data for world cup 2014 40 | dataWC <- data.frame( 41 | start = c( 42 | "2014-07-04 13:00", 43 | "2014-07-04 17:00", 44 | "2014-07-05 13:00", 45 | "2014-07-05 17:00", 46 | "2014-07-08 17:00", 47 | "2014-07-09 17:00", 48 | "2014-07-13 16:00" 49 | ), 50 | content = c( 51 | templateWC("quarter-finals", "France", "Germany", 0, 1), 52 | templateWC("quarter-finals", "Brazil", "Colombia", 2, 1), 53 | templateWC("quarter-finals", "Argentina", "Belgium", 1, 0), 54 | templateWC("quarter-finals", "Netherlands", "Costa Rica", "0 (4)", "0 (3)"), 55 | templateWC("semi-finals", "Brazil", "Germany", 1, 7), 56 | templateWC("semi-finals", "Netherlands", "Argentina", "0 (2)", "0 (4)"), 57 | templateWC("final", "Germany", "Argentina", 1, 0) 58 | ), 59 | goalsHome = c(0, 2, 1, 4, 1, 2, 1), 60 | goalsAway = c(1, 1, 0, 3, 7, 4, 0) 61 | ) 62 | 63 | # Data for groups example (this data also gets exported in the package) 64 | timevisData <- data.frame( 65 | content = c( 66 | "Open", "Open", "Half price entry", 67 | "Open", "Adults only", "Open", "Staff meeting", 68 | "Open", "Men only", "Women only", "Open", 69 | "Hot water", "Very hot water", 70 | "Siesta"), 71 | start = c( 72 | "2016-05-01 06:00:00", "2016-05-01 14:00:00", "2016-05-01 08:00:00", 73 | "2016-05-01 08:00:00", "2016-05-01 14:00:00", 74 | "2016-05-01 16:00:00", "2016-05-01 19:30:00", 75 | "2016-05-01 08:00:00", "2016-05-01 14:00:00", "2016-05-01 16:00:00", "2016-05-01 18:00:00", 76 | "2016-05-01 09:00:00", "2016-05-01 14:00:00", 77 | "2016-05-01 12:00:00"), 78 | end = c( 79 | "2016-05-01 12:00:00", "2016-05-01 22:00:00", "2016-05-01 10:00:00", 80 | "2016-05-01 12:00:00", "2016-05-01 16:00:00", 81 | "2016-05-01 20:00:00", NA, 82 | "2016-05-01 12:00:00", "2016-05-01 16:00:00", "2016-05-01 18:00:00", "2016-05-01 20:00:00", 83 | "2016-05-01 12:00:00", "2016-05-01 17:00:00", 84 | "2016-05-01 14:00:00"), 85 | group = c(rep("gym", 3), rep("pool", 4), rep("sauna", 4), rep("tub", 2), NA), 86 | type = c(rep("range", 6), "point", rep("range", 6), "background") 87 | ) 88 | timevisDataGroups <- data.frame( 89 | id = c("gym", "pool", "sauna", "tub"), 90 | content = c("Gym", "Pool", "Sauna", "Hot Tub") 91 | ) 92 | -------------------------------------------------------------------------------- /inst/example/server.R: -------------------------------------------------------------------------------- 1 | library(timevis) 2 | 3 | source("sampleData.R") 4 | source("utils.R") 5 | 6 | function(input, output, session) { 7 | output$timelineBasic <- renderTimevis({ 8 | timevis(dataBasic) 9 | }) 10 | 11 | output$timelineWC <- renderTimevis({ 12 | timevis(dataWC) 13 | }) 14 | 15 | output$timelineGroups <- renderTimevis({ 16 | groups <- timevisDataGroups 17 | if (input$nested) { 18 | groups$nestedGroups <- I(list(NA, list("sauna", "tub"), NA, NA)) 19 | } 20 | timevis(data = timevisData, groups = groups, options = list(editable = TRUE)) 21 | }) 22 | 23 | output$timelineCustom <- renderTimevis({ 24 | config <- list( 25 | editable = TRUE, 26 | align = "center", 27 | orientation = "top", 28 | snap = NULL, 29 | margin = list(item = 30, axis = 50) 30 | ) 31 | timevis(dataBasic, zoomFactor = 1, options = config) 32 | }) 33 | 34 | output$timelineInteractive <- renderTimevis({ 35 | config <- list( 36 | editable = TRUE, 37 | multiselect = TRUE 38 | ) 39 | timevis(dataBasic, options = config) 40 | }) 41 | 42 | output$visible <- renderText( 43 | paste(input$timelineInteractive_visible, collapse = " ") 44 | ) 45 | output$selected <- renderText( 46 | paste(input$timelineInteractive_selected, collapse = " ") 47 | ) 48 | output$window <- renderText( 49 | paste(prettyDate(input$timelineInteractive_window[1]), 50 | "to", 51 | prettyDate(input$timelineInteractive_window[2])) 52 | ) 53 | output$table <- renderTable({ 54 | data <- input$timelineInteractive_data 55 | data$start <- prettyDate(data$start) 56 | if (!is.null(data$end)) { 57 | data$end <- prettyDate(data$end) 58 | } 59 | data 60 | }) 61 | output$selectIdsOutput <- renderUI({ 62 | selectInput("selectIds", tags$h4("Select items:"), input$timelineInteractive_ids, 63 | multiple = TRUE) 64 | }) 65 | output$removeIdsOutput <- renderUI({ 66 | selectInput("removeIds", tags$h4("Remove item"), input$timelineInteractive_ids) 67 | }) 68 | 69 | observeEvent(input$fit, { 70 | fitWindow("timelineInteractive") 71 | }) 72 | observeEvent(input$setWindowAnim, { 73 | setWindow("timelineInteractive", "2016-01-07", "2016-01-25") 74 | }) 75 | observeEvent(input$setWindowNoAnim, { 76 | setWindow("timelineInteractive", "2016-01-07", "2016-01-25", 77 | options = list(animation = FALSE)) 78 | }) 79 | observeEvent(input$center, { 80 | centerTime("timelineInteractive", "2016-01-23") 81 | }) 82 | observeEvent(input$focus2, { 83 | centerItem("timelineInteractive", 4) 84 | }) 85 | observeEvent(input$focusSelection, { 86 | centerItem("timelineInteractive", input$timelineInteractive_selected) 87 | }) 88 | observeEvent(input$selectItems, { 89 | setSelection("timelineInteractive", input$selectIds, 90 | options = list(focus = input$selectFocus)) 91 | }) 92 | observeEvent(input$addBtn, { 93 | addItem("timelineInteractive", 94 | data = list(id = randomID(), 95 | content = input$addText, 96 | start = input$addDate)) 97 | }) 98 | observeEvent(input$removeItem, { 99 | removeItem("timelineInteractive", input$removeIds) 100 | }) 101 | observeEvent(input$addTime, { 102 | addCustomTime("timelineInteractive", "2016-01-17", randomID()) 103 | }) 104 | observeEvent(input$zoomIn, { 105 | zoomIn("timelineInteractive", input$zoomBy, animation = input$animate) 106 | }) 107 | observeEvent(input$zoomOut, { 108 | zoomOut("timelineInteractive", input$zoomBy, animation = input$animate) 109 | }) 110 | 111 | shared_df <- crosstalk::SharedData$new(dataWC) 112 | 113 | output$timelineCrosstalk <- renderTimevis({ 114 | timevis(shared_df, options = list(multiselect = TRUE)) 115 | }) 116 | output$plot <- d3scatter::renderD3scatter({ 117 | d3scatter::d3scatter(shared_df, x = ~goalsHome, y = ~goalsAway, 118 | x_label = "Home Goals", y_label = "Away Goals") 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | - TODO: make a pull request on crosstalk website to add timevis as a compatible widget 4 | - Add {crosstalk} support for linked brushing and filtering with other crosstalk-enabled widgets (#95) 5 | - If `id` column is not provided in the data for a timevis, the row names will be used as IDs 6 | 7 | # timevis 2.1.0 (2022-10-22) 8 | 9 | - Add functions `zoomIn()` and `zoomOut()` 10 | - List-columns can now be passed to `timevis()` in the `data` or `groups` dataframe, to support `nestedGroups` and any other parameters that may require nested lists (#11) 11 | - Boolean values can be passed to `timevis()` in the `data` or `groups` dataframe, to support `subgroupStack = TRUE` option (#117) 12 | - Simplify documentation and augment with more examples 13 | 14 | # timevis 2.0.0 (2021-12-20) 15 | 16 | - **BREAKING CHANGE** Upgrade to visjs-Timeline version 7.4.9 which includes many new features and performance improvements. You may notice some small differences in behaviour when upgrading as the new version includes 5 years of developments. (#50) 17 | - One of the major differences is that previously when a config option (such as `editable=TRUE`) was set on an existing timeline, the option would apply retroactively to all existing items. In the new version, existing items don't change and only new items will inherit the new options. 18 | - You may also notice a difference in the way that editable items "snap" to the timeline when you modify their time. You can control the snapping behaviour, for example to only allow items to snap to round hours you can use `options = list(editable = TRUE, snap = htmlwidgets::JS("function (date, scale, step) { var hour = 60 * 60 * 1000; return Math.round(date / hour) * hour; }"))` 19 | - Add documentation about `id` parameter (#103) 20 | 21 | # timevis 1.0.0 (2020-09-15) 22 | 23 | - **BREAKING CHANGE** API functions now work in shiny modules without having to specify the namespace. This means that if you previously explicitly used a namespace when calling API functions, you need to remove the namespace function (#90) 24 | - Add `timezone` parameter to `timevis()` - supports showing the timeline in a different timezone than your local machine (#67) 25 | - Add `setCustomTime()` and `setCurrentTime()` functions (#20) 26 | - Add `_visible` Shiny input that sends the items that are currently visible in the timeline to Shiny as an input (#22) 27 | - Fixed bug: using `setSelection()` did not trigger the Shiny selected input (#82) 28 | - Add documentation about how to add custom style to timevis (#45) 29 | - Add documentation about how to use BCE dates (#99) 30 | - Add documentation tab to the demo app 31 | 32 | # timevis 0.5 (2019-01-16) 33 | 34 | - `tibble`s converted to `data.frame`s (Fixes issue #53, @muschellij2). 35 | - added documentation for how to extend the timevis object in JavaScript 36 | - added an option to not load the javascript dependencies (#25) 37 | - Fix issue #47: Leading whitespace when getting selected item Id as a string 38 | - Show correct timezone in demo app 39 | - Add demo advanced data `timevisData` and `timevisDataGroups` 40 | 41 | # timevis 0.4 (2016-09-16) 42 | 43 | ### Bug fixes 44 | 45 | - timevis and visNetwork can work together in the same app (the bug is fixed on timevis' end, so it will only work if a timevis widget is defined before a visNetwork one until visNetwork also fix the bug) (#11) 46 | - re-defining the data input handler does not cause a warning 47 | 48 | # timevis 0.3 (2016-08-29) 49 | 50 | #### Misc 51 | 52 | - Added VignetteBuilder field to DESCRIPTION file as per CRAN's request 53 | 54 | # timevis 0.2 (2016-08-24) 55 | 56 | ### Breaking changes 57 | 58 | - added support for groups (#1) (parameter order for `timevis()` has changed) 59 | 60 | ### New features 61 | 62 | - add `fit` param to `timevis()` that determines if to fit the items on the timeline by default or not 63 | - timevis can now be included inside regular R markdown documents 64 | - all the API functions can now work on timeline widgets that are not yet initialized, which means they can work in rmarkdown documents or in the console 65 | - all the API functions can now work with pipes (`%>%` pipelines) 66 | - the API functions can accept either an ID of a widget or a widget object. This is useful because now the API functions can either be called from the server of a Shiny app at any point, or they can be called directly using the widget when it is being initialized 67 | 68 | ### Bug fixes 69 | 70 | - when re-rendering a timeline, the old data are not removed (#3) 71 | - can now use a dataframe that results from merging/binding two dataframes as input (#7) 72 | - zoom buttons show up when using a custom `shinytheme()` (#9) 73 | 74 | ### Misc 75 | 76 | - removed the `getData` `getWindow` `getIds` `getSelected` parameters and instead just return that info always (#4) 77 | - refactor and modularize Shiny app code 78 | - UI improvements to Shiny app 79 | - added source code to Shiny app 80 | - added social media meta tags to Shiny app 81 | - add a lot of responsive CSS to make the app look well in smaller screens 82 | 83 | # timevis 0.1 (2016-07-25) 84 | 85 | Initial CRAN release 86 | -------------------------------------------------------------------------------- /inst/example/www/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | } 4 | 5 | #header { 6 | text-align: center; 7 | color: #fdfdfd; 8 | text-shadow: 0 0 1px #000; 9 | padding: 20px 0 80px; 10 | background: #0088cc url('background.jpg'); 11 | border-bottom: 1px solid #ddd; 12 | margin: 0 -15px; 13 | } 14 | 15 | #title { 16 | font-size: 5em; 17 | text-shadow: 0 0 5px #000; 18 | } 19 | 20 | #subtitle { 21 | font-size: 2em; 22 | margin-top: -5px; 23 | } 24 | 25 | #subsubtitle { 26 | font-size: 1.3em; 27 | } 28 | 29 | #subsubtitle a { 30 | color: #fdfdfd; 31 | text-decoration: underline; 32 | } 33 | 34 | #mainnav { 35 | margin-top: -50px; 36 | } 37 | 38 | #mainnav>li.active>a, 39 | #mainnav>li.active>a:hover, 40 | #mainnav>li.active>a:focus { 41 | border-top: 2px solid orange; 42 | border-bottom: 0; 43 | font-weight: bold; 44 | } 45 | 46 | #mainnav>li { 47 | margin-left: 10px; 48 | } 49 | 50 | #mainnav>li>a { 51 | background-color: #f0f0f0; 52 | border: 1px solid #dddddd; 53 | padding: 0 20px; 54 | height: 50px; 55 | line-height: 50px; 56 | font-size: 1.2em; 57 | } 58 | 59 | #mainnav>li>a:hover, 60 | #mainnav>li>a:focus { 61 | background-color: #dddddd; 62 | } 63 | 64 | #mainnav>li>a i.fa { 65 | margin-right: 2px; 66 | } 67 | .tab-content>.tab-pane { 68 | padding-top: 40px; 69 | } 70 | 71 | .vis-item .vis-item-overflow { 72 | overflow: visible; 73 | } 74 | 75 | #samplecode { 76 | margin: 50px -15px 0; 77 | padding: 20px 25px; 78 | border-top: 1px solid #ddd; 79 | background: #fcfcfc; 80 | } 81 | 82 | #samplecode pre { 83 | font-size: 1em; 84 | background: #fff; 85 | } 86 | 87 | @media only screen and (max-width: 1100px) { 88 | #header { 89 | padding: 15px 0 65px; 90 | } 91 | #title { 92 | font-size: 4em; 93 | } 94 | #subtitle { 95 | font-size: 2em; 96 | } 97 | #subsubtitle { 98 | font-size: 1.2em; 99 | } 100 | #mainnav { 101 | margin: -35px 0 0 -10px; 102 | } 103 | #mainnav>li { 104 | margin-left: 5px; 105 | } 106 | #mainnav>li>a { 107 | padding: 0 10px; 108 | height: 35px; 109 | line-height: 35px; 110 | font-size: 1.1em; 111 | } 112 | #samplecode pre { 113 | font-size: 0.9em; 114 | } 115 | .sourcecode { 116 | font-size: 1.1em; 117 | } 118 | } 119 | 120 | @media only screen and (max-width: 850px) { 121 | #header { 122 | padding: 15px 0 65px; 123 | } 124 | #title { 125 | font-size: 3.5em; 126 | } 127 | #subtitle { 128 | font-size: 1.7em; 129 | } 130 | #subsubtitle { 131 | font-size: 1.2em; 132 | } 133 | #mainnav { 134 | margin: -35px -15px 0; 135 | } 136 | #mainnav>li { 137 | margin: 0; 138 | float: none; 139 | } 140 | #mainnav>li>a { 141 | margin-right: 0; 142 | padding: 0 10px; 143 | height: 35px; 144 | line-height: 35px; 145 | font-size: 1.1em; 146 | border-radius: 0; 147 | border-bottom-width: 0; 148 | } 149 | #mainnav>li.active>a, 150 | #mainnav>li.active>a:hover, 151 | #mainnav>li.active>a:focus { 152 | border-top: 0 none; 153 | background: #fafafa; 154 | } 155 | #samplecode pre { 156 | font-size: 0.9em; 157 | } 158 | .sourcecode { 159 | font-size: 1.1em; 160 | } 161 | } 162 | 163 | #samplecode .codeformat { 164 | font-size: 1.3em; 165 | margin-bottom: 5px; 166 | } 167 | 168 | .sourcecode { 169 | text-align: center; 170 | font-size: 1.2em; 171 | border-top: 1px solid #ddd; 172 | padding-bottom: 10px; 173 | margin: 0 -15px; 174 | padding-top: 10px; 175 | background: #f8f8f8; 176 | } 177 | 178 | .sourcecode:hover { 179 | background: #f0f0f0; 180 | } 181 | 182 | #timelineCustom { 183 | font-family: purisa, 'comic sans', cursive; 184 | } 185 | 186 | #timelineCustom .vis-timeline { 187 | border: 2px solid purple; 188 | font-size: 12pt; 189 | background: #ffecea; 190 | } 191 | 192 | #timelineCustom .vis-item { 193 | border-color: #F991A3; 194 | background-color: pink; 195 | font-size: 15pt; 196 | color: purple; 197 | box-shadow: 5px 5px 20px rgba(128,128,128, 0.5); 198 | } 199 | 200 | #timelineCustom .vis-item, 201 | #timelineCustom .vis-item.vis-line { 202 | border-width: 3px; 203 | } 204 | 205 | #timelineCustom .vis-item.vis-dot { 206 | border-width: 10px; 207 | border-radius: 10px; 208 | } 209 | 210 | #timelineCustom .vis-item.vis-selected { 211 | border-color: green; 212 | background-color: lightgreen; 213 | } 214 | 215 | #timelineCustom .vis-background .vis-minor.vis-odd { 216 | background: #ffd7d7; 217 | } 218 | 219 | #timelineCustom .vis-time-axis .vis-text { 220 | color: purple; 221 | padding-top: 10px; 222 | padding-left: 10px; 223 | } 224 | 225 | #timelineCustom .vis-time-axis .vis-grid.vis-minor { 226 | border-width: 2px; 227 | border-color: pink; 228 | } 229 | 230 | #timelineCustom .vis-time-axis .vis-grid.vis-major { 231 | border-width: 2px; 232 | border-color: #F991A3; 233 | } 234 | 235 | #timelineInteractive { 236 | box-shadow: 2px 2px 3px #444; 237 | margin-bottom: 30px; 238 | } 239 | 240 | #interactiveActions .btn { 241 | margin-bottom: 10px; 242 | margin-right: 7px; 243 | white-space: normal; 244 | } 245 | 246 | #timelinedata { 247 | margin-bottom: 15px; 248 | } 249 | 250 | .optionsSection { 251 | border: 1px solid #EEE; 252 | border-radius: 3px; 253 | background: #FCFCFC; 254 | padding: 10px; 255 | margin-bottom: 20px; 256 | } 257 | 258 | .optionsSection h4 { 259 | font-size: 1.3em; 260 | font-weight: bold; 261 | margin-bottom: 5px; 262 | margin-top: 0; 263 | } 264 | -------------------------------------------------------------------------------- /inst/example/ui.R: -------------------------------------------------------------------------------- 1 | library(timevis) 2 | 3 | source("ui-helpers.R") 4 | 5 | fluidPage( 6 | shinydisconnect::disconnectMessage2(), 7 | title = "timevis - An R package for creating timeline visualizations", 8 | tags$head( 9 | tags$link(href = "style.css", rel = "stylesheet"), 10 | 11 | # Favicon 12 | tags$link(rel = "shortcut icon", type="image/x-icon", href="https://daattali.com/shiny/img/favicon.ico"), 13 | 14 | # Facebook OpenGraph tags 15 | tags$meta(property = "og:title", content = share$title), 16 | tags$meta(property = "og:type", content = "website"), 17 | tags$meta(property = "og:url", content = share$url), 18 | tags$meta(property = "og:image", content = share$image), 19 | tags$meta(property = "og:description", content = share$description), 20 | 21 | # Twitter summary cards 22 | tags$meta(name = "twitter:card", content = "summary_large_image"), 23 | tags$meta(name = "twitter:site", content = paste0("@", share$twitter_user)), 24 | tags$meta(name = "twitter:creator", content = paste0("@", share$twitter_user)), 25 | tags$meta(name = "twitter:title", content = share$title), 26 | tags$meta(name = "twitter:description", content = share$description), 27 | tags$meta(name = "twitter:image", content = share$image) 28 | ), 29 | div(id = "header", 30 | div(id = "title", 31 | "timevis" 32 | ), 33 | div(id = "subtitle", 34 | "An R package for creating timeline visualizations"), 35 | div(id = "subsubtitle", 36 | "By", 37 | tags$a(href = "https://deanattali.com/", "Dean Attali"), 38 | HTML("•"), 39 | "Available", 40 | tags$a(href = "https://github.com/daattali/timevis", "on GitHub"), 41 | HTML("•"), 42 | tags$a(href = "https://github.com/sponsors/daattali", "Support my work"), "❤" 43 | ), 44 | br(), 45 | tags$a( 46 | "View Documentation", icon("external-link"), 47 | href = "https://github.com/daattali/timevis#readme", 48 | class = "btn btn-lg btn-default", 49 | style = "text-shadow: none;" 50 | ) 51 | ), 52 | tabsetPanel( 53 | id = "mainnav", 54 | tabPanel( 55 | div(icon("calendar"), "Basic timeline"), 56 | timevisOutput("timelineBasic"), 57 | div( 58 | id = "samplecode", 59 | fluidRow( 60 | column( 61 | 6, 62 | div(class = "codeformat", 63 | "In R console or R markdown documents"), 64 | tags$pre(codeConsole) 65 | ), 66 | column( 67 | 6, 68 | div(class = "codeformat", 69 | "In Shiny apps"), 70 | tags$pre(codeShiny) 71 | ) 72 | ) 73 | ) 74 | ), 75 | 76 | tabPanel( 77 | div(icon("cog"), "Custom style"), 78 | timevisOutput("timelineCustom") 79 | ), 80 | 81 | tabPanel( 82 | div(icon("trophy"), "World Cup 2014"), 83 | timevisOutput("timelineWC") 84 | ), 85 | 86 | tabPanel( 87 | div(icon("users"), "Groups"), 88 | checkboxInput("nested", "Use nested groups (Sauna and Hot Tub are collapsible under Pool)", FALSE, width = "auto"), 89 | timevisOutput("timelineGroups") 90 | ), 91 | 92 | tabPanel( 93 | div(icon("sliders-h"), "Fully interactive"), 94 | fluidRow( 95 | column( 96 | 8, 97 | fluidRow(column(12, 98 | timevisOutput("timelineInteractive") 99 | )), 100 | fluidRow( 101 | column( 102 | 12, 103 | div(id = "interactiveActions", 104 | class = "optionsSection", 105 | tags$h4("Actions:"), 106 | actionButton("fit", "Fit all items"), 107 | actionButton("setWindowAnim", "Set window 2016-01-07 to 2016-01-25"), 108 | actionButton("setWindowNoAnim", "Set window without animation"), 109 | actionButton("center", "Center around 2016-01-23"), 110 | actionButton("focus2", "Focus item 4"), 111 | actionButton("focusSelection", "Focus current selection"), 112 | actionButton("addTime", "Add a draggable vertical bar 2016-01-17") 113 | ) 114 | ) 115 | ), 116 | fluidRow( 117 | column( 118 | 3, 119 | div(class = "optionsSection", 120 | uiOutput("selectIdsOutput", inline = TRUE), 121 | actionButton("selectItems", "Select"), 122 | checkboxInput("selectFocus", "Focus on selection", FALSE) 123 | ) 124 | ), 125 | column( 126 | 3, 127 | div(class = "optionsSection", 128 | textInput("addText", tags$h4("Add item:"), "New item"), 129 | dateInput("addDate", NULL, "2016-01-15"), 130 | actionButton("addBtn", "Add") 131 | ) 132 | ), 133 | column( 134 | 3, 135 | div(class = "optionsSection", 136 | uiOutput("removeIdsOutput", inline = TRUE), 137 | actionButton("removeItem", "Remove") 138 | ) 139 | ), 140 | column( 141 | 3, 142 | div( 143 | class = "optionsSection", 144 | sliderInput("zoomBy", tags$h4("Zoom:"), min = 0, max = 1, value = 0.5, step = 0.1), 145 | checkboxInput("animate", "Animate?", TRUE), 146 | actionButton("zoomIn", "Zoom In"), 147 | actionButton("zoomOut", "Zoom Out") 148 | ) 149 | ) 150 | ) 151 | ), 152 | column(4, 153 | div( 154 | id = "timelinedata", 155 | class = "optionsSection", 156 | tags$h4("Data:"), 157 | tableOutput("table"), 158 | hr(), 159 | div(tags$strong("Visible window:"), 160 | textOutput("window", inline = TRUE)), 161 | div(tags$strong("Selected items:"), 162 | textOutput("selected", inline = TRUE)), 163 | div(tags$strong("Visible items:"), 164 | textOutput("visible", inline = TRUE)), 165 | ) 166 | ) 167 | ) 168 | ), 169 | tabPanel( 170 | div(icon("link"), "Crosstalk"), 171 | div( 172 | id = "crosstalk-tab", 173 | tags$p(strong( 174 | "{timevis} is fully compatible with", 175 | tags$a("Crosstalk", href = "https://github.com/rstudio/crosstalk"), 176 | )), 177 | tags$p( 178 | "The data in the timeline and the plot below are linked - select any events in the timeline to see", 179 | "them highlighted on the plot, and vice versa." 180 | ), 181 | HTML("Use ctrl or shift to select multiple events"), 182 | fluidRow( 183 | column( 184 | 6, 185 | timevisOutput("timelineCrosstalk") 186 | ), 187 | column( 188 | 6, 189 | d3scatter::d3scatterOutput("plot") 190 | ) 191 | ), 192 | br() 193 | ) 194 | ) 195 | ), 196 | div(class = "sourcecode", 197 | "The exact code for all the timelines in this app is", 198 | tags$a(href = "https://github.com/daattali/timevis/tree/master/inst/example", 199 | "on GitHub") 200 | ) 201 | ) 202 | -------------------------------------------------------------------------------- /inst/htmlwidgets/timevis.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************/ 2 | /* Dean Attali 2016-2023 */ 3 | /* timevis */ 4 | /* Create timeline visualizations in R using htmlwidgets and vis.js */ 5 | /*********************************************************************/ 6 | 7 | HTMLWidgets.widget({ 8 | 9 | name : 'timevis', 10 | 11 | type : 'output', 12 | 13 | factory : function(el, width, height) { 14 | 15 | var elementId = el.id; 16 | var container = document.getElementById(elementId); 17 | var timeline = new vis.Timeline(container, [], {}); 18 | var initialized = false; 19 | var ctSel = null; 20 | var ctFil = null; 21 | var allItems; 22 | 23 | return { 24 | 25 | renderValue: function(opts) { 26 | // alias this 27 | var that = this; 28 | 29 | if (!initialized) { 30 | initialized = true; 31 | 32 | // attach the widget to the DOM 33 | container.widget = that; 34 | 35 | // Set up the zoom button click listeners 36 | var zoomMenu = container.getElementsByClassName("zoom-menu")[0]; 37 | zoomMenu.getElementsByClassName("zoom-in")[0] 38 | .onclick = function(ev) { that.zoomInTimevis(opts.zoomFactor); }; 39 | zoomMenu.getElementsByClassName("zoom-out")[0] 40 | .onclick = function(ev) { that.zoomOutTimevis(opts.zoomFactor); }; 41 | 42 | // set listeners to events and pass data back to Shiny 43 | if (HTMLWidgets.shinyMode) { 44 | 45 | // Items have been manually selected 46 | timeline.on('select', function (properties) { 47 | Shiny.onInputChange( 48 | elementId + "_selected", 49 | properties.items 50 | ); 51 | }); 52 | Shiny.onInputChange( 53 | elementId + "_selected", 54 | timeline.getSelection() 55 | ); 56 | 57 | // The range of the window has changes (by dragging or zooming) 58 | timeline.on('rangechanged', function (properties) { 59 | Shiny.onInputChange( 60 | elementId + "_window", 61 | [timeline.getWindow().start, timeline.getWindow().end] 62 | ); 63 | }); 64 | Shiny.onInputChange( 65 | elementId + "_window", 66 | [timeline.getWindow().start, timeline.getWindow().end] 67 | ); 68 | 69 | // The data in the timeline has changed 70 | timeline.itemsData.on('*', function (event, properties, senderId) { 71 | Shiny.onInputChange( 72 | elementId + "_data" + ":timevisDF", 73 | timeline.itemsData.get() 74 | ); 75 | }); 76 | Shiny.onInputChange( 77 | elementId + "_data" + ":timevisDF", 78 | timeline.itemsData.get() 79 | ); 80 | 81 | // An item was added or removed, send back the list of IDs 82 | timeline.itemsData.on('add', function (event, properties, senderId) { 83 | Shiny.onInputChange( 84 | elementId + "_ids", 85 | timeline.itemsData.getIds() 86 | ); 87 | }); 88 | timeline.itemsData.on('remove', function (event, properties, senderId) { 89 | Shiny.onInputChange( 90 | elementId + "_ids", 91 | timeline.itemsData.getIds() 92 | ); 93 | }); 94 | Shiny.onInputChange( 95 | elementId + "_ids", 96 | timeline.itemsData.getIds() 97 | ); 98 | 99 | // Visible items have changed 100 | var sendShinyVisible = function() { 101 | Shiny.onInputChange( 102 | elementId + "_visible", 103 | timeline.getVisibleItems() 104 | ); 105 | }; 106 | timeline.on('rangechanged', sendShinyVisible); 107 | timeline.itemsData.on('add', sendShinyVisible); 108 | timeline.itemsData.on('remove', sendShinyVisible); 109 | setTimeout(sendShinyVisible, 0); 110 | } 111 | 112 | // if a crosstalk dataframe is used, initialize crosstalk 113 | if (typeof(crosstalk) !== "undefined" && opts.crosstalk) { 114 | ctSel = new crosstalk.SelectionHandle(opts.crosstalk.group); 115 | ctSel.on("change", function(e) { 116 | if (e.sender !== ctSel) { 117 | that.setSelection({ itemId : e.value }); 118 | } 119 | }); 120 | timeline.on('select', function (properties) { 121 | ctSel.set(properties.items); 122 | }); 123 | 124 | ctFil = new crosstalk.FilterHandle(opts.crosstalk.group); 125 | ctFil.on("change", function(e) { 126 | if (e.value === null) { 127 | that.setItems({ data : allItems }); 128 | } else { 129 | let keys = e.value; 130 | keys = keys.map(String); // workaround for https://github.com/rstudio/crosstalk/issues/140 131 | that.setItems({ data : allItems.filter(function(item) { return keys.includes(item.id); } ) }); 132 | } 133 | // after doing a filter, a new set of items is used so the selection needs to be re-done 134 | if (ctSel !== null) { 135 | that.setSelection({ itemId : ctSel.value }); 136 | } 137 | }); 138 | } 139 | } 140 | 141 | // set the custom configuration options 142 | if (Array === opts.options.constructor) { 143 | opts['options'] = {}; 144 | } 145 | if (opts['height'] !== null && 146 | typeof opts['options']['height'] === "undefined") { 147 | opts['options']['height'] = opts['height']; 148 | } 149 | if (opts['timezone'] !== null) { 150 | opts['options']['moment'] = function(date) { 151 | return vis.moment(date).utcOffset(opts['timezone']); 152 | }; 153 | } 154 | timeline.setOptions(opts.options); 155 | 156 | // set the data items and groups 157 | timeline.itemsData.clear(); 158 | timeline.itemsData.add(opts.items); 159 | timeline.setGroups(opts.groups); 160 | 161 | // fit the items on the timeline 162 | if (opts.fit) { 163 | timeline.fit({ animation : false }); 164 | } 165 | 166 | // Show or hide the zoom button 167 | var zoomMenu = container.getElementsByClassName("zoom-menu")[0]; 168 | if (opts.showZoom) { 169 | zoomMenu.setAttribute("data-show-zoom", true); 170 | } else { 171 | zoomMenu.removeAttribute("data-show-zoom"); 172 | } 173 | 174 | // Now that the timeline is initialized, call any outstanding API 175 | // functions that the user wantd to run on the timeline before it was 176 | // ready 177 | var numApiCalls = opts['api'].length; 178 | for (var i = 0; i < numApiCalls; i++) { 179 | var call = opts['api'][i]; 180 | var method = call.method; 181 | delete call['method']; 182 | try { 183 | that[method](call); 184 | } catch(err) {} 185 | } 186 | 187 | // If crosstalk is enabled, respect its selection 188 | allItems = opts.items; 189 | if (ctFil !== null && ctFil.filteredKeys !== null) { 190 | let keys = ctFil.filteredKeys; 191 | keys = keys.map(String); 192 | that.setItems({ data : allItems.filter(function(item) { return keys.includes(item.id); } ) }); 193 | } 194 | if (ctSel !== null) { 195 | that.setSelection({ itemId : ctSel.value }); 196 | } 197 | }, 198 | 199 | resize : function(width, height) { 200 | // the timeline widget knows how to resize itself automatically 201 | }, 202 | 203 | // zoom the timeline in/out 204 | // I had to work out the math on paper so that zooming in and then out 205 | // will exactly negate each other 206 | zoomInTimevis : function(percentage, animation) { 207 | if (typeof animation === "undefined") { 208 | animation = true; 209 | } 210 | var range = timeline.getWindow(); 211 | var start = range.start.valueOf(); 212 | var end = range.end.valueOf(); 213 | var interval = end - start; 214 | var newInterval = interval / (1 + percentage); 215 | var distance = (interval - newInterval) / 2; 216 | var newStart = start + distance; 217 | var newEnd = end - distance; 218 | 219 | timeline.setWindow({ 220 | start : newStart, 221 | end : newEnd, 222 | animation : animation 223 | }); 224 | }, 225 | zoomOutTimevis : function(percentage, animation) { 226 | if (typeof animation === "undefined") { 227 | animation = true; 228 | } 229 | var range = timeline.getWindow(); 230 | var start = range.start.valueOf(); 231 | var end = range.end.valueOf(); 232 | var interval = end - start; 233 | var newStart = start - interval * percentage / 2; 234 | var newEnd = end + interval * percentage / 2; 235 | 236 | timeline.setWindow({ 237 | start : newStart, 238 | end : newEnd, 239 | animation : animation 240 | }); 241 | }, 242 | 243 | // export the timeline object for others to use if they want to 244 | timeline : timeline, 245 | 246 | /* API functions that manipulate a timeline's data */ 247 | addItem : function(params) { 248 | timeline.itemsData.add(params.data); 249 | }, 250 | addItems : function(params) { 251 | timeline.itemsData.add(params.data); 252 | }, 253 | removeItem : function(params) { 254 | timeline.itemsData.remove(params.itemId); 255 | }, 256 | addCustomTime : function(params) { 257 | timeline.addCustomTime(params.time, params.itemId); 258 | }, 259 | removeCustomTime : function(params) { 260 | timeline.removeCustomTime(params.itemId); 261 | }, 262 | setCustomTime : function(params) { 263 | timeline.setCustomTime(params.time, params.itemId); 264 | }, 265 | setCurrentTime : function(params) { 266 | timeline.setCurrentTime(params.time); 267 | }, 268 | fitWindow : function(params) { 269 | timeline.fit(params.options); 270 | }, 271 | centerTime : function(params) { 272 | timeline.moveTo(params.time, params.options); 273 | }, 274 | centerItem : function(params) { 275 | if (typeof params.options === 'undefined') { 276 | params.options = { 'zoom' : false }; 277 | } else if (typeof params.options.zoom === 'undefined') { 278 | params.options.zoom = false; 279 | } 280 | timeline.focus(params.itemId, params.options); 281 | }, 282 | setItems : function(params) { 283 | timeline.itemsData.clear(); 284 | timeline.itemsData.add(params.data); 285 | }, 286 | setGroups : function(params) { 287 | timeline.setGroups(params.data); 288 | }, 289 | setOptions : function(params) { 290 | timeline.setOptions(params.options); 291 | }, 292 | setSelection : function(params) { 293 | timeline.setSelection(params.itemId, params.options); 294 | if (HTMLWidgets.shinyMode) { 295 | Shiny.onInputChange( 296 | elementId + "_selected", 297 | params.itemId 298 | ); 299 | } 300 | }, 301 | setWindow : function(params) { 302 | timeline.setWindow(params.start, params.end, params.options); 303 | }, 304 | zoomIn : function(params) { 305 | timeline.zoomIn(params.percent, { animation : params.animation }); 306 | }, 307 | zoomOut : function(params) { 308 | timeline.zoomOut(params.percent, { animation : params.animation }); 309 | }, 310 | }; 311 | } 312 | }); 313 | 314 | // Attach message handlers if in shiny mode (these correspond to API) 315 | if (HTMLWidgets.shinyMode) { 316 | var fxns = 317 | ['addItem', 'addItems', 'removeItem', 'addCustomTime', 'removeCustomTime', 318 | 'fitWindow', 'centerTime', 'centerItem', 'setItems', 'setGroups', 319 | 'setOptions', 'setSelection', 'setWindow', 'setCustomTime', 'setCurrentTime', 320 | 'zoomIn', 'zoomOut']; 321 | 322 | var addShinyHandler = function(fxn) { 323 | return function() { 324 | Shiny.addCustomMessageHandler( 325 | "timevis:" + fxn, function(message) { 326 | var el = document.getElementById(message.id); 327 | if (el) { 328 | delete message['id']; 329 | el.widget[fxn](message); 330 | } 331 | } 332 | ); 333 | } 334 | }; 335 | 336 | for (var i = 0; i < fxns.length; i++) { 337 | addShinyHandler(fxns[i])(); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

timevis

2 |

3 | 📅 Create interactive timeline visualizations in R 4 |

5 | Demo 6 | · 7 | by Dean Attali 8 |

9 | 10 |

11 | 12 | R build status 13 | 14 | 15 | CRAN version 16 | 17 | 18 | 19 | 20 |

21 | 22 | --- 23 | 24 | 25 | 26 | {timevis} lets you create rich and *fully interactive* timeline 27 | visualizations in R. Timelines can be included in Shiny apps or R 28 | markdown documents. 29 | {timevis} includes an extensive API to manipulate a timeline after 30 | creation, and supports getting data out of the visualization into R. 31 | This package is based on the [visjs](https://visjs.github.io/vis-timeline/docs/timeline/) Timeline 32 | JavaScript library. 33 | 34 | **Need Shiny help? [I'm available for consulting](https://attalitech.com/).**
35 | **If you find {timevis} useful, please consider [supporting my work](https://github.com/sponsors/daattali)! ❤** 36 | 37 |

38 | 39 | 40 | 41 |

42 | 43 | > This package is part of a larger ecosystem of packages with a shared vision: solving common Shiny issues and improving Shiny apps with minimal effort, minimal code changes, and clear documentation. Other packages for your Shiny apps: 44 | 45 | | Package | Description | Demo | 46 | |---|---|---| 47 | | [shinyjs](https://deanattali.com/shinyjs/) | 💡 Easily improve the user experience of your Shiny apps in seconds | [🔗](https://deanattali.com/shinyjs/overview#demo) | 48 | | [shinyalert](https://github.com/daattali/shinyalert/) | 🗯️ Easily create pretty popup messages (modals) in Shiny | [🔗](https://daattali.com/shiny/shinyalert-demo/) | 49 | | [shinyscreenshot](https://github.com/daattali/shinyscreenshot/) | 📷 Capture screenshots of entire pages or parts of pages in Shiny apps | [🔗](https://daattali.com/shiny/shinyscreenshot-demo/) | 50 | | [shinycssloaders](https://github.com/daattali/shinycssloaders/) | ⌛ Add loading animations to a Shiny output while it's recalculating | [🔗](https://daattali.com/shiny/shinycssloaders-demo/) | 51 | | [colourpicker](https://github.com/daattali/colourpicker/) | 🎨 A colour picker tool for Shiny and for selecting colours in plots | [🔗](https://daattali.com/shiny/colourInput/) | 52 | | [shinybrowser](https://github.com/daattali/shinybrowser/) | 🌐 Find out information about a user's web browser in Shiny apps | [🔗](https://daattali.com/shiny/shinybrowser-demo/) | 53 | | [shinydisconnect](https://github.com/daattali/shinydisconnect/) | 🔌 Show a nice message when a Shiny app disconnects or errors | [🔗](https://daattali.com/shiny/shinydisconnect-demo/) | 54 | | [shinytip](https://github.com/daattali/shinytip/) | 💬 Simple flexible tooltips for Shiny apps | WIP | 55 | | [shinymixpanel](https://github.com/daattali/shinymixpanel/) | 🔍 Track user interactions with Mixpanel in Shiny apps or R scripts | WIP | 56 | | [shinyforms](https://github.com/daattali/shinyforms/) | 📝 Easily create questionnaire-type forms with Shiny | WIP | 57 | 58 | ## Demo 59 | 60 | [Click here](https://daattali.com/shiny/timevis-demo/) to view an 61 | interactive demo of many {timevis} features. 62 | 63 | If you create a cool timeline with {timevis}, I’d love 64 | to [hear about it](https://deanattali.com/contact/)\! 65 | 66 |

67 | 68 | Sponsors 🏆 69 | 70 |

71 | 72 | > There are no sponsors yet 73 | 74 | [Become the first sponsor for 75 | {timevis}\!](https://github.com/sponsors/daattali/sponsorships?tier_id=39856) 76 | 77 | ## Table of contents 78 | 79 | - [Installation](#install) 80 | - [How to use](#usage) 81 | - [Slightly more advanced examples](#advanced-examples) 82 | - [Interactivity](#interactivity) 83 | - [Groups](#groups) 84 | - [Functions to manipulate a timeline](#manipulate-api) 85 | - [In a Shiny app](#shiny-apps) 86 | - [Retrieving data from the widget](#retrieve-data) 87 | - [Crosstalk support](#crosstalk) 88 | 89 |

90 | 91 | Installation 92 | 93 |

94 | 95 | **For most users:** To install the stable CRAN version: 96 | 97 | ``` r 98 | install.packages("timevis") 99 | ``` 100 | 101 | **For advanced users:** To install the latest development version from GitHub: 102 | 103 | ``` r 104 | install.packages("remotes") 105 | remotes::install_github("daattali/timevis") 106 | ``` 107 | 108 |

109 | 110 | How to use 111 | 112 |

113 | 114 | You can view a minimal timeline without any data by simply running 115 | 116 | ``` r 117 | library(timevis) 118 | timevis() 119 | ``` 120 | 121 | ![Minimal timeline](inst/img/minimal.png) 122 | 123 | You can add data to the timeline by supplying a data.frame 124 | 125 | ``` r 126 | data <- data.frame( 127 | id = 1:4, 128 | content = c("Item one" , "Item two" ,"Ranged item", "Item four"), 129 | start = c("2016-01-10", "2016-01-11", "2016-01-20", "2016-02-14 15:00:00"), 130 | end = c(NA , NA, "2016-02-04", NA) 131 | ) 132 | 133 | timevis(data) 134 | ``` 135 | 136 | ![Basic timeline](inst/img/basic.png) 137 | 138 | Every item must have a `content` and a `start` variable. If the item is 139 | a time range rather than a single point in time, you can supply an `end` as 140 | well. `id` is only required if you want to access or manipulate an item. 141 | 142 | There are more variables that can be used in the data.frame – they are 143 | all documented in the help file for `?timevis()` under the **Data 144 | format** section. 145 | 146 | By default, a timeline will show the current date as a red vertical line 147 | and will have zoom in/out buttons. You can supply many customization 148 | options to `timevis()` in order to get it just right (see `?timevis()` 149 | for details). 150 | 151 |

152 | 153 | Slightly more advanced examples 154 | 155 |

156 | 157 | The content of an item can even include HTML, which makes it easy to 158 | show any kind of data in a timeline, such as the matches of the 2014 159 | World Cup: 160 | 161 | ![World cup timeline](inst/img/worldcup.png) 162 | 163 | If you know some CSS, you can completely customize the look of the 164 | timeline: 165 | 166 | ![Custom style timeline](inst/img/customstyle.png) 167 | 168 |

169 | 170 | Interactivity 171 | 172 |

173 | 174 | The timeline lets the user interact with it seamlessly. You can click on 175 | the zoom in/out buttons or drag the timeline left/right in order to move 176 | to past/future dates. 177 | 178 | If you set the `editable = TRUE` option, then the user will be able to 179 | add new items by double clicking, modify items by dragging, and delete 180 | items by selecting them. 181 | 182 |

183 | 184 | Groups 185 | 186 |

187 | 188 | You can use the groups feature to group together multiple items into 189 | "buckets". When using groups, all items with the same group 190 | are placed on one line. A vertical axis is displayed showing the group 191 | names. Grouping items can be useful for a wide range of applications, 192 | for example when showing availability of multiple people, rooms, or 193 | other resources next to each other. You can also think of groups as 194 | "adding a Y axis". 195 | 196 | Here is an example of a timeline that has four groups: "Gym", "Pool", "Sauna", "Hot Tub": 197 | 198 | ![Groups timeline](inst/img/groups.png) 199 | 200 | In order to use groups, items in the data need to have group ids, and a 201 | separate dataframe containing the group information needs to be 202 | provided. More information about using groups is available in the help file for `?timevis()` under the **Groups** 203 | section. 204 | 205 | Groups can also contain nested groups. The next example is similar to the previous one, except 206 | "Sauna" and "Hot Tub" are now nested under "Pool": 207 | 208 | ![Nested groups timeline](inst/img/nestedgroups.png) 209 | 210 | Refer to the [visjs Timeline](https://visjs.github.io/vis-timeline/docs/timeline/) documentation to see all the options that are supported. 211 | 212 |

213 | 214 | Functions to manipulate a timeline 215 | 216 |

217 | 218 | There are many functions that allow programmatic manipulation of a 219 | timeline. For example: `addItem()` programmatically adds a new item, 220 | `centerItem()` moves the timeline so that a given item is centered, 221 | `setWindow()` sets the start and end dates of the timeline, 222 | `setOptions()` updates the configuration options, and many more 223 | functions are available. 224 | 225 | There are two ways to call these timeline manipulation functions: 226 | 227 | ### 1\. Timeline manipulation using `%>%` on `timevis()` 228 | 229 | You can manipulate a timeline widget during its creation by chaining 230 | functions to the `timevis()` call. For example: 231 | 232 | timevis() %>% 233 | addItem(list(id = "item1", content = "one", start = "2016-08-01")) %>% 234 | centerItem("item1") 235 | 236 | This method of manipulating a timeline is especially useful when 237 | creating timeline widgets in the R console or in R markdown documents 238 | because it can be used directly when initializing the widget. 239 | 240 | ### 2\. Timeline manipulation using a timeline’s ID 241 | 242 | In Shiny apps, you can manipulate a timeline widget at any point after 243 | its creation by referring to its ID. For example: 244 | 245 |
246 | library(shiny)
247 | 
248 | ui <- fluidPage(
249 |   timevisOutput("mytime"),
250 |   actionButton("btn", "Add item and center")
251 | )
252 | 
253 | server <- function(input, output, session) {
254 |   output$mytime <- renderTimevis(timevis())
255 |   observeEvent(input$btn, {
256 |     addItem("mytime", list(id = "item1", content = "one", start = "2016-08-01"))
257 |     centerItem("mytime", "item1")
258 |   })
259 | }
260 | 
261 | shinyApp(ui = ui, server = server)
262 | 
263 | 264 | You can even chain these functions and use this manipulation code 265 | instead of the bold code: 266 | 267 | addItem("mytime", list(id = "item1", content = "one", start = "2016-08-01")) %>% 268 | centerItem("item1") 269 | 270 | *Technical note: If you’re trying to understand how both methods of 271 | timeline manipulation work, it might seem very bizarre to you. The 272 | reason they work is that every manipulation function accepts either a 273 | `timevis` object or the ID of one. In order to make chaining work, the 274 | return value from these functions depend on the input: if a `timevis` 275 | object was given, then an updated `timevis` object is returned, and if 276 | an ID was given, then the same ID is returned.* 277 | 278 | ### Extending timevis 279 | 280 | If you need to perform any actions on the timeline object that are not supported by the {timevis} API, you may be able to do so by manipulating the timeline's JavaScript object directly. The timeline object is available via `document.getElementById("id").widget.timeline` (replace `id` with the timeline's id). 281 | 282 | This timeline object is the direct widget that vis.js creates, and you can see the [visjs documentation](https://visjs.github.io/vis-timeline/docs/timeline/#Methods) to see what actions you can perform on that object. 283 | 284 |

285 | 286 | In a Shiny app 287 | 288 |

289 | 290 | You can add a timeline to a Shiny app by adding `timevisOutput()` to the 291 | UI and `renderTimevis(timevis())` to the server. 292 | 293 |

294 | 295 | Retrieving data from the widget 296 | 297 |

298 | 299 | It is possible to retrieve data from a timeline in a Shiny app. When a 300 | timeline widget is created in a Shiny app, there are four pieces of 301 | information that are always accessible as Shiny inputs. These inputs 302 | have special names based on the timeline’s id. Suppose that a timeline 303 | is created with an `outputId` of **"mytime"**, then the following four 304 | input variables will be available: 305 | 306 | - **input$mytime\_data** - will return a data.frame containing the 307 | data of the items in the timeline. The input is updated every time 308 | an item is modified, added, or removed. 309 | - **input$mytime\_ids** - will return the IDs (a vector) of all the 310 | items in the timeline. The input is updated every time an item is 311 | added or removed from the timeline. 312 | - **input$mytime\_selected** - will return the IDs (a vector) of the 313 | selected items in the timeline. The input is updated every time an 314 | item is selected or unselected by the user. Note that this will not 315 | get updated if an item is selected programmatically using the API 316 | functions. 317 | - **input$mytime\_window** - will return a 2-element vector containing 318 | the minimum and maximum dates currently visible in the timeline. The 319 | input is updated every time the viewable window of dates is updated 320 | (by zooming or moving the window). 321 | - **input$mytime\_visible** - will return a list of IDs of items 322 | currently visible in the timeline. 323 | 324 |

325 | 326 | Crosstalk support 327 | 328 |

329 | 330 | {timevis} is fully compatible with [crosstalk](https://github.com/rstudio/crosstalk). This means that you can provide it with a crosstalk `SharedData` object to select/filter data across multiple Shiny widgets. 331 | 332 | Here is a simple example: 333 | 334 | ``` r 335 | df <- data.frame(start = c(Sys.Date(), Sys.Date() - 1, Sys.Date() - 2), content = 1:3) 336 | shared_df <- crosstalk::SharedData$new(df) 337 | crosstalk::bscols( 338 | timevis(shared_df, options = list(multiselect = TRUE), showZoom = FALSE, width = "100%"), 339 | DT::datatable(shared_df) 340 | ) 341 | ``` 342 | 343 | If you select any events in the timeline (use *ctrl* or *shift* to select multiple events), then the table will automatically select those as well, and vice versa. 344 | 345 | ----- 346 | 347 | You can view examples of many of the features supported by checking out 348 | the [demo Shiny app](https://daattali.com/shiny/timevis-demo/). If you 349 | want to see how those examples were created, the full code for the 350 | examples is inside 351 | [inst/example](https://github.com/daattali/timevis/tree/master/inst/example). 352 | 353 | Lastly, if you want to learn how to develop an htmlwidget to have 354 | similar features as this package, you can check out the 355 | [`timevisBasic`](https://github.com/daattali/timevisBasic) package or 356 | [my tutorial on htmlwidgets 357 | tips](https://deanattali.com/blog/advanced-htmlwidgets-tips/). 358 | 359 | ## Credits 360 | 361 | Logo design by [Alfredo Hernández](https://aldomann.com/). 362 | -------------------------------------------------------------------------------- /man/timevis.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/timevis.R 3 | \name{timevis} 4 | \alias{timevis} 5 | \title{Create a timeline visualization} 6 | \usage{ 7 | timevis( 8 | data, 9 | groups, 10 | showZoom = TRUE, 11 | zoomFactor = 0.5, 12 | fit = TRUE, 13 | options = list(), 14 | width = NULL, 15 | height = NULL, 16 | elementId = NULL, 17 | loadDependencies = TRUE, 18 | timezone = NULL 19 | ) 20 | } 21 | \arguments{ 22 | \item{data}{A dataframe (or \link[crosstalk]{SharedData} object for \{crosstalk\} support) containing the timeline items. 23 | Each item on the timeline is represented by a row in the dataframe. \code{start} and 24 | \code{content} are the only two required columns. 25 | See the \strong{Data format} section below for more 26 | details. For a full list of all supported columns, see the Data Format section in the 27 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Data_Format}{official 28 | visjs Timeline documentation}.} 29 | 30 | \item{groups}{A dataframe containing the groups data (optional). See the 31 | \strong{Groups} section below for more details.} 32 | 33 | \item{showZoom}{If \code{TRUE} (default), then include "Zoom In"/"Zoom Out" 34 | buttons on the widget.} 35 | 36 | \item{zoomFactor}{How much to zoom when zooming out. A zoom factor of 0.5 37 | means that when zooming out the timeline will show 50\% more content. For 38 | example, if the timeline currently shows 20 days, then after zooming out with 39 | a \code{zoomFactor} of 0.5, the timeline will show 30 days, and zooming out 40 | again will show 45 days. Similarly, zooming out from 20 days with a 41 | \code{zoomFactor} of 1 will results in showing 40 days.} 42 | 43 | \item{fit}{If \code{TRUE}, then fit all the data on the timeline when the 44 | timeline initializes. Otherwise, the timeline will be set to show the 45 | current date.} 46 | 47 | \item{options}{A named list containing any extra configuration options to 48 | customize the timeline. All available options can be found in the 49 | \href{https://visjs.github.io/vis-timeline/docs/timeline/#Configuration_Options}{official 50 | Timeline documentation}. Note that any options that define a JavaScript 51 | function must be wrapped in a call to \code{htmlwidgets::JS()}. See the 52 | examples section below to see example usage. If using \{crosstalk\}, it's recommended 53 | to use \code{list(multiselect = TRUE)}.} 54 | 55 | \item{width}{Fixed width for timeline (in css units). Ignored when used in a 56 | Shiny app -- use the \code{width} parameter in 57 | \code{\link[timevis]{timevisOutput}}. 58 | It is not recommended to use this parameter because the widget knows how to 59 | adjust its width automatically.} 60 | 61 | \item{height}{Fixed height for timeline (in css units). It is recommended to 62 | not use this parameter since the widget knows how to adjust its height 63 | automatically.} 64 | 65 | \item{elementId}{Use an explicit element ID for the widget (rather than an 66 | automatically generated one). Ignored when used in a Shiny app.} 67 | 68 | \item{loadDependencies}{Whether to load JQuery and bootstrap 69 | dependencies (you should only set to \code{FALSE} if you manually include 70 | them)} 71 | 72 | \item{timezone}{By default, the timevis widget displays times in the local 73 | time of the browser rendering it. You can set timevis to display times in 74 | another time zone by providing a number between -15 to 15 to specify the 75 | number of hours offset from UTC. For example, use \code{0} to display in UTC, 76 | and use \code{-4} to display in a timezone that is 4 hours behind UTC.} 77 | } 78 | \value{ 79 | A timeline visualization \code{htmlwidgets} object 80 | } 81 | \description{ 82 | \code{timevis} lets you create rich and fully interactive timeline visualizations. 83 | Timelines can be included in Shiny apps or R markdown documents. 84 | \code{timevis} Includes an extensive 85 | API to manipulate a timeline after creation, and supports getting data out of 86 | the visualization into R. Based on the \href{https://visjs.github.io/vis-timeline/docs/timeline/}{'visjs'} 87 | Timeline JavaScript library.\cr\cr 88 | View a \href{https://daattali.com/shiny/timevis-demo/}{demo Shiny app} 89 | or see the full \href{https://github.com/daattali/timevis#readme}{README} on 90 | GitHub.\cr\cr 91 | \strong{Important note: This package provides a way to use the 92 | \href{https://visjs.github.io/vis-timeline/docs/timeline/}{visjs Timeline JavaScript library} within R. 93 | The visjs Timeline library has too many features that cannot all be documented 94 | here. To see the full details on what the timeline can support, please read the 95 | official documentation of visjs Timeline.} 96 | } 97 | \section{Data format}{ 98 | 99 | The \code{data} parameter supplies the input dataframe that describes the 100 | items in the timeline. The following is a subset of the variables supported 101 | in the items dataframe. \strong{The full list of supported variables can be found in 102 | the \href{https://visjs.github.io/vis-timeline/docs/timeline/#Data_Format}{official 103 | visjs documentation}}. 104 | \itemize{ 105 | \item{\strong{\code{start}}} - (required) The start date of the item, for 106 | example \code{"1988-11-22"} or \code{"1988-11-22 16:30:00"}. To specify BCE 107 | dates you must use 6 digits (for example \code{"-000600"} corresponds to year 600BCE). 108 | To specify dates between year 0 and year 99 CE, you must use 4 digits. 109 | \item{\strong{\code{content}}} - (required) The contents of the item. This 110 | can be plain text or HTML code. 111 | \item{\strong{\code{end}}} - The end date of the item. The end date is 112 | optional. If end date is provided, the item is displayed as a range. If 113 | not, the item is displayed as a single point on the timeline. 114 | \item{\strong{\code{id}}} - A unique ID for the item. If not provided, 115 | then the row names will be used as IDs. The ID is used when removing or 116 | selecting items (using \code{\link[timevis]{removeItem}} or 117 | \code{\link[timevis]{setSelection}}). 118 | \item{\strong{\code{type}}} - The type of the item. Can be 'box' (default), 119 | 'point', 'range', or 'background'. Types 'box' and 'point' need only a 120 | start date, types 'range' and 'background' need both a start and end date. 121 | \item{\strong{\code{title}}} - Add a title for the item, displayed when 122 | hovering the mouse over the item. The title can only contain plain text. 123 | \item{\strong{\code{editable}}} - If \code{TRUE}, the item can be 124 | manipulated with the mouse. Overrides the global \code{editable} 125 | configuration option if it is set. An editable item can be removed or 126 | have its start/end dates modified by clicking on it. 127 | \item{\strong{\code{group}}} - The ID of a group. When a \code{group} is 128 | provided, all items with the same group are placed on one line. A vertical 129 | axis is displayed showing the group names. See more details in the 130 | \strong{Groups} section below. 131 | \item{\strong{\code{className}}} - A className can be used to give items an 132 | individual CSS style. 133 | \item{\strong{\code{style}}} - A CSS text string to apply custom styling 134 | for an individual item, for example \code{color: red;}. 135 | } 136 | \code{start} and \code{content} are the only required variables for each 137 | item, while the rest of the variables are optional. If you include a variable 138 | that is only used for some rows, you can use \code{NA} for the rows where 139 | it's not used. The items data of a timeline can either be set by supplying 140 | the \code{data} argument to \code{timevis()}, or by calling the 141 | \code{\link[timevis]{setItems}} function. 142 | } 143 | 144 | \section{Groups}{ 145 | 146 | The \code{groups} parameter must be provided if the data items have groups 147 | (ie. if any of the items have a \code{group} variable). When using groups, all 148 | items with the same group are placed on one line. A vertical axis is 149 | displayed showing the group names. Grouping items can be useful for a wide range 150 | of applications, for example when showing availability of multiple people, 151 | rooms, or other resources next to each other. You can also think of groups as 152 | "adding a Y axis".\cr\cr 153 | The following is a subset of the variables supported in 154 | the groups dataframe. \strong{The full list of supported variables can be found in 155 | the \href{https://visjs.github.io/vis-timeline/docs/timeline/#groups}{official 156 | visjs documentation}}. 157 | \itemize{ 158 | \item{\strong{\code{id}}} - (required) An ID for the group. The group will 159 | display all items having a \code{group} variable which matches this ID. 160 | \item{\strong{\code{content}}} - (required) The contents of the group. This 161 | can be plain text or HTML code. 162 | \item{\strong{\code{title}}} - Add a title for the group, displayed when 163 | hovering the mouse over the group's label. The title can only contain 164 | plain text. 165 | \item{\strong{\code{nestedGroups}}} - List of group ids nested in the group. 166 | The syntax for defining a dataframe with a list inside a column can be tricky, 167 | see the examples below for sample usage. 168 | \item{\strong{\code{className}}} - A className can be used to give groups 169 | an individual CSS style. 170 | \item{\strong{\code{style}}} - A CSS text string to apply custom styling 171 | for an individual group label, for example \code{color: red;}. 172 | } 173 | \code{id} and \code{content} are the only required variables for each group, 174 | while the rest of the variables are optional. If you include a variable that 175 | is only used for some rows, you can use \code{NA} for the rows where it's 176 | not used. The groups data of a timeline can either be set by supplying the 177 | \code{groups} argument to \code{timevis()}, or by calling the 178 | \code{\link[timevis]{setGroups}} function. 179 | } 180 | 181 | \section{Getting data out of a timeline in Shiny}{ 182 | 183 | When a timeline widget is created in a Shiny app, there are four pieces of 184 | information that are always accessible as Shiny inputs. These inputs have 185 | special names based on the timeline's ID. Suppose that a timeline is created 186 | with an \code{outputId} of \strong{"mytime"}, then the following four input 187 | variables will be available: 188 | \itemize{ 189 | \item{\strong{\code{input$mytime_data}}} - will return a data.frame containing 190 | the data of the items in the timeline. The input is updated every time 191 | an item is modified, added, or removed. 192 | \item{\strong{\code{input$mytime_ids}}} - will return the IDs (a vector) of 193 | all the items in the timeline. The input is updated every time an item 194 | is added or removed from the timeline. 195 | \item{\strong{\code{input$mytime_selected}}} - will return the IDs (a vector) 196 | of the selected items in the timeline. The input is updated every time an 197 | item is selected or unselected by the user. Note that this will not get updated if 198 | an item is selected programmatically using 199 | \code{\link[timevis]{setSelection}}. 200 | \item{\strong{\code{input$mytime_window}}} - will return a 2-element vector 201 | containing the minimum and maximum dates currently visible in the timeline. 202 | The input is updated every time the viewable window of dates is updated 203 | (by zooming or moving the window). 204 | \item{\strong{\code{input$mytime_visible}}} - will return a list of IDs of items currently 205 | visible in the timeline. 206 | } 207 | All four inputs will return a value upon initialization of the timeline and 208 | every time the corresponding value is updated. 209 | } 210 | 211 | \section{Extending timevis}{ 212 | 213 | If you need to perform any actions on the timeline object that are not 214 | supported by this package's API, you may be able to do so by manipulating the 215 | timeline's JavaScript object directly. The timeline object is available via 216 | \code{document.getElementById("id").widget.timeline} (replace \code{id} with 217 | the timeline's ID).\cr\cr 218 | This timeline object is the direct widget that \code{vis.js} creates, and you 219 | can see the \href{https://visjs.github.io/vis-timeline/docs/timeline/}{visjs documentation} to 220 | see what actions you can perform on that object. 221 | } 222 | 223 | \section{Customizing the timevis look and style using CSS}{ 224 | 225 | To change the styling of individual items or group labels, use the 226 | \code{className} and \code{style} columns in the \code{data} or \code{groups} 227 | dataframes.\cr\cr 228 | When running a Shiny app, you can use CSS files to apply custom styling to 229 | other components of the timevis widget. When using timevis outside of a Shiny 230 | app, you can use CSS in the following way:\cr 231 | \preformatted{ 232 | tv <- timevis( 233 | data.frame( 234 | content = "Today", 235 | start = Sys.Date() 236 | ) 237 | ) 238 | 239 | style <- " 240 | .vis-timeline { 241 | border-color: #269026; 242 | background-color: lightgreen; 243 | font-size: 15px; 244 | color: green; 245 | } 246 | 247 | .vis-item { 248 | border: 2px solid #5ace5a; 249 | font-size: 12pt; 250 | background: #d9ffd9; 251 | font-family: cursive; 252 | padding: 5px; 253 | } 254 | " 255 | 256 | tv <- tagList(tags$style(style), tv) 257 | htmltools::html_print(tv) 258 | } 259 | } 260 | 261 | \examples{ 262 | \dontrun{ 263 | # For more examples, see https://daattali.com/shiny/timevis-demo/ 264 | 265 | #----------------------- Most basic ----------------- 266 | timevis() 267 | 268 | #----------------------- Minimal data ----------------- 269 | timevis( 270 | data.frame(content = c("one", "two"), 271 | start = c("2016-01-10", "2016-01-12")) 272 | ) 273 | 274 | #----------------------- Hide the zoom buttons, allow items to be editable ----------------- 275 | timevis( 276 | data.frame(content = c("one", "two"), 277 | start = c("2016-01-10", "2016-01-12")), 278 | showZoom = FALSE, 279 | options = list(editable = TRUE, height = "200px") 280 | ) 281 | 282 | #----------------------- You can use \%>\% pipes to create timevis pipelines ----------------- 283 | timevis() \%>\% 284 | setItems(data.frame( 285 | content = c("one", "two"), 286 | start = c("2016-01-10", "2016-01-12") 287 | )) \%>\% 288 | setOptions(list(editable = TRUE)) \%>\% 289 | addItem(list(id = 3, content = "three", start = "2016-01-11")) \%>\% 290 | setSelection("3") \%>\% 291 | fitWindow(list(animation = FALSE)) 292 | 293 | #------- Items can be a single point or a range, and can contain HTML ------- 294 | timevis( 295 | data.frame(content = c("one", "two

HTML is supported

"), 296 | start = c("2016-01-10", "2016-01-18"), 297 | end = c("2016-01-14", NA), 298 | style = c(NA, "color: red;") 299 | ) 300 | ) 301 | 302 | #----------------------- Alternative look for each item ----------------- 303 | timevis( 304 | data.frame(content = c("one", "two"), 305 | start = c("2016-01-10", "2016-01-14"), 306 | end = c(NA, "2016-01-18"), 307 | type = c("point", "background")) 308 | ) 309 | 310 | #----------------------- Using a function in the configuration options ----------------- 311 | timevis( 312 | data.frame(content = "double click anywhere
in the timeline
to add an item", 313 | start = "2016-01-01"), 314 | options = list( 315 | editable = TRUE, 316 | onAdd = htmlwidgets::JS('function(item, callback) { 317 | item.content = "Hello!
" + item.content; 318 | callback(item); 319 | }') 320 | ) 321 | ) 322 | 323 | 324 | #----------------------- Using a custom format for hours ------------------ 325 | timevis( 326 | data.frame( 327 | content = c("one", "two"), 328 | start = c("2020-01-10", "2020-01-10 04:00:00") 329 | ), 330 | options = list( 331 | format = htmlwidgets::JS("{ minorLabels: { minute: 'h:mma', hour: 'ha' }}") 332 | ) 333 | ) 334 | 335 | #----------------------- Allowing editable items to "snap" to round hours only ------------- 336 | timevis( 337 | data.frame( 338 | content = c("one", "two"), 339 | start = c("2020-01-10", "2020-01-10 04:00:00") 340 | ), 341 | options = list( 342 | editable = TRUE, 343 | snap = htmlwidgets::JS("function (date, scale, step) { 344 | var hour = 60 * 60 * 1000; 345 | return Math.round(date / hour) * hour; 346 | }") 347 | ) 348 | ) 349 | 350 | #----------------------- Using groups ----------------- 351 | timevis(data = data.frame( 352 | start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2), 353 | content = c("one", "two", "three", "four"), 354 | group = c(1, 2, 1, 2)), 355 | groups = data.frame(id = 1:2, content = c("G1", "G2")) 356 | ) 357 | 358 | #----------------------- Using nested groups -------------------- 359 | timevis( 360 | data = data.frame( 361 | start = c("2022-01-01", "2022-01-02", "2022-01-03", "2022-01-04", "2022-01-05"), 362 | content = c("item 1", "item 2", "item 3", "item 4", "item 5"), 363 | group = 1:5 364 | ), 365 | groups = data.frame( 366 | id = 1:5, 367 | content = c("John", "Lee", "Clean", "Cook", "Shop"), 368 | nestedGroups = I(list(c(3, 4), 5, NA, NA, NA)) 369 | ) 370 | ) 371 | } 372 | 373 | #----------------------- Getting data out of the timeline into Shiny ----------------- 374 | if (interactive()) { 375 | library(shiny) 376 | 377 | data <- data.frame( 378 | start = c("2015-04-04", "2015-04-05 11:00:00", "2015-04-06 15:00:00"), 379 | end = c("2015-04-08", NA, NA), 380 | content = c("

Vacation!!!

", "Acupuncture", "Massage"), 381 | style = c("color: red;", NA, NA) 382 | ) 383 | 384 | ui <- fluidPage( 385 | timevisOutput("appts"), 386 | div("Selected items:", textOutput("selected", inline = TRUE)), 387 | div("Visible window:", textOutput("window", inline = TRUE)), 388 | tableOutput("table") 389 | ) 390 | 391 | server <- function(input, output) { 392 | output$appts <- renderTimevis( 393 | timevis( 394 | data, 395 | options = list(editable = TRUE, multiselect = TRUE, align = "center") 396 | ) 397 | ) 398 | 399 | output$selected <- renderText( 400 | paste(input$appts_selected, collapse = " ") 401 | ) 402 | 403 | output$window <- renderText( 404 | paste(input$appts_window[1], "to", input$appts_window[2]) 405 | ) 406 | 407 | output$table <- renderTable( 408 | input$appts_data 409 | ) 410 | } 411 | shinyApp(ui, server) 412 | } 413 | 414 | } 415 | \seealso{ 416 | \href{https://daattali.com/shiny/timevis-demo/}{Demo Shiny app} 417 | } 418 | -------------------------------------------------------------------------------- /inst/htmlwidgets/lib/vis-7.4.9/vis-timeline-graph2d.min.css: -------------------------------------------------------------------------------- 1 | .vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-vertical-rtl{position:absolute;border-right:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis-custom-time{background-color:#6e94ff;width:2px;cursor:move;z-index:1}.vis-custom-time>.vis-custom-time-marker{background-color:inherit;color:#fff;font-size:12px;white-space:nowrap;padding:3px 5px;top:0;cursor:auto;z-index:inherit}.vis-current-time{background-color:#ff7f6e;width:2px;z-index:1;pointer-events:none}.vis-rolling-mode-btn{height:40px;width:40px;position:absolute;top:7px;right:20px;border-radius:50%;font-size:28px;cursor:pointer;opacity:.8;color:#fff;font-weight:700;text-align:center;background:#3876c2}.vis-rolling-mode-btn:before{content:"\26F6"}.vis-rolling-mode-btn:hover{opacity:1}.vis-panel{position:absolute;padding:0;margin:0;box-sizing:border-box}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-left.vis-panel.vis-vertical-scroll,.vis-right.vis-panel.vis-vertical-scroll{height:100%;overflow-x:hidden;overflow-y:scroll}.vis-left.vis-panel.vis-vertical-scroll{direction:rtl}.vis-left.vis-panel.vis-vertical-scroll .vis-content,.vis-right.vis-panel.vis-vertical-scroll{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll .vis-content{direction:rtl}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-background{overflow:hidden}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}.vis-timeline{position:relative;border:1px solid #bfbfbf;overflow:hidden;padding:0;margin:0;box-sizing:border-box}.vis-loading-screen{width:100%;height:100%;position:absolute;top:0;left:0}.vis [class*=span]{min-height:0;width:auto}.vis-item{position:absolute;color:#1a1a1a;border-color:#97b0f8;border-width:1px;background-color:#d5ddf6;display:inline-block;z-index:1}.vis-item.vis-selected{border-color:#ffc200;background-color:#fff785;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-point.vis-selected{background-color:#fff785}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:none}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item-visible-frame{white-space:nowrap}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-onUpdateTime-tooltip{position:absolute;background:#4f81bd;color:#fff;width:200px;text-align:center;white-space:nowrap;padding:5px;border-radius:1px;transition:.4s;-o-transition:.4s;-moz-transition:.4s;-webkit-transition:.4s}.vis-item .vis-delete,.vis-item .vis-delete-rtl{position:absolute;top:0;width:24px;height:24px;box-sizing:border-box;padding:0 5px;cursor:pointer;-webkit-transition:background .2s linear;-moz-transition:background .2s linear;-ms-transition:background .2s linear;-o-transition:background .2s linear;transition:background .2s linear}.vis-item .vis-delete{right:-24px}.vis-item .vis-delete-rtl{left:-24px}.vis-item .vis-delete-rtl:after,.vis-item .vis-delete:after{content:"\00D7";color:red;font-family:arial,sans-serif;font-size:22px;font-weight:700;-webkit-transition:color .2s linear;-moz-transition:color .2s linear;-ms-transition:color .2s linear;-o-transition:color .2s linear;transition:color .2s linear}.vis-item .vis-delete-rtl:hover,.vis-item .vis-delete:hover{background:red}.vis-item .vis-delete-rtl:hover:after,.vis-item .vis-delete:hover:after{color:#fff}.vis-item .vis-drag-center{position:absolute;width:100%;height:100%;top:0;left:0;cursor:move}.vis-item.vis-range .vis-drag-left{left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-left,.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0}.vis-item.vis-range .vis-drag-right{right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-item.vis-cluster{vertical-align:center;text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-cluster-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item.vis-cluster-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}div.vis-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none;z-index:5}.vis-itemset{position:relative;padding:0;margin:0;box-sizing:border-box}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-nesting-group{cursor:pointer}.vis-label.vis-nested-group.vis-group-level-unknown-but-gte1{background:#f5f5f5}.vis-label.vis-nested-group.vis-group-level-0{background-color:#fff}.vis-ltr .vis-label.vis-nested-group.vis-group-level-0 .vis-inner{padding-left:0}.vis-rtl .vis-label.vis-nested-group.vis-group-level-0 .vis-inner{padding-right:0}.vis-label.vis-nested-group.vis-group-level-1{background-color:rgba(0,0,0,.05)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-1 .vis-inner{padding-left:15px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-1 .vis-inner{padding-right:15px}.vis-label.vis-nested-group.vis-group-level-2{background-color:rgba(0,0,0,.1)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-2 .vis-inner{padding-left:30px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-2 .vis-inner{padding-right:30px}.vis-label.vis-nested-group.vis-group-level-3{background-color:rgba(0,0,0,.15)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-3 .vis-inner{padding-left:45px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-3 .vis-inner{padding-right:45px}.vis-label.vis-nested-group.vis-group-level-4{background-color:rgba(0,0,0,.2)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-4 .vis-inner{padding-left:60px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-4 .vis-inner{padding-right:60px}.vis-label.vis-nested-group.vis-group-level-5{background-color:rgba(0,0,0,.25)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-5 .vis-inner{padding-left:75px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-5 .vis-inner{padding-right:75px}.vis-label.vis-nested-group.vis-group-level-6{background-color:rgba(0,0,0,.3)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-6 .vis-inner{padding-left:90px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-6 .vis-inner{padding-right:90px}.vis-label.vis-nested-group.vis-group-level-7{background-color:rgba(0,0,0,.35)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-7 .vis-inner{padding-left:105px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-7 .vis-inner{padding-right:105px}.vis-label.vis-nested-group.vis-group-level-8{background-color:rgba(0,0,0,.4)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-8 .vis-inner{padding-left:120px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-8 .vis-inner{padding-right:120px}.vis-label.vis-nested-group.vis-group-level-9{background-color:rgba(0,0,0,.45)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-9 .vis-inner{padding-left:135px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-9 .vis-inner{padding-right:135px}.vis-label.vis-nested-group{background-color:rgba(0,0,0,.5)}.vis-ltr .vis-label.vis-nested-group .vis-inner{padding-left:150px}.vis-rtl .vis-label.vis-nested-group .vis-inner{padding-right:150px}.vis-group-level-unknown-but-gte1{border:1px solid red}.vis-label.vis-nesting-group:before{display:inline-block;width:15px}.vis-label.vis-nesting-group.expanded:before{content:"\25BC"}.vis-label.vis-nesting-group.collapsed:before{content:"\25B6"}.vis-rtl .vis-label.vis-nesting-group.collapsed:before{content:"\25C0"}.vis-ltr .vis-label:not(.vis-nesting-group):not(.vis-group-level-0){padding-left:15px}.vis-rtl .vis-label:not(.vis-nesting-group):not(.vis-group-level-0){padding-right:15px}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-labelset{overflow:hidden}.vis-labelset,.vis-labelset .vis-label{position:relative;box-sizing:border-box}.vis-labelset .vis-label{left:0;top:0;width:100%;color:#4d4d4d;border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-group-is-dragging{background:rgba(0,0,0,.1)}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper:after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:transparent;width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(180deg,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);border:1px solid #999;box-shadow:0 0 3px 0 #aaa;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2,#385380);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(180deg,#3876c2 0,#385380);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#3876c2",endColorstr="#385380",GradientType=0);box-shadow:0 0 1px 0 #111927;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:none}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(180deg,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#9d9d9d",endColorstr="#c8c8c8",GradientType=0)}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(180deg,#dedede 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);border:1px solid #999;box-shadow:0 0 3px 0 #aaa;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0) rgba(136,183,213,0) rgba(136,183,213,0) rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0) rgba(194,225,245,0) rgba(194,225,245,0) #f2faff;border-width:12px;margin-top:-12px}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px hsla(0,0%,60.4%,.55)}.vis-legend-text{white-space:nowrap;display:inline-block} 2 | /*# sourceMappingURL=vis-timeline-graph2d.min.css.map */ -------------------------------------------------------------------------------- /R/api.R: -------------------------------------------------------------------------------- 1 | callJS <- function() { 2 | # get the parameters from the function that have a value 3 | message <- Filter(function(x) !is.symbol(x), as.list(parent.frame(1))) 4 | session <- shiny::getDefaultReactiveDomain() 5 | 6 | # If a timevis widget was passed in, this is during a chain pipeline in the 7 | # initialization of the widget, so keep track of the desired function call 8 | # by adding it to a list of functions that should be performed when the widget 9 | # is ready 10 | if (methods::is(message$id, "timevis")) { 11 | widget <- message$id 12 | message$id <- NULL 13 | widget$x$api <- c(widget$x$api, list(message)) 14 | return(widget) 15 | } 16 | # If an ID was passed, the widget already exists and we can simply call the 17 | # appropriate JS function 18 | else if (is.character(message$id)) { 19 | message$id <- session$ns(message$id) 20 | method <- paste0("timevis:", message$method) 21 | session$sendCustomMessage(method, message) 22 | return(message$id) 23 | } else { 24 | stop("The `id` argument must be either a timevis htmlwidget or an ID 25 | of a timevis htmlwidget.", call. = FALSE) 26 | } 27 | } 28 | 29 | #' Add a single item to a timeline 30 | #' 31 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 32 | #' @param data A named list containing the item data to add. 33 | #' @examples 34 | #' \dontrun{ 35 | #' timevis() %>% 36 | #' addItem(list(start = Sys.Date(), content = "Today")) 37 | #' } 38 | #' 39 | #' if (interactive()) { 40 | #' library(shiny) 41 | #' shinyApp( 42 | #' ui = fluidPage( 43 | #' timevisOutput("timeline"), 44 | #' actionButton("btn", "Add item today") 45 | #' ), 46 | #' server = function(input, output) { 47 | #' output$timeline <- renderTimevis( 48 | #' timevis() 49 | #' ) 50 | #' observeEvent(input$btn, { 51 | #' addItem("timeline", list(start = Sys.Date(), content = "Today")) 52 | #' }) 53 | #' } 54 | #' ) 55 | #' } 56 | #' @export 57 | addItem <- function(id, data) { 58 | method <- "addItem" 59 | callJS() 60 | } 61 | 62 | #' Add multiple items to a timeline 63 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 64 | #' @param data A dataframe containing the items data to add. 65 | #' @examples 66 | #' \dontrun{ 67 | #' timevis() %>% 68 | #' addItems(data.frame(start = c(Sys.Date(), Sys.Date() - 1), 69 | #' content = c("Today", "Yesterday"))) 70 | #' } 71 | #' 72 | #' if (interactive()) { 73 | #' library(shiny) 74 | #' shinyApp( 75 | #' ui = fluidPage( 76 | #' timevisOutput("timeline"), 77 | #' actionButton("btn", "Add items today and yesterday") 78 | #' ), 79 | #' server = function(input, output) { 80 | #' output$timeline <- renderTimevis( 81 | #' timevis() 82 | #' ) 83 | #' observeEvent(input$btn, { 84 | #' addItems("timeline", 85 | #' data.frame(start = c(Sys.Date(), Sys.Date() - 1), 86 | #' content = c("Today", "Yesterday"))) 87 | #' }) 88 | #' } 89 | #' ) 90 | #' } 91 | #' @export 92 | addItems <- function(id, data) { 93 | method <- "addItems" 94 | data <- dataframeToD3(data) 95 | callJS() 96 | } 97 | 98 | #' Remove an item from a timeline 99 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 100 | #' @param itemId The id of the item to remove 101 | #' @examples 102 | #' \dontrun{ 103 | #' timevis(data.frame(start = Sys.Date(), content = c("1", "2"))) %>% 104 | #' removeItem(2) 105 | #' } 106 | #' 107 | #' if (interactive()) { 108 | #' library(shiny) 109 | #' shinyApp( 110 | #' ui = fluidPage( 111 | #' timevisOutput("timeline"), 112 | #' actionButton("btn", "Remove item 2") 113 | #' ), 114 | #' server = function(input, output) { 115 | #' output$timeline <- renderTimevis( 116 | #' timevis(data.frame( 117 | #' start = Sys.Date(), content = c("1", "2")) 118 | #' ) 119 | #' ) 120 | #' observeEvent(input$btn, { 121 | #' removeItem("timeline", 2) 122 | #' }) 123 | #' } 124 | #' ) 125 | #' } 126 | #' @export 127 | removeItem <- function(id, itemId) { 128 | method <- "removeItem" 129 | itemId <- as.character(itemId) 130 | callJS() 131 | } 132 | 133 | #' Add a new vertical bar at a time point that can be dragged by the user 134 | #' 135 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 136 | #' @param time The date/time to add 137 | #' @param itemId The id of the custom time bar 138 | #' @examples 139 | #' \dontrun{ 140 | #' timevis() %>% 141 | #' addCustomTime(Sys.Date() - 1, "yesterday") 142 | #' } 143 | #' 144 | #' if (interactive()) { 145 | #' library(shiny) 146 | #' shinyApp( 147 | #' ui = fluidPage( 148 | #' timevisOutput("timeline"), 149 | #' actionButton("btn", "Add time bar 24 hours ago") 150 | #' ), 151 | #' server = function(input, output) { 152 | #' output$timeline <- renderTimevis( 153 | #' timevis() 154 | #' ) 155 | #' observeEvent(input$btn, { 156 | #' addCustomTime("timeline", Sys.Date() - 1, "yesterday") 157 | #' }) 158 | #' } 159 | #' ) 160 | #' } 161 | #' @export 162 | addCustomTime <- function(id, time, itemId) { 163 | method <- "addCustomTime" 164 | callJS() 165 | } 166 | 167 | #' Adjust the time of a custom time bar 168 | #' 169 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 170 | #' @param time The new date/time 171 | #' @param itemId The id of the custom time bar 172 | #' @examples 173 | #' \dontrun{ 174 | #' timevis() %>% 175 | #' addCustomTime(Sys.Date(), "yesterday") %>% 176 | #' setCustomTime(Sys.Date() - 1, "yesterday") 177 | #' } 178 | #' 179 | #' if (interactive()) { 180 | #' library(shiny) 181 | #' shinyApp( 182 | #' ui = fluidPage( 183 | #' timevisOutput("timeline"), 184 | #' actionButton("btn", "Set time bar 24 hours ago") 185 | #' ), 186 | #' server = function(input, output) { 187 | #' output$timeline <- renderTimevis( 188 | #' timevis() %>% addCustomTime(Sys.Date(), "yesterday") 189 | #' ) 190 | #' observeEvent(input$btn, { 191 | #' setCustomTime("timeline", Sys.Date() - 1, "yesterday") 192 | #' }) 193 | #' } 194 | #' ) 195 | #' } 196 | #' @export 197 | setCustomTime <- function(id, time, itemId) { 198 | method <- "setCustomTime" 199 | callJS() 200 | } 201 | 202 | #' Adjust the time of the current time bar 203 | #' 204 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 205 | #' @param time The new date/time 206 | #' @examples 207 | #' \dontrun{ 208 | #' timevis() %>% 209 | #' setCurrentTime(Sys.Date()) 210 | #' } 211 | #' 212 | #' if (interactive()) { 213 | #' library(shiny) 214 | #' shinyApp( 215 | #' ui = fluidPage( 216 | #' timevisOutput("timeline"), 217 | #' actionButton("btn", "Set current time to beginning of today") 218 | #' ), 219 | #' server = function(input, output) { 220 | #' output$timeline <- renderTimevis( 221 | #' timevis() 222 | #' ) 223 | #' observeEvent(input$btn, { 224 | #' setCurrentTime("timeline", Sys.Date()) 225 | #' }) 226 | #' } 227 | #' ) 228 | #' } 229 | #' @export 230 | setCurrentTime <- function(id, time) { 231 | method <- "setCurrentTime" 232 | callJS() 233 | } 234 | 235 | #' Remove a custom time previously added 236 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 237 | #' @param itemId The id of the custom time bar 238 | #' @examples 239 | #' \dontrun{ 240 | #' timevis() %>% 241 | #' addCustomTime(Sys.Date() - 1, "yesterday") %>% 242 | #' addCustomTime(Sys.Date() + 1, "tomorrow") %>% 243 | #' removeCustomTime("yesterday") 244 | #' } 245 | #' 246 | #' if (interactive()) { 247 | #' library(shiny) 248 | #' shinyApp( 249 | #' ui = fluidPage( 250 | #' timevisOutput("timeline"), 251 | #' actionButton("btn0", "Add custom time"), 252 | #' actionButton("btn", "Remove custom time bar") 253 | #' ), 254 | #' server = function(input, output) { 255 | #' output$timeline <- renderTimevis( 256 | #' timevis() 257 | #' ) 258 | #' observeEvent(input$btn0, { 259 | #' addCustomTime("timeline", Sys.Date() - 1, "yesterday") 260 | #' }) 261 | #' observeEvent(input$btn, { 262 | #' removeCustomTime("timeline", "yesterday") 263 | #' }) 264 | #' } 265 | #' ) 266 | #' } 267 | #' @export 268 | removeCustomTime <- function(id, itemId) { 269 | method <- "removeCustomTime" 270 | callJS() 271 | } 272 | 273 | #' Adjust the visible window such that it fits all items 274 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 275 | #' @param options Named list of options controlling the animation. Most common 276 | #' option is \code{"animation" = TRUE/FALSE}. For a full list of options, see 277 | #' the "fit" method in the 278 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 279 | #' Timeline documentation} 280 | #' @examples 281 | #' if (interactive()) { 282 | #' library(shiny) 283 | #' shinyApp( 284 | #' ui = fluidPage( 285 | #' timevisOutput("timeline"), 286 | #' actionButton("btn", "Fit all items") 287 | #' ), 288 | #' server = function(input, output) { 289 | #' output$timeline <- renderTimevis( 290 | #' timevis(data.frame( 291 | #' start = c(Sys.Date(), Sys.Date() - 1), content = c("1", "2") 292 | #' )) 293 | #' ) 294 | #' observeEvent(input$btn, { 295 | #' fitWindow("timeline", list(animation = FALSE)) 296 | #' }) 297 | #' } 298 | #' ) 299 | #' } 300 | #' @export 301 | fitWindow <- function(id, options) { 302 | method <- "fitWindow" 303 | callJS() 304 | } 305 | 306 | #' Move the window such that the given time is centered 307 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 308 | #' @param time The date/time to center around 309 | #' @param options Named list of options controlling the animation. Most common 310 | #' option is \code{"animation" = TRUE/FALSE}. For a full list of options, see 311 | #' the "moveTo" method in the 312 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 313 | #' Timeline documentation} 314 | #' @examples 315 | #' \dontrun{ 316 | #' timevis() %>% 317 | #' centerTime(Sys.Date() - 1) 318 | #' } 319 | #' 320 | #' if (interactive()) { 321 | #' library(shiny) 322 | #' shinyApp( 323 | #' ui = fluidPage( 324 | #' timevisOutput("timeline"), 325 | #' actionButton("btn", "Center around 24 hours ago") 326 | #' ), 327 | #' server = function(input, output) { 328 | #' output$timeline <- renderTimevis( 329 | #' timevis() 330 | #' ) 331 | #' observeEvent(input$btn, { 332 | #' centerTime("timeline", Sys.Date() - 1) 333 | #' }) 334 | #' } 335 | #' ) 336 | #' } 337 | #' @export 338 | centerTime <- function(id, time, options) { 339 | method <- "centerTime" 340 | callJS() 341 | } 342 | 343 | #' Move the window such that given item or items are centered 344 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 345 | #' @param itemId A vector (or single value) of the item ids to center 346 | #' @param options Named list of options controlling mainly the animation. 347 | #' Most common option is \code{"animation" = TRUE/FALSE}. For a full list of 348 | #' options, see the "focus" method in the 349 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 350 | #' Timeline documentation} 351 | #' @examples 352 | #' \dontrun{ 353 | #' timevis(data.frame( 354 | #' start = c(Sys.Date() - 1, Sys.Date(), Sys.Date() + 1), 355 | #' content = c("Item 1", "Item 2", "Item 3")) 356 | #' ) %>% 357 | #' centerItem(1) 358 | #' } 359 | #' 360 | #' if (interactive()) { 361 | #' library(shiny) 362 | #' shinyApp( 363 | #' ui = fluidPage( 364 | #' timevisOutput("timeline"), 365 | #' actionButton("btn", "Center around item 1") 366 | #' ), 367 | #' server = function(input, output) { 368 | #' output$timeline <- renderTimevis( 369 | #' timevis( 370 | #' data.frame( 371 | #' start = c(Sys.Date() - 1, Sys.Date(), Sys.Date() + 1), 372 | #' content = c("Item 1", "Item 2", "Item 3")) 373 | #' ) 374 | #' ) 375 | #' observeEvent(input$btn, { 376 | #' centerItem("timeline", 1) 377 | #' }) 378 | #' } 379 | #' ) 380 | #' } 381 | #' @export 382 | centerItem <- function(id, itemId, options) { 383 | method <- "centerItem" 384 | itemId <- as.character(itemId) 385 | callJS() 386 | } 387 | 388 | #' Set the items of a timeline 389 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 390 | #' @param data A dataframe containing the item data to use. 391 | #' @examples 392 | #' \dontrun{ 393 | #' timevis(data.frame(start = Sys.Date(), content = "Today")) %>% 394 | #' setItems(data.frame(start = Sys.Date() - 1, content = "yesterday")) 395 | #' } 396 | #' 397 | #' if (interactive()) { 398 | #' library(shiny) 399 | #' shinyApp( 400 | #' ui = fluidPage( 401 | #' timevisOutput("timeline"), 402 | #' actionButton("btn", "Change the data to yesterday") 403 | #' ), 404 | #' server = function(input, output) { 405 | #' output$timeline <- renderTimevis( 406 | #' timevis(data.frame(start = Sys.Date(), content = "Today")) 407 | #' ) 408 | #' observeEvent(input$btn, { 409 | #' setItems("timeline", 410 | #' data.frame(start = Sys.Date() - 1, content = "yesterday")) 411 | #' }) 412 | #' } 413 | #' ) 414 | #' } 415 | #' @export 416 | setItems <- function(id, data) { 417 | method <- "setItems" 418 | data <- dataframeToD3(data) 419 | callJS() 420 | } 421 | 422 | #' Set the groups of a timeline 423 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 424 | #' @param data A dataframe containing the groups data to use. 425 | #' @examples 426 | #' \dontrun{ 427 | #' timevis(data = data.frame( 428 | #' start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2), 429 | #' content = c("one", "two", "three", "four"), 430 | #' group = c(1, 2, 1, 2)), 431 | #' groups = data.frame(id = 1:2, content = c("G1", "G2")) 432 | #' ) %>% 433 | #' setGroups(data.frame(id = 1:2, content = c("Group 1", "Group 2"))) 434 | #' } 435 | #' 436 | #' if (interactive()) { 437 | #' library(shiny) 438 | #' shinyApp( 439 | #' ui = fluidPage( 440 | #' timevisOutput("timeline"), 441 | #' actionButton("btn", "Change group names") 442 | #' ), 443 | #' server = function(input, output) { 444 | #' output$timeline <- renderTimevis( 445 | #' timevis(data = data.frame( 446 | #' start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2), 447 | #' content = c("one", "two", "three", "four"), 448 | #' group = c(1, 2, 1, 2)), 449 | #' groups = data.frame(id = 1:2, content = c("G1", "G2"))) 450 | #' 451 | #' ) 452 | #' observeEvent(input$btn, { 453 | #' setGroups("timeline", 454 | #' data.frame(id = 1:2, content = c("Group 1", "Group 2"))) 455 | #' }) 456 | #' } 457 | #' ) 458 | #' } 459 | #' @export 460 | setGroups <- function(id, data) { 461 | method <- "setGroups" 462 | data <- dataframeToD3(data) 463 | callJS() 464 | } 465 | 466 | #' Update the configuration options of a timeline 467 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 468 | #' @param options A named list containing updated configuration options to use. 469 | #' See the \code{options} parameter of the 470 | #' \code{\link[timevis]{timevis}} function to see more details. 471 | #' @examples 472 | #' \dontrun{ 473 | #' timevis( 474 | #' data.frame(start = Sys.Date(), content = "Today"), 475 | #' options = list(showCurrentTime = FALSE, orientation = "top") 476 | #' ) %>% 477 | #' setOptions(list(editable = TRUE, showCurrentTime = TRUE)) 478 | #' } 479 | #' 480 | #' if (interactive()) { 481 | #' library(shiny) 482 | #' shinyApp( 483 | #' ui = fluidPage( 484 | #' timevisOutput("timeline"), 485 | #' actionButton("btn", "Show current time and allow items to be editable") 486 | #' ), 487 | #' server = function(input, output) { 488 | #' output$timeline <- renderTimevis( 489 | #' timevis( 490 | #' data.frame(start = Sys.Date(), content = "Today"), 491 | #' options = list(showCurrentTime = FALSE, orientation = "top") 492 | #' ) 493 | #' ) 494 | #' observeEvent(input$btn, { 495 | #' setOptions("timeline", list(editable = TRUE, showCurrentTime = TRUE)) 496 | #' }) 497 | #' } 498 | #' ) 499 | #' } 500 | #' @export 501 | setOptions <- function(id, options) { 502 | method <- "setOptions" 503 | callJS() 504 | } 505 | 506 | #' Select one or multiple items on a timeline 507 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 508 | #' @param itemId A vector (or single value) of the item ids to select 509 | #' @param options Named list of options controlling mainly the animation. 510 | #' Most common options are \code{focus = TRUE/FALSE} and 511 | #' \code{"animation" = TRUE/FALSE}. For a full list of options, see 512 | #' the "setSelection" method in the 513 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 514 | #' Timeline documentation} 515 | #' @examples 516 | #' \dontrun{ 517 | #' timevis(data.frame(start = Sys.Date(), content = 1:3)) %>% 518 | #' setSelection(2) 519 | #' } 520 | #' 521 | #' if (interactive()) { 522 | #' library(shiny) 523 | #' shinyApp( 524 | #' ui = fluidPage( 525 | #' timevisOutput("timeline"), 526 | #' actionButton("btn", "Select item 2") 527 | #' ), 528 | #' server = function(input, output) { 529 | #' output$timeline <- renderTimevis( 530 | #' timevis( 531 | #' data.frame(start = Sys.Date(), content = 1:3) 532 | #' ) 533 | #' ) 534 | #' observeEvent(input$btn, { 535 | #' setSelection("timeline", 2) 536 | #' }) 537 | #' } 538 | #' ) 539 | #' } 540 | #' @export 541 | setSelection <- function(id, itemId, options) { 542 | method <- "setSelection" 543 | callJS() 544 | } 545 | 546 | #' Set the current visible window 547 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 548 | #' @param start The start date/time to show in the timeline 549 | #' @param end The end date/time to show in the timeline 550 | #' @param options Named list of options controlling mainly the animation. 551 | #' Most common option is \code{animation = TRUE/FALSE}. For a full list of 552 | #' options, see the "setWindow" method in the 553 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Methods}{official 554 | #' Timeline documentation} 555 | #' @examples 556 | #' \dontrun{ 557 | #' timevis() %>% 558 | #' setWindow(Sys.Date() - 1, Sys.Date() + 1) 559 | #' } 560 | #' 561 | #' if (interactive()) { 562 | #' library(shiny) 563 | #' shinyApp( 564 | #' ui = fluidPage( 565 | #' timevisOutput("timeline"), 566 | #' actionButton("btn", "Set window to show between yesterday to tomorrow") 567 | #' ), 568 | #' server = function(input, output) { 569 | #' output$timeline <- renderTimevis( 570 | #' timevis() 571 | #' ) 572 | #' observeEvent(input$btn, { 573 | #' setWindow("timeline", Sys.Date() - 1, Sys.Date() + 1) 574 | #' }) 575 | #' } 576 | #' ) 577 | #' } 578 | #' @export 579 | setWindow <- function(id, start, end, options) { 580 | method <- "setWindow" 581 | callJS() 582 | } 583 | 584 | #' Zoom in/out the current visible window 585 | #' 586 | #' @param id Timeline id or a \code{timevis} object (the output from \code{timevis()}) 587 | #' @param percent The amount to zoom in or out. Must be a number between 0 and 1. 588 | #' A value of 0.5 means that after zooming out the timeline will show 50% more content. 589 | #' @param animation Whether or not to animate the zoom. 590 | #' @examples 591 | #' \dontrun{ 592 | #' timevis() %>% 593 | #' zoomIn() 594 | #' 595 | #' timevis() %>% 596 | #' zoomOut(0.3) 597 | #' } 598 | #' 599 | #' if (interactive()) { 600 | #' library(shiny) 601 | #' shinyApp( 602 | #' ui = fluidPage( 603 | #' timevisOutput("timeline"), 604 | #' sliderInput("zoom", "Zoom by", min = 0, max = 1, value = 0.5, step = 0.1), 605 | #' checkboxInput("animate", "Animate?", TRUE), 606 | #' actionButton("zoomIn", "Zoom IN"), 607 | #' actionButton("zoomOut", "Zoom OUT") 608 | #' ), 609 | #' server = function(input, output) { 610 | #' output$timeline <- renderTimevis( 611 | #' timevis() 612 | #' ) 613 | #' observeEvent(input$zoomIn, { 614 | #' zoomIn("timeline", percent = input$zoom, animation = input$animate) 615 | #' }) 616 | #' observeEvent(input$zoomOut, { 617 | #' zoomOut("timeline", percent = input$zoom, animation = input$animate) 618 | #' }) 619 | #' } 620 | #' ) 621 | #' } 622 | #' @name zoom 623 | NULL 624 | 625 | #' @export 626 | #' @rdname zoom 627 | zoomIn <- function(id, percent = 0.5, animation = TRUE) { 628 | method <- "zoomIn" 629 | callJS() 630 | } 631 | 632 | #' @export 633 | #' @rdname zoom 634 | zoomOut <- function(id, percent = 0.5, animation = TRUE) { 635 | method <- "zoomOut" 636 | callJS() 637 | } 638 | -------------------------------------------------------------------------------- /R/timevis.R: -------------------------------------------------------------------------------- 1 | #' Create a timeline visualization 2 | #' 3 | #' \code{timevis} lets you create rich and fully interactive timeline visualizations. 4 | #' Timelines can be included in Shiny apps or R markdown documents. 5 | #' \code{timevis} Includes an extensive 6 | #' API to manipulate a timeline after creation, and supports getting data out of 7 | #' the visualization into R. Based on the \href{https://visjs.github.io/vis-timeline/docs/timeline/}{'visjs'} 8 | #' Timeline JavaScript library.\cr\cr 9 | #' View a \href{https://daattali.com/shiny/timevis-demo/}{demo Shiny app} 10 | #' or see the full \href{https://github.com/daattali/timevis#readme}{README} on 11 | #' GitHub.\cr\cr 12 | #' **Important note: This package provides a way to use the 13 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/}{visjs Timeline JavaScript library} within R. 14 | #' The visjs Timeline library has too many features that cannot all be documented 15 | #' here. To see the full details on what the timeline can support, please read the 16 | #' official documentation of visjs Timeline.** 17 | #' 18 | #' @param data A dataframe (or \link[crosstalk]{SharedData} object for \{crosstalk\} support) containing the timeline items. 19 | #' Each item on the timeline is represented by a row in the dataframe. \code{start} and 20 | #' \code{content} are the only two required columns. 21 | #' See the \strong{Data format} section below for more 22 | #' details. For a full list of all supported columns, see the Data Format section in the 23 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Data_Format}{official 24 | #' visjs Timeline documentation}. 25 | #' @param groups A dataframe containing the groups data (optional). See the 26 | #' \strong{Groups} section below for more details. 27 | #' @param showZoom If \code{TRUE} (default), then include "Zoom In"/"Zoom Out" 28 | #' buttons on the widget. 29 | #' @param zoomFactor How much to zoom when zooming out. A zoom factor of 0.5 30 | #' means that when zooming out the timeline will show 50% more content. For 31 | #' example, if the timeline currently shows 20 days, then after zooming out with 32 | #' a \code{zoomFactor} of 0.5, the timeline will show 30 days, and zooming out 33 | #' again will show 45 days. Similarly, zooming out from 20 days with a 34 | #' \code{zoomFactor} of 1 will results in showing 40 days. 35 | #' @param fit If \code{TRUE}, then fit all the data on the timeline when the 36 | #' timeline initializes. Otherwise, the timeline will be set to show the 37 | #' current date. 38 | #' @param options A named list containing any extra configuration options to 39 | #' customize the timeline. All available options can be found in the 40 | #' \href{https://visjs.github.io/vis-timeline/docs/timeline/#Configuration_Options}{official 41 | #' Timeline documentation}. Note that any options that define a JavaScript 42 | #' function must be wrapped in a call to \code{htmlwidgets::JS()}. See the 43 | #' examples section below to see example usage. If using \{crosstalk\}, it's recommended 44 | #' to use `list(multiselect = TRUE)`. 45 | #' @param width Fixed width for timeline (in css units). Ignored when used in a 46 | #' Shiny app -- use the \code{width} parameter in 47 | #' \code{\link[timevis]{timevisOutput}}. 48 | #' It is not recommended to use this parameter because the widget knows how to 49 | #' adjust its width automatically. 50 | #' @param height Fixed height for timeline (in css units). It is recommended to 51 | #' not use this parameter since the widget knows how to adjust its height 52 | #' automatically. 53 | #' @param elementId Use an explicit element ID for the widget (rather than an 54 | #' automatically generated one). Ignored when used in a Shiny app. 55 | #' @param loadDependencies Whether to load JQuery and bootstrap 56 | #' dependencies (you should only set to \code{FALSE} if you manually include 57 | #' them) 58 | #' @param timezone By default, the timevis widget displays times in the local 59 | #' time of the browser rendering it. You can set timevis to display times in 60 | #' another time zone by providing a number between -15 to 15 to specify the 61 | #' number of hours offset from UTC. For example, use `0` to display in UTC, 62 | #' and use `-4` to display in a timezone that is 4 hours behind UTC. 63 | #' @return A timeline visualization \code{htmlwidgets} object 64 | #' @section Data format: 65 | #' The \code{data} parameter supplies the input dataframe that describes the 66 | #' items in the timeline. The following is a subset of the variables supported 67 | #' in the items dataframe. \strong{The full list of supported variables can be found in 68 | #' the \href{https://visjs.github.io/vis-timeline/docs/timeline/#Data_Format}{official 69 | #' visjs documentation}}. 70 | #' \itemize{ 71 | #' \item{\strong{\code{start}}} - (required) The start date of the item, for 72 | #' example \code{"1988-11-22"} or \code{"1988-11-22 16:30:00"}. To specify BCE 73 | #' dates you must use 6 digits (for example `"-000600"` corresponds to year 600BCE). 74 | #' To specify dates between year 0 and year 99 CE, you must use 4 digits. 75 | #' \item{\strong{\code{content}}} - (required) The contents of the item. This 76 | #' can be plain text or HTML code. 77 | #' \item{\strong{\code{end}}} - The end date of the item. The end date is 78 | #' optional. If end date is provided, the item is displayed as a range. If 79 | #' not, the item is displayed as a single point on the timeline. 80 | #' \item{\strong{\code{id}}} - A unique ID for the item. If not provided, 81 | #' then the row names will be used as IDs. The ID is used when removing or 82 | #' selecting items (using \code{\link[timevis]{removeItem}} or 83 | #' \code{\link[timevis]{setSelection}}). 84 | #' \item{\strong{\code{type}}} - The type of the item. Can be 'box' (default), 85 | #' 'point', 'range', or 'background'. Types 'box' and 'point' need only a 86 | #' start date, types 'range' and 'background' need both a start and end date. 87 | #' \item{\strong{\code{title}}} - Add a title for the item, displayed when 88 | #' hovering the mouse over the item. The title can only contain plain text. 89 | #' \item{\strong{\code{editable}}} - If \code{TRUE}, the item can be 90 | #' manipulated with the mouse. Overrides the global \code{editable} 91 | #' configuration option if it is set. An editable item can be removed or 92 | #' have its start/end dates modified by clicking on it. 93 | #' \item{\strong{\code{group}}} - The ID of a group. When a \code{group} is 94 | #' provided, all items with the same group are placed on one line. A vertical 95 | #' axis is displayed showing the group names. See more details in the 96 | #' \strong{Groups} section below. 97 | #' \item{\strong{\code{className}}} - A className can be used to give items an 98 | #' individual CSS style. 99 | #' \item{\strong{\code{style}}} - A CSS text string to apply custom styling 100 | #' for an individual item, for example \code{color: red;}. 101 | #' } 102 | #' \code{start} and \code{content} are the only required variables for each 103 | #' item, while the rest of the variables are optional. If you include a variable 104 | #' that is only used for some rows, you can use \code{NA} for the rows where 105 | #' it's not used. The items data of a timeline can either be set by supplying 106 | #' the \code{data} argument to \code{timevis()}, or by calling the 107 | #' \code{\link[timevis]{setItems}} function. 108 | #' @section Groups: 109 | #' The \code{groups} parameter must be provided if the data items have groups 110 | #' (ie. if any of the items have a \code{group} variable). When using groups, all 111 | #' items with the same group are placed on one line. A vertical axis is 112 | #' displayed showing the group names. Grouping items can be useful for a wide range 113 | #' of applications, for example when showing availability of multiple people, 114 | #' rooms, or other resources next to each other. You can also think of groups as 115 | #' "adding a Y axis".\cr\cr 116 | #' The following is a subset of the variables supported in 117 | #' the groups dataframe. \strong{The full list of supported variables can be found in 118 | #' the \href{https://visjs.github.io/vis-timeline/docs/timeline/#groups}{official 119 | #' visjs documentation}}. 120 | #' \itemize{ 121 | #' \item{\strong{\code{id}}} - (required) An ID for the group. The group will 122 | #' display all items having a \code{group} variable which matches this ID. 123 | #' \item{\strong{\code{content}}} - (required) The contents of the group. This 124 | #' can be plain text or HTML code. 125 | #' \item{\strong{\code{title}}} - Add a title for the group, displayed when 126 | #' hovering the mouse over the group's label. The title can only contain 127 | #' plain text. 128 | #' \item{\strong{\code{nestedGroups}}} - List of group ids nested in the group. 129 | #' The syntax for defining a dataframe with a list inside a column can be tricky, 130 | #' see the examples below for sample usage. 131 | #' \item{\strong{\code{className}}} - A className can be used to give groups 132 | #' an individual CSS style. 133 | #' \item{\strong{\code{style}}} - A CSS text string to apply custom styling 134 | #' for an individual group label, for example \code{color: red;}. 135 | #' } 136 | #' \code{id} and \code{content} are the only required variables for each group, 137 | #' while the rest of the variables are optional. If you include a variable that 138 | #' is only used for some rows, you can use \code{NA} for the rows where it's 139 | #' not used. The groups data of a timeline can either be set by supplying the 140 | #' \code{groups} argument to \code{timevis()}, or by calling the 141 | #' \code{\link[timevis]{setGroups}} function. 142 | #' @section Getting data out of a timeline in Shiny: 143 | #' When a timeline widget is created in a Shiny app, there are four pieces of 144 | #' information that are always accessible as Shiny inputs. These inputs have 145 | #' special names based on the timeline's ID. Suppose that a timeline is created 146 | #' with an \code{outputId} of \strong{"mytime"}, then the following four input 147 | #' variables will be available: 148 | #' \itemize{ 149 | #' \item{\strong{\code{input$mytime_data}}} - will return a data.frame containing 150 | #' the data of the items in the timeline. The input is updated every time 151 | #' an item is modified, added, or removed. 152 | #' \item{\strong{\code{input$mytime_ids}}} - will return the IDs (a vector) of 153 | #' all the items in the timeline. The input is updated every time an item 154 | #' is added or removed from the timeline. 155 | #' \item{\strong{\code{input$mytime_selected}}} - will return the IDs (a vector) 156 | #' of the selected items in the timeline. The input is updated every time an 157 | #' item is selected or unselected by the user. Note that this will not get updated if 158 | #' an item is selected programmatically using 159 | #' \code{\link[timevis]{setSelection}}. 160 | #' \item{\strong{\code{input$mytime_window}}} - will return a 2-element vector 161 | #' containing the minimum and maximum dates currently visible in the timeline. 162 | #' The input is updated every time the viewable window of dates is updated 163 | #' (by zooming or moving the window). 164 | #' \item{\strong{\code{input$mytime_visible}}} - will return a list of IDs of items currently 165 | #' visible in the timeline. 166 | #' } 167 | #' All four inputs will return a value upon initialization of the timeline and 168 | #' every time the corresponding value is updated. 169 | #' @section Extending timevis: 170 | #' If you need to perform any actions on the timeline object that are not 171 | #' supported by this package's API, you may be able to do so by manipulating the 172 | #' timeline's JavaScript object directly. The timeline object is available via 173 | #' \code{document.getElementById("id").widget.timeline} (replace \code{id} with 174 | #' the timeline's ID).\cr\cr 175 | #' This timeline object is the direct widget that \code{vis.js} creates, and you 176 | #' can see the \href{https://visjs.github.io/vis-timeline/docs/timeline/}{visjs documentation} to 177 | #' see what actions you can perform on that object. 178 | #' @section Customizing the timevis look and style using CSS: 179 | #' To change the styling of individual items or group labels, use the 180 | #' \code{className} and \code{style} columns in the \code{data} or \code{groups} 181 | #' dataframes.\cr\cr 182 | #' When running a Shiny app, you can use CSS files to apply custom styling to 183 | #' other components of the timevis widget. When using timevis outside of a Shiny 184 | #' app, you can use CSS in the following way:\cr 185 | #' \preformatted{ 186 | #' tv <- timevis( 187 | #' data.frame( 188 | #' content = "Today", 189 | #' start = Sys.Date() 190 | #' ) 191 | #' ) 192 | #' 193 | #' style <- " 194 | #' .vis-timeline { 195 | #' border-color: #269026; 196 | #' background-color: lightgreen; 197 | #' font-size: 15px; 198 | #' color: green; 199 | #' } 200 | #' 201 | #' .vis-item { 202 | #' border: 2px solid #5ace5a; 203 | #' font-size: 12pt; 204 | #' background: #d9ffd9; 205 | #' font-family: cursive; 206 | #' padding: 5px; 207 | #' } 208 | #' " 209 | #' 210 | #' tv <- tagList(tags$style(style), tv) 211 | #' htmltools::html_print(tv) 212 | #' } 213 | #' @examples 214 | #' \dontrun{ 215 | #' # For more examples, see https://daattali.com/shiny/timevis-demo/ 216 | #' 217 | #' #----------------------- Most basic ----------------- 218 | #' timevis() 219 | #' 220 | #' #----------------------- Minimal data ----------------- 221 | #' timevis( 222 | #' data.frame(content = c("one", "two"), 223 | #' start = c("2016-01-10", "2016-01-12")) 224 | #' ) 225 | #' 226 | #' #----------------------- Hide the zoom buttons, allow items to be editable ----------------- 227 | #' timevis( 228 | #' data.frame(content = c("one", "two"), 229 | #' start = c("2016-01-10", "2016-01-12")), 230 | #' showZoom = FALSE, 231 | #' options = list(editable = TRUE, height = "200px") 232 | #' ) 233 | #' 234 | #' #----------------------- You can use %>% pipes to create timevis pipelines ----------------- 235 | #' timevis() %>% 236 | #' setItems(data.frame( 237 | #' content = c("one", "two"), 238 | #' start = c("2016-01-10", "2016-01-12") 239 | #' )) %>% 240 | #' setOptions(list(editable = TRUE)) %>% 241 | #' addItem(list(id = 3, content = "three", start = "2016-01-11")) %>% 242 | #' setSelection("3") %>% 243 | #' fitWindow(list(animation = FALSE)) 244 | #' 245 | #' #------- Items can be a single point or a range, and can contain HTML ------- 246 | #' timevis( 247 | #' data.frame(content = c("one", "two

HTML is supported

"), 248 | #' start = c("2016-01-10", "2016-01-18"), 249 | #' end = c("2016-01-14", NA), 250 | #' style = c(NA, "color: red;") 251 | #' ) 252 | #' ) 253 | #' 254 | #' #----------------------- Alternative look for each item ----------------- 255 | #' timevis( 256 | #' data.frame(content = c("one", "two"), 257 | #' start = c("2016-01-10", "2016-01-14"), 258 | #' end = c(NA, "2016-01-18"), 259 | #' type = c("point", "background")) 260 | #' ) 261 | #' 262 | #' #----------------------- Using a function in the configuration options ----------------- 263 | #' timevis( 264 | #' data.frame(content = "double click anywhere
in the timeline
to add an item", 265 | #' start = "2016-01-01"), 266 | #' options = list( 267 | #' editable = TRUE, 268 | #' onAdd = htmlwidgets::JS('function(item, callback) { 269 | #' item.content = "Hello!
" + item.content; 270 | #' callback(item); 271 | #' }') 272 | #' ) 273 | #' ) 274 | #' 275 | #' 276 | #' #----------------------- Using a custom format for hours ------------------ 277 | #' timevis( 278 | #' data.frame( 279 | #' content = c("one", "two"), 280 | #' start = c("2020-01-10", "2020-01-10 04:00:00") 281 | #' ), 282 | #' options = list( 283 | #' format = htmlwidgets::JS("{ minorLabels: { minute: 'h:mma', hour: 'ha' }}") 284 | #' ) 285 | #' ) 286 | #' 287 | #' #----------------------- Allowing editable items to "snap" to round hours only ------------- 288 | #' timevis( 289 | #' data.frame( 290 | #' content = c("one", "two"), 291 | #' start = c("2020-01-10", "2020-01-10 04:00:00") 292 | #' ), 293 | #' options = list( 294 | #' editable = TRUE, 295 | #' snap = htmlwidgets::JS("function (date, scale, step) { 296 | #' var hour = 60 * 60 * 1000; 297 | #' return Math.round(date / hour) * hour; 298 | #' }") 299 | #' ) 300 | #' ) 301 | #' 302 | #' #----------------------- Using groups ----------------- 303 | #' timevis(data = data.frame( 304 | #' start = c(Sys.Date(), Sys.Date(), Sys.Date() + 1, Sys.Date() + 2), 305 | #' content = c("one", "two", "three", "four"), 306 | #' group = c(1, 2, 1, 2)), 307 | #' groups = data.frame(id = 1:2, content = c("G1", "G2")) 308 | #' ) 309 | #' 310 | #' #----------------------- Using nested groups -------------------- 311 | #' timevis( 312 | #' data = data.frame( 313 | #' start = c("2022-01-01", "2022-01-02", "2022-01-03", "2022-01-04", "2022-01-05"), 314 | #' content = c("item 1", "item 2", "item 3", "item 4", "item 5"), 315 | #' group = 1:5 316 | #' ), 317 | #' groups = data.frame( 318 | #' id = 1:5, 319 | #' content = c("John", "Lee", "Clean", "Cook", "Shop"), 320 | #' nestedGroups = I(list(c(3, 4), 5, NA, NA, NA)) 321 | #' ) 322 | #' ) 323 | #' } 324 | #' 325 | #' #----------------------- Getting data out of the timeline into Shiny ----------------- 326 | #' if (interactive()) { 327 | #' library(shiny) 328 | #' 329 | #' data <- data.frame( 330 | #' start = c("2015-04-04", "2015-04-05 11:00:00", "2015-04-06 15:00:00"), 331 | #' end = c("2015-04-08", NA, NA), 332 | #' content = c("

Vacation!!!

", "Acupuncture", "Massage"), 333 | #' style = c("color: red;", NA, NA) 334 | #' ) 335 | #' 336 | #' ui <- fluidPage( 337 | #' timevisOutput("appts"), 338 | #' div("Selected items:", textOutput("selected", inline = TRUE)), 339 | #' div("Visible window:", textOutput("window", inline = TRUE)), 340 | #' tableOutput("table") 341 | #' ) 342 | #' 343 | #' server <- function(input, output) { 344 | #' output$appts <- renderTimevis( 345 | #' timevis( 346 | #' data, 347 | #' options = list(editable = TRUE, multiselect = TRUE, align = "center") 348 | #' ) 349 | #' ) 350 | #' 351 | #' output$selected <- renderText( 352 | #' paste(input$appts_selected, collapse = " ") 353 | #' ) 354 | #' 355 | #' output$window <- renderText( 356 | #' paste(input$appts_window[1], "to", input$appts_window[2]) 357 | #' ) 358 | #' 359 | #' output$table <- renderTable( 360 | #' input$appts_data 361 | #' ) 362 | #' } 363 | #' shinyApp(ui, server) 364 | #' } 365 | #' 366 | #' @seealso \href{https://daattali.com/shiny/timevis-demo/}{Demo Shiny app} 367 | #' @export 368 | timevis <- function(data, groups, showZoom = TRUE, zoomFactor = 0.5, fit = TRUE, 369 | options = list(), width = NULL, height = NULL, elementId = NULL, 370 | loadDependencies = TRUE, timezone = NULL) { 371 | 372 | # Validate the input data 373 | if (missing(data)) { 374 | data <- data.frame() 375 | } 376 | if (crosstalk::is.SharedData(data)) { 377 | crosstalk_opts <- list( 378 | key = data$key(), 379 | group = data$groupName() 380 | ) 381 | data <- data$origData() 382 | 383 | if ("id" %in% names(data) && !isTRUE(all.equal(data$id, crosstalk_opts$key))) { 384 | warning("timevis: the `id` column of the data was overwritten by the SharedData key.") 385 | } 386 | data$id <- crosstalk_opts$key 387 | } else { 388 | crosstalk_opts <- NULL 389 | } 390 | if (!is.data.frame(data)) { 391 | stop("timevis: 'data' must be a data.frame", 392 | call. = FALSE) 393 | } 394 | if (nrow(data) > 0 && 395 | (!"start" %in% colnames(data) || anyNA(data[['start']]))) { 396 | stop("timevis: 'data' must contain a 'start' date for each item", 397 | call. = FALSE) 398 | } 399 | if (!missing(groups) && !is.data.frame(groups)) { 400 | stop("timevis: 'groups' must be a data.frame", 401 | call. = FALSE) 402 | } 403 | if (!missing(groups) && nrow(groups) > 0 && 404 | (!"id" %in% colnames(groups) || !"content" %in% colnames(groups) )) { 405 | stop("timevis: 'groups' must contain a 'content' and 'id' variables", 406 | call. = FALSE) 407 | } 408 | if (!is.bool(showZoom)) { 409 | stop("timevis: 'showZoom' must be either 'TRUE' or 'FALSE'", 410 | call. = FALSE) 411 | } 412 | if (!is.numeric(zoomFactor) || length(zoomFactor) > 1 || zoomFactor <= 0) { 413 | stop("timevis: 'zoomFactor' must be a positive number", 414 | call. = FALSE) 415 | } 416 | if (!is.bool(fit)) { 417 | stop("timevis: 'fit' must be either 'TRUE' or 'FALSE'", 418 | call. = FALSE) 419 | } 420 | if (is.null(options)) { 421 | options <- list() 422 | } 423 | if (!is.list(options)) { 424 | stop("timevis: 'options' must be a named list", 425 | call. = FALSE) 426 | } 427 | if (!is.null(timezone)) { 428 | if (!is.numeric(timezone) || length(timezone) != 1 || 429 | timezone < -15 || timezone > 15) { 430 | stop("timevis: 'timezone' must be a number between -15 and 15", 431 | call. = FALSE) 432 | } 433 | } 434 | 435 | if (!"id" %in% names(data)) { 436 | data$id <- row.names(data) 437 | } 438 | 439 | items <- dataframeToD3(data) 440 | if (missing(groups)) { 441 | groups <- NULL 442 | } else { 443 | groups <- dataframeToD3(groups) 444 | } 445 | 446 | if (!is.null(options[["start"]]) || !is.null(options[["end"]])) { 447 | fit <- FALSE 448 | } 449 | 450 | # forward options using x 451 | x = list( 452 | items = items, 453 | groups = groups, 454 | showZoom = showZoom, 455 | zoomFactor = zoomFactor, 456 | fit = fit, 457 | options = options, 458 | height = height, 459 | timezone = timezone, 460 | crosstalk = crosstalk_opts 461 | ) 462 | 463 | # Allow a list of API functions to be called on the timevis after 464 | # initialization 465 | x$api <- list() 466 | 467 | deps <- NULL 468 | 469 | if (!is.null(crosstalk_opts)) { 470 | deps <- crosstalk::crosstalkLibs() 471 | } 472 | # add dependencies so that the zoom buttons will work in non-Shiny mode 473 | if (loadDependencies) { 474 | deps <- append( 475 | deps, 476 | list( 477 | rmarkdown::html_dependency_jquery(), 478 | rmarkdown::html_dependency_bootstrap("default") 479 | ) 480 | ) 481 | } 482 | 483 | # create widget 484 | htmlwidgets::createWidget( 485 | name = 'timevis', 486 | x, 487 | width = width, 488 | height = height, 489 | package = 'timevis', 490 | elementId = elementId, 491 | dependencies = deps 492 | ) 493 | } 494 | 495 | #' Shiny bindings for timevis 496 | #' 497 | #' Output and render functions for using timevis within Shiny 498 | #' applications and interactive Rmd documents. 499 | #' 500 | #' @param outputId output variable to read from 501 | #' @param width,height Must be a valid CSS unit (like \code{'100\%'}, 502 | #' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a 503 | #' string and have \code{'px'} appended. \code{height} will probably not 504 | #' have an effect; instead, use the \code{height} parameter in 505 | #' \code{\link[timevis]{timevis}}. 506 | #' @param expr An expression that generates a timevis 507 | #' @param env The environment in which to evaluate \code{expr}. 508 | #' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This 509 | #' is useful if you want to save an expression in a variable. 510 | #' 511 | #' @name timevis-shiny 512 | #' @seealso \code{\link[timevis]{timevis}}. 513 | #' 514 | #' @examples 515 | #' if (interactive()) { 516 | #' library(shiny) 517 | #' 518 | #' #----------------------- Most basic example ----------------- 519 | #' shinyApp( 520 | #' ui = fluidPage(timevisOutput("timeline")), 521 | #' server = function(input, output) { 522 | #' output$timeline <- renderTimevis( 523 | #' timevis() 524 | #' ) 525 | #' } 526 | #' ) 527 | #' 528 | #' 529 | #' #----------------------- More advanced example ----------------- 530 | #' data <- data.frame( 531 | #' start = c("2015-04-04", "2015-04-05 11:00:00", "2015-04-06 15:00:00"), 532 | #' end = c("2015-04-08", NA, NA), 533 | #' content = c("

Vacation!!!

", "Acupuncture", "Massage"), 534 | #' style = c("color: red;", NA, NA) 535 | #' ) 536 | #' 537 | #' ui <- fluidPage( 538 | #' timevisOutput("appts"), 539 | #' div("Selected items:", textOutput("selected", inline = TRUE)), 540 | #' div("Visible window:", textOutput("window", inline = TRUE)), 541 | #' tableOutput("table") 542 | #' ) 543 | #' 544 | #' server <- function(input, output) { 545 | #' output$appts <- renderTimevis( 546 | #' timevis( 547 | #' data, 548 | #' options = list(editable = TRUE, multiselect = TRUE, align = "center") 549 | #' ) 550 | #' ) 551 | #' 552 | #' output$selected <- renderText( 553 | #' paste(input$appts_selected, collapse = " ") 554 | #' ) 555 | #' 556 | #' output$window <- renderText( 557 | #' paste(input$appts_window[1], "to", input$appts_window[2]) 558 | #' ) 559 | #' 560 | #' output$table <- renderTable( 561 | #' input$appts_data 562 | #' ) 563 | #' } 564 | #' shinyApp(ui, server) 565 | #' } 566 | #' 567 | #' @export 568 | timevisOutput <- function(outputId, width = '100%', height = 'auto') { 569 | htmlwidgets::shinyWidgetOutput(outputId, 'timevis', width, height, package = 'timevis') 570 | } 571 | 572 | #' @rdname timevis-shiny 573 | #' @export 574 | renderTimevis <- function(expr, env = parent.frame(), quoted = FALSE) { 575 | if (!quoted) { expr <- substitute(expr) } # force quoted 576 | htmlwidgets::shinyRenderWidget(expr, timevisOutput, env, quoted = TRUE) 577 | } 578 | 579 | # Add custom HTML to wrap the widget to allow for a zoom in/out menu 580 | timevis_html <- function(id, style, class, ...){ 581 | htmltools::tags$div( 582 | id = id, class = class, style = style, 583 | htmltools::tags$div( 584 | class = "btn-group zoom-menu", 585 | htmltools::tags$button( 586 | type = "button", 587 | class = "btn btn-default btn-lg zoom-in", 588 | title = "Zoom in", 589 | "+" 590 | ), 591 | htmltools::tags$button( 592 | type = "button", 593 | class = "btn btn-default btn-lg zoom-out", 594 | title = "Zoom out", 595 | "-" 596 | ) 597 | ) 598 | ) 599 | } 600 | --------------------------------------------------------------------------------