I want to have a plot in my shiny app that the user can click on or choose certain regions, so I'm using the click and brush arguments of plotOutput. My problem is that when a brush is initiated, the click handler is also called. I want to know when a click is made, and I want to know when a brush is made, but if a click is part of a brush then I want to ignore it.
Example: in the following app, if you just brush (click somewhere and drag the mouse), you get a "click" message as well as a "brush" message. I want to only get the "brush" message in that case.
library(shiny)
library(ggplot2)
runApp(shinyApp(
ui = fluidPage(
plotOutput("plot", click = "click", brush = "brush")
),
server = function(input, output, session) {
output$plot <- renderPlot({
ggplot(mtcars, aes(wt, mpg)) + geom_point()
})
observeEvent(input$click, {
cat("clicked\n")
})
observeEvent(input$brush, {
cat("brushed\n")
})
}
))
I am developing a Shiny app for interactive heatmaps (https://github.com/jokergoo/InteractiveComplexHeatmap). On the heatmap, users can click on the heatmap to see the value for that cell or select a region to catch a sub-heatmap. I don't like double click on heatmap either which is very unfriendly to users.
Following is my solution to differentiate click and brush, may be helpful for you:
A runnable code:
library(shiny)
library(grid)
library(glue)
library(circlize) # only for random colors
ui = fluidPage(
plotOutput("plot", width = 600, height = 400,
click = "click", brush = "brush"),
tags$script(HTML("
$('#plot').mousedown(function(e) {
var parentOffset = $(this).offset();
var relX = e.pageX - parentOffset.left;
var relY = e.pageY - parentOffset.top;
Shiny.setInputValue('x1', relX);
Shiny.setInputValue('y1', relY);
}).mouseup(function(e) {
var parentOffset = $(this).offset();
var relX = e.pageX - parentOffset.left;
var relY = e.pageY - parentOffset.top;
Shiny.setInputValue('x2', relX);
Shiny.setInputValue('y2', relY);
Shiny.setInputValue('action', Math.random());
});
")),
fluidRow(
column(3, htmlOutput("output1")),
column(3, htmlOutput("output2"))
)
)
server = function(input, output, session) {
output$plot = renderPlot({
grid.newpage()
grid.rect()
})
observeEvent(input$action, {
if(input$x1 == input$x2 && input$y1 == input$y2) {
output$output1 = renderText({
isolate(glue("
<pre style='background-color:{rand_color(1)}'>
a click:
x1 = {input$x1}
y1 = {input$y1}
x2 = {input$x2}
y2 = {input$y2}
input$click$coords_css$x = {input$click$coords_css$x}
input$click$coords_css$y = {input$click$coords_css$y}</pre>"))
})
} else {
output$output2 = renderText({
isolate(glue("
<pre style='background-color:{rand_color(1)}'>
a brush:
x1 = {input$x1}
y1 = {input$y1}
x2 = {input$x2}
y2 = {input$y2}
input$brush$coords_css$xmin = {input$brush$coords_css$xmin}
input$brush$coords_css$ymin = {input$brush$coords_css$ymin}
input$brush$coords_css$xmax = {input$brush$coords_css$xmax}
input$brush$coords_css$ymax = {input$brush$coords_css$ymax}</pre>"))
})
}
})
}
shinyApp(ui, server)
And a demo is here:
The idea is to compare the mouse positions at "mousedown" and "mouseup". And at "mouseup" triggers a reactive value (here is action) so that in server function, it can respond to it. (This is implemented in tags$script(...)).
As you can see in the figure, click and brush change the box colors independently, and doing brush won't affect the box for click.
I know it's just a workaround, but my only solution for this was to undo the action of the last click when the brush is activated; I needed this for a plot where the user could add points by clicking.
Using the brushing will first create the point and remove it after releasing the click button.
Just a drawback: sometimes you click and do a micro-brush without noticing, in this case it won't create the point obviously. My app:
library(shiny); library(dplyr); library(ggplot2)
ui <- fluidPage(
fluidRow(
h1("add by clicking anywhere on the plot"),
plotOutput("mainplot",
click="mainplot_click",
brush=brushOpts(id="mainplot_brush"))
),
fluidRow(
actionButton("add", "add"),
actionButton("reset", "reset")
)
)
server <- function(input, output, session) {
vals = reactiveValues(
keeprows = rep(TRUE, nrow(mtcars)),
mydata = mtcars
)
observeEvent(input$mainplot_click, handlerExpr = {
my.x = input$mainplot_click$x
my.y = input$mainplot_click$y
vals$mydata <- vals$mydata %>% bind_rows(data.frame("wt"=my.x, "mpg"=my.y))
})
output$mainplot = renderPlot({
temp = vals$mydata
ggplot() +geom_point(data=temp, col="black", fill="white", size=3) + aes(x=wt, y=mpg)
})
observeEvent(input$mainplot_brush, handlerExpr = {
vals$mydata <- vals$mydata %>% head(-1)
})
observeEvent(input$reset, handlerExpr = {
vals$mydata <- mtcars
vals$keeprows <- rep(TRUE, nrow(mtcars))
})
session$onSessionEnded(stopApp)
}
shinyApp(ui, server)
I solved this problem with a set of flags, recognizing that a drag event triggers a click (mouse down), followed by brush, followed by a second click (actually mouse up). I delay processing of the first click, then cancel the first click if I handle the brush. And if I am handling the brush, then I know that there will be a second click that I should ignore.
single.click.up <- FALSE
single.click.down <- FALSE
brush.click <- FALSE
# Brush events trigger plot1_click twice (down and up) with a brush event in between.
observe({
input$plot1_click
if (single.click.down) {
if (brush.click) {
single.click.up <<- TRUE
message("single click down ignored")
} else {
message("single click down processed")
}
single.click.down <<- FALSE
} else if (single.click.up) {
message("single click up, reset brush click")
brush.click <<- FALSE
single.click.up <<- FALSE
} else {
single.click.down <<- TRUE
invalidateLater(1000)
message("new single click. delay.")
}
})
observeEvent( input$plot1_brush, {
brush.click <<- TRUE
message("Processing brush")
})
There's probably a cleaner way to do this. And strange things could happen if events were lost or slow to be processed. But it works for me.
Related
For my app, I need the user to click an area on a plot, then the plot responds to that click and shows a new plot that is linked to that input click.
However, currently it runs, but the new plot only displays for 3 seconds and then goes back to the original plot. I found a similar example here. This plot updates based on a list of reactiveValues then draws those new points the user selected. I think if I find a way to modify it to draw the new plot immediatly after the clicked event it should work, but stuck on this part.
So ideally the below example will add the new point immediatly after beind clicked. In my case I will need to display the new plot until a new area of the plot is clicked.
Example from the link
library(shiny)
ui <- basicPage(
plotOutput("plot1", click = "plot_click"),
verbatimTextOutput("info"),
actionButton("updateplot", "Update Plot:")
)
server <- function(input, output) {
val <- reactiveValues(clickx = NULL, clicky = NULL)
observe({
input$plot_click
isolate({
val$clickx = c(val$clickx, input$plot_click$x)
val$clicky = c(val$clicky, input$plot_click$y)
})
}) #adding clicks to list
output$plot1 <- renderPlot({
plot(mtcars$wt, mtcars$mpg)
input$updateplot
isolate({
points(val$clickx, val$clicky)
})
})
output$info <- renderText({
paste0("x = ", val$clickx, ", y = ",val$clicky, "\n")
})
}
shinyApp(ui, server)
This example shows a main plot and upon click in the main plot it shows a sub plot. Once clicked again it shows again the main plot.
The idea is that a counter (trigger) is increased whenever a click happens. It is important to use req(.) in the observer, lest the observer fires again when the mouse button is released (in this case input$plot_click is set to NULL).
The renderPlot(.) then takes a dependency on this trigger and shows either the main or the sub plot.
Update. If you want to use info from the input$plot_click object you should save it as well, to avoid that it get NULL.
library(shiny)
library(ggplot2)
ui <- basicPage(
plotOutput("plot1", click = "plot_click")
)
server <- function(input, output) {
plot_data <- reactiveValues(trigger = 0, x = NA, y = NA)
observe({
req(input$plot_click)
isolate(plot_data$trigger <- plot_data$trigger + 1)
plot_data$x <- input$plot_click$x
plot_data$y <- input$plot_click$y
})
output$plot1 <- renderPlot({
if (plot_data$trigger %% 2 == 0) {
plot(1:10, main = "Main Plot")
} else {
ggplot() + geom_point(aes(x = plot_data$x, y = plot_data$y), size = 5, shape = 19)
}
})
}
shinyApp(ui, server)
Just drop the isolate in your renderPlot:
library(shiny)
ui <- basicPage(
plotOutput("plot1", click = "plot_click"),
verbatimTextOutput("info")
)
server <- function(input, output) {
val <- reactiveValues(clickx = NULL, clicky = NULL)
observe({
input$plot_click
isolate({
val$clickx = c(val$clickx, input$plot_click$x)
val$clicky = c(val$clicky, input$plot_click$y)
})
}) #adding clicks to list
output$plot1 <- renderPlot({
plot(mtcars$wt, mtcars$mpg)
points(val$clickx, val$clicky)
})
output$info <- renderText({
paste0("x = ", val$clickx, ", y = ",val$clicky, "\n")
})
}
shinyApp(ui, server)
I'm working with a Shiny app where I need to calculate processes and while the calc progress is executing, I'm using a progressBar to show the process.
The problem is that the progress bar is too small, and I don't like the way is shown.
So, I was thinking that maybe there's a way to implement a progress bar using a Shiny modal (there's a function called modalDialog).
My idea is that when the user runs the calc, a modal will be opened showing a progressBar.
This is the progress code:
withProgress(message = 'Runing GSVA', value = 0, {
incProgress(1, detail = "This may take a while...")
functionToGenerate()
})
Any idea?
I would suggest customizing the CSS class of the notification:
If you inspect the element of the notifier you see that it has the class "shiny-notification". So you can overwrite some properties of that class with tags$style(). In the example below (for the template: see ?withProgress) i decided to adjust height+width to make it bigger and top+left to center it.
ui <- fluidPage(
tags$head(
tags$style(
HTML(".shiny-notification {
height: 100px;
width: 800px;
position:fixed;
top: calc(50% - 50px);;
left: calc(50% - 400px);;
}
"
)
)
),
plotOutput("plot")
)
server <- function(input, output) {
output$plot <- renderPlot({
withProgress(message = 'Calculation in progress',
detail = 'This may take a while...', value = 0, {
for (i in 1:15) {
incProgress(1/15)
Sys.sleep(0.25)
}
})
plot(cars)
})
}
runApp(shinyApp(ui, server), launch.browser = TRUE)
I wrote a progress bar function in the package shinyWidgets, you can put it in a modal, but it's tricky to use with shiny::showModal, so you can create your own modal manually like the below. It's more code to write but it works fine.
library("shiny")
library("shinyWidgets")
ui <- fluidPage(
actionButton(inputId = "go", label = "Launch long calculation"), #, onclick = "$('#my-modal').modal().focus();"
# You can open the modal server-side, you have to put this in the ui :
tags$script("Shiny.addCustomMessageHandler('launch-modal', function(d) {$('#' + d).modal().focus();})"),
tags$script("Shiny.addCustomMessageHandler('remove-modal', function(d) {$('#' + d).modal('hide');})"),
# Code for creating a modal
tags$div(
id = "my-modal",
class="modal fade", tabindex="-1", `data-backdrop`="static", `data-keyboard`="false",
tags$div(
class="modal-dialog",
tags$div(
class = "modal-content",
tags$div(class="modal-header", tags$h4(class="modal-title", "Calculation in progress")),
tags$div(
class="modal-body",
shinyWidgets::progressBar(id = "pb", value = 0, display_pct = TRUE)
),
tags$div(class="modal-footer", tags$button(type="button", class="btn btn-default", `data-dismiss`="modal", "Dismiss"))
)
)
)
)
server <- function(input, output, session) {
value <- reactiveVal(0)
observeEvent(input$go, {
shinyWidgets::updateProgressBar(session = session, id = "pb", value = 0) # reinitialize to 0 if you run the calculation several times
session$sendCustomMessage(type = 'launch-modal', "my-modal") # launch the modal
# run calculation
for (i in 1:10) {
Sys.sleep(0.5)
newValue <- value() + 1
value(newValue)
shinyWidgets::updateProgressBar(session = session, id = "pb", value = 100/10*i)
}
Sys.sleep(0.5)
# session$sendCustomMessage(type = 'remove-modal', "my-modal") # hide the modal programmatically
})
}
shinyApp(ui = ui, server = server)
I am designing a Shiny app which contains a plotly scatter plot. I would like for the user to be able to click on the graph to record an event using the event_data function, but then be able to clear that event on the click of an actionButton. Some example code can be seen below:
library(shiny)
library(plotly)
ui <- fluidPage(
actionButton("clearEvent", label = "clear event"),
verbatimTextOutput("plotVal"),
plotlyOutput('plot1')
)
server <- function(input, output, session) {
output$plot1 <- renderPlotly({
d <- diamonds[sample(nrow(diamonds), 1000), ]
plot_ly(d, x = ~carat, y = ~price, color = ~carat,
size = ~carat, text = ~paste("Clarity: ", clarity))
})
output$plotVal <- renderPrint({
e <- event_data("plotly_click")
if (is.null(e)) {
NULL
} else {
e
}
})
observeEvent(input[["clearEvent"]], {
e <- NULL
})
}
shinyApp(ui = ui, server = server)
This doesn't clear the event like I would expect, however. Looking into the code for event_data shows that this is probably because it is stored within the session object itself. Any ideas how I can overwrite it?
The only similar thing I have come across is Clear plotly click event but it's very hacky and doesn't seem to work for me.
In your example, e is just defined in the renderPrint and in the observeEvent and not globally so even if e is changed in the observeEvent, it does not trigger anything in the renderPrint.
You can use reactiveValues for this:
data <- reactiveValues(e=NULL)
observe({
data$e <- event_data("plotly_click")
})
output$plotVal <- renderPrint({
e <- data$e
if (is.null(e)) {
NULL
} else {
e
}
})
observeEvent(input[["clearEvent"]], {
data$e <- NULL
})
data$e is changed whenever the user click the plot or the button, and since there is a dependency on data$e in the renderPrint, that gets updated whenever data$e is changed.
The previous answer partially solves the problem, however, the user cannot click on the same plotly marker again, at least no update is triggered. That problem can be tackled by manually resetting the source of event_data("plotly_click") withshinyjs:
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 know that similar question was already answered, however the solution creates a new actionButton with different label upon string-input. What I need is to keep the button(the counter of the button), because when I change the label and create a new button it has a counter of 0(not clicked).
So basically I need something like an update function to just change the label of the actionButton, when it is pressed. You press it once and the label changes.
input$Button <- renderUI({
if(input$Button >= 1) label <- "new label"
else label <- "old label"
actionButton("Button", label = label)
})
Something like this, but without reseting the value of the button(by creating a whole new one).
Thanks!
reactiveValues() can help. Check http://shiny.rstudio.com/articles/reactivity-overview.html for details.
In the following example, I renamed your input$Button to input$click to avoid double usage of the "Button" name.
Since we wrap the label in a renderUI(), input$click initially fires once it is created?!?, that's why I put the label
condition as: if(vars$counter >= 2)
An alternative solution could be to remove the read-only attribute (found here: https://github.com/rstudio/shiny/issues/167)
attr(input, "readonly") <- FALSE
input$click <- 1
For an example
paste the following in your R console:
ui <- bootstrapPage(
uiOutput('Button')
)
server <- function(input, output) {
# store the counter outside your input/button
vars = reactiveValues(counter = 0)
output$Button <- renderUI({
actionButton("click", label = label())
})
# increase the counter
observe({
if(!is.null(input$click)){
input$click
isolate({
vars$counter <- vars$counter + 1
})
}
})
label <- reactive({
if(!is.null(input$click)){
if(vars$counter >= 2) label <- "new label"
else label <- "old label"
}
})
}
# run the app
shinyApp(ui = ui, server = server)
You can use updateActionButton from native shiny package:
ui <- fluidPage(
actionButton('someButton', ""),
h3("Button value:"),
verbatimTextOutput("buttonValue"),
textInput("newLabel", "new Button Label:", value = "some label")
)
server <- function(input, output, session) {
output$buttonValue <- renderPrint({
input$someButton
})
observeEvent(input$newLabel, {
updateActionButton(session, "someButton", label = input$newLabel)
})
}
shinyApp(ui, server)
A few years later some small addition: If you want to switch between button icons, e.g. play / pause button (and switching between labels would be similar) you could do something like this (based on shosaco's answer).
library(shiny)
ui <- fluidPage(
fluidRow(
actionButton("PlayPause", NULL, icon = icon("play"))
)
)
server <- function(input, output, session) {
# whenever the ActionButton is clicked, 1 is added to input$PlayPause
# check if input$PlayPause is even or odd with modulo 2
# (returns the remainder of division by 2)
observeEvent(input$PlayPause, {
if (input$PlayPause %% 2 != 0) {
updateActionButton(session, "PlayPause", NULL, icon = icon("pause"))
} else {
updateActionButton(session, "PlayPause", NULL, icon = icon("play"))
}
})
}
shinyApp(ui = ui, server = server)
I am doing a project where I use the shiny server and connect R to mongodb to fetch results from database and display it dynamically.
However, I face the following problem in it. I initially get the results from db and make a plot. After this plot is done, I want the user to make make two mouse clicks on the plot based on which it should take the two values as xlim and plot a zoomed version of the previous plot. However, I am not able to do it successfully.
Here is the code that I have written.
ui.R
library(shiny)
shinyUI(fluidPage(
titlePanel("LOAD AND PERFORMANCE DASHBOARD"),
sidebarLayout(
sidebarPanel(
fluidRow(
selectInput("select", label = h3("Select type of testing"),
choices = list("Performance Testing"=1, "Capacity Testing"=2)),
radioButtons("radio", label = h3("Select parameter to plot"),
choices = list("Disk" = 1, "Flit" = 2,"CPU" = 3,"Egress" =4,
"Memory" = 5))
)),
mainPanel(
plotOutput("plot",clickId="plot_click"),
textOutput("text1"),
plotOutput("plot2")
)
)
))
server.R
library(shiny)
library(rmongodb)
cursor <- vector()
shinyServer(function(input, output) {
initialize <- reactive({
mongo = mongo.create(host = "localhost")
})
calculate <- reactive({
if(input$radio==1)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "disk")
else if(input$radio==2)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "flit")
else if(input$radio==3)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "cpu")
else if(input$radio==4)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "egress")
else if(input$radio==5)
xvalue <- mongo.distinct(mongo,ns = "mydb.vload", "memory")
})
output$plot <- renderPlot({
initialize()
value <- calculate()
plot(value,xlab="Time",ylab="% Consumed")
lines(value)
cursor <- value
})
output$text1 <- renderText({
paste("You have selected",input$plot_click$x)
})
output$plot2 <- renderPlot({
plot(cursor[cursor<input$plot_click$x && cursor>first_click ],xlab="Time",ylab="% Consumed") lines(cursor)
first_click <- input$plot_click$x
})
})
Thanks in advance for the help :)
Here's a simple example that demonstrates the behavior you want, just run this code (or save as a file and source it). This code uses the new observeEvent function that debuted in Shiny 0.11, which just hit CRAN over the weekend.
The basic idea is that we track two reactive values, click1 and range. click1 represents the first mouse click, if any exists; and range represents the x-values of both mouse clicks. Clicking on the plot simply manipulates these two reactive values, and the plotting operation reads them.
library(shiny)
ui <- fluidPage(
h1("Plot click demo"),
plotOutput("plot", clickId = "plot_click"),
actionButton("reset", "Reset zoom")
)
server <- function(input, output, session) {
v <- reactiveValues(
click1 = NULL, # Represents the first mouse click, if any
range = NULL # After two clicks, this stores the range of x
)
# Handle clicks on the plot
observeEvent(input$plot_click, {
if (is.null(v$click1)) {
# We don't have a first click, so this is the first click
v$click1 <- input$plot_click
} else {
# We already had a first click, so this is the second click.
# Make a range from the previous click and this one.
v$range <- range(v$click1$x, input$plot_click$x)
# And clear the first click so the next click starts a new
# range.
v$click1 <- NULL
}
})
observeEvent(input$reset, {
# Reset both the range and the first click, if any.
v$range <- NULL
v$click1 <- NULL
})
output$plot <- renderPlot({
plot(cars, xlim = v$range)
if (!is.null(v$click1$x))
abline(v = v$click1$x)
})
}
shinyApp(ui, server)