├── .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 | | %s |
24 |
25 | | %s |
26 | %s - %s |
27 | %s |
28 |
29 |
30 |  |
31 | |
32 |  |
33 |
34 |
',
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 |
13 |
14 |
15 |
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 |
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 | 
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 | 
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 | 
162 |
163 | If you know some CSS, you can completely customize the look of the
164 | timeline:
165 |
166 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------