Change plot based on click input in R shiny - r

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)

Related

Progress bar closes too soon with ggplot

I wonder if someone can explain the behavior of the progressBar().
I have trimmed my shiny app to the bare minimum to reproduce this post.
Now to the problem. When I select "AllRuns", the progress bar pops up and then goes away
before the graphic is displayed. But when I select "scatter", the progress bar nicely waits
until the scatter plot is displayed on the main panel. Is this a normal behavior?
How can I make the progress bar wait until the graphic displays when "AllRuns" is selected?
UPDATE The dataset can be read into R from google docs. it takes about 20 seconds to load into R.
library(shiny)
library(tidyverse)
library(DT)
library(data.table)
final <- fread("https://docs.google.com/spreadsheets/d/170235QwbmgQvr0GWmT-8yBsC7Vk6p_dmvYxrZNfsKqk/pub?output=csv")
runs<- c("AllRuns","scatter")
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "run",
label = "Chinook Runs",
choices = runs,
selected = "AllRuns"),
sliderInput(inputId = "Yearslider",
label="Years to plot",
sep="",
min=2000,
max=2014,
value=c(2010,2012))),
mainPanel(
plotOutput("plot")
)))
server <- function(input, output,session) {
session$onSessionEnded(function() {
stopApp()
})
plot_all <- reactive({
final[final$year >= input$Yearslider[1] & final$year <= input$Yearslider[2], ]
})
plotscatter <- reactive({
rnorm(100000)
})
dataInput <- reactive({
if (input$run == "AllRuns") {
plot_all()
}else{
plotscatter()
}
})
# Plot data
create_plots <- reactive({
withProgress(message="Creating graphic....",value = 0, {
n <- 10
for (i in 1:n) {
incProgress(1/n, detail = input$run)
Sys.sleep(0.1)
}
#Make the plots
theme_set(theme_classic())
switch(input$run,
"AllRuns" = ggplot(plot_all(),aes(SampleDate,Count,color = race2)) +
geom_point() + theme_bw() +
labs(x="",y="Number in thousands",title="All Salmon Runs combined"),
"scatter" = plot(plotscatter(),col="lightblue")
)
})#Progress bar closing brackets
})#create_plots closing brackets
output$plot <- renderPlot({
create_plots()
})
}
# Run the application
shinyApp(ui = ui, server = server)
It is simple that the progress bar is updated by the for-loop and the plot code only run after the for-loop. So the progress-bar reach the end, then plot code started. This kind of progress-bar would work if you are process something along with for-loop for example
list_of_files # assume you have a list of data file to read and process
max_progress <- length(list_of_files)
withProgress(message="Creating graphic....",value = 0, {
for (i in 1:max_progress) {
data <- read_csv(list_of_files[i])
... # doing something here
# once the processing code done next line of code will update the progress bar
incProgress(1/n, detail = input$run)
}
})
If you want to display loading one way to do it is using shinycssloaders::withSpinner() on the UI part which would show an animation of loading while UI is updating by server side.
The withProgress would be more useful when you have a list of items to process.
library(shiny)
library(tidyverse)
library(DT)
library(data.table)
library(shinycssloaders)
final <- fread("https://docs.google.com/spreadsheets/d/170235QwbmgQvr0GWmT-8yBsC7Vk6p_dmvYxrZNfsKqk/pub?output=csv")
runs<- c("AllRuns","scatter")
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput(inputId = "run",
label = "Chinook Runs",
choices = runs,
selected = "AllRuns"),
sliderInput(inputId = "Yearslider",
label="Years to plot",
sep="",
min=2000,
max=2014,
value=c(2010,2012))),
mainPanel(
withSpinner(plotOutput("plot"))
)))
server <- function(input, output,session) {
session$onSessionEnded(function() {
stopApp()
})
plot_all <- reactive({
final[final$year >= input$Yearslider[1] & final$year <= input$Yearslider[2], ]
})
plotscatter <- reactive({
rnorm(100000)
})
dataInput <- reactive({
if (input$run == "AllRuns") {
plot_all()
}else{
plotscatter()
}
})
# Plot data
create_plots <- reactive({
#Make the plots
theme_set(theme_classic())
switch(input$run,
"AllRuns" = ggplot(plot_all(),aes(SampleDate,Count,color = race2)) +
geom_point() + theme_bw() +
labs(x="",y="Number in thousands",title="All Salmon Runs combined"),
"scatter" = plot(plotscatter(),col="lightblue")
)
})#create_plots closing brackets
output$plot <- renderPlot({
create_plots()
})
}
# Run the application
shinyApp(ui = ui, server = server)

R shiny: Hide NA before using plot click to return values in conditional panel

I am using plot_click to interrogate a graph in Shiny and would like the conditional panel to show 2 bits of information. However at the moment, the conditional panel shows 'NA' until i perform the plot click, how do i make this disappear?
library(ggplot2)
library(shiny)
# make some data
df <- data.frame(ID=c(1,2),x=c(33,7),y=c(50,16),name=c("Tom","Bill"),link=c("https://mylink.com","https://anotherlink.com"), stringsAsFactors=FALSE)
# Shiny app
ui <- basicPage(
plotOutput("plot", click = "plot_click"),
verbatimTextOutput("selection"),
conditionalPanel("!is.na(output.nametext)",
h4(textOutput("nametext")),
h4(textOutput("urltext")))
)
server <- function(input, output,session) {
output$plot <- renderPlot({
ggplot(data=df,aes(x=x,y=y))+
geom_point()+
scale_x_continuous(limits = c(0, 68))+
scale_y_continuous(limits = c(0, 52.5))
})
output$selection <- renderPrint({
nearPoints(df, input$plot_click)
})
info <- reactive({
t <- as.data.frame(nearPoints(df, input$plot_click))
s <- t[1,4]
u <- t[1,5]
list(s=s,u=u)
})
output$nametext <- renderText({info()$s})
output$urltext <- renderText({info()$u})
}
runApp(shinyApp(ui, server), launch.browser = TRUE)
in the conditionalPanel in the UI, i've tried !is.na(output.nametext), output.nametext != null, output.nametext==true, plot_click==true, plot_click!=null and more. None of them remove the NA that exists before i perform the click.
One solution would be to simply use:
output$nametext <- renderText({
if(!is.na(info()$s)){
info()$s
}
})
You could also use the space to inform the user he should click a point to see information:
output$urltext <- renderText({
if(!is.na(info()$s)){
info()$u
}else{
print("Click on a point to get additional information")
}
})

Mouse click event in rshiny

I'm trying to use click events using the plot_click option in RShiny. What I want to do is:I want to select a particular bubble from the first chart and then the chart below should be populated only for the above selected car. How to do this? Here is my code :
ui <- basicPage(
plotOutput("plot1", click = "plot_click"),
plotOutput("plot2")
)
server <- function(input, output) {
output$plot1 <- renderPlot({
plot(mt$wt, mt$mpg)
})
output$plot2 <- renderPlot({
test <- data.frame(nearPoints(mt, input$plot_click, xvar = "wt", yvar = "mpg"))
test2 <- filter(test,Car_name)
car <- test2[1,1]
mt2 <- filter(mt,Car_name == car)
plot(mt2$wt,mt2$mpg)
})
}
shinyApp(ui, server)
I rearranged your server-function a bit. I moved the selected points to a reactive Value, which can be used by print/plot outputs.
Furthermore, i am not exactly sure what you want to achievewith all that filtering. Maybe you could change your original question an make a reproducible example out of it with the mtcars-data, as it seems you are using that.
library(shiny)
ui <- basicPage(
plotOutput("plot1", click = "plot_click"),
verbatimTextOutput("info"),
plotOutput("plot2")
)
server <- function(input, output) {
output$plot1 <- renderPlot({
plot(mtcars$wt, mtcars$mpg)
})
selected_points <- reactiveValues(pts = NULL)
observeEvent(input$plot_click, {
x <- nearPoints(mtcars, input$plot_click, xvar = "wt", yvar = "mpg")
selected_points$pts <- x
})
output$info <- renderPrint({
selected_points$pts
})
output$plot2 <- renderPlot({
req(input$plot_click)
test <- selected_points$pts
plot(test$wt,test$mpg)
})
}
shinyApp(ui, server)
The clicked points are stored in the selected_points reactive Value, which is assigned in the observeEvent function.
If you filter a lot in the plot2-function, you would have to use req() or validate(), as it may be possible that no value is left over and therefore nothing can be plotted.
I hope that helps.

ggplot2: how to differentiate click from brush?

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.

Interactive plotting in shiny using mouse clicks

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)

Resources