-
Notifications
You must be signed in to change notification settings - Fork 634
Add more input events #1392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add more input events #1392
Changes from 28 commits
Commits
Show all changes
61 commits
Select commit
Hold shift + click to select a range
379e482
add restyle event, addresses #1321
cpsievert 133e5ff
Merge branch 'master' into input-events
cpsievert 06de984
add an example of mapping parcoord ranges back to the observations of…
cpsievert 8d71818
Merge branch 'master' into input-events
cpsievert 39553e6
New shiny input event 'plotly_brush' returns the x/y range of rectang…
cpsievert eb5a093
add plotly_selecting and plotly_brushing
cpsievert 40f039c
Merge branch 'master' into input-events
cpsievert f949d7c
use Shiny.setInputValue() over Shiny.onInputChange()
cpsievert 849f5f1
Relay full annotation info on 'plotly_clickannotation' event
cpsievert 5514bfe
Relay 'plotly_afterplot' event
cpsievert 5a54e41
Relay traces tied to legend events
cpsievert f23838f
Allow shiny input priority to be configurable at the plot-level, clos…
cpsievert 5576397
fix legend event data and add example app
cpsievert fe1f075
add a 'proxy method' for modifying the plot config; add annotation ex…
cpsievert 80a750d
add double-click, unhover, and deselect events
cpsievert 1522f59
update list of supported events and use match.arg() to make sure a su…
cpsievert e7f718e
avoid fromJSON simplification for legend events
cpsievert 1e10fc7
document
cpsievert b8789dd
tidy up documentation and function signature
cpsievert 6e9e636
'plotly_unhover' emits data
cpsievert 2abe932
attempt to dynamically register events
cpsievert 65f473a
make sure events are registered once; clean up some other bugs
cpsievert f2b5426
always send message on flush and tidy up registration logic
cpsievert eb6cadb
consistent use of [].map
cpsievert d32b86f
silly off-by-one mistake
cpsievert 8f00e01
tidy up R docs
cpsievert 14d674d
resolve priority of 'null' events server-side (with a message)
cpsievert 0eac8b5
plotly_brushed is more consistent naming than plotly_brush
cpsievert d2cf6a2
Merge branch 'master' into input-events
cpsievert dc91b70
Merge branch 'master' into input-events
cpsievert 95b3121
improve parcoords example app
cpsievert c20fbf4
Treat Shiny.addCustomMessageHandler like the 'global' operation that …
cpsievert e4d240a
clean-up comments
cpsievert 5b37957
update event priority example app
cpsievert d2e4042
only add message handler when in shinyMode
cpsievert 8377bec
revert to 6e9e636
cpsievert f293486
force user to specify inputs/events to register as part of plot object
cpsievert c52909a
use modern jsonlite::parse_json for safer JSON parsing
cpsievert f62c50e
Track plot events in user's session data to throw informative errors
cpsievert 1437d64
Merge branch 'master' into input-events
cpsievert ecbe79d
more clean-up
cpsievert f2937d7
events currently need their own dedicated input value
cpsievert d6ea8bc
merge with master
cpsievert a159ded
fix event_data shiny example
cpsievert d4c1d58
remove .clientValue prefix
cpsievert 834498a
always set input values with event priority and leverage reactive cac…
cpsievert 89296a5
fix test expectations
cpsievert d2ffbb0
maintain backwards-compatibility; document priority argument; update …
cpsievert a3b826d
more news
cpsievert e5dde36
clean-up
cpsievert 9128db3
new bullet about event_register() and event_unregister()
cpsievert 7671756
more news improvements
cpsievert 781643d
add a shinytest test for event_data app
cpsievert bd56d2e
update event_data tests
cpsievert 3519787
disable shinytest tests during visual test runs
cpsievert 71d5d48
actually disable this time
cpsievert fe2684a
actually, no really, disable shinytest in vdiffr run
cpsievert 39d9e92
load plotly in example shiny apps
cpsievert 6155442
throw a warning (not an error) if the input value that event_data() i…
cpsievert 8db07c3
Wait to check input values have been registered until after shiny's r…
cpsievert 35a6887
warn instead of stop
cpsievert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,26 +56,67 @@ 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 relevant shiny input. 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 | ||
#' @references | ||
#' * <https://plotly-book.cpsievert.me/shiny-plotly-inputs.html> | ||
#' * <https://plot.ly/javascript/plotlyjs-function-reference/> | ||
#' @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) | ||
|
||
# make sure the input event is sensible | ||
event <- match.arg(event) | ||
priority <- match.arg(priority) | ||
|
||
isNullEvent <- event %in% c("plotly_doubleclick", "plotly_deselect", "plotly_afterplot") | ||
if (isNullEvent && priority != "event") { | ||
message( | ||
"The input value tied to a '", event, "' event never changes. ", | ||
"Setting `priority = 'event'` so that the event actually triggers re-execution." | ||
) | ||
priority <- "event" | ||
} | ||
|
||
# register event on client-side | ||
session$onFlushed(function() { | ||
session$sendCustomMessage( | ||
type = "plotlyEventData", | ||
message = list(event = event, source = source, priority = priority) | ||
) | ||
}, once = FALSE) | ||
|
||
|
||
src <- sprintf(".clientValue-%s-%s-%s", event, source, priority) | ||
val <- session$rootScope()$input[[src]] | ||
if (is.null(val)) val else jsonlite::fromJSON(val) | ||
|
||
# legend clicking returns trace(s), which shouldn't be simplified... | ||
fromJSONfunc <- if (event %in% c("plotly_legendclick", "plotly_legenddoubleclick")) from_JSON else jsonlite::fromJSON | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
if (is.null(val)) val else fromJSONfunc(val) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
library(shiny) | ||
|
||
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
library(shiny) | ||
|
||
ui <- fluidPage( | ||
plotlyOutput("gg"), | ||
verbatimTextOutput("click"), | ||
verbatimTextOutput("doubleclick") | ||
) | ||
|
||
server <- function(input, output, session) { | ||
|
||
output$gg <- renderPlotly({ | ||
ggplot(mtcars, aes(wt, mpg, color = factor(cyl))) + | ||
geom_point() + | ||
facet_wrap(~vs) | ||
}) | ||
|
||
output$click <- renderPrint({ | ||
event_data("plotly_legendclick") | ||
}) | ||
|
||
output$doubleclick <- renderPrint({ | ||
event_data("plotly_legenddoubleclick") | ||
}) | ||
} | ||
|
||
shinyApp(ui, server) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
library(plotly) | ||
library(shiny) | ||
|
||
ui <- fluidPage( | ||
plotlyOutput("parcoords"), | ||
tableOutput("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") | ||
}) | ||
|
||
# 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]+")) | ||
# 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 <- renderTable({ | ||
iris_selected() | ||
}) | ||
} | ||
|
||
shinyApp(ui, server) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
library(plotly) | ||
library(shiny) | ||
|
||
ui <- fluidPage( | ||
checkboxInput("priority", "Shiny event priority", FALSE), | ||
plotlyOutput("p") | ||
) | ||
|
||
server <- function(input, output, session) { | ||
|
||
output$p <- renderPlotly({ | ||
title <- if (input$priority) { | ||
"Clicking on the same point repeatedly will keep triggering console output" | ||
} else { | ||
"Clicking on the same point won't trigger more output" | ||
} | ||
|
||
plot_ly(mtcars, x = ~wt, y = ~mpg) %>% | ||
layout(title = title) %>% | ||
config(priority = if (input$priority) "event") | ||
}) | ||
|
||
observeEvent(event_data("plotly_click"), { | ||
print("clicked!") | ||
}) | ||
|
||
} | ||
|
||
shinyApp(ui, server) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.