I am using Plotly's event_data("plotly_click") to do stuff (opening a modal) after the user clicked on a marker in a scatter plot. Afterwards (e.g. closing the modal), event_data("plotly_click") does of course not change and clicking on the same marker therefore does not trigger the same action again.
Minimal example:
library(plotly)
ui <- fluidPage(
plotlyOutput("plot")
)
server <- function(input, output, session) {
output$plot <- renderPlotly({
mtcars %>% plot_ly(x=~disp, y=~cyl)
})
# Do stuff after clicking on a marker in the plot
observeEvent(event_data("plotly_click"), {
print("do some stuff now") # this is not executed after second click on same marker
})
}
shinyApp(ui, server)
I have tried workarounds with shinyjs's onclick, to no avail (it works well in empty areas of the plot but not when clicking on markers):
shinyjs::onclick(id="plot", print("clicked"))
I have also tried using a reactive Value that stores the last click and is reset immediately afterwards (e.g. by event_data("plotly_hover")), but all tries fail because event_data("plotly_click") remains in its old value.
Can anyone help?
[Edit: The issue has been fixed in Plotly 4.9.0. See answer below. This answer works up to Plotly 4.8.0. From plotly 4.9.0., delete the string .clientValue- from the source code or use below answer.]
I finally solved this issue. I know this is bothering some people, so I'll post my solution here:
Basically I use shinyjs package to reset the data about the last click (the source where event_data("plotly_click") gets its information from) on a certain event (a button in my case).
The definition of the function is (note that "A" needs to be replaced with plotly-source string if used):
extendShinyjs(text = "shinyjs.resetClick = function() { Shiny.onInputChange('.clientValue-plotly_click-A', 'null'); }")
Then this is called upon button click by js$resetClick().
Minimal example:
library(shiny)
library(plotly)
library(shinyjs)
ui <- shinyUI(
fluidPage(
useShinyjs(),
# code to reset plotlys event_data("plotly_click", source="A") to NULL -> executed upon action button click
# note that "A" needs to be replaced with plotly source string if used
extendShinyjs(text = "shinyjs.resetClick = function() { Shiny.onInputChange('.clientValue-plotly_click-A', 'null'); }"),
actionButton("reset", "Reset plotly click value"),
plotlyOutput("plot"),
verbatimTextOutput("clickevent")
)
)
server <- shinyServer(function(input, output) {
output$plot <- renderPlotly({
plot_ly(mtcars, x=~cyl, y=~mpg)
})
output$clickevent <- renderPrint({
event_data("plotly_click")
})
observeEvent(input$reset, {
js$resetClick()
})
})
shinyApp(ui, server)
The issue has finally been fixed on Plotly side: https://github.com/ropensci/plotly/issues/1043
event_data("plotly_click", priority = "event") updates on every click, not only on shiny input change (as before). Working from Plotly 4.9.0 on.
Minimal example using Plotly 4.9.0:
library(shiny)
library(plotly)
ui <- shinyUI(
fluidPage(
plotlyOutput("plot", height = 200),
verbatimTextOutput("time_last_click")
)
)
server <- shinyServer(function(input, output) {
output$plot <- renderPlotly({
plot_ly(mtcars[1,], x=~cyl, y=~mpg, size = 1)
})
output$time_last_click <- renderPrint({
tmp <- event_data("plotly_click", priority = "event")
Sys.time()
})
})
shinyApp(ui, server)
I had the same problem, and came up with a solution where I specified the source argument of the plotly object to be a reactive value as follows:
In plot_ly(data,x,y,...,source = x) and event_data(...,source = x) let x be an element of a reactiveValues object. When your event triggers, change the value of x (increment or hash), which instantiates a new event_data() object.
Worked like a charm.
Related
In my shiny app, I have a render that I want to only execute after a radio button changes values, but only the first time this happens. Is there a way to make it reactive to the first change in value, but not subsequent ones?
Below you will find that observeEvent has arguments such as ignoreInit and once, I would advise that you go and have a look at the function definitions on the official website Event handler. I have also added the shinyjs library with its disable function which I think is handy here.
rm(list=ls())
library(shiny)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
radioButtons("nums", "Will Execute only Once",c("1000" = 1000,"10000" = 10000), selected = 0),
plotOutput("Plot")
)
server <- function(input, output) {
v <- reactiveValues()
observeEvent(input$nums, {
v$data <- rnorm(input$nums)
},ignoreInit = TRUE, once = TRUE)
output$Plot <- renderPlot({
if(is.null(v$data)){
return()
}
disable('nums')
hist(v$data)
box()
})
}
shinyApp(ui, server)
I'm trying to use plotly click events in the context of a shiny app. Following the official demo I'm using this bit of code to update a date picker and jump to another tab in my app on click:
observe({
d <- event_data("plotly_click", source = 'plot')
if(!is.null(d) & (input$navPanel == 'overview')) {
d %>% filter(curveNumber == 0) %>% select(x) -> selected_date
updateDateInput(session, "date", value = lubridate::ymd(selected_date$x))
updateTabsetPanel(session, "navPanel", selected = "details")
}
However, when I then try to switch back from the details to the overview tab, I get immediately thrown back to the details tab. I'm assuming that this happens because the event is never cleared, i.e. d is not null when the tab gets changed and so the condition in the if-clause evaluates to TRUE.
So, how do I clear the click event programmatically? Adding d <- NULL to the end of the conditional doesn't seem to do it.
I have same problem, and the workaround I've found is to store the old state in a global variable, and do the updates only when that variable changes and not on the !is.null()
selected_date <- 0 # declare outside the server function
server <- function(input, output, session) {
observe({
d <- event_data("plotly_click")
new_value <- ifelse(is.null(d),"0",d$x) # 0 if no selection
if(selected_date!=new_value) {
selected_date <<- new_value
if(selected_date !=0 && input$navPanel == 'overview')
updateDateInput(session, "date", value = lubridate::ymd(selected_date))
}
})
...
}
This also allows you to add a behaviour whenever the element is unselected
I solved this by using shinyjs and manually resetting the event_data("plotly_click") with the help of the Shiny.onInputChange function, which sets values in the input vector manually:
library(shiny)
library(plotly)
library(shinyjs)
ui <- shinyUI(
fluidPage(
useShinyjs(),
# code to reset plotlys event_data("plotly_click", source="A") to NULL -> executed upon action button click
# note that "A" needs to be replaced with plotly source string if used
extendShinyjs(text = "shinyjs.resetClick = function() { Shiny.onInputChange('.clientValue-plotly_click-A', 'null'); }"),
actionButton("reset", "Reset plotly click value"),
plotlyOutput("plot"),
verbatimTextOutput("clickevent")
)
)
server <- shinyServer(function(input, output) {
output$plot <- renderPlotly({
plot_ly(mtcars, x=~cyl, y=~mpg)
})
output$clickevent <- renderPrint({
event_data("plotly_click")
})
observeEvent(input$reset, {
js$resetClick()
})
})
shinyApp(ui, server)
I am hoping to get some clarity on Shiny's reactivity behavior using the simplified code below as example.
When y is updated in the app, the graph updates.
When x is updated in the app, the graph does NOT update.
I have read Shiny's tutorials and my understanding is that given that I have wrapped both test() and plot() functions in observeEvent, both parameters should not cause the graph to update when changed.
Can someone help explain the logic behind this?
library(shiny)
test <- function(x){x*2}
shinyServer(function(input, output, session) {
observeEvent(input$run, {
x = test(input$x)
output$distPlot <- renderPlot({
if(input$y){
x = x+2
}
plot(x)
})
})
})
shinyUI(fluidPage(
sidebarLayout(
sidebarPanel(
numericInput("x", "x:", 10),
checkboxInput("y", label = "y", value = FALSE),
actionButton("run", "run")
),
mainPanel(
plotOutput("distPlot")
)
)
))
If you put the line x = test(input$x) inside of the renderPlot it will react when either x or y changes. Essentially the observer creates a reactive output when the action button is clicked the first time, then you simply have a reactive element that responds to changes to inputs inside of it. Hope that helps.
To make it so the graph only updates when the button is clicked, you will probably need to put the data that is being graphed in a eventReactive and use that as the input for the graph.
Something like this:
data <- eventReactive(input$run, {
x = test(input$x)
if(input$y){
x = x+2
}
x
})
output$distPlot <- renderPlot({
plot(data())
})
In my Shiny App, there are a few numericInput and selectInput.
Shiny updates outputs during typing, especially when users type is slower in the numericInput.
sumbitButton could you be used to stop automatically updading. But I prefer to not to use it.
How could I let Shiny waits for a longer time for numericInput?
Thanks for any suggestion. Let me know if my question is not clear.
You can use debounce on the reactive function that uses your Inputs.
Setting it to 2000 milliseconds felt OK to me.
If you use the input directly in a render function you might need to create the data to use in your render function in a reactive function.
An example is here: https://shiny.rstudio.com/reference/shiny/latest/debounce.html
## Only run examples in interactive R sessions
if (interactive()) {
options(device.ask.default = FALSE)
library(shiny)
library(magrittr)
ui <- fluidPage(
plotOutput("plot", click = clickOpts("hover")),
helpText("Quickly click on the plot above, while watching the result table below:"),
tableOutput("result")
)
server <- function(input, output, session) {
hover <- reactive({
if (is.null(input$hover))
list(x = NA, y = NA)
else
input$hover
})
hover_d <- hover %>% debounce(1000)
hover_t <- hover %>% throttle(1000)
output$plot <- renderPlot({
plot(cars)
})
output$result <- renderTable({
data.frame(
mode = c("raw", "throttle", "debounce"),
x = c(hover()$x, hover_t()$x, hover_d()$x),
y = c(hover()$y, hover_t()$y, hover_d()$y)
)
})
}
shinyApp(ui, server)
}
I need to place a text panel in my shiny app that disappears 10 seconds after it starts (like an advice), anyone know if it is posible? I've tryed to use the command "invalidLater", but it always appear again.
Thanks
Louro, J.
As #bunk is showing a good way is to use invalidateLater, here's some examples:
library(shinyjs)
ui <- shinyUI(
fluidPage(
tags$head(
tags$script(
HTML(
'
Shiny.addCustomMessageHandler("registerTimer", function(message){
console.log("Timer registered for $("+message.selector+")with delay "+message.delay);
setTimeout(removeElementFromDOM, message.delay, message.selector);
});
function removeElementFromDOM(selector){
$("#"+selector).remove();
}
'
)
)
),
uiOutput("ui1"),
textOutput("ui2"),
plotOutput("plt1"),
div(id="txtDiv","Some text here")
)
)
rm(active)
server <- shinyServer(function(input,output, session){
data <- data.frame("x"=runif(10),"y"=runif(10))
txt1 <- "Some text"
makeReactiveBinding('txt1')
makeReactiveBinding('data')
# Remove with javascript
session$sendCustomMessage('registerTimer',
message=list(selector='txtDiv',delay=4000))
# Hide with shinyjs
output$plt1 <- renderPlot({
if (is.null(data)){
hide("plt1")
} else{
plot(x~y,data)
}
})
output$ui2 <- renderText({
txt1
})
# Continously update, output nothing after time
output$ui1 <- renderUI({
invalidateLater(1000, session);
if ((active <<- exists('active'))) return()
div("Text here")
})
# Triggers change
reactiveTimer(2000,{
txt1 <- NULL
})
reactiveTimer(3000,{
data <- NULL
})
})
shinyApp(ui=ui,server=server)
ui1 uses the invalidateLater method, ui2 uses a reactive value that is set to NULL and plt1 is a variation of ui2 where shinyjs is used to hide the plotOutput.
Edited
I've added a Javascript solution to this you can use it on any element of the DOM.