Skip to content

Commit

Permalink
return data into shiny by default (fixes #4) and add a fit argument t…
Browse files Browse the repository at this point in the history
…o timevis
  • Loading branch information
daattali committed Jul 27, 2016
1 parent cee3d5a commit 3426686
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 124 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: timevis
Title: Create Interactive Timeline Visualizations in R
Version: 0.1.1
Version: 0.1.2
Authors@R: c(
person("Dean", "Attali", email = "daattali@gmail.com",
role = c("aut", "cre"), comment = "R interface"),
Expand Down
12 changes: 10 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# timevis 0.1.2

2016-07-26

- fix bug: When re-rendering a timeline, the old data are not removed (#3)
- removed the `getData` `getWindow` `getIds` `getSelected` parameters and instead just return that info always (#4)
- add `fit` param to `timevis()` that determines if to fit the items on the timeline by default or not

# timevis 0.1.1

2017-07-25
2016-07-25

- refactor and modularize Shiny app code
- UI improvements to Shiny app
Expand All @@ -9,6 +17,6 @@

# timevis 0.1

2017-07-25
2016-07-25

Initial CRAN release
89 changes: 37 additions & 52 deletions R/timevis.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@
#' \item{\strong{\code{id}}} - An id for the item. Using an id is not required
#' but highly recommended. An id is needed when removing or selecting items
#' (using \code{\link[timevis]{removeItem}} or
#' \code{\link[timevis]{setSelection}}) or when requesting to return
#' selected items (using \code{getSelected}).
#' \code{\link[timevis]{setSelection}}).
#' \item{\strong{\code{type}}} - The type of the item. Can be 'box' (default),
#' 'point', 'range', or 'background'. Types 'box' and 'point' need only a
#' start date, types 'range' and 'background' need both a start and end date.
Expand All @@ -52,30 +51,9 @@
#' a \code{zoomFactor} of 0.5, the timeline will show 30 days, and zooming out
#' again will show 45 days. Similarly, zooming out from 20 days with a
#' \code{zoomFactor} of 1 will results in showing 40 days.
#' @param getSelected If \code{TRUE}, then the selected items will
#' be accessible as a Shiny input. The input returns the ids of the selected
#' items and will be updated every time a new
#' item is selected by the user. The input name will be the timeline's id
#' appended by "_selected". For example, if the timeline \code{outputId}
#' is "mytimeline", then use \code{input$mytimeline_selected}.
#' @param getData If \code{TRUE}, then the items data will be accessible as a
#' Shiny input. The input returns a dataframe and will be updated every time
#' an item is modified, added, or removed. The input name will be the timeline's
#' id appended by "_data". For example, if the timeline \code{outputId} is
#' "mytimeline", then use \code{input$mytimeline_data}.
#' @param getIds If \code{TRUE}, then the IDs of the data items will be
#' accessible as a Shiny input. The input returns a vector of IDs and will be
#' updated every time an item is added or removed from the timeline. The input
#' name will be the timeline's id appended by "_ids". For example, if the
#' timeline \code{outputId} is "mytimeline", then use
#' \code{input$mytimeline_ids}.
#' @param getWindow If \code{TRUE}, then the current visible window will be
#' accessible as a Shiny input. The input returns a 2-element vector containing
#' the minimum and maximum dates currently visible in the timeline. The input
#' will be updated every time the window is updated (by zooming or moving).
#' The input name will be the timeline's id appended by "_window". For example,
#' if the timeline \code{outputId} is "mytimeline", then use
#' \code{input$mytimeline_window}.
#' @param fit If \code{TRUE}, then fit all the data on the timeline when the
#' timeline initializes. Otherwise, the timeline will be set to show the
#' current date.
#' @param options A named list containing any extra configuration options to
#' customize the timeline. All available options can be found in the
#' \href{http://visjs.org/docs/timeline/#Configuration_Options}{official
Expand All @@ -92,9 +70,33 @@
#' automatically.
#' @param elementId Use an explicit element ID for the widget (rather than an
#' automatically generated one). Ignored when used in a Shiny app.
#'
#' @return A timeline visualization \code{htmlwidgets} object
#'
#' @section Getting data out of a timeline in Shiny:
#' When a timeline widget is created in a Shiny app, there are four pieces of
#' information that are always accessible as Shiny inputs. These inputs have
#' special names based on the timeline's id. Suppose that a timeline is created
#' with an \code{outputId} of "mytime", then the following four input variables
#' will be available and get updated whenever the user interacts with the
#' timeline:
#' \itemize{
#' \item{\strong{\code{input$mytime_data}}} - will return a data.frame with
#' the data of the items in the timeline. The input is updated every time
#' an item is modified, added, or removed.
#' \item{\strong{\code{input$mytime_ids}}} - will return the IDs (a vector) of
#' all the items in the timeline. The input is updated every time an item
#' is added or removed from the timeline.
#' \item{\strong{\code{input$mytime_selected}}} - will return the IDs (a vector)
#' of the selected items in the timeline. The input is updated every time a
#' new item is selected by the user. Note that this will not get updated if
#' an item is selected programmatically using
#' \code{\link[timevis]{setSelection}}.
#' \item{\strong{\code{input$mytime_window}}} - will return a 2-element vector
#' containing the minimum and maximum dates currently visible in the timeline.
#' The input is updated every time the viewable window of dates is updated
#' (by zooming or moving the window).
#' }
#' All four inputs will return a value upon initialization of the timeline and
#' every time the corresponding value is updated.
#' @examples
#' # For more examples, see http://daattali.com/shiny/timevis-demo/
#'
Expand Down Expand Up @@ -180,7 +182,7 @@
#' server <- function(input, output) {
#' output$appts <- renderTimevis(
#' timevis(
#' data, getSelected = TRUE, getData = TRUE, getWindow = TRUE,
#' data,
#' options = list(editable = TRUE, multiselect = TRUE, align = "center")
#' )
#' )
Expand All @@ -202,10 +204,8 @@
#'
#' @seealso \href{http://daattali.com/shiny/timevis-demo/}{Demo Shiny app}
#' @export
timevis <- function(data, showZoom = TRUE, zoomFactor = 0.5,
getSelected = FALSE, getData = FALSE, getIds = FALSE,
getWindow = FALSE, options, width = NULL, height = NULL,
elementId = NULL) {
timevis <- function(data, showZoom = TRUE, zoomFactor = 0.5, fit = TRUE,
options, width = NULL, height = NULL, elementId = NULL) {

# Validate the input data
if (missing(data)) {
Expand All @@ -228,20 +228,8 @@ timevis <- function(data, showZoom = TRUE, zoomFactor = 0.5,
stop("timevis: 'zoomFactor' must be a positive number",
call. = FALSE)
}
if (!is.bool(getSelected)) {
stop("timevis: 'getSelected' must be either 'TRUE' or 'FALSE'",
call. = FALSE)
}
if (!is.bool(getData)) {
stop("timevis: 'getData' must be either 'TRUE' or 'FALSE'",
call. = FALSE)
}
if (!is.bool(getIds)) {
stop("timevis: 'getIds' must be either 'TRUE' or 'FALSE'",
call. = FALSE)
}
if (!is.bool(getWindow)) {
stop("timevis: 'getWindow' must be either 'TRUE' or 'FALSE'",
if (!is.bool(fit)) {
stop("timevis: 'fit' must be either 'TRUE' or 'FALSE'",
call. = FALSE)
}
if (missing(options) || is.null(options)) {
Expand All @@ -259,10 +247,7 @@ timevis <- function(data, showZoom = TRUE, zoomFactor = 0.5,
items = items,
showZoom = showZoom,
zoomFactor = zoomFactor,
getSelected = getSelected,
getData = getData,
getIds = getIds,
getWindow = getWindow,
fit = fit,
options = options,
height = height
)
Expand Down Expand Up @@ -338,7 +323,7 @@ timevis <- function(data, showZoom = TRUE, zoomFactor = 0.5,
#' server <- function(input, output) {
#' output$appts <- renderTimevis(
#' timevis(
#' data, getSelected = TRUE, getData = TRUE, getWindow = TRUE,
#' data,
#' options = list(editable = TRUE, multiselect = TRUE, align = "center")
#' )
#' )
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,32 @@ There are many functions that allow programmatic manipulation of a
timeline. For example, `addItem()` programmatically adds a new item,
`centerItem()` moves the timeline so that the given item is centered,
`setWindow()` sets the start and end dates of the timeline, and more
functions are available.
functions are available. Note that all these functions take the
timeline's ID as their first argument.

It is also possible to retrieve data from a timeline in a Shiny app.
There are several types of data that are available, such as retriving
the IDs of the currently selected items, or getting the data in the
timeline (as a dataframe). These data will be available as input
variables. For more information, read about the `getSelected`,
`getWindow`, `getData`, and `getIds` parameters of `timevis()`.
When a timeline widget is created in a Shiny app, there are four pieces
of information that are always accessible as Shiny inputs. These inputs
have special names based on the timeline's id. Suppose that a timeline
is created with an `outputId of` "mytime", then the following four input
variables will be available and get updated whenever the user interacts
with the timeline:

- **input$mytime\_data** - will return a data.frame with the data of
the items in the timeline. The input is updated every time an item
is modified, added, or removed.
- **input$mytime\_ids** - will return the IDs (a vector) of all the
items in the timeline. The input is updated every time an item is
added or removed from the timeline.
- **input$mytime\_selected** - will return the IDs (a vector) of the
selected items in the timeline. The input is updated every time a
new item is selected by the user. Note that this will not get
updated if an item is selected programmatically using the
API functions.
- **input$mytime\_window** - will return a 2-element vector containing
the minimum and maximum dates currently visible in the timeline. The
input is updated every time the viewable window of dates is updated
(by zooming or moving the window).

You can view examples of many of the features supported by checking out
the [demo Shiny app](http://daattali.com/shiny/timevis-demo/). If you
Expand Down
3 changes: 1 addition & 2 deletions inst/example/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ function(input, output, session) {
editable = TRUE,
multiselect = TRUE
)
timevis(dataBasic, getSelected = TRUE, getData = TRUE, getIds = TRUE,
getWindow = TRUE, options = config)
timevis(dataBasic, options = config)
})

output$selected <- renderText(
Expand Down
2 changes: 1 addition & 1 deletion inst/example/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fluidPage(
"By",
tags$a(href = "http://deanattali.com/", "Dean Attali"),
HTML("&bull;"),
"Package & examples",
"Available",
tags$a(href = "https://github.com/daattali/timevis", "on GitHub"),
HTML("&bull;"),
tags$a(href = "http://daattali.com/shiny/", "More apps"), "by Dean"
Expand Down
2 changes: 1 addition & 1 deletion inst/htmlwidgets/timevis.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
top: 5px;
z-index: 15;
}
.timevis.html-widget .zoom-menu.show-zoom {
.timevis.html-widget .zoom-menu[data-show-zoom] {
display: block;
}
.timevis.html-widget .zoom-menu .btn {
Expand Down
56 changes: 32 additions & 24 deletions inst/htmlwidgets/timevis.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,44 @@ HTMLWidgets.widget({

var elementId = el.id;
var container = document.getElementById(elementId);
var timeline = null;
var timeline = new vis.Timeline(container, [], {});
var initialized = false;

return {

renderValue: function(x) {
// alias this
var that = this;

// First time the renderTimevis function is called, initialize
if (timeline === null) {
timeline = new vis.Timeline(container, [], {});
if (!initialized) {
initialized = true;

// attach the timeline object to the DOM
container.timeline = timeline;
}

// set the data items
timeline.itemsData.clear();
timeline.itemsData.add(x.items);
timeline.fit({ animation : false });

// Show and initialize the zoom buttons
if (x.showZoom) {
// Set up the zoom button click listeners
var zoomMenu = container.getElementsByClassName("zoom-menu")[0];
zoomMenu.className += " show-zoom";
zoomMenu.getElementsByClassName("zoom-in")[0]
.onclick = function(ev) { that.zoomIn(x.zoomFactor); };
zoomMenu.getElementsByClassName("zoom-out")[0]
.onclick = function(ev) { that.zoomOut(x.zoomFactor); };
}

// set listeners to events the user wants to know about
if (HTMLWidgets.shinyMode){
if (x.getSelected) {
// set listeners to events and pass data back to Shiny
if (HTMLWidgets.shinyMode) {

// Items have been manually selected
timeline.on('select', function (properties) {
Shiny.onInputChange(
elementId + "_selected",
properties.items
);
});
// Also send the initial data when the widget starts
Shiny.onInputChange(
elementId + "_selected",
timeline.getSelection()
);
}
if (x.getWindow) {

// The range of the window has changes (by dragging or zooming)
timeline.on('rangechanged', function (properties) {
Shiny.onInputChange(
elementId + "_window",
Expand All @@ -71,8 +62,8 @@ HTMLWidgets.widget({
elementId + "_window",
[timeline.getWindow().start, timeline.getWindow().end]
);
}
if (x.getData) {

// The data in the timeline has changed
timeline.itemsData.on('*', function (event, properties, senderId) {
Shiny.onInputChange(
elementId + "_data" + ":timevisDF",
Expand All @@ -83,8 +74,8 @@ HTMLWidgets.widget({
elementId + "_data" + ":timevisDF",
timeline.itemsData.get()
);
}
if (x.getIds) {

// An item was added or removed, send back the list of IDs
timeline.itemsData.on('add', function (event, properties, senderId) {
Shiny.onInputChange(
elementId + "_ids",
Expand All @@ -104,6 +95,23 @@ HTMLWidgets.widget({
}
}

// set the data items
timeline.itemsData.clear();
timeline.itemsData.add(x.items);

// fit the items on the timeline
if (x.fit) {
timeline.fit({ animation : false });
}

// Show or hide the zoom button
var zoomMenu = container.getElementsByClassName("zoom-menu")[0];
if (x.showZoom) {
zoomMenu.setAttribute("data-show-zoom", true);
} else {
zoomMenu.removeAttribute("data-show-zoom");
}

// set the custom configuration options
if (Array === x.options.constructor) {
x['options'] = {};
Expand Down
2 changes: 1 addition & 1 deletion man/timevis-shiny.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3426686

Please sign in to comment.