admin管理员组

文章数量:1337125

I have a shiny app that uses a function to generate N number of plots (in this example 10).

Rather than use a map to create all 10 charts at the same time, below I am trying to use the futures package to render each plot one by one as they are ready.

library(shiny)
library(plotly)
library(purrr)
library(future)
library(promises)

# Enable parallel processing
plan(multisession)

# Generate 10 titles
titles <- paste0("Chart", 1:10)

# Create the list of lists
charts <- map(titles, ~ list(title = .x)) %>%
  setNames(titles)

chart_func <- function(title) {
  Sys.sleep(runif(1, 0.5, 2))  # Simulate some computation time
  plotly::plot_ly() %>%
    layout(
      title = title
    )
}

ui <- fluidPage(
  actionButton("make_plots", "Make Plots"),
  uiOutput("all_plots")
)

server <- function(input, output, session) {
  
  # Define plot names
  plot_names <- names(charts)
  
  # Render the UI with placeholders for all plots
  output$all_plots <- renderUI({
    purrr::map(plot_names, ~tags$div(class = "single-plot", plotlyOutput(.x)))
  })
  

  # Generate plots asynchronously upon button click
  observeEvent(input$make_plots, {
    
    map(charts, function(chart) {
      future({
        chart_func(chart$title)
      }, seed = TRUE) %...>% { result ->
        output[[chart$title]] <- renderPlotly({
          result
        })
      }
    })
    
  })
}

shinyApp(ui, server)

This code is close but I am getting errors and unsure what to try next. I know that method chaining is correct I just need some help with the output.

I have a shiny app that uses a function to generate N number of plots (in this example 10).

Rather than use a map to create all 10 charts at the same time, below I am trying to use the futures package to render each plot one by one as they are ready.

library(shiny)
library(plotly)
library(purrr)
library(future)
library(promises)

# Enable parallel processing
plan(multisession)

# Generate 10 titles
titles <- paste0("Chart", 1:10)

# Create the list of lists
charts <- map(titles, ~ list(title = .x)) %>%
  setNames(titles)

chart_func <- function(title) {
  Sys.sleep(runif(1, 0.5, 2))  # Simulate some computation time
  plotly::plot_ly() %>%
    layout(
      title = title
    )
}

ui <- fluidPage(
  actionButton("make_plots", "Make Plots"),
  uiOutput("all_plots")
)

server <- function(input, output, session) {
  
  # Define plot names
  plot_names <- names(charts)
  
  # Render the UI with placeholders for all plots
  output$all_plots <- renderUI({
    purrr::map(plot_names, ~tags$div(class = "single-plot", plotlyOutput(.x)))
  })
  

  # Generate plots asynchronously upon button click
  observeEvent(input$make_plots, {
    
    map(charts, function(chart) {
      future({
        chart_func(chart$title)
      }, seed = TRUE) %...>% { result ->
        output[[chart$title]] <- renderPlotly({
          result
        })
      }
    })
    
  })
}

shinyApp(ui, server)

This code is close but I am getting errors and unsure what to try next. I know that method chaining is correct I just need some help with the output.

Share Improve this question edited Nov 21, 2024 at 14:55 ismirsehregal 33.7k5 gold badges45 silver badges92 bronze badges asked Nov 19, 2024 at 22:56 Maya GansMaya Gans 811 silver badge4 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 4

To unblock the session calculating the plots you'll need to wrap the future with shiny::ExtendedTask(). Using future or future_promise without ExtendedTask will only unblock concurrent shiny sessions - please see this related but closed issue.

Please check ?shiny::ExtendedTask:

The ExtendedTask class allows you to have an expensive operation that is started by a reactive effect, and whose (eventual) results can be accessed by a regular observer, calc, or output; but during the course of the operation, the current session is completely unblocked, allowing the user to continue using the rest of the app while the operation proceeds in the background.

Applied to your example:

library(shiny)
library(plotly)
library(purrr)
library(future)
library(promises)
library(bslib)

# Enable parallel processing
plan(multisession)

# Generate 10 titles
titles <- paste0("Chart", 1:10)

# Create the list of lists
charts <- map(titles, ~ list(title = .x)) %>%
  setNames(titles)

chart_func <- function(title) {
  Sys.sleep(runif(1, 0.5, 2))  # Simulate some computation time
  plotly::plot_ly(type = "scatter", mode = "markers") %>%
    layout(
      title = title
    )
}

ui <- fluidPage(
  input_task_button("make_plots", "Make Plots"),
  uiOutput("all_plots")
)

server <- function(input, output, session) {
  # Define plot names
  plot_names <- names(charts)
  
  compute_plot <- ExtendedTask$new(function(chart) {
    future_promise({
      list(title = chart$title, plotly_obj = chart_func(chart$title))
    }, seed = TRUE)
  }) |> bind_task_button("make_plots")
  
  # Render the UI with placeholders for all plots
  output$all_plots <- renderUI({
    purrr::map(plot_names, ~tags$div(class = "single-plot", plotlyOutput(.x)))
  })
  
  # Generate plots asynchronously upon button click
  observeEvent(input$make_plots, {
    map(charts, function(chart) {
      compute_plot$invoke(chart)
    })
  })
  
  observeEvent(compute_plot$result(), {
    output[[compute_plot$result()$title]] <- renderPlotly({
      isolate(compute_plot$result()$plotly_obj)
    })
  })
}

shinyApp(ui, server)

Please also check this related article.

本文标签: rHow to use futures to render each plot one by one rather than all at the same timeStack Overflow