R shiny: Update tabsetpanel before finishing all the observeEvent code - r

I want to update the tabsetpanel immediately, and not wait untill finishing the download function. here you can find a simple code It has a button, and when it presed, it simulate a download, and update a tabsetpanel. I want to update the panel before finishing the download.
Thanks!
server <- function(input, output,session) {
observeEvent(input$goPlot,{
updateTabsetPanel(session, "inTabset",
selected = 'Summary'
)
output$plot <- renderPlot({
input$goPlot # Re-run when button is clicked
# Create 0-row data frame which will be used to store data
dat <- data.frame(x = numeric(0), y = numeric(0))
withProgress(message = 'Making plot', value = 0, {
# Number of times we'll go through the loop
n <- 10
for (i in 1:n) {
# Each time through the loop, add another row of data. This is
# a stand-in for a long-running computation.
dat <- rbind(dat, data.frame(x = rnorm(1), y = rnorm(1)))
# Increment the progress bar, and update the detail text.
incProgress(1/n, detail = paste("Doing part", i))
# Pause for 0.1 seconds to simulate a long computation.
Sys.sleep(1)
}
})
plot(dat$x, dat$y)
})
})
}
ui <- shinyUI(fluidPage(
actionButton('goPlot', 'Go plot'),
tabsetPanel(id = "inTabset",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary")
)
)
)
shinyApp(ui = ui, server = server)

Shiny only updates the UI after all invalidated observe or reactive statements has been updated. Therefore you have to build reactive chains when you want a workflow like this. I solved this though pulling out the data preparation in a separate reactive statement (this is not really necessary but always a good idea) then I moved the plot to the summary tab. I supposed the reason to switch tab was to see the plot. Please correct me if this is not correct. But this postpones the calculations until the tab is shown. Now to prevent that the calculations start before the goPlot button is clicked I just added the line
req(input$goPlot)
to the beginning of the the reactive statement.
server <- function(input, output,session) {
observeEvent(input$goPlot,{
updateTabsetPanel(session, "inTabset",
selected = 'Summary'
)
generate_plot <- reactive({
req(input$goPlot)
# Create 0-row data frame which will be used to store data
dat <- data.frame(x = numeric(0), y = numeric(0))
withProgress(message = 'Making plot', value = 0, {
# Number of times we'll go through the loop
n <- 10
for (i in 1:n) {
# Each time through the loop, add another row of data. This is
# a stand-in for a long-running computation.
dat <- rbind(dat, data.frame(x = rnorm(1), y = rnorm(1)))
# Increment the progress bar, and update the detail text.
incProgress(1/n, detail = paste("Doing part", i))
# Pause for 0.1 seconds to simulate a long computation.
Sys.sleep(1)
}
})
plot(dat$x, dat$y)
})
output$plot <- renderPlot({
generate_plot()
})
})
}
ui <- shinyUI(fluidPage(
actionButton('goPlot', 'Go plot'),
tabsetPanel(id = "inTabset",
tabPanel("Plot"),
tabPanel("Summary", plotOutput("plot"))
)
)
)
shinyApp(ui = ui, server = server)
Hope this helps!!

You can do:
observeEvent(input$goPlot, {
updateTabsetPanel(session, "inTabset",
selected = 'Summary'
)
})
output$plot <- renderPlot({
req(input$inTabset == "Summary") # require "Summary" is the active tab
input$goPlot # Re-run when button is clicked
......
Or execute some Javascript code to change the active tab, e.g. with shinyjs:
library(shiny)
library(shinyjs)
server <- function(input, output,session) {
observeEvent(input$goPlot, {
runjs("$('a[data-value=Summary]').click();") # go to Summary tab
output$plot <- renderPlot({
input$goPlot # Re-run when button is clicked
# Create 0-row data frame which will be used to store data
dat <- data.frame(x = numeric(0), y = numeric(0))
withProgress(message = 'Making plot', value = 0, {
# Number of times we'll go through the loop
n <- 10
for (i in 1:n) {
# Each time through the loop, add another row of data. This is
# a stand-in for a long-running computation.
dat <- rbind(dat, data.frame(x = rnorm(1), y = rnorm(1)))
# Increment the progress bar, and update the detail text.
incProgress(1/n, detail = paste("Doing part", i))
# Pause for 0.1 seconds to simulate a long computation.
Sys.sleep(1)
}
})
plot(dat$x, dat$y)
})
})
}
ui <- shinyUI(fluidPage(
useShinyjs(),
actionButton('goPlot', 'Go plot'),
tabsetPanel(id = "inTabset",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary")
)
))
shinyApp(ui = ui, server = server)

Not really an answer, I know, but I do not really understand why the following does not work. It ensures the correct execution order, but the problem persists. I guess the problem is the updates are not flushed before both have finished.
server <- function(input, output,session) {
rv <- reactiveValues(goPlot_wait = 0)
observeEvent(input$goPlot,{
cat("A EXECUTED\n")
updateTabsetPanel(session, "inTabset", selected = 'Summary')
rv$goPlot_wait <- rv$goPlot_wait + 1
})
observeEvent(rv$goPlot_wait,{
if(rv$goPlot_wait == 0) {
return()
}
cat("B EXECUTED\n")
output$plot <- renderPlot({
# Create 0-row data frame which will be used to store data
dat <- data.frame(x = numeric(0), y = numeric(0))
withProgress(message = 'Making plot', value = 0, {
# Number of times we'll go through the loop
n <- 10
for (i in 1:n) {
# Each time through the loop, add another row of data. This is
# a stand-in for a long-running computation.
dat <- rbind(dat, data.frame(x = rnorm(1), y = rnorm(1)))
# Increment the progress bar, and update the detail text.
incProgress(1/n, detail = paste("Doing part", i))
# Pause for 0.1 seconds to simulate a long computation.
Sys.sleep(0.25)
}
})
plot(dat$x, dat$y)
})
})
}
ui <- shinyUI(fluidPage(
actionButton('goPlot', 'Go plot'),
tabsetPanel(id = "inTabset",
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary"))))
shinyApp(ui = ui, server = server)
When running this app and pressing the button, I get:
> shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:6800
A EXECUTED
B EXECUTED
Yet the tabset is updated after the plot has rendered. Perhaps someone can shed some light on what is going on here.

Related

How can I stop rhandsontable from starting an infinite loop after inputs in quick succession?

I'm using rhandsontable in a shiny app to manually update a dataframe to be displayed with ggplot2.
When adding/changing values in the rhandsontable in quick succession, the table starts an infinite loop, which immobilizes the entire shiny application.
Below an example. By quickly changing the values in the table, the app gets stuck.
library(shiny)
library(rhandsontable)
library(ggplot2)
ui <- fluidPage(
rHandsontableOutput("hot"),
plotOutput("plot")
)
server <- function(input, output, session) {
reactive.table <- reactiveValues(values = data.frame(x = c(1,2), y = c(1,2)))
observe({
if (!is.null(input$hot)) {
reactive.table$values <<- hot_to_r(input$hot)
}
})
output$hot <- renderRHandsontable({
rhandsontable(reactive.table$values)
})
output$plot <- renderPlot({
Sys.sleep(2)
ggplot(reactive.table$values, aes(x = x, y = y)) +
geom_point()
})
}
shinyApp(ui = ui, server = server)
Is there a way to stop the infinite loop or to disable further inputs until the plot is rendered?
Thanks.
I don't know how to stop rhandsontable from starting the infinite loop, but you can avoid inputs in quick succession altogether.
Taking your reprex, I restrict changes to be at least 1 second apart, and it works just fine.
library(shiny)
library(rhandsontable)
library(ggplot2)
ui <- fluidPage(
rHandsontableOutput("hot"),
plotOutput("plot")
)
server <- function(input, output, session) {
reactive.table <- reactiveValues(values = data.frame(x = c(1,2), y = c(1,2)))
rv_timer <- reactiveValues(
prev = NULL, current = NULL
)
observe({
if (!is.null(input$hot)) {
# if it's first time editing table:
if (is.null(rv_timer$prev)) {
rv_timer$prev <- Sys.time()
reactive.table$values <- hot_to_r(input$hot)
return(NULL)
}
# if it's not the first time to edit table, get current clock time:
rv_timer$current <- Sys.time()
# if the difference btwn prev recorded time and current time is less
# than 1second, don't do anything, just return:
if ((rv_timer$current - rv_timer$prev) < 1) {
return(NULL)
}
# otherwise proceed as normal:
reactive.table$values <- hot_to_r(input$hot)
# finally set current clock time as `rv_timer$prev` for use in the next
# invalidation:
rv_timer$prev <- Sys.time()
}
})
output$hot <- renderRHandsontable({
rhandsontable(reactive.table$values)
})
output$plot <- renderPlot({
Sys.sleep(2)
ggplot(reactive.table$values, aes(x = x, y = y)) +
geom_point()
})
}
shinyApp(ui = ui, server = server)
Might be a bug in {rhansontable}.

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)

How to show a progressBar in a single function in shiny?

Here is an example. The progress bar just jumps from 0% to 100% due a single function getres(). How to indicate the progress smoothly?
library("shiny")
library("shinyWidgets")
library("DESeq2")
library("airway")
data(airway)
getres <- function(eset){
dds<-DESeqDataSet(eset, design = ~cell + dex)
keep <- rowSums(counts(dds)) >= 10
dds <- dds[keep,]
dds <- DESeq(dds)
res <- results(dds)
return(res)
}
ui <- fluidPage(
tags$h1("Progress bar in Sweet Alert"),
useSweetAlert(), # /!\ needed with 'progressSweetAlert'
actionButton(
inputId = "go",
label = "Launch long calculation !"
)
)
server <- function(input, output, session) {
observeEvent(input$go, {
progressSweetAlert(
session = session, id = "myprogress",
title = "Work in progress",
display_pct = TRUE, value = 0
)
for (i in seq_len(1)) {
Sys.sleep(0.1)
updateProgressBar(
session = session,
id = "myprogress",
res<-getres(airway),
value = i
)
}
closeSweetAlert(session = session)
sendSweetAlert(
session = session,
title =" Calculation completed !",
type = "success"
)
})
}
shinyApp(ui = ui, server = server)
I was unable to run your example as airway and DESeq2 are not available for R 3.6+. BUT there is an interesting package that I have been meaning to try out called waiter.
Within waiter there is waitress which will "let you display loading bars on the entire screen or specific elements only."
There is a great demo app where you play with the different functions.
Here is an example from the docs!
library(shiny)
library(waiter)
ui <- navbarPage(
"Waitress on nav",
tabPanel(
"home",
use_waitress(),
plotOutput("plot")
)
)
server <- function(input, output){
# now waitress ranges from 0 to 100
waitress <- Waitress$new("nav", theme = "overlay", min = 0, max = 10)
output$plot <- renderPlot({
for(i in 1:10){
waitress$inc(1) # increase by 10%
Sys.sleep(.5)
}
hist(runif(100))
waitress$close() # hide when done
})
}
shinyApp(ui, server)
Hope this helps or gives you other ideas!

Run Shiny Reactive after Another Finishes

I have two outputs, a print and a plot. I would like to execute the print after the run button is pressed (working) and then when the print completes the plot part executes.
The reason for this is the print part does some calculations that take a few minutes and the output from that needs to go to the plot command.
Simple example:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton('run','Run')
),
mainPanel(
verbatimTextOutput("Descriptive"),
plotOutput("plotData",width = "700px", height = "500px")
)
)
)
server <- function(input, output) {
output$Descriptive <- renderPrint({
if(input$run>0){
return(isolate({
cat('Number of rows:', nrow(mtcars))
mpg2 <<- mtcars$mpg+3
cyl2 <<- mtcars$cyl+3
}))
}else{return(invisible())}
})
#### RUN AFTER DESCRIPTIVE COMPLETES ####
output$plotData <- renderPlot({
plot(mpg2,cyl2)
})
}
shinyApp(ui = ui, server = server)
I would suggest you to store the variable as reactiveValues and make the plot dependent on them. By this you can avoid the current global assignment and also make the plot update dependent on a change in its variables.
It could look like this:
global <- reactiveValues(mpg2 = mtcars$mpg, cyl2 = mtcars$cyl, txt = "")
observe({
if(input$run > 0){
Sys.sleep(5) # simulate minutes of calculating
global$txt <- paste('Number of rows:', nrow(mtcars))
global$mpg2 <- mtcars$mpg + 3
global$cyl2 <- mtcars$cyl + 3
}
})
Your app would look like this:
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
actionButton('run','Run')
),
mainPanel(
verbatimTextOutput("Descriptive"),
plotOutput("plotData",width = "700px", height = "500px")
)
)
)
server <- function(input, output) {
global <- reactiveValues(mpg2 = mtcars$mpg, cyl2 = mtcars$cyl, txt = "")
observe({
if(input$run > 0){
Sys.sleep(5) # simulate minutes of calculating
global$txt <- paste('Number of rows:', nrow(mtcars))
global$mpg2 <- mtcars$mpg + 3
global$cyl2 <- mtcars$cyl + 3
}
})
output$Descriptive <- renderPrint({
if(nchar(global$txt)) return(global$txt)
})
#### RUN AFTER DESCRIPTIVE COMPLETES ####
output$plotData <- renderPlot({
plot(global$mpg2, global$cyl2)
})
}
shinyApp(ui = ui, server = server)

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