Shiny - Loading page elements one by one

Viewed 43

I'm working on a Shiny application. Part of it is to recommend to users a gallery of ML generated ggplot2 plots based on their own data (or example data from R packages). There will be at least 50 plots generated and displayed every for users to choose from, possibly many more. My problem is that all plots are displayed at one time, with huge waiting times. I would like to find a way to have them displayed one by one, as soon as one has been generated. In the simple example below, instead of all 4 plots showing at one time, I'd like to have them shown individually as soon as they're ready.

  ui <- fluidPage(
        fluidRow(
          splitLayout(
            style = "height: 160px; text-align:center",
            plotOutput("Mosaic_Plot1"), plotOutput("Mosaic_Plot2"), plotOutput("Mosaic_Plot3"), plotOutput("Mosaic_Plot4")
          ),
          splitLayout(
            style = "height: 40px; text-align:center",
            actionButton("mosEdit1", "Edit this plot"), actionButton("mosEdit2", "Edit this plot"), actionButton("mosEdit3", "Edit this plot"), 
            actionButton("mosEdit4", "Edit this plot")
          )
        )
      )
  server <- function(input, output, session) {
    output$Mosaic_Plot1 <- renderPlot({ggplot(data = diamonds, aes(carat, price)) + geom_point()}, width = 280, height = 160)
    output$Mosaic_Plot2 <- renderPlot({ggplot(data = diamonds, aes(x = color, y = price, color = color)) + geom_point() + geom_jitter()}, width = 280, height = 160)
    output$Mosaic_Plot3 <- renderPlot({ggplot(data = diamonds, aes(carat)) + geom_histogram()}, width = 280, height = 160)
    output$Mosaic_Plot4 <- renderPlot({ggplot(data = diamonds, aes(depth, table)) + geom_point()}, width = 280, height = 160)
  }
  shinyApp(ui, server)

I tried several options with embedded uiOutputs, fillPage, ... but nothing worked so far. Many thanks for any suggestions on how to make this work.

1 Answers

Sure, the trick is take control of the reactivity. We can use observe and invalidateLater to create a loop, and then output charts 1 by 1. Below is a minimal example:

example of charts loading 1 by 1

library(shiny)

ui <- fluidPage(
    
    selectInput("input_1", label = "input_1", choices = c(10, 20, 30)),
    
    column(6, 
        plotOutput("plot_1"),
        plotOutput("plot_2")
    ),
    column(6, 
        plotOutput("plot_3"),
        plotOutput("plot_4")
    )
    
)

server <- function(input, output, session) {
    
    #Function which produces plots
    func_plot <- function(id) {
        
        #Simulate long random processing time
        Sys.sleep(sample(1:4, 1))
        
        #Produce bar plot
        barplot(seq(from = 1, to = isolate(input$input_1)), main = paste("Chart", id))
        
    }
    
    #Loop that runs once per second
    counter <- 1
    observe({
        
        if(counter <= 4) {
        
            if(counter == 1) {output$plot_1 <- renderPlot({func_plot(id = 1)})}
            if(counter == 2) {output$plot_2 <- renderPlot({func_plot(id = 2)})}
            if(counter == 3) {output$plot_3 <- renderPlot({func_plot(id = 3)})}
            if(counter == 4) {output$plot_4 <- renderPlot({func_plot(id = 4)})}
                
            counter <<- counter + 1
            
        }
        
        invalidateLater(1000)
        
    })
    
    #Watch for changes to inputs
    observeEvent(input$input_1, {
        
        #Optional: Clear plots before updating, to avoid having a mix of updated and un-updated plots
        output$plot_1 <- renderPlot({NULL})
        output$plot_2 <- renderPlot({NULL})
        output$plot_3 <- renderPlot({NULL})
        output$plot_4 <- renderPlot({NULL})
        
        counter <<- 1
        
    }, ignoreInit = TRUE)
    
}

shinyApp(ui, server)
Related