diff --git a/.gitignore b/.gitignore index 728e389..1fc9dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .Rhistory .RData .DS_Store +.directory +rsconnect diff --git a/DESCRIPTION b/DESCRIPTION index 439a0d7..a63c1c0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -32,8 +32,9 @@ Imports: htmlwidgets (>= 0.6), htmltools (>= 0.3.5), zoo (>= 1.7-10), - xts (>= 0.9-7) + xts (>= 0.9-7), + shiny (>= 0.10.2.1) Suggests: testthat -Enhances: rmarkdown (>= 0.3.3), shiny (>= 0.10.2.1) +Enhances: rmarkdown (>= 0.3.3) RoxygenNote: 5.0.1 diff --git a/NAMESPACE b/NAMESPACE index b357d0c..a769ecb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,7 @@ export(dyAnnotation) export(dyAxis) export(dyCSS) export(dyCallbacks) +export(dyCrosshair) export(dyEvent) export(dyHighlight) export(dyLegend) @@ -18,12 +19,17 @@ export(dyRoller) export(dySeries) export(dySeriesData) export(dyShading) +export(dySliderInput) +export(dyUnzoom) export(dygraph) export(dygraphOutput) export(renderDygraph) +importFrom(grDevices,col2rgb) importFrom(htmltools,htmlDependency) importFrom(htmlwidgets,JS) importFrom(magrittr,"%>%") +importFrom(shiny,animationOptions) +importFrom(shiny,icon) importFrom(stats,end) importFrom(stats,start) importFrom(zoo,as.yearmon) diff --git a/R/plugin.R b/R/plugin.R index e3395d5..c9440ab 100644 --- a/R/plugin.R +++ b/R/plugin.R @@ -49,3 +49,106 @@ dyPlugin <- function(dygraph, name, path, options = list(), version = "1.0") { # return dygraph dygraph } + +#' dyUnzoom +#' +#' @inheritParams dyPlugin +#' +#' @return A dygraph with the specified plugin enabled. +#' +#' @details The dyUnzoom plugin adds an "Unzoom" button to the graph when it's displaying +#' in a zoomed state (this is a bit more discoverable than the default double- +#' click gesture for unzooming). Note that this plugin has no options (see +#' below for an example with options). +#' +#' @examples +#' library(dygraphs) +#' dygraph(mdeaths) %>% +#' dyUnzoom() +#' +#' @export +dyUnzoom <-function(dygraph) { + dyPlugin( + dygraph = dygraph, + name = "Unzoom", + path = system.file("examples/plugins/unzoom.js", package = "dygraphs") + ) +} + +#' dyCrosshair +#' +#' @inheritParams dyPlugin +#' @param direction Direction for crosshairs. Defaults to 'both'. Valid arguments are +# 'both', 'horizontal', and 'vertical'. +#' +#' @return A dygraph with the specified plugin enabled. +#' +#' @details The dyCrosshair plugin draws a crosshair line over the point closest to the +#' mouse when the user hovers over the graph. It has a "direction" option which +#' is provided in the R wrapper function and then forwarded to the plugin using +#' the "options" argument to dyPlugin. +#' +#' @examples +#' library(dygraphs) +#' dygraph(mdeaths) %>% +#' dyCrosshair() +#' +#' @export +dyCrosshair <- function(dygraph, direction = c("both", "horizontal", "vertical")) { + dyPlugin( + dygraph = dygraph, + name = "Crosshair", + path = system.file("examples/plugins/crosshair.js", package = "dygraphs"), + options = list(direction = match.arg(direction)) + ) +} + +#' dySliderInput +#' +#' @inheritParams dyPlugin +#' @param color Color to draw slider. Defaults to 'red'. +#' @param strokePattern Line type for slider. Defaults to 'dashed'. Valid arguments are +#' 'dashed', 'solid', 'dotted', and 'dotdash'. +#' @param animate 'TRUE' to show simple animation controls with default settings; +#' 'FALSE' not to; or a custom settings list, such as those created using 'animationOptions'. +#' +#' @return A dygraph with the specified plugin enabled. +#' +#' @details The dySliderInput plugin turns the dyDygraph into a slider input widget. The +#' user can click on a point along the graph and the graph will place a vertical +#' line on the graph. The user can also use the animation options to scroll +#' through points along the graph. +#' +#' @examples +#' library(dygraphs) +#' dygraph(mdeaths) %>% +#' dySliderInput() +#' +#' @importFrom grDevices col2rgb +#' @importFrom shiny icon +#' @importFrom shiny animationOptions +#' +#' @export +dySliderInput <- function(dygraph, color = 'red', strokePattern = c('dashed', 'solid', 'dotted', 'dotdash'), animate = FALSE) { + # process args + stopifnot(length(color)==1) + col <- paste(col2rgb(color)[,1], collapse=',') + alpha <- (col2rgb(color, alpha=TRUE)[4,1] / 255) + if (identical(animate, TRUE)) + animate <- animationOptions() + if (identical(animate, FALSE)) + animate <- NULL + if (!is.null(animate)) { + if (is.null(animate$playButton)) + animate$playButton <- as.character(icon("play", lib = "glyphicon")) + if (is.null(animate$pauseButton)) + animate$pauseButton <- as.character(icon("pause", lib = "glyphicon")) + } + # add plugin + dyPlugin( + dygraph = dygraph, + name = "SliderInput", + path = system.file("examples/plugins/sliderinput.js", package = "dygraphs"), + options = list(strokeStyle = paste0(col, ',', alpha), strokePattern=resolveStrokePattern(match.arg(strokePattern)), animate = animate) + ) +} diff --git a/inst/NEWS b/inst/NEWS index fc68445..bd69c9c 100644 --- a/inst/NEWS +++ b/inst/NEWS @@ -5,6 +5,7 @@ dygraphs 1.1.1.3 (unreleased) * Support for non-date values in shiny input bindings (#132) +* Fix for inconsistant format in date-time callbacks dygraphs 1.1.1.2 -------------------------------------------------------------------------------- diff --git a/inst/examples/plugins/plugins.R b/inst/examples/plugins/plugins.R index ee62c54..2414e41 100644 --- a/inst/examples/plugins/plugins.R +++ b/inst/examples/plugins/plugins.R @@ -35,7 +35,6 @@ dyCrosshair <- function(dygraph, direction = c("both", "horizontal", "vertical") ) } - # Our plugin wrapper functions can now be incorporated directly into a dygraph # pipeline along with other dygraphs functions: @@ -43,7 +42,7 @@ library(dygraphs) dygraph(ldeaths) %>% dyRangeSelector() %>% dyUnzoom() %>% - dyCrosshair(direction = "vertical") + dyCrosshair(direction = "vertical") %>% diff --git a/inst/examples/plugins/sliderinput.js b/inst/examples/plugins/sliderinput.js new file mode 100644 index 0000000..75b9693 --- /dev/null +++ b/inst/examples/plugins/sliderinput.js @@ -0,0 +1,344 @@ +/** + * @license + * Copyright 2016 Jeffrey Owen Hanson (jeffrey.hanson@uqconnect.edu.au) + * MIT-licensed (http://opensource.org/licenses/) + */ + +/*global Dygraph:false */ +/*jshint globalstrict: true */ +Dygraph.Plugins.SliderInput = (function() { + "use strict"; + + /** + * Creates the sliderinput + * + * @constructor + */ + + var sliderinput = function(opt_options) { + /* create widgets */ + this.canvas_ = document.createElement("canvas"); // canvas to draw bars on + this.button_ = null + + /* pass arguments from R */ + opt_options = opt_options || {}; + this.strokeStyle_ = opt_options.strokeStyle || null; + this.strokePattern_ = opt_options.strokePattern || null; + this.animate_ = opt_options.animate || null; + + /* set default parameters */ + this.bar_point_ = null; // point associated with bar, if not drawn then null + this.closest_point_ = null; // closest point to cursor + this.over_ = false; // true when mouse is over the canvas + this.animated_ = false; // true when the graph is in an animated state + this.timer_ = null; // timer object used for animation + this.graph_height_= null; // graph height when mouse is over graph + this.graph_width_= null; // graph width when mouse is over graph + this.ignore_next_click_ = false; // should next click be ignored? + }; + + sliderinput.prototype.toString = function() { + return "SliderInput Plugin"; + }; + + /** + * @param {Dygraph} g Graph instance. + * @return {object.} Mapping of event names to callbacks. + */ + sliderinput.prototype.activate = function(g) { + /* initialise canvas */ + g.graphDiv.appendChild(this.canvas_); + + /* return methods */ + return { + willDrawChart: this.willDrawChart, + didDrawChart: this.didDrawChart, + select: this.select + }; + }; + + sliderinput.prototype.willDrawChart = function (e) { + /* initialise */ + var g = e.dygraph; + + // short-circuit: skip redeclaring all this stuff if we've already been over it + if (this.button_ !== null) { + var showButton = (this.animate_ !== null) && this.over_; + this.show(showButton); + return; + } + + // define click event + this.click = function(point) { + /* simulate the user clicking on a point in the graph */ + if (point !== null) { + Shiny.onInputChange(g.maindiv_.id + "_click", { + date: g.shinyValueFormatter(point.xval), + x_closest_point: g.shinyValueFormatter(point.xval), + y_closest_point: point.yval, + '.nonce': Math.random() // Force reactivity if click hasn't changed + }) + } else { + Shiny.onInputChange(g.maindiv_.id + "_click", { + date: 'NA', + x_closest_point: 'NA', + y_closest_point: 'NA', + '.nonce': Math.random() // Force reactivity if click hasn't changed + }) + } + }; + + // define function to determine if any points inside graph + this.anyPointsInsideGraph = function() { + var date_range = g.xAxisRange(); + var no_points_inside_range = (date_range[0] > g.layout_.points[0][0].xval) && (date_range[1] < g.layout_.points[0][1].xval) && (g.layout_.points[0].length == 2); + return(!no_points_inside_range); + }; + + // define function to get the first point inside a graph + this.getFirstPoint = function() { + var counter = 0; + var point = g.layout_.points[0][counter]; + var date_range = g.xAxisRange(); + while (point.xval < date_range[0]) { + counter++; + point = g.layout_.points[0][counter]; + } + return point; + } + + // define function to get the last point inside a graph + this.getLastPoint = function() { + var counter = g.layout_.points[0].length-1; + var point = g.layout_.points[0][counter]; + var date_range = g.xAxisRange(); + while (point.xval > date_range[1]) { + counter--; + point = g.layout_.points[0][counter]; + } + return point; + } + + /* animation button */ + // create the button + this.button_ = document.createElement('button'); + this.button_.innerHTML = this.animate_.playButton; + this.button_.style.display = 'none'; + this.button_.style.position = 'absolute'; + var area = g.plotter_.area; + this.button_.style.top = (area.y + 10) + 'px'; + this.button_.style.left = (area.x + 30) + 'px'; + this.button_.style.zIndex = 11; + var parent = g.graphDiv; + var main = g.maindiv_; + main.insertBefore(this.button_, main.firstChild); + + // add event hadling to the button + var self = this; + this.button_.onclick = (function() { + /* function definitions */ + + // move slider to first point + var resetSliderToStart = function() { + if (self.anyPointsInsideGraph()) { + var new_point = self.getFirstPoint(); + self.clear_bars(); // clear bars + self.add_bar(new_point); // add new bar + self.click(new_point); // click on point + } else { + self.stop_animation(); + } + }; + + // can slider to the next point? + var canStepNext = function() { + if (self.anyPointsInsideGraph()) { + return(self.bar_point_.idx < self.getLastPoint().idx); + } else { + self.stop_animation(); + } + }; + + // move slider to the next point + var stepSliderToNext = function() { + if (self.anyPointsInsideGraph()) { + var new_point = g.layout_.points[0][(self.bar_point_.idx - g.layout_.points[0][0].idx) + 1]; + self.clear_bars(); // clear bars + self.add_bar(new_point); // add new bar + self.click(new_point); // click on point + } else { + self.stop_animation(); + } + }; + + var start_animation = function() { + /* check that there are points on the graph, and if there are none then exit */ + if (!self.anyPointsInsideGraph()) { + return; + } + + /* start animation */ + // if no bar selected, then select first point in window + self.animated_ = true; + self.button_.innerHTML = self.animate_.pauseButton; + if (self.bar_point_ == null) { + resetSliderToStart(); + } + + // main animation function + var animation_workhorse = function() { + if (self.animate_.loop && !canStepNext()) { + resetSliderToStart(); + } else { + stepSliderToNext() + if (!self.animate_.loop && !canStepNext()) { + self.stop_animation(); + } + } + }; + + // start animation + self.timer_ = setInterval(animation_workhorse, self.animate_.interval); + }; + + self.stop_animation = function() { + /* stop animation */ + self.animated_ = false; + self.button_.innerHTML = self.animate_.playButton; + clearTimeout(self.timer_); + }; + + /* main operations */ + // swap state + self.animated_ = !self.animated_ + // start/stop animation + if (self.animated_) { + start_animation(); + } else { + self.stop_animation(); + } + }); + g.addAndTrackEvent(main, 'mouseover', function() { + /* show start/stop buttons */ + self.show(true); + self.over_ = true; + }); + g.addAndTrackEvent(main, 'mouseout', function() { + /* hide start/stop buttons */ + self.show(false); + self.over_ = false; + }); + g.addAndTrackEvent(parent, 'click', function() { + /* check if click should be ignored */ + if (self.ignore_next_click_) { + self.ignore_next_click_ = false; + return; + } + /* draw new bar */ + if (self.animated_) { + self.stop_animation(); // stop animation if animated + } + self.clear_bars(); // clear bars + if (self.anyPointsInsideGraph()) { + self.add_bar(self.closest_point_); // add new line if closest point with graph range + // click is handled automatically by dygraphs in-build event handler + } + }); + }; + + sliderinput.prototype.didDrawChart = function(e) { + /* initialise */ + var g = e.dygraph; + /* move bar when zooming in or out*/ + if ((this.bar_point_ !== null)) { + // draw new bar if any points inside plotting region + if (this.anyPointsInsideGraph()) { + if ((this.bar_point_.idx >= g.boundaryIds_[0][0]) && (this.bar_point_.idx <= g.boundaryIds_[0][1])) { + // redraw bar for point + var new_point = g.layout_.points[0][(this.bar_point_.idx - g.layout_.points[0][0].idx)]; + this.clear_bars(); // clear bars + this.add_bar(new_point); // add new bar + // do not simulate clicking a point since the same date-time will be returned as the previous + } else if (this.bar_point_.idx < g.boundaryIds_[0][0]) { + // draw bar on left side of plotting region + var new_point = g.getFirstPoint(); + this.clear_bars(); // clear bars + this.add_bar(new_point); // add new bar + this.click(new_point); // click on point + } else { + // draw bar on right side of plotting region + var new_point = g.getLastPoint(); + this.clear_bars(); // clear bars + this.add_bar(new_point); // add new bar + this.click(new_point); // click on point + } + } else { + this.clear_bars(); // clear bars + this.click(null); // return NA to indicate that no data is shown + } + + // ignore next click caused by zooming + this.ignore_next_click_ = true; + + } + }; + + sliderinput.prototype.add_bar = function(point) { + /* add bar to canvas */ + // extract values from point + var canvas_position = Math.floor(point.canvasx) + 0.5; + // set up canvas + var width = this.graph_width_; + var height = this.graph_height_; + this.canvas_.width = width; + this.canvas_.height = height; + this.canvas_.style.width = width + "px"; // for IE + this.canvas_.style.height = height + "px"; // for IE + // draw bar on canvas + var ctx = this.canvas_.getContext("2d"); + ctx.strokeStyle = "rgba("+this.strokeStyle_+")"; + ctx.setLineDash(this.strokePattern_); + ctx.beginPath(); + ctx.moveTo(canvas_position, 0); + ctx.lineTo(canvas_position, height); + ctx.stroke(); + ctx.closePath(); + this.bar_point_ = point; + }; + + sliderinput.prototype.clear_bars = function() { + /* clear all bars from graph */ + // remove points + var ctx = this.canvas_.getContext("2d"); + ctx.clearRect(0, 0, this.canvas_.width, this.canvas_.height); + this.bar_point_ = null; + }; + + sliderinput.prototype.show = function(enabled) { + /* show animation buttons */ + this.button_.style.display = enabled ? '' : 'none'; + }; + + sliderinput.prototype.select = function(e) { + /* set bar variables */ + this.graph_height_ = e.dygraph.height_; + this.graph_width_ = e.dygraph.width_; + this.closest_point_ = e.dygraph.selPoints_[0]; + }; + + sliderinput.prototype.destroy = function() { + this.button_.parentElement.removeChild(this.button_); + this.ignore_next_click = null; + this.bar_point_ = null; + this.over_ = null; + this.timer_ = null; + this.animated_ = null; + this.canvas_ = null; + this.x_closest_point_ = null; + this.graph_height_ = null; + this.graph_width_ = null; + }; + + return sliderinput; + +})(); diff --git a/inst/examples/shiny/DESCRIPTION b/inst/examples/shiny_1/DESCRIPTION similarity index 100% rename from inst/examples/shiny/DESCRIPTION rename to inst/examples/shiny_1/DESCRIPTION diff --git a/inst/examples/shiny/server.R b/inst/examples/shiny_1/server.R similarity index 56% rename from inst/examples/shiny/server.R rename to inst/examples/shiny_1/server.R index 4b48392..a5d9d1b 100644 --- a/inst/examples/shiny/server.R +++ b/inst/examples/shiny_1/server.R @@ -17,19 +17,22 @@ shinyServer(function(input, output) { }) output$from <- renderText({ - strftime(req(input$dygraph_date_window[[1]]), "%d %b %Y") + format(strptime(req(input$dygraph_date_window[[1]]), '%b %d, %Y %H:%M:%S'), '%Y/%m/%d %H:%M:%S') }) output$to <- renderText({ - strftime(req(input$dygraph_date_window[[2]]), "%d %b %Y") + format(strptime(req(input$dygraph_date_window[[2]]), '%b %d, %Y %H:%M:%S'), '%Y/%m/%d %H:%M:%S') }) output$clicked <- renderText({ - strftime(req(input$dygraph_click$x), "%d %b %Y") + format(strptime(req(input$dygraph_click$x), '%b %d, %Y %H:%M:%S'), '%Y/%m/%d %H:%M:%S') }) output$point <- renderText({ - paste0('X = ', strftime(req(input$dygraph_click$x_closest_point), "%d %b %Y"), - '; Y = ', req(input$dygraph_click$y_closest_point)) + paste0( + 'X = ', format(strptime(req(input$dygraph_click$x_closest_point), '%b %d, %Y %H:%M:%S'), '%Y/%m/%d %H:%M:%S'), + '; Y = ', req(input$dygraph_click$y_closest_point) + ) }) + }) diff --git a/inst/examples/shiny/ui.R b/inst/examples/shiny_1/ui.R similarity index 100% rename from inst/examples/shiny/ui.R rename to inst/examples/shiny_1/ui.R diff --git a/inst/examples/shiny_2/DESCRIPTION b/inst/examples/shiny_2/DESCRIPTION new file mode 100644 index 0000000..ffcc173 --- /dev/null +++ b/inst/examples/shiny_2/DESCRIPTION @@ -0,0 +1,7 @@ +Title: Fork-tailed Swift +Author: Jeffrey Owen Hanson +AuthorUrl: http://jeffrey-hanson.com +License: MIT +DisplayMode: normal +Type: Shiny + diff --git a/inst/examples/shiny_2/bird.rds b/inst/examples/shiny_2/bird.rds new file mode 100644 index 0000000..97cfe43 Binary files /dev/null and b/inst/examples/shiny_2/bird.rds differ diff --git a/inst/examples/shiny_2/global.R b/inst/examples/shiny_2/global.R new file mode 100644 index 0000000..228aefe --- /dev/null +++ b/inst/examples/shiny_2/global.R @@ -0,0 +1,31 @@ +# load packages +library(dygraphs) +library(leaflet) +library(dplyr) +library(xts) + +# download and prepare data +if (file.exists('bird.rds')) { + # obtain data from file + bird <- readRDS('bird.rds') +} else { + # obtain data from web - warning takes an hour with decent internet connection + library(spocc) + limit <- 100000 # set lower for debugging + bird <- occ(query='Apus pacificus', from='gbif',has_coords=TRUE, limit=limit, + gbifopts=list(basisOfRecord='HUMAN_OBSERVATION', hasGeospatialIssue=FALSE, year='1990,2016'))$gbif$data[[1]] %>% + mutate(label=paste0("Recorded by: ", recordedBy)) %>% + select(name, longitude, latitude, eventDate, label) %>% + rename(date=eventDate) %>% + mutate(year_month = format(strptime(date, '%Y-%m-%d'), format='%Y-%m')) + saveRDS(bird, file='bird.rds', compress='xz') +} + +# calculate number of observations per year/month +bird.monthly <- bird %>% + mutate(year_month = format(strptime(date, '%Y-%m-%d'), format='%Y-%m')) %>% + group_by(year_month) %>% + summarise(n_observations = n()) +bird.monthly <- as.xts(bird.monthly$n_observations, order.by=as.Date(paste0(bird.monthly$year_month, '-01'), format='%Y-%m-%d')) + + diff --git a/inst/examples/shiny_2/server.R b/inst/examples/shiny_2/server.R new file mode 100644 index 0000000..b691db0 --- /dev/null +++ b/inst/examples/shiny_2/server.R @@ -0,0 +1,40 @@ +shinyServer(function(input, output) { + # map + output$map <- renderLeaflet({ + leaflet() %>% + addProviderTiles('Esri.WorldImagery') %>% + setView(lng=70, lat=-20, zoom=2) + }) + # dygraph + output$dygraph <- renderDygraph({ + dygraph(bird.monthly, main='Fork-tailed Swift') %>% + dySeries('V1', drawPoints=TRUE, label='GBIF Observations') %>% + dyRangeSelector() %>% + dySliderInput(color='red', strokePattern='dashed', animate=animationOptions(interval=2000, loop=TRUE)) + }) + # update map based on dygraph + observe({ + if (length(input$dygraph_click$x_closest_point) > 0) { + # clear map + p <- leafletProxy('map') %>% + clearMarkers() %>% + clearMarkerClusters() + + # update map with new data + if (!is.na(input$dygraph_click$x_closest_point)) { + # get date + curr.ym <- format(strptime(input$dygraph_click$x_closest_point, '%b %d, %Y %H:%M:%S'), format='%Y-%m') + # generate new data to plot + if (!is.na(curr.ym)) { + curr.obs <- bird %>% filter(year_month == curr.ym) + # add new data to plot + p <- p %>% addMarkers(lng=curr.obs$longitude, lat=curr.obs$latitude, + clusterOptions=markerClusterOptions(), popup=paste0(curr.obs$label)) + } + } + + # render map + p + } + }) +}) diff --git a/inst/examples/shiny_2/styles.css b/inst/examples/shiny_2/styles.css new file mode 100644 index 0000000..857013a --- /dev/null +++ b/inst/examples/shiny_2/styles.css @@ -0,0 +1,15 @@ +div.outer { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + padding: 0; +} + +#controls { + background-color: white; + padding: 0 20px 20px 20px; +} + diff --git a/inst/examples/shiny_2/ui.R b/inst/examples/shiny_2/ui.R new file mode 100644 index 0000000..64b7450 --- /dev/null +++ b/inst/examples/shiny_2/ui.R @@ -0,0 +1,12 @@ +shinyUI(fluidPage( + div(class='outer', + tags$head( + includeCSS('styles.css') + ), + leafletOutput('map', width='100%', height='100%'), + absolutePanel( + id='controls', class='panel panel-default', fixed=TRUE, draggable=FALSE, top='auto', left='5%', right='5%', bottom=1, width='auto', height='auto', + dygraphOutput('dygraph', height=175) + ) + ) +)) diff --git a/inst/htmlwidgets/dygraphs.js b/inst/htmlwidgets/dygraphs.js index f942886..b206eb3 100644 --- a/inst/htmlwidgets/dygraphs.js +++ b/inst/htmlwidgets/dygraphs.js @@ -75,9 +75,16 @@ HTMLWidgets.widget({ if ((attrs.axes.x.ticker === undefined) && x.fixedtz) attrs.axes.x.ticker = this.customDateTickerFixedTZ(x.tzone); + if ((this.shinyValueFormatter === undefined) && x.fixedtz) + this.shinyValueFormatter = this.xValueFormatterFixedTZ('seconds', x.tzone); + // provide an automatic x value formatter if none is already specified if ((attrs.axes.x.valueFormatter === undefined) && (x.fixedtz != true)) attrs.axes.x.valueFormatter = this.xValueFormatter(x.scale); + + if ((this.shinyValueFormatter === undefined) && x.fixedtz != true) { + this.shinyValueFormatter = this.xValueFormatter('seconds'); + } // convert time to js time attrs.file[0] = attrs.file[0].map(function(value) { @@ -91,7 +98,6 @@ HTMLWidgets.widget({ } } - // transpose array attrs.file = HTMLWidgets.transposeArray2D(attrs.file); @@ -202,7 +208,10 @@ HTMLWidgets.widget({ dygraph.userDateWindow = attrs.dateWindow; if (x.group != null) groups[x.group].push(dygraph); - + + // add shinyValueFormatter so that plugins can access this for a consistent interface + dygraph.shinyValueFormatter = this.shinyValueFormatter; + // add shiny inputs for date window and click if (HTMLWidgets.shinyMode) { var isDate = x.format == "date"; @@ -408,7 +417,12 @@ HTMLWidgets.widget({ date.getDate() + ', ' + date.getFullYear(); else - return date.toLocaleString(); + return monthNames[date.getMonth()] + ' ' + + date.getDate() + ', ' + + date.getFullYear() + ' ' + + date.getHours() + ':' + + date.getMinutes() + ':' + + date.getSeconds(); } }, @@ -623,6 +637,8 @@ HTMLWidgets.widget({ // check for an existing drawCallback var prevDrawCallback = dygraph.getOption("drawCallback"); + // store formatter function + var shinyValueFormatter = this.shinyValueFormatter; // install the callback dygraph.updateOptions({ @@ -633,7 +649,7 @@ HTMLWidgets.widget({ // fire input change var range = dygraph.xAxisRange(); if (isDate) - range = [new Date(range[0]), new Date(range[1])]; + range = [shinyValueFormatter(range[0]), shinyValueFormatter(range[1])]; Shiny.onInputChange(id + "_date_window", range); } }); @@ -641,7 +657,10 @@ HTMLWidgets.widget({ addClickShinyInput: function(id, isDate) { + // check for an existing clickCallBackk var prevClickCallback = dygraph.getOption("clickCallback") + // store formatter function + var shinyValueFormatter = this.shinyValueFormatter; dygraph.updateOptions({ clickCallback: function(e, x, points) { @@ -650,13 +669,13 @@ HTMLWidgets.widget({ if (prevClickCallback) prevClickCallback(e, x, points); - // fire input change + // fire input change Shiny.onInputChange(el.id + "_click", { - x: isDate ? new Date(x) : x, - x_closest_point: isDate ? new Date(points[0].xval) : points[0].xval, - y_closest_point: points[0].yval, - '.nonce': Math.random() // Force reactivity if click hasn't changed - }); + x: isDate ? shinyValueFormatter(x) : x, + x_closest_point: isDate ? shinyValueFormatter(points[0].xval) : points[0].xval, + y_closest_point: points[0].yval, + '.nonce': Math.random() // Force reactivity if click hasn't changed + }); } }); }, diff --git a/man/dyCrosshair.Rd b/man/dyCrosshair.Rd new file mode 100644 index 0000000..a07da6e --- /dev/null +++ b/man/dyCrosshair.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plugin.R +\name{dyCrosshair} +\alias{dyCrosshair} +\title{dyCrosshair} +\usage{ +dyCrosshair(dygraph, direction = c("both", "horizontal", "vertical")) +} +\arguments{ +\item{dygraph}{Dygraph to add plugin to} + +\item{direction}{Direction for crosshairs. Defaults to 'both'. Valid arguments are} +} +\value{ +A dygraph with the specified plugin enabled. +} +\description{ +dyCrosshair +} +\details{ +The dyCrosshair plugin draws a crosshair line over the point closest to the +mouse when the user hovers over the graph. It has a "direction" option which +is provided in the R wrapper function and then forwarded to the plugin using +the "options" argument to dyPlugin. +} +\examples{ +library(dygraphs) +dygraph(mdeaths) \%>\% + dyCrosshair() + +} + diff --git a/man/dySliderInput.Rd b/man/dySliderInput.Rd new file mode 100644 index 0000000..90ad6a5 --- /dev/null +++ b/man/dySliderInput.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plugin.R +\name{dySliderInput} +\alias{dySliderInput} +\title{dySliderInput} +\usage{ +dySliderInput(dygraph, color = "red", strokePattern = c("dashed", "solid", + "dotted", "dotdash"), animate = FALSE) +} +\arguments{ +\item{dygraph}{Dygraph to add plugin to} + +\item{color}{Color to draw slider. Defaults to 'red'.} + +\item{strokePattern}{Line type for slider. Defaults to 'dashed'. Valid arguments are +'dashed', 'solid', 'dotted', and 'dotdash'.} + +\item{animate}{'TRUE' to show simple animation controls with default settings; +'FALSE' not to; or a custom settings list, such as those created using 'animationOptions'.} +} +\value{ +A dygraph with the specified plugin enabled. +} +\description{ +dySliderInput +} +\details{ +The dySliderInput plugin turns the dyDygraph into a slider input widget. The +user can click on a point along the graph and the graph will place a vertical +line on the graph. The user can also use the animation options to scroll +through points along the graph. +} +\examples{ +library(dygraphs) +dygraph(mdeaths) \%>\% + dySliderInput() + +} + diff --git a/man/dyUnzoom.Rd b/man/dyUnzoom.Rd new file mode 100644 index 0000000..56c1a11 --- /dev/null +++ b/man/dyUnzoom.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plugin.R +\name{dyUnzoom} +\alias{dyUnzoom} +\title{dyUnzoom} +\usage{ +dyUnzoom(dygraph) +} +\arguments{ +\item{dygraph}{Dygraph to add plugin to} +} +\value{ +A dygraph with the specified plugin enabled. +} +\description{ +dyUnzoom +} +\details{ +The dyUnzoom plugin adds an "Unzoom" button to the graph when it's displaying +in a zoomed state (this is a bit more discoverable than the default double- +click gesture for unzooming). Note that this plugin has no options (see +below for an example with options). +} +\examples{ +library(dygraphs) +dygraph(mdeaths) \%>\% + dyUnzoom() + +} + diff --git a/tests/testthat/test-crosshair.R b/tests/testthat/test-crosshair.R new file mode 100644 index 0000000..d5c2ae9 --- /dev/null +++ b/tests/testthat/test-crosshair.R @@ -0,0 +1,9 @@ + +context("dyCrosshair") + +test_that("crosshair creation", { + d <- dygraph(nhtemp, main = "New Haven Temperatures") %>% + dyCrosshair() + expect_identical(d$dependencies[[1]]$name, 'Dygraph.Plugins.Crosshair') +}) + diff --git a/tests/testthat/test-sider-input.R b/tests/testthat/test-sider-input.R new file mode 100644 index 0000000..e4f05cc --- /dev/null +++ b/tests/testthat/test-sider-input.R @@ -0,0 +1,9 @@ + +context("dySliderInput") + +test_that("slider input creation", { + d <- dygraph(nhtemp, main = "New Haven Temperatures") %>% + dySliderInput() + expect_identical(d$dependencies[[1]]$name, 'Dygraph.Plugins.SliderInput') +}) + diff --git a/tests/testthat/test-unzoom.R b/tests/testthat/test-unzoom.R new file mode 100644 index 0000000..d934d1d --- /dev/null +++ b/tests/testthat/test-unzoom.R @@ -0,0 +1,9 @@ + +context("dyUnzoom") + +test_that("unzoom input creation", { + d <- dygraph(nhtemp, main = "New Haven Temperatures") %>% + dyUnzoom() + expect_identical(d$dependencies[[1]]$name, 'Dygraph.Plugins.Unzoom') +}) +