diff --git a/.travis.yml b/.travis.yml index 8c2c2619b7..f92a04f9ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,3 +33,6 @@ before_script: # work around temporary travis + R 3.5 bug r_packages: devtools + +# temporary: needed for plotly input testing in shiny +r_github_packages: rstudio/shinytest diff --git a/DESCRIPTION b/DESCRIPTION index 7396b3b25e..6089fe3366 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -27,7 +27,7 @@ Imports: tools, scales, httr, - jsonlite, + jsonlite (>= 1.6), magrittr, digest, viridisLite, @@ -54,6 +54,7 @@ Suggests: knitr, devtools, shiny (>= 1.1.0), + shinytest (> 1.3.0), curl, rmarkdown, vdiffr, diff --git a/NAMESPACE b/NAMESPACE index a3efffa31f..502ad5200f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -128,6 +128,8 @@ export(do) export(do_) export(embed_notebook) export(event_data) +export(event_register) +export(event_unregister) export(export) export(filter) export(filter_) @@ -246,7 +248,8 @@ importFrom(httr,config) importFrom(httr,content) importFrom(httr,stop_for_status) importFrom(httr,warn_for_status) -importFrom(jsonlite,fromJSON) +importFrom(jsonlite,parse_json) +importFrom(jsonlite,read_json) importFrom(jsonlite,toJSON) importFrom(lazyeval,all_dots) importFrom(lazyeval,f_eval) diff --git a/NEWS.md b/NEWS.md index 90b264decd..729910c7b1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,11 @@ * The `orca_serve()` function was added for efficient exporting of many plotly graphs. For examples, see `help(orca_serve)`. * The `orca()` function gains new arguments `more_args` and `...` for finer control over the underlying system commands. +* Improvements related to accessing plotly.js events in shiny: + * The `event` argument of the `event_data()` function now supports the following events: `plotly_selecting`, `plotly_brushed`, `plotly_brushing`, `plotly_restyle`, `plotly_legendclick`, `plotly_legenddoubleclick`, `plotly_clickannotation`, `plotly_afterplot`, `plotly_doubleclick`, `plotly_deselect`, `plotly_unhover`. For examples, see `plotly_example("shiny", "event_data")`, `plotly_example("shiny", "event_data_legends")`, and `plotly_example("shiny", "event_data_annotation")`, + * New `event_register()` and `event_unregister()` functions for declaring which events to transmit over the wire (i.e., from the browser to the shiny server). Events that are likely to have large overhead are not registered by default, so you'll need to register these: `plotly_selecting`, `plotly_unhover`, `plotly_restyle`, `plotly_legendclick`, and `plotly_legenddoubleclick`. + * A new `priority` argument. By setting `priority='event'`, the `event` is treated like a true event: any reactive expression using the `event` becomes invalidated (regardless of whether the input values has changed). For an example, see `plotly_example("shiny", "event_priority")`. +* The `method` argument of `plotlyProxyInvoke()` gains support for a `"reconfig"` method. This makes it possible to change just the configuration of a plot. For an example use, see `plotly_example("shiny", "event_data_annotation")`. ## IMPROVEMENTS diff --git a/R/imports.R b/R/imports.R index 6b5402c742..9af72e544f 100644 --- a/R/imports.R +++ b/R/imports.R @@ -5,7 +5,7 @@ #' @importFrom stats setNames complete.cases quantile is.leaf #' @importFrom tidyr unnest #' @importFrom viridisLite viridis -#' @importFrom jsonlite toJSON fromJSON +#' @importFrom jsonlite toJSON parse_json read_json #' @importFrom httr GET POST PATCH content config add_headers stop_for_status warn_for_status #' @importFrom htmlwidgets createWidget sizingPolicy saveWidget onRender prependContent #' @importFrom lazyeval f_eval is_formula all_dots is_lang f_new diff --git a/R/plotly_build.R b/R/plotly_build.R index 57f42e070f..d00df27d9a 100644 --- a/R/plotly_build.R +++ b/R/plotly_build.R @@ -368,6 +368,9 @@ plotly_build.plotly <- function(p, registerFrames = TRUE) { p <- registerFrames(p, frameMapping = frameMapping) } + # set the default plotly.js events to register in shiny + p <- shiny_defaults_set(p) + p <- verify_guides(p) # verify plot attributes are legal according to the plotly.js spec diff --git a/R/proxy.R b/R/proxy.R index 8f34339da3..eb204b34ba 100644 --- a/R/proxy.R +++ b/R/proxy.R @@ -101,7 +101,8 @@ plotlyjs_methods <- function() { c( "restyle", "relayout", "update", "addTraces", "deleteTraces", "moveTraces", "extendTraces", "prependTraces", "purge", "toImage", "downloadImage", "animate", - "newPlot", "react", "validate", "makeTemplate", "validateTemplate", "addFrames" + "newPlot", "react", "validate", "makeTemplate", "validateTemplate", "addFrames", + "reconfig" ) } diff --git a/R/shiny.R b/R/shiny.R index 54c9eac29f..a1b024d3cb 100644 --- a/R/shiny.R +++ b/R/shiny.R @@ -46,7 +46,7 @@ renderPlotly <- function(expr, env = parent.frame(), quoted = FALSE) { shiny::snapshotPreprocessOutput( renderFunc, function(value) { - json <- from_JSON_safe(value) + json <- from_JSON(value) json$x <- json$x[setdiff(names(json$x), c("visdat", "cur_data", "attrs"))] to_JSON(json) } @@ -55,11 +55,22 @@ renderPlotly <- function(expr, env = parent.frame(), quoted = FALSE) { # Converts a plot, OR a promise of a plot, to plotly prepareWidget <- function(x) { - if (promises::is.promising(x)) { - promises::then(x, ggplotly) + p <- if (promises::is.promising(x)) { + promises::then(x, plotly_build) } else { - ggplotly(x) + plotly_build(x) } + register_plot_events(p) + p +} + +register_plot_events <- function(p) { + session <- getDefaultReactiveDomain() + eventIDs <- paste(p$x$shinyEvents, p$x$source, sep = "-") + session$userData$plotlyShinyEventIDs <- unique(c( + session$userData$plotlyShinyEventIDs, + eventIDs + )) } @@ -67,26 +78,162 @@ prepareWidget <- function(x) { #' #' This function must be called within a reactive shiny context. #' -#' @param event The type of plotly event. Currently 'plotly_hover', -#' 'plotly_click', 'plotly_selected', and 'plotly_relayout' are supported. +#' @param event The type of plotly event. All supported events are listed in the +#' function signature above (i.e., the usage section). #' @param source a character string of length 1. Match the value of this string -#' with the source argument in [plot_ly()] to retrieve the -#' event data corresponding to a specific plot (shiny apps can have multiple plots). +#' with the `source` argument in [plot_ly()] (or [ggplotly()]) to respond to +#' events emitted from that specific plot. #' @param session a shiny session object (the default should almost always be used). +#' @param priority the priority of the corresponding shiny input value. +#' If equal to `"event"`, then [event_data()] always triggers re-execution, +#' instead of re-executing only when the relevant shiny input value changes +#' (the default). #' @export +#' @seealso [event_register], [event_unregister] +#' @references +#' * +#' * #' @author Carson Sievert #' @examples \dontrun{ #' plotly_example("shiny", "event_data") #' } -event_data <- function(event = c("plotly_hover", "plotly_click", "plotly_selected", - "plotly_relayout"), source = "A", - session = shiny::getDefaultReactiveDomain()) { +event_data <- function( + event = c( + "plotly_hover", "plotly_unhover", "plotly_click", "plotly_doubleclick", + "plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", + "plotly_deselect", "plotly_relayout", "plotly_restyle", "plotly_legendclick", + "plotly_legenddoubleclick", "plotly_clickannotation", "plotly_afterplot" + ), + source = "A", + session = shiny::getDefaultReactiveDomain(), + priority = c("input", "event") +) { if (is.null(session)) { stop("No reactive domain detected. This function can only be called \n", "from within a reactive shiny context.") } - src <- sprintf(".clientValue-%s-%s", event[1], source) - val <- session$rootScope()$input[[src]] - if (is.null(val)) val else jsonlite::fromJSON(val) + + event <- match.arg(event) + eventID <- paste(event, source, sep = "-") + + # It's possible for event_data() to execute before any + # relevant input values have been registered (i.e, before + # relevant plotly graphs have been executed). Therefore, + # we delay checking that a relevant input value has been + # registered until shiny flushes + session$onFlushed( + function() { + eventIDRegistered <- eventID %in% session$userData$plotlyShinyEventIDs + if (!eventIDRegistered) { + warning( + "The '", event, "' event tied a source ID of '", source, "' ", + "is not registered. In order to obtain this event data, ", + "please add `event_register(p, '", event, "')` to the plot (`p`) ", + "that you wish to obtain event data from.", + call. = FALSE + ) + } + } + ) + + # legend clicking returns trace(s), which shouldn't be simplified... + parseJSON <- if (event %in% c("plotly_legendclick", "plotly_legenddoubleclick")) { + from_JSON + } else { + function(x) jsonlite::parse_json(x, simplifyVector = TRUE) + } + + # Handle NULL sensibly + parseJSONVal <- function(x) { + if (is.null(x)) x else parseJSON(x) + } + + parsedInputValue <- function() { + parseJSONVal(session$rootScope()$input[[eventID]]) + } + + priority <- match.arg(priority) + if (priority == "event") { + # Shiny.setInputValue() is always called with event priority + # so simply return the parse input value + return(parsedInputValue()) + + } else { + + eventHasStorage <- eventID %in% names(session$userData$plotlyInputStore) + + if (!eventHasStorage) { + # store input value as a reactive value to leverage caching + session$userData$plotlyInputStore <- session$userData$plotlyInputStore %||% shiny::reactiveValues() + session$userData$plotlyInputStore[[eventID]] <- shiny::isolate(parsedInputValue()) + shiny::observe({ + session$userData$plotlyInputStore[[eventID]] <- parsedInputValue() + }, priority = 10000, domain = session) + } + + session$userData$plotlyInputStore[[eventID]] + } + +} + + +#' Register a shiny input value +#' +#' @inheritParams event_data +#' @seealso [event_data] +#' @export +#' @author Carson Sievert +event_register <- function(p, event = NULL) { + event <- match.arg(event, event_data_events()) + shiny_event_add(p, event) +} + +#' Un-register a shiny input value +#' +#' @inheritParams event_data +#' @seealso [event_data] +#' @export +#' @author Carson Sievert +event_unregister <- function(p, event = NULL) { + event <- match.arg(event, event_data_events()) + shiny_event_remove(p, event) +} + + +# helpers +shiny_event_add <- function(p, event) { + p <- shiny_defaults_set(p) + p$x$shinyEvents <- unique(c(p$x$shinyEvents, event)) + p +} + +shiny_event_remove <- function(p, event) { + p <- shiny_defaults_set(p) + p$x$shinyEvents <- setdiff(p$x$shinyEvents, event) + p +} + +shiny_defaults_set <- function(p) { + p$x$shinyEvents <- p$x$shinyEvents %||% shiny_event_defaults() + p +} + +shiny_event_defaults <- function() { + c( + "plotly_hover", + "plotly_click", + "plotly_selected", + "plotly_relayout", + "plotly_brushed", + "plotly_brushing", + "plotly_clickannotation", + "plotly_doubleclick", + "plotly_deselect", + "plotly_afterplot" + ) +} + +event_data_events <- function() { + eval(formals(event_data)$event) } diff --git a/R/utils.R b/R/utils.R index 9cef455cd9..9dbfa4b217 100644 --- a/R/utils.R +++ b/R/utils.R @@ -949,7 +949,7 @@ grab <- function(what = "username") { # try to grab an object key from a JSON file (returns empty string on error) try_file <- function(f, what) { - tryCatch(jsonlite::fromJSON(f)[[what]], error = function(e) NULL) + tryCatch(jsonlite::read_json(f)[[what]], error = function(e) NULL) } # preferred defaults for toJSON mapping @@ -960,14 +960,7 @@ to_JSON <- function(x, ...) { # preferred defaults for toJSON mapping from_JSON <- function(x, ...) { - jsonlite::fromJSON(x, simplifyDataFrame = FALSE, simplifyMatrix = FALSE, ...) -} - -from_JSON_safe <- function(txt, ...) { - if (!jsonlite::validate(txt)) { - stop("Expected a valid JSON string.") - } - from_JSON(txt, ...) + jsonlite::parse_json(x, simplifyVector = TRUE, simplifyDataFrame = FALSE, simplifyMatrix = FALSE, ...) } i <- function(x) { diff --git a/inst/examples/shiny/event_data/app.R b/inst/examples/shiny/event_data/app.R index 0ffdb32114..9dd4eff730 100644 --- a/inst/examples/shiny/event_data/app.R +++ b/inst/examples/shiny/event_data/app.R @@ -6,7 +6,10 @@ ui <- fluidPage( plotlyOutput("plot"), verbatimTextOutput("hover"), verbatimTextOutput("click"), - verbatimTextOutput("brush") + verbatimTextOutput("selected"), + verbatimTextOutput("selecting"), + verbatimTextOutput("brushed"), + verbatimTextOutput("brushing") ) server <- function(input, output, session) { @@ -14,13 +17,14 @@ server <- function(input, output, session) { nms <- row.names(mtcars) output$plot <- renderPlotly({ - if (identical(input$plotType, "ggplotly")) { - p <- ggplot(mtcars, aes(x = mpg, y = wt, customdata = nms)) + geom_point() - ggplotly(p) %>% layout(dragmode = "select") + p <- if (identical(input$plotType, "ggplotly")) { + ggplotly(ggplot(mtcars, aes(x = mpg, y = wt, customdata = nms)) + geom_point()) } else { - plot_ly(mtcars, x = ~mpg, y = ~wt, customdata = nms) %>% - layout(dragmode = "select") + plot_ly(mtcars, x = ~mpg, y = ~wt, customdata = nms) } + p %>% + layout(dragmode = "select") %>% + event_register("plotly_selecting") }) output$hover <- renderPrint({ @@ -33,11 +37,26 @@ server <- function(input, output, session) { if (is.null(d)) "Click events appear here (double-click to clear)" else d }) - output$brush <- renderPrint({ + output$selected <- renderPrint({ d <- event_data("plotly_selected") if (is.null(d)) "Click and drag events (i.e., select/lasso) appear here (double-click to clear)" else d }) + output$selecting <- renderPrint({ + d <- event_data("plotly_selecting") + if (is.null(d)) "Click and drag events (i.e., select/lasso) appear here (double-click to clear)" else d + }) + + output$brush <- renderPrint({ + d <- event_data("plotly_brushed") + if (is.null(d)) "Extents of the selection brush will appear here." else d + }) + + output$brushing <- renderPrint({ + d <- event_data("plotly_brushing") + if (is.null(d)) "Extents of the selection brush will appear here." else d + }) + } shinyApp(ui, server, options = list(display.mode = "showcase")) diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/001.json b/inst/examples/shiny/event_data/tests/mytest-expected/001.json new file mode 100644 index 0000000000..58e5e31244 --- /dev/null +++ b/inst/examples/shiny/event_data/tests/mytest-expected/001.json @@ -0,0 +1,457 @@ +{ + "input": { + "plotType": "ggplotly" + }, + "output": { + "brushing": "[1] \"Extents of the selection brush will appear here.\"", + "click": "[1] \"Click events appear here (double-click to clear)\"", + "hover": "[1] \"Hover events appear here (unhover to clear)\"", + "plot": { + "x": { + "data": [ + { + "x": [ + 21, + 21, + 22.8, + 21.4, + 18.7, + 18.1, + 14.3, + 24.4, + 22.8, + 19.2, + 17.8, + 16.4, + 17.3, + 15.2, + 10.4, + 10.4, + 14.7, + 32.4, + 30.4, + 33.9, + 21.5, + 15.5, + 15.2, + 13.3, + 19.2, + 27.3, + 26, + 30.4, + 15.8, + 19.7, + 15, + 21.4 + ], + "y": [ + 2.62, + 2.875, + 2.32, + 3.215, + 3.44, + 3.46, + 3.57, + 3.19, + 3.15, + 3.44, + 3.44, + 4.07, + 3.73, + 3.78, + 5.25, + 5.424, + 5.345, + 2.2, + 1.615, + 1.835, + 2.465, + 3.52, + 3.435, + 3.84, + 3.845, + 1.935, + 2.14, + 1.513, + 3.17, + 2.77, + 3.57, + 2.78 + ], + "text": [ + "mpg: 21.0
wt: 2.620
nms: Mazda RX4", + "mpg: 21.0
wt: 2.875
nms: Mazda RX4 Wag", + "mpg: 22.8
wt: 2.320
nms: Datsun 710", + "mpg: 21.4
wt: 3.215
nms: Hornet 4 Drive", + "mpg: 18.7
wt: 3.440
nms: Hornet Sportabout", + "mpg: 18.1
wt: 3.460
nms: Valiant", + "mpg: 14.3
wt: 3.570
nms: Duster 360", + "mpg: 24.4
wt: 3.190
nms: Merc 240D", + "mpg: 22.8
wt: 3.150
nms: Merc 230", + "mpg: 19.2
wt: 3.440
nms: Merc 280", + "mpg: 17.8
wt: 3.440
nms: Merc 280C", + "mpg: 16.4
wt: 4.070
nms: Merc 450SE", + "mpg: 17.3
wt: 3.730
nms: Merc 450SL", + "mpg: 15.2
wt: 3.780
nms: Merc 450SLC", + "mpg: 10.4
wt: 5.250
nms: Cadillac Fleetwood", + "mpg: 10.4
wt: 5.424
nms: Lincoln Continental", + "mpg: 14.7
wt: 5.345
nms: Chrysler Imperial", + "mpg: 32.4
wt: 2.200
nms: Fiat 128", + "mpg: 30.4
wt: 1.615
nms: Honda Civic", + "mpg: 33.9
wt: 1.835
nms: Toyota Corolla", + "mpg: 21.5
wt: 2.465
nms: Toyota Corona", + "mpg: 15.5
wt: 3.520
nms: Dodge Challenger", + "mpg: 15.2
wt: 3.435
nms: AMC Javelin", + "mpg: 13.3
wt: 3.840
nms: Camaro Z28", + "mpg: 19.2
wt: 3.845
nms: Pontiac Firebird", + "mpg: 27.3
wt: 1.935
nms: Fiat X1-9", + "mpg: 26.0
wt: 2.140
nms: Porsche 914-2", + "mpg: 30.4
wt: 1.513
nms: Lotus Europa", + "mpg: 15.8
wt: 3.170
nms: Ford Pantera L", + "mpg: 19.7
wt: 2.770
nms: Ferrari Dino", + "mpg: 15.0
wt: 3.570
nms: Maserati Bora", + "mpg: 21.4
wt: 2.780
nms: Volvo 142E" + ], + "customdata": [ + "Mazda RX4", + "Mazda RX4 Wag", + "Datsun 710", + "Hornet 4 Drive", + "Hornet Sportabout", + "Valiant", + "Duster 360", + "Merc 240D", + "Merc 230", + "Merc 280", + "Merc 280C", + "Merc 450SE", + "Merc 450SL", + "Merc 450SLC", + "Cadillac Fleetwood", + "Lincoln Continental", + "Chrysler Imperial", + "Fiat 128", + "Honda Civic", + "Toyota Corolla", + "Toyota Corona", + "Dodge Challenger", + "AMC Javelin", + "Camaro Z28", + "Pontiac Firebird", + "Fiat X1-9", + "Porsche 914-2", + "Lotus Europa", + "Ford Pantera L", + "Ferrari Dino", + "Maserati Bora", + "Volvo 142E" + ], + "type": "scatter", + "mode": "markers", + "marker": { + "autocolorscale": false, + "color": "rgba(0,0,0,1)", + "opacity": 1, + "size": 5.66929133858268, + "symbol": "circle", + "line": { + "width": 1.88976377952756, + "color": "rgba(0,0,0,1)" + } + }, + "hoveron": "points", + "showlegend": false, + "xaxis": "x", + "yaxis": "y", + "hoverinfo": "text", + "frame": null + } + ], + "layout": { + "margin": { + "t": 25.7412480974125, + "r": 7.30593607305936, + "b": 39.6955859969559, + "l": 31.4155251141553 + }, + "plot_bgcolor": "rgba(235,235,235,1)", + "paper_bgcolor": "rgba(255,255,255,1)", + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "xaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 9.225, + 35.075 + ], + "tickmode": "array", + "ticktext": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "tickvals": [ + 10, + 15, + 20, + 25, + 30, + 35 + ], + "categoryorder": "array", + "categoryarray": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "y", + "title": "mpg", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "yaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 1.31745, + 5.61955 + ], + "tickmode": "array", + "ticktext": [ + "2", + "3", + "4", + "5" + ], + "tickvals": [ + 2, + 3, + 4, + 5 + ], + "categoryorder": "array", + "categoryarray": [ + "2", + "3", + "4", + "5" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "x", + "title": "wt", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "shapes": [ + { + "type": "rect", + "fillcolor": null, + "line": { + "color": null, + "width": 0, + "linetype": [ + + ] + }, + "yref": "paper", + "xref": "paper", + "x0": 0, + "x1": 1, + "y0": 0, + "y1": 1 + } + ], + "showlegend": false, + "legend": { + "bgcolor": "rgba(255,255,255,1)", + "bordercolor": "transparent", + "borderwidth": 1.88976377952756, + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 11.689497716895 + } + }, + "hovermode": "closest", + "barmode": "relative", + "dragmode": "select" + }, + "config": { + "doubleClick": "reset", + "cloud": false + }, + "source": "A", + "shinyEvents": [ + "plotly_hover", + "plotly_click", + "plotly_selected", + "plotly_relayout", + "plotly_brushed", + "plotly_brushing", + "plotly_clickannotation", + "plotly_doubleclick", + "plotly_deselect", + "plotly_afterplot", + "plotly_selecting" + ], + "highlight": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0 + }, + "base_url": "https://plot.ly" + }, + "evals": [ + + ], + "jsHooks": [ + + ], + "deps": [ + { + "name": "typedarray", + "version": "0.1", + "src": { + "href": "typedarray-0.1" + }, + "meta": null, + "script": "typedarray.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "jquery", + "version": "1.11.3", + "src": { + "href": "jquery-1.11.3" + }, + "meta": null, + "script": "jquery.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "crosstalk", + "version": "1.0.0", + "src": { + "href": "crosstalk-1.0.0" + }, + "meta": null, + "script": "js/crosstalk.min.js", + "stylesheet": "css/crosstalk.css", + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "plotly-htmlwidgets-css", + "version": "1.42.5", + "src": { + "href": "plotly-htmlwidgets-css-1.42.5" + }, + "meta": null, + "script": null, + "stylesheet": "plotly-htmlwidgets.css", + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "plotly-main", + "version": "1.42.5", + "src": { + "href": "plotly-main-1.42.5" + }, + "meta": null, + "script": "plotly-latest.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + } + ] + }, + "selected": "[1] \"Click and drag events (i.e., select/lasso) appear here (double-click to clear)\"", + "selecting": "[1] \"Click and drag events (i.e., select/lasso) appear here (double-click to clear)\"" + }, + "export": { + + } +} diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/001.png b/inst/examples/shiny/event_data/tests/mytest-expected/001.png new file mode 100644 index 0000000000..be76d74718 Binary files /dev/null and b/inst/examples/shiny/event_data/tests/mytest-expected/001.png differ diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/002.json b/inst/examples/shiny/event_data/tests/mytest-expected/002.json new file mode 100644 index 0000000000..d1acd9501e --- /dev/null +++ b/inst/examples/shiny/event_data/tests/mytest-expected/002.json @@ -0,0 +1,474 @@ +{ + "input": { + ".clientValue-default-plotlyCrosstalkOpts": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0, + "color": [ + + ] + }, + "plotly_afterplot-A": "\"plot\"", + "plotly_click-A": "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", + "plotly_hover-A": null, + "plotType": "ggplotly" + }, + "output": { + "brushing": "[1] \"Extents of the selection brush will appear here.\"", + "click": " curveNumber pointNumber x y customdata\n1 0 7 24.4 3.19 Merc 240D", + "hover": "[1] \"Hover events appear here (unhover to clear)\"", + "plot": { + "x": { + "data": [ + { + "x": [ + 21, + 21, + 22.8, + 21.4, + 18.7, + 18.1, + 14.3, + 24.4, + 22.8, + 19.2, + 17.8, + 16.4, + 17.3, + 15.2, + 10.4, + 10.4, + 14.7, + 32.4, + 30.4, + 33.9, + 21.5, + 15.5, + 15.2, + 13.3, + 19.2, + 27.3, + 26, + 30.4, + 15.8, + 19.7, + 15, + 21.4 + ], + "y": [ + 2.62, + 2.875, + 2.32, + 3.215, + 3.44, + 3.46, + 3.57, + 3.19, + 3.15, + 3.44, + 3.44, + 4.07, + 3.73, + 3.78, + 5.25, + 5.424, + 5.345, + 2.2, + 1.615, + 1.835, + 2.465, + 3.52, + 3.435, + 3.84, + 3.845, + 1.935, + 2.14, + 1.513, + 3.17, + 2.77, + 3.57, + 2.78 + ], + "text": [ + "mpg: 21.0
wt: 2.620
nms: Mazda RX4", + "mpg: 21.0
wt: 2.875
nms: Mazda RX4 Wag", + "mpg: 22.8
wt: 2.320
nms: Datsun 710", + "mpg: 21.4
wt: 3.215
nms: Hornet 4 Drive", + "mpg: 18.7
wt: 3.440
nms: Hornet Sportabout", + "mpg: 18.1
wt: 3.460
nms: Valiant", + "mpg: 14.3
wt: 3.570
nms: Duster 360", + "mpg: 24.4
wt: 3.190
nms: Merc 240D", + "mpg: 22.8
wt: 3.150
nms: Merc 230", + "mpg: 19.2
wt: 3.440
nms: Merc 280", + "mpg: 17.8
wt: 3.440
nms: Merc 280C", + "mpg: 16.4
wt: 4.070
nms: Merc 450SE", + "mpg: 17.3
wt: 3.730
nms: Merc 450SL", + "mpg: 15.2
wt: 3.780
nms: Merc 450SLC", + "mpg: 10.4
wt: 5.250
nms: Cadillac Fleetwood", + "mpg: 10.4
wt: 5.424
nms: Lincoln Continental", + "mpg: 14.7
wt: 5.345
nms: Chrysler Imperial", + "mpg: 32.4
wt: 2.200
nms: Fiat 128", + "mpg: 30.4
wt: 1.615
nms: Honda Civic", + "mpg: 33.9
wt: 1.835
nms: Toyota Corolla", + "mpg: 21.5
wt: 2.465
nms: Toyota Corona", + "mpg: 15.5
wt: 3.520
nms: Dodge Challenger", + "mpg: 15.2
wt: 3.435
nms: AMC Javelin", + "mpg: 13.3
wt: 3.840
nms: Camaro Z28", + "mpg: 19.2
wt: 3.845
nms: Pontiac Firebird", + "mpg: 27.3
wt: 1.935
nms: Fiat X1-9", + "mpg: 26.0
wt: 2.140
nms: Porsche 914-2", + "mpg: 30.4
wt: 1.513
nms: Lotus Europa", + "mpg: 15.8
wt: 3.170
nms: Ford Pantera L", + "mpg: 19.7
wt: 2.770
nms: Ferrari Dino", + "mpg: 15.0
wt: 3.570
nms: Maserati Bora", + "mpg: 21.4
wt: 2.780
nms: Volvo 142E" + ], + "customdata": [ + "Mazda RX4", + "Mazda RX4 Wag", + "Datsun 710", + "Hornet 4 Drive", + "Hornet Sportabout", + "Valiant", + "Duster 360", + "Merc 240D", + "Merc 230", + "Merc 280", + "Merc 280C", + "Merc 450SE", + "Merc 450SL", + "Merc 450SLC", + "Cadillac Fleetwood", + "Lincoln Continental", + "Chrysler Imperial", + "Fiat 128", + "Honda Civic", + "Toyota Corolla", + "Toyota Corona", + "Dodge Challenger", + "AMC Javelin", + "Camaro Z28", + "Pontiac Firebird", + "Fiat X1-9", + "Porsche 914-2", + "Lotus Europa", + "Ford Pantera L", + "Ferrari Dino", + "Maserati Bora", + "Volvo 142E" + ], + "type": "scatter", + "mode": "markers", + "marker": { + "autocolorscale": false, + "color": "rgba(0,0,0,1)", + "opacity": 1, + "size": 5.66929133858268, + "symbol": "circle", + "line": { + "width": 1.88976377952756, + "color": "rgba(0,0,0,1)" + } + }, + "hoveron": "points", + "showlegend": false, + "xaxis": "x", + "yaxis": "y", + "hoverinfo": "text", + "frame": null + } + ], + "layout": { + "margin": { + "t": 25.7412480974125, + "r": 7.30593607305936, + "b": 39.6955859969559, + "l": 31.4155251141553 + }, + "plot_bgcolor": "rgba(235,235,235,1)", + "paper_bgcolor": "rgba(255,255,255,1)", + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "xaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 9.225, + 35.075 + ], + "tickmode": "array", + "ticktext": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "tickvals": [ + 10, + 15, + 20, + 25, + 30, + 35 + ], + "categoryorder": "array", + "categoryarray": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "y", + "title": "mpg", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "yaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 1.31745, + 5.61955 + ], + "tickmode": "array", + "ticktext": [ + "2", + "3", + "4", + "5" + ], + "tickvals": [ + 2, + 3, + 4, + 5 + ], + "categoryorder": "array", + "categoryarray": [ + "2", + "3", + "4", + "5" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "x", + "title": "wt", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "shapes": [ + { + "type": "rect", + "fillcolor": null, + "line": { + "color": null, + "width": 0, + "linetype": [ + + ] + }, + "yref": "paper", + "xref": "paper", + "x0": 0, + "x1": 1, + "y0": 0, + "y1": 1 + } + ], + "showlegend": false, + "legend": { + "bgcolor": "rgba(255,255,255,1)", + "bordercolor": "transparent", + "borderwidth": 1.88976377952756, + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 11.689497716895 + } + }, + "hovermode": "closest", + "barmode": "relative", + "dragmode": "select" + }, + "config": { + "doubleClick": "reset", + "cloud": false + }, + "source": "A", + "shinyEvents": [ + "plotly_hover", + "plotly_click", + "plotly_selected", + "plotly_relayout", + "plotly_brushed", + "plotly_brushing", + "plotly_clickannotation", + "plotly_doubleclick", + "plotly_deselect", + "plotly_afterplot", + "plotly_selecting" + ], + "highlight": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0 + }, + "base_url": "https://plot.ly" + }, + "evals": [ + + ], + "jsHooks": [ + + ], + "deps": [ + { + "name": "typedarray", + "version": "0.1", + "src": { + "href": "typedarray-0.1" + }, + "meta": null, + "script": "typedarray.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "jquery", + "version": "1.11.3", + "src": { + "href": "jquery-1.11.3" + }, + "meta": null, + "script": "jquery.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "crosstalk", + "version": "1.0.0", + "src": { + "href": "crosstalk-1.0.0" + }, + "meta": null, + "script": "js/crosstalk.min.js", + "stylesheet": "css/crosstalk.css", + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "plotly-htmlwidgets-css", + "version": "1.42.5", + "src": { + "href": "plotly-htmlwidgets-css-1.42.5" + }, + "meta": null, + "script": null, + "stylesheet": "plotly-htmlwidgets.css", + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "plotly-main", + "version": "1.42.5", + "src": { + "href": "plotly-main-1.42.5" + }, + "meta": null, + "script": "plotly-latest.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + } + ] + }, + "selected": "[1] \"Click and drag events (i.e., select/lasso) appear here (double-click to clear)\"", + "selecting": "[1] \"Click and drag events (i.e., select/lasso) appear here (double-click to clear)\"" + }, + "export": { + + } +} diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/002.png b/inst/examples/shiny/event_data/tests/mytest-expected/002.png new file mode 100644 index 0000000000..bb21b27531 Binary files /dev/null and b/inst/examples/shiny/event_data/tests/mytest-expected/002.png differ diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/003.json b/inst/examples/shiny/event_data/tests/mytest-expected/003.json new file mode 100644 index 0000000000..286218da04 --- /dev/null +++ b/inst/examples/shiny/event_data/tests/mytest-expected/003.json @@ -0,0 +1,478 @@ +{ + "input": { + ".clientValue-default-plotlyCrosstalkOpts": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0, + "color": [ + + ] + }, + "plotly_afterplot-A": "\"plot\"", + "plotly_brushed-A": "{\"x\":[23.95978500551268,25.98332414553473],\"y\":[3.0020072289156627,3.5073743975903615]}", + "plotly_brushing-A": "{\"x\":[23.95978500551268,25.98332414553473],\"y\":[3.0020072289156627,3.5073743975903615]}", + "plotly_click-A": "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", + "plotly_hover-A": null, + "plotly_selected-A": "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", + "plotly_selecting-A": "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", + "plotType": "ggplotly" + }, + "output": { + "brushing": "$x\n[1] 23.95979 25.98332\n\n$y\n[1] 3.002007 3.507374", + "click": " curveNumber pointNumber x y customdata\n1 0 7 24.4 3.19 Merc 240D", + "hover": "[1] \"Hover events appear here (unhover to clear)\"", + "plot": { + "x": { + "data": [ + { + "x": [ + 21, + 21, + 22.8, + 21.4, + 18.7, + 18.1, + 14.3, + 24.4, + 22.8, + 19.2, + 17.8, + 16.4, + 17.3, + 15.2, + 10.4, + 10.4, + 14.7, + 32.4, + 30.4, + 33.9, + 21.5, + 15.5, + 15.2, + 13.3, + 19.2, + 27.3, + 26, + 30.4, + 15.8, + 19.7, + 15, + 21.4 + ], + "y": [ + 2.62, + 2.875, + 2.32, + 3.215, + 3.44, + 3.46, + 3.57, + 3.19, + 3.15, + 3.44, + 3.44, + 4.07, + 3.73, + 3.78, + 5.25, + 5.424, + 5.345, + 2.2, + 1.615, + 1.835, + 2.465, + 3.52, + 3.435, + 3.84, + 3.845, + 1.935, + 2.14, + 1.513, + 3.17, + 2.77, + 3.57, + 2.78 + ], + "text": [ + "mpg: 21.0
wt: 2.620
nms: Mazda RX4", + "mpg: 21.0
wt: 2.875
nms: Mazda RX4 Wag", + "mpg: 22.8
wt: 2.320
nms: Datsun 710", + "mpg: 21.4
wt: 3.215
nms: Hornet 4 Drive", + "mpg: 18.7
wt: 3.440
nms: Hornet Sportabout", + "mpg: 18.1
wt: 3.460
nms: Valiant", + "mpg: 14.3
wt: 3.570
nms: Duster 360", + "mpg: 24.4
wt: 3.190
nms: Merc 240D", + "mpg: 22.8
wt: 3.150
nms: Merc 230", + "mpg: 19.2
wt: 3.440
nms: Merc 280", + "mpg: 17.8
wt: 3.440
nms: Merc 280C", + "mpg: 16.4
wt: 4.070
nms: Merc 450SE", + "mpg: 17.3
wt: 3.730
nms: Merc 450SL", + "mpg: 15.2
wt: 3.780
nms: Merc 450SLC", + "mpg: 10.4
wt: 5.250
nms: Cadillac Fleetwood", + "mpg: 10.4
wt: 5.424
nms: Lincoln Continental", + "mpg: 14.7
wt: 5.345
nms: Chrysler Imperial", + "mpg: 32.4
wt: 2.200
nms: Fiat 128", + "mpg: 30.4
wt: 1.615
nms: Honda Civic", + "mpg: 33.9
wt: 1.835
nms: Toyota Corolla", + "mpg: 21.5
wt: 2.465
nms: Toyota Corona", + "mpg: 15.5
wt: 3.520
nms: Dodge Challenger", + "mpg: 15.2
wt: 3.435
nms: AMC Javelin", + "mpg: 13.3
wt: 3.840
nms: Camaro Z28", + "mpg: 19.2
wt: 3.845
nms: Pontiac Firebird", + "mpg: 27.3
wt: 1.935
nms: Fiat X1-9", + "mpg: 26.0
wt: 2.140
nms: Porsche 914-2", + "mpg: 30.4
wt: 1.513
nms: Lotus Europa", + "mpg: 15.8
wt: 3.170
nms: Ford Pantera L", + "mpg: 19.7
wt: 2.770
nms: Ferrari Dino", + "mpg: 15.0
wt: 3.570
nms: Maserati Bora", + "mpg: 21.4
wt: 2.780
nms: Volvo 142E" + ], + "customdata": [ + "Mazda RX4", + "Mazda RX4 Wag", + "Datsun 710", + "Hornet 4 Drive", + "Hornet Sportabout", + "Valiant", + "Duster 360", + "Merc 240D", + "Merc 230", + "Merc 280", + "Merc 280C", + "Merc 450SE", + "Merc 450SL", + "Merc 450SLC", + "Cadillac Fleetwood", + "Lincoln Continental", + "Chrysler Imperial", + "Fiat 128", + "Honda Civic", + "Toyota Corolla", + "Toyota Corona", + "Dodge Challenger", + "AMC Javelin", + "Camaro Z28", + "Pontiac Firebird", + "Fiat X1-9", + "Porsche 914-2", + "Lotus Europa", + "Ford Pantera L", + "Ferrari Dino", + "Maserati Bora", + "Volvo 142E" + ], + "type": "scatter", + "mode": "markers", + "marker": { + "autocolorscale": false, + "color": "rgba(0,0,0,1)", + "opacity": 1, + "size": 5.66929133858268, + "symbol": "circle", + "line": { + "width": 1.88976377952756, + "color": "rgba(0,0,0,1)" + } + }, + "hoveron": "points", + "showlegend": false, + "xaxis": "x", + "yaxis": "y", + "hoverinfo": "text", + "frame": null + } + ], + "layout": { + "margin": { + "t": 25.7412480974125, + "r": 7.30593607305936, + "b": 39.6955859969559, + "l": 31.4155251141553 + }, + "plot_bgcolor": "rgba(235,235,235,1)", + "paper_bgcolor": "rgba(255,255,255,1)", + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "xaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 9.225, + 35.075 + ], + "tickmode": "array", + "ticktext": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "tickvals": [ + 10, + 15, + 20, + 25, + 30, + 35 + ], + "categoryorder": "array", + "categoryarray": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "y", + "title": "mpg", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "yaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 1.31745, + 5.61955 + ], + "tickmode": "array", + "ticktext": [ + "2", + "3", + "4", + "5" + ], + "tickvals": [ + 2, + 3, + 4, + 5 + ], + "categoryorder": "array", + "categoryarray": [ + "2", + "3", + "4", + "5" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "x", + "title": "wt", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "shapes": [ + { + "type": "rect", + "fillcolor": null, + "line": { + "color": null, + "width": 0, + "linetype": [ + + ] + }, + "yref": "paper", + "xref": "paper", + "x0": 0, + "x1": 1, + "y0": 0, + "y1": 1 + } + ], + "showlegend": false, + "legend": { + "bgcolor": "rgba(255,255,255,1)", + "bordercolor": "transparent", + "borderwidth": 1.88976377952756, + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 11.689497716895 + } + }, + "hovermode": "closest", + "barmode": "relative", + "dragmode": "select" + }, + "config": { + "doubleClick": "reset", + "cloud": false + }, + "source": "A", + "shinyEvents": [ + "plotly_hover", + "plotly_click", + "plotly_selected", + "plotly_relayout", + "plotly_brushed", + "plotly_brushing", + "plotly_clickannotation", + "plotly_doubleclick", + "plotly_deselect", + "plotly_afterplot", + "plotly_selecting" + ], + "highlight": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0 + }, + "base_url": "https://plot.ly" + }, + "evals": [ + + ], + "jsHooks": [ + + ], + "deps": [ + { + "name": "typedarray", + "version": "0.1", + "src": { + "href": "typedarray-0.1" + }, + "meta": null, + "script": "typedarray.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "jquery", + "version": "1.11.3", + "src": { + "href": "jquery-1.11.3" + }, + "meta": null, + "script": "jquery.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "crosstalk", + "version": "1.0.0", + "src": { + "href": "crosstalk-1.0.0" + }, + "meta": null, + "script": "js/crosstalk.min.js", + "stylesheet": "css/crosstalk.css", + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "plotly-htmlwidgets-css", + "version": "1.42.5", + "src": { + "href": "plotly-htmlwidgets-css-1.42.5" + }, + "meta": null, + "script": null, + "stylesheet": "plotly-htmlwidgets.css", + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "plotly-main", + "version": "1.42.5", + "src": { + "href": "plotly-main-1.42.5" + }, + "meta": null, + "script": "plotly-latest.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + } + ] + }, + "selected": " curveNumber pointNumber x y customdata\n1 0 7 24.4 3.19 Merc 240D", + "selecting": " curveNumber pointNumber x y customdata\n1 0 7 24.4 3.19 Merc 240D" + }, + "export": { + + } +} diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/003.png b/inst/examples/shiny/event_data/tests/mytest-expected/003.png new file mode 100644 index 0000000000..2640eabc08 Binary files /dev/null and b/inst/examples/shiny/event_data/tests/mytest-expected/003.png differ diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/004.json b/inst/examples/shiny/event_data/tests/mytest-expected/004.json new file mode 100644 index 0000000000..b8a05e0e02 --- /dev/null +++ b/inst/examples/shiny/event_data/tests/mytest-expected/004.json @@ -0,0 +1,479 @@ +{ + "input": { + ".clientValue-default-plotlyCrosstalkOpts": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0, + "color": [ + + ] + }, + "plotly_afterplot-A": "\"plot\"", + "plotly_brushed-A": null, + "plotly_brushing-A": null, + "plotly_click-A": null, + "plotly_deselect-A": "\"plot\"", + "plotly_hover-A": null, + "plotly_selected-A": null, + "plotly_selecting-A": null, + "plotType": "ggplotly" + }, + "output": { + "brushing": "[1] \"Extents of the selection brush will appear here.\"", + "click": "[1] \"Click events appear here (double-click to clear)\"", + "hover": "[1] \"Hover events appear here (unhover to clear)\"", + "plot": { + "x": { + "data": [ + { + "x": [ + 21, + 21, + 22.8, + 21.4, + 18.7, + 18.1, + 14.3, + 24.4, + 22.8, + 19.2, + 17.8, + 16.4, + 17.3, + 15.2, + 10.4, + 10.4, + 14.7, + 32.4, + 30.4, + 33.9, + 21.5, + 15.5, + 15.2, + 13.3, + 19.2, + 27.3, + 26, + 30.4, + 15.8, + 19.7, + 15, + 21.4 + ], + "y": [ + 2.62, + 2.875, + 2.32, + 3.215, + 3.44, + 3.46, + 3.57, + 3.19, + 3.15, + 3.44, + 3.44, + 4.07, + 3.73, + 3.78, + 5.25, + 5.424, + 5.345, + 2.2, + 1.615, + 1.835, + 2.465, + 3.52, + 3.435, + 3.84, + 3.845, + 1.935, + 2.14, + 1.513, + 3.17, + 2.77, + 3.57, + 2.78 + ], + "text": [ + "mpg: 21.0
wt: 2.620
nms: Mazda RX4", + "mpg: 21.0
wt: 2.875
nms: Mazda RX4 Wag", + "mpg: 22.8
wt: 2.320
nms: Datsun 710", + "mpg: 21.4
wt: 3.215
nms: Hornet 4 Drive", + "mpg: 18.7
wt: 3.440
nms: Hornet Sportabout", + "mpg: 18.1
wt: 3.460
nms: Valiant", + "mpg: 14.3
wt: 3.570
nms: Duster 360", + "mpg: 24.4
wt: 3.190
nms: Merc 240D", + "mpg: 22.8
wt: 3.150
nms: Merc 230", + "mpg: 19.2
wt: 3.440
nms: Merc 280", + "mpg: 17.8
wt: 3.440
nms: Merc 280C", + "mpg: 16.4
wt: 4.070
nms: Merc 450SE", + "mpg: 17.3
wt: 3.730
nms: Merc 450SL", + "mpg: 15.2
wt: 3.780
nms: Merc 450SLC", + "mpg: 10.4
wt: 5.250
nms: Cadillac Fleetwood", + "mpg: 10.4
wt: 5.424
nms: Lincoln Continental", + "mpg: 14.7
wt: 5.345
nms: Chrysler Imperial", + "mpg: 32.4
wt: 2.200
nms: Fiat 128", + "mpg: 30.4
wt: 1.615
nms: Honda Civic", + "mpg: 33.9
wt: 1.835
nms: Toyota Corolla", + "mpg: 21.5
wt: 2.465
nms: Toyota Corona", + "mpg: 15.5
wt: 3.520
nms: Dodge Challenger", + "mpg: 15.2
wt: 3.435
nms: AMC Javelin", + "mpg: 13.3
wt: 3.840
nms: Camaro Z28", + "mpg: 19.2
wt: 3.845
nms: Pontiac Firebird", + "mpg: 27.3
wt: 1.935
nms: Fiat X1-9", + "mpg: 26.0
wt: 2.140
nms: Porsche 914-2", + "mpg: 30.4
wt: 1.513
nms: Lotus Europa", + "mpg: 15.8
wt: 3.170
nms: Ford Pantera L", + "mpg: 19.7
wt: 2.770
nms: Ferrari Dino", + "mpg: 15.0
wt: 3.570
nms: Maserati Bora", + "mpg: 21.4
wt: 2.780
nms: Volvo 142E" + ], + "customdata": [ + "Mazda RX4", + "Mazda RX4 Wag", + "Datsun 710", + "Hornet 4 Drive", + "Hornet Sportabout", + "Valiant", + "Duster 360", + "Merc 240D", + "Merc 230", + "Merc 280", + "Merc 280C", + "Merc 450SE", + "Merc 450SL", + "Merc 450SLC", + "Cadillac Fleetwood", + "Lincoln Continental", + "Chrysler Imperial", + "Fiat 128", + "Honda Civic", + "Toyota Corolla", + "Toyota Corona", + "Dodge Challenger", + "AMC Javelin", + "Camaro Z28", + "Pontiac Firebird", + "Fiat X1-9", + "Porsche 914-2", + "Lotus Europa", + "Ford Pantera L", + "Ferrari Dino", + "Maserati Bora", + "Volvo 142E" + ], + "type": "scatter", + "mode": "markers", + "marker": { + "autocolorscale": false, + "color": "rgba(0,0,0,1)", + "opacity": 1, + "size": 5.66929133858268, + "symbol": "circle", + "line": { + "width": 1.88976377952756, + "color": "rgba(0,0,0,1)" + } + }, + "hoveron": "points", + "showlegend": false, + "xaxis": "x", + "yaxis": "y", + "hoverinfo": "text", + "frame": null + } + ], + "layout": { + "margin": { + "t": 25.7412480974125, + "r": 7.30593607305936, + "b": 39.6955859969559, + "l": 31.4155251141553 + }, + "plot_bgcolor": "rgba(235,235,235,1)", + "paper_bgcolor": "rgba(255,255,255,1)", + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "xaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 9.225, + 35.075 + ], + "tickmode": "array", + "ticktext": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "tickvals": [ + 10, + 15, + 20, + 25, + 30, + 35 + ], + "categoryorder": "array", + "categoryarray": [ + "10", + "15", + "20", + "25", + "30", + "35" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "y", + "title": "mpg", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "yaxis": { + "domain": [ + 0, + 1 + ], + "automargin": true, + "type": "linear", + "autorange": false, + "range": [ + 1.31745, + 5.61955 + ], + "tickmode": "array", + "ticktext": [ + "2", + "3", + "4", + "5" + ], + "tickvals": [ + 2, + 3, + 4, + 5 + ], + "categoryorder": "array", + "categoryarray": [ + "2", + "3", + "4", + "5" + ], + "nticks": null, + "ticks": "outside", + "tickcolor": "rgba(51,51,51,1)", + "ticklen": 3.65296803652968, + "tickwidth": 0.66417600664176, + "showticklabels": true, + "tickfont": { + "color": "rgba(77,77,77,1)", + "family": "", + "size": 11.689497716895 + }, + "tickangle": 0, + "showline": false, + "linecolor": null, + "linewidth": 0, + "showgrid": true, + "gridcolor": "rgba(255,255,255,1)", + "gridwidth": 0.66417600664176, + "zeroline": false, + "anchor": "x", + "title": "wt", + "titlefont": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 14.6118721461187 + }, + "hoverformat": ".2f" + }, + "shapes": [ + { + "type": "rect", + "fillcolor": null, + "line": { + "color": null, + "width": 0, + "linetype": [ + + ] + }, + "yref": "paper", + "xref": "paper", + "x0": 0, + "x1": 1, + "y0": 0, + "y1": 1 + } + ], + "showlegend": false, + "legend": { + "bgcolor": "rgba(255,255,255,1)", + "bordercolor": "transparent", + "borderwidth": 1.88976377952756, + "font": { + "color": "rgba(0,0,0,1)", + "family": "", + "size": 11.689497716895 + } + }, + "hovermode": "closest", + "barmode": "relative", + "dragmode": "select" + }, + "config": { + "doubleClick": "reset", + "cloud": false + }, + "source": "A", + "shinyEvents": [ + "plotly_hover", + "plotly_click", + "plotly_selected", + "plotly_relayout", + "plotly_brushed", + "plotly_brushing", + "plotly_clickannotation", + "plotly_doubleclick", + "plotly_deselect", + "plotly_afterplot", + "plotly_selecting" + ], + "highlight": { + "on": "plotly_click", + "persistent": false, + "dynamic": false, + "selectize": false, + "opacityDim": 0.2, + "selected": { + "opacity": 1 + }, + "debounce": 0 + }, + "base_url": "https://plot.ly" + }, + "evals": [ + + ], + "jsHooks": [ + + ], + "deps": [ + { + "name": "typedarray", + "version": "0.1", + "src": { + "href": "typedarray-0.1" + }, + "meta": null, + "script": "typedarray.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "jquery", + "version": "1.11.3", + "src": { + "href": "jquery-1.11.3" + }, + "meta": null, + "script": "jquery.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "crosstalk", + "version": "1.0.0", + "src": { + "href": "crosstalk-1.0.0" + }, + "meta": null, + "script": "js/crosstalk.min.js", + "stylesheet": "css/crosstalk.css", + "head": null, + "attachment": null, + "package": null, + "all_files": true + }, + { + "name": "plotly-htmlwidgets-css", + "version": "1.42.5", + "src": { + "href": "plotly-htmlwidgets-css-1.42.5" + }, + "meta": null, + "script": null, + "stylesheet": "plotly-htmlwidgets.css", + "head": null, + "attachment": null, + "all_files": false + }, + { + "name": "plotly-main", + "version": "1.42.5", + "src": { + "href": "plotly-main-1.42.5" + }, + "meta": null, + "script": "plotly-latest.min.js", + "stylesheet": null, + "head": null, + "attachment": null, + "all_files": false + } + ] + }, + "selected": "[1] \"Click and drag events (i.e., select/lasso) appear here (double-click to clear)\"", + "selecting": "[1] \"Click and drag events (i.e., select/lasso) appear here (double-click to clear)\"" + }, + "export": { + + } +} diff --git a/inst/examples/shiny/event_data/tests/mytest-expected/004.png b/inst/examples/shiny/event_data/tests/mytest-expected/004.png new file mode 100644 index 0000000000..be76d74718 Binary files /dev/null and b/inst/examples/shiny/event_data/tests/mytest-expected/004.png differ diff --git a/inst/examples/shiny/event_data/tests/mytest.R b/inst/examples/shiny/event_data/tests/mytest.R new file mode 100644 index 0000000000..6516d1a5b1 --- /dev/null +++ b/inst/examples/shiny/event_data/tests/mytest.R @@ -0,0 +1,28 @@ +app <- ShinyDriver$new("../", shinyOptions = list(display.mode = "normal")) +app$snapshotInit("mytest") + +app$snapshot() +app$setInputs(`plotly_hover-A` = "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_click-A` = "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_hover-A` = character(0), allowInputNoBinding_ = TRUE) +app$snapshot() +app$setInputs(`plotly_brushing-A` = "{\"x\":[25.726819184123485,25.98332414553473],\"y\":[1.3174499999999998,5.61955]}", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_selecting-A` = "[]", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushing-A` = "{\"x\":[24.64379823594267,25.98332414553473],\"y\":[3.2093373493975905,3.5073743975903615]}", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushing-A` = "{\"x\":[24.045286659316428,25.98332414553473],\"y\":[3.040881626506024,3.5073743975903615]}", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_selecting-A` = "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushing-A` = "{\"x\":[23.95978500551268,25.98332414553473],\"y\":[3.0020072289156627,3.5073743975903615]}", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_selected-A` = "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushed-A` = "{\"x\":[23.95978500551268,25.98332414553473],\"y\":[3.0020072289156627,3.5073743975903615]}", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_hover-A` = "[{\"curveNumber\":0,\"pointNumber\":7,\"x\":24.4,\"y\":3.19,\"customdata\":\"Merc 240D\"}]", allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_hover-A` = character(0), allowInputNoBinding_ = TRUE) +app$snapshot() +app$setInputs(`plotly_selected-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushed-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_selected-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_selecting-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushed-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_brushing-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_click-A` = character(0), allowInputNoBinding_ = TRUE) +app$setInputs(`plotly_deselect-A` = "\"plot\"", allowInputNoBinding_ = TRUE) +app$snapshot() diff --git a/inst/examples/shiny/event_data_3D/app.R b/inst/examples/shiny/event_data_3D/app.R index 1afbe06fdb..602bb03e5c 100644 --- a/inst/examples/shiny/event_data_3D/app.R +++ b/inst/examples/shiny/event_data_3D/app.R @@ -20,7 +20,7 @@ server <- function(input, output, session) { output$click <- renderPrint({ d <- event_data("plotly_click") - if (is.null(d)) "Click events appear here (double-click to clear)" else d + if (is.null(d)) "Click events appear here" else d }) } diff --git a/inst/examples/shiny/event_data_annotation/app.R b/inst/examples/shiny/event_data_annotation/app.R new file mode 100644 index 0000000000..b2e1fedb09 --- /dev/null +++ b/inst/examples/shiny/event_data_annotation/app.R @@ -0,0 +1,27 @@ +library(shiny) +library(plotly) + +ui <- fluidPage( + plotlyOutput("p"), + checkboxInput("edit", "Enable edit mode? Capturing annotation click events in edit mode is not possible.", FALSE), + verbatimTextOutput("data") +) + +server <- function(input, output, session) { + + output$p <- renderPlotly({ + plot_ly(mtcars) %>% + add_annotations(x = ~wt, y = ~mpg, text = row.names(mtcars), captureevents = TRUE) + }) + + observeEvent(input$edit, { + plotlyProxy("p", session) %>% + plotlyProxyInvoke("reconfig", editable = input$edit) + }) + + output$data <- renderPrint({ + event_data("plotly_clickannotation") + }) +} + +shinyApp(ui, server) diff --git a/inst/examples/shiny/event_data_legends/app.R b/inst/examples/shiny/event_data_legends/app.R new file mode 100644 index 0000000000..3ce4281f5f --- /dev/null +++ b/inst/examples/shiny/event_data_legends/app.R @@ -0,0 +1,29 @@ +library(shiny) + +ui <- fluidPage( + plotlyOutput("gg"), + verbatimTextOutput("click"), + verbatimTextOutput("doubleclick") +) + +server <- function(input, output, session) { + + output$gg <- renderPlotly({ + p <- ggplot(mtcars, aes(wt, mpg, color = factor(cyl))) + + geom_point() + + facet_wrap(~vs) + ggplotly(p) %>% + event_register("plotly_legendclick") %>% + event_register("plotly_legenddoubleclick") + }) + + output$click <- renderPrint({ + event_data("plotly_legendclick") + }) + + output$doubleclick <- renderPrint({ + event_data("plotly_legenddoubleclick") + }) +} + +shinyApp(ui, server) diff --git a/inst/examples/shiny/event_data_parcoords/app.R b/inst/examples/shiny/event_data_parcoords/app.R new file mode 100644 index 0000000000..39eb0fab44 --- /dev/null +++ b/inst/examples/shiny/event_data_parcoords/app.R @@ -0,0 +1,72 @@ +library(plotly) +library(shiny) + +ui <- fluidPage( + plotlyOutput("parcoords"), + verbatimTextOutput("data") +) + +server <- function(input, output, session) { + + iris_numeric <- dplyr::select_if(iris, is.numeric) + + output$parcoords <- renderPlotly({ + dims <- Map(function(x, y) { + list(values = x, range = range(x), label = y) + }, iris_numeric, names(iris_numeric), USE.NAMES = FALSE) + plot_ly(type = 'parcoords', dimensions = dims, source = "pcoords") %>% + layout(margin = list(r = 30)) %>% + event_register("plotly_restyle") + }) + + # maintain a collection of selection ranges + # since each parcoord dimension is allowed to have multiple + # selected ranges, this reactive values data structure is + # allowed + # list( + # var1 = list(c(min1, max1), c(min2, max2), ...), + # var2 = list(c(min1, max1)), + # ... + # ) + ranges <- reactiveValues() + observeEvent(event_data("plotly_restyle", source = "pcoords"), { + d <- event_data("plotly_restyle", source = "pcoords") + # what is the relevant dimension (i.e. variable)? + dimension <- as.numeric(stringr::str_extract(names(d[[1]]), "[0-9]+")) + # If the restyle isn't related to a dimension, exit early. + if (!length(dimension)) return() + # careful of the indexing in JS (0) versus R (1)! + dimension_name <- names(iris_numeric)[[dimension + 1]] + # a given dimension can have multiple selected ranges + # these will come in as 3D arrays, but a list of vectors + # is nicer to work with + info <- d[[1]][[1]] + + ranges[[dimension_name]] <- if (length(dim(info)) == 3) { + lapply(seq_len(dim(info)[2]), function(i) info[,i,]) + } else { + list(as.numeric(info)) + } + }) + + ## filter the dataset down to the rows that match the selection ranges + iris_selected <- reactive({ + keep <- TRUE + for (i in names(ranges)) { + range_ <- ranges[[i]] + keep_var <- FALSE + for (j in seq_along(range_)) { + rng <- range_[[j]] + keep_var <- keep_var | dplyr::between(iris[[i]], min(rng), max(rng)) + } + keep <- keep & keep_var + } + iris[keep, ] + }) + + output$data <- renderPrint({ + tibble::as_tibble(iris_selected()) + }) +} + +shinyApp(ui, server) diff --git a/inst/examples/shiny/event_priority/app.R b/inst/examples/shiny/event_priority/app.R new file mode 100644 index 0000000000..0ff5d11fb7 --- /dev/null +++ b/inst/examples/shiny/event_priority/app.R @@ -0,0 +1,29 @@ +library(shiny) +library(plotly) + +ui <- fluidPage( + plotlyOutput("p"), + textOutput("time1"), + textOutput("time2") +) + +server <- function(input, output, session) { + + output$p <- renderPlotly({ + plot_ly(x = 1, y = 1, marker = list(size = 100)) %>% + add_trace(text = "Click me", mode = "markers+text") + }) + + output$time1 <- renderText({ + event_data("plotly_click") + paste("Input priority: ", Sys.time()) + }) + + output$time2 <- renderText({ + event_data("plotly_click", priority = "event") + paste("Event priority: ", Sys.time()) + }) + +} + +shinyApp(ui, server) diff --git a/inst/examples/shiny/proxy_relayout/app.R b/inst/examples/shiny/proxy_relayout/app.R index e818f5838b..8f4da760fd 100644 --- a/inst/examples/shiny/proxy_relayout/app.R +++ b/inst/examples/shiny/proxy_relayout/app.R @@ -17,6 +17,8 @@ server <- function(input, output, session) { observeEvent(event_data("plotly_relayout"), { d <- event_data("plotly_relayout") + # unfortunately, the data structure emitted is different depending on + # whether the relayout is triggered from the rangeslider or the plot xmin <- if (length(d[["xaxis.range[0]"]])) d[["xaxis.range[0]"]] else d[["xaxis.range"]][1] xmax <- if (length(d[["xaxis.range[1]"]])) d[["xaxis.range[1]"]] else d[["xaxis.range"]][2] if (is.null(xmin) || is.null(xmax)) return(NULL) diff --git a/inst/htmlwidgets/plotly.js b/inst/htmlwidgets/plotly.js index 4efc4152e1..63c41c60b3 100644 --- a/inst/htmlwidgets/plotly.js +++ b/inst/htmlwidgets/plotly.js @@ -181,7 +181,6 @@ HTMLWidgets.widget({ graphDiv.data = undefined; graphDiv.layout = undefined; var plot = Plotly.plot(graphDiv, x); - } // Trigger plotly.js calls defined via `plotlyProxy()` @@ -192,6 +191,13 @@ HTMLWidgets.widget({ if (!gd) { throw new Error("Couldn't find plotly graph with id: " + msg.id); } + // This isn't an official plotly.js method, but it's the only current way to + // change just the configuration of a plot + // https://community.plot.ly/t/update-config-function/9057 + if (msg.method == "reconfig") { + Plotly.react(gd, gd.data, gd.layout, msg.args); + return; + } if (!Plotly[msg.method]) { throw new Error("Unknown method " + msg.method); } @@ -274,53 +280,85 @@ HTMLWidgets.widget({ }); } + + var legendEventData = function(d) { + // if legendgroup is not relevant just return the trace + var trace = d.data[d.curveNumber]; + if (!trace.legendgroup) return trace; + + // if legendgroup was specified, return all traces that match the group + var legendgrps = d.data.map(function(trace){ return trace.legendgroup; }); + var traces = []; + for (i = 0; i < legendgrps.length; i++) { + if (legendgrps[i] == trace.legendgroup) { + traces.push(d.data[i]); + } + } + + return traces; + }; + + // send user input event data to shiny if (HTMLWidgets.shinyMode) { - // https://plot.ly/javascript/zoom-events/ - graphDiv.on('plotly_relayout', function(d) { - Shiny.onInputChange( - ".clientValue-plotly_relayout-" + x.source, - JSON.stringify(d) - ); - }); - graphDiv.on('plotly_hover', function(d) { - Shiny.onInputChange( - ".clientValue-plotly_hover-" + x.source, - JSON.stringify(eventDataWithKey(d)) - ); - }); - graphDiv.on('plotly_click', function(d) { - Shiny.onInputChange( - ".clientValue-plotly_click-" + x.source, - JSON.stringify(eventDataWithKey(d)) - ); + + // Some events clear other input values + // TODO: always register these? + var eventClearMap = { + plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"], + plotly_unhover: ["plotly_hover"], + plotly_doubleclick: ["plotly_click"] + }; + + Object.keys(eventClearMap).map(function(evt) { + graphDiv.on(evt, function() { + var inputsToClear = eventClearMap[evt]; + inputsToClear.map(function(input) { + Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"}); + }); + }); }); - graphDiv.on('plotly_selected', function(d) { + + var eventDataFunctionMap = { + plotly_click: eventDataWithKey, + plotly_hover: eventDataWithKey, + plotly_unhover: eventDataWithKey, // If 'plotly_selected' has already been fired, and you click // on the plot afterwards, this event fires `undefined`?!? // That might be considered a plotly.js bug, but it doesn't make // sense for this input change to occur if `d` is falsy because, // even in the empty selection case, `d` is truthy (an object), // and the 'plotly_deselect' event will reset this input - if (d) { - Shiny.onInputChange( - ".clientValue-plotly_selected-" + x.source, - JSON.stringify(eventDataWithKey(d)) + plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_brushed: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_brushing: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_legendclick: legendEventData, + plotly_legenddoubleclick: legendEventData, + plotly_clickannotation: function(d) { return d.fullAnnotation } + }; + + var registerShinyValue = function(event) { + var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id }; + // some events are unique to the R package + var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event; + // register the event + graphDiv.on(plotlyJSevent, function(d) { + Shiny.setInputValue( + event + "-" + x.source, + JSON.stringify(eventDataPreProcessor(d)), + {priority: "event"} ); - } - }); - graphDiv.on('plotly_unhover', function(eventData) { - Shiny.onInputChange(".clientValue-plotly_hover-" + x.source, null); - }); - graphDiv.on('plotly_doubleclick', function(eventData) { - Shiny.onInputChange(".clientValue-plotly_click-" + x.source, null); - }); - // 'plotly_deselect' is code for doubleclick when in select mode - graphDiv.on('plotly_deselect', function(eventData) { - Shiny.onInputChange(".clientValue-plotly_selected-" + x.source, null); - Shiny.onInputChange(".clientValue-plotly_click-" + x.source, null); - }); - } + }); + } + + var shinyEvents = x.shinyEvents || []; + shinyEvents.map(registerShinyValue); + } // Given an array of {curveNumber: x, pointNumber: y} objects, // return a hash of { diff --git a/man/config.Rd b/man/config.Rd index ea710d7726..6adf60175d 100644 --- a/man/config.Rd +++ b/man/config.Rd @@ -4,8 +4,7 @@ \alias{config} \title{Set the default configuration for plotly} \usage{ -config(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, - mathjax = NULL) +config(p, ..., cloud = FALSE, locale = NULL, mathjax = NULL) } \arguments{ \item{p}{a plotly object} @@ -13,8 +12,6 @@ config(p, ..., collaborate = TRUE, cloud = FALSE, locale = NULL, \item{...}{these arguments are documented at \url{https://github.com/plotly/plotly.js/blob/master/src/plot_api/plot_config.js}} -\item{collaborate}{include the collaborate mode bar button (unique to the R pkg)?} - \item{cloud}{include the send data to cloud button?} \item{locale}{locale to use. See \href{https://github.com/plotly/plotly.js/tree/master/dist#to-include-localization}{here} for more info.} diff --git a/man/event_data.Rd b/man/event_data.Rd index f5c405d1e0..c42ae3fe4a 100644 --- a/man/event_data.Rd +++ b/man/event_data.Rd @@ -4,19 +4,29 @@ \alias{event_data} \title{Access plotly user input event data in shiny} \usage{ -event_data(event = c("plotly_hover", "plotly_click", "plotly_selected", - "plotly_relayout"), source = "A", - session = shiny::getDefaultReactiveDomain()) +event_data(event = c("plotly_hover", "plotly_unhover", "plotly_click", + "plotly_doubleclick", "plotly_selected", "plotly_selecting", + "plotly_brushed", "plotly_brushing", "plotly_deselect", + "plotly_relayout", "plotly_restyle", "plotly_legendclick", + "plotly_legenddoubleclick", "plotly_clickannotation", + "plotly_afterplot"), source = "A", + session = shiny::getDefaultReactiveDomain(), priority = c("input", + "event")) } \arguments{ -\item{event}{The type of plotly event. Currently 'plotly_hover', -'plotly_click', 'plotly_selected', and 'plotly_relayout' are supported.} +\item{event}{The type of plotly event. All supported events are listed in the +function signature above (i.e., the usage section).} \item{source}{a character string of length 1. Match the value of this string -with the source argument in \code{\link[=plot_ly]{plot_ly()}} to retrieve the -event data corresponding to a specific plot (shiny apps can have multiple plots).} +with the \code{source} argument in \code{\link[=plot_ly]{plot_ly()}} (or \code{\link[=ggplotly]{ggplotly()}}) to respond to +events emitted from that specific plot.} \item{session}{a shiny session object (the default should almost always be used).} + +\item{priority}{the priority of the corresponding shiny input value. +If equal to \code{"event"}, then \code{\link[=event_data]{event_data()}} always triggers re-execution, +instead of re-executing only when the relevant shiny input value changes +(the default).} } \description{ This function must be called within a reactive shiny context. @@ -26,6 +36,15 @@ This function must be called within a reactive shiny context. plotly_example("shiny", "event_data") } } +\references{ +\itemize{ +\item \url{https://plotly-book.cpsievert.me/shiny-plotly-inputs.html} +\item \url{https://plot.ly/javascript/plotlyjs-function-reference/} +} +} +\seealso{ +\link{event_register}, \link{event_unregister} +} \author{ Carson Sievert } diff --git a/man/event_register.Rd b/man/event_register.Rd new file mode 100644 index 0000000000..fe833bd13c --- /dev/null +++ b/man/event_register.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/shiny.R +\name{event_register} +\alias{event_register} +\title{Register a shiny input value} +\usage{ +event_register(p, event = NULL) +} +\arguments{ +\item{event}{The type of plotly event. All supported events are listed in the +function signature above (i.e., the usage section).} +} +\description{ +Register a shiny input value +} +\seealso{ +\link{event_data} +} +\author{ +Carson Sievert +} diff --git a/man/event_unregister.Rd b/man/event_unregister.Rd new file mode 100644 index 0000000000..3d7cd599b9 --- /dev/null +++ b/man/event_unregister.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/shiny.R +\name{event_unregister} +\alias{event_unregister} +\title{Un-register a shiny input value} +\usage{ +event_unregister(p, event = NULL) +} +\arguments{ +\item{event}{The type of plotly event. All supported events are listed in the +function signature above (i.e., the usage section).} +} +\description{ +Un-register a shiny input value +} +\seealso{ +\link{event_data} +} +\author{ +Carson Sievert +} diff --git a/tests/testthat/test-plotly-shiny.R b/tests/testthat/test-plotly-shiny.R new file mode 100644 index 0000000000..fd837fa0cb --- /dev/null +++ b/tests/testthat/test-plotly-shiny.R @@ -0,0 +1,36 @@ +context("shiny") + + +test_that("event_register() registers an event", { + p <- plotly_build(plot_ly(x = 1)) + + expect_false("plotly_selecting" %in% p$x$shinyEvents) + + p <- event_register(p, "plotly_selecting") + + expect_true("plotly_selecting" %in% p$x$shinyEvents) +}) + + +test_that("event_unregister() de-registers an event", { + p <- plotly_build(plot_ly(x = 1)) + + expect_true("plotly_selected" %in% p$x$shinyEvents) + + p <- event_unregister(p, "plotly_selected") + + expect_false("plotly_selected" %in% p$x$shinyEvents) +}) + + +test_that("event_data shiny app works", { + skip_on_cran() + skip_if_not_installed("shinytest") + if (enable_vdiffr) skip("shiny testing not performed during visual testing") + + # Use compareImages=FALSE because the expected image screenshots were created + # on a Mac, and they will differ from screenshots taken on the CI platform, + # which runs on Linux. + appdir <- system.file(package = "plotly", "examples", "shiny", "event_data") + shinytest::expect_pass(shinytest::testApp(appdir, compareImages = FALSE)) +})