rstudio / shiny

Easy interactive web applications with R
https://shiny.posit.co/
Other
5.36k stars 1.87k forks source link

Shiny cache causes delay in rendering with renderUI #3685

Open saggrawal76 opened 2 years ago

saggrawal76 commented 2 years ago

System details

Browser Version: Chrome - Version 104.0.5112.81 (Official Build) (64-bit)

Output of sessionInfo():

# sessionInfo() output goes here
R version 4.2.0 (2022-04-22)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.6 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8   
 [6] LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] data.table_1.14.2    plotly_4.10.0        ggplot2_3.3.6        shinyWidgets_0.7.1   highcharter_0.9.4    shinydashboard_0.7.2 shiny_1.7.2         

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.9        lubridate_1.8.0   lattice_0.20-45   tidyr_1.2.0       zoo_1.8-10        assertthat_0.2.1  digest_0.6.29     utf8_1.2.2       
 [9] mime_0.12         R6_2.5.1          backports_1.4.1   httr_1.4.3        pillar_1.8.0      rlang_1.0.4       lazyeval_0.2.2    curl_4.3.2       
[17] rstudioapi_0.13   fontawesome_0.3.0 TTR_0.24.3        jquerylib_0.1.4   labeling_0.4.2    textshaping_0.3.6 stringr_1.4.0     htmlwidgets_1.5.4
[25] igraph_1.3.4      munsell_0.5.0     broom_1.0.0       compiler_4.2.0    httpuv_1.6.5      systemfonts_1.0.4 pkgconfig_2.0.3   htmltools_0.5.3  
[33] tidyselect_1.1.2  tibble_3.1.8      fansi_1.0.3       viridisLite_0.4.0 crayon_1.5.1      dplyr_1.0.9       withr_2.5.0       later_1.3.0      
[41] grid_4.2.0        jsonlite_1.8.0    xtable_1.8-4      gtable_0.3.0      lifecycle_1.0.1   magrittr_2.0.3    scales_1.2.0      rlist_0.4.6.2    
[49] quantmod_0.4.20   cli_3.3.0         stringi_1.7.8     cachem_1.0.6      farver_2.1.1      promises_1.2.0.1  bslib_0.4.0       ellipsis_0.3.2   
[57] xts_0.12.1        ragg_1.2.2        generics_0.1.3    vctrs_0.4.1       tools_4.2.0       glue_1.6.2        purrr_0.3.4       crosstalk_1.2.0  
[65] fastmap_1.1.0     yaml_2.3.5        colorspace_2.0-3  memoise_2.0.1     sass_0.4.2 

Example application or steps to reproduce the problem

```R # Minimal, self-contained example app code goes here library(shiny) library(shinydashboard) library(shinyWidgets) library(plotly) library(ggplot2) library(data.table) siderbar <- dashboardSidebar(sidebarMenu()) ui <- fluidPage( sidebarLayout( sidebarPanel( selectInput( inputId = "inp_species", label = "Select by:", choices = c("setosa", "versicolor", "virginica"), selected = "setosa" ), radioButtons( inputId = "radioTest", label = "Choose one:", choices = c("Simple Plot" = "simple", "Plotly" = "plotly"), inline = FALSE, selected = "simple" ) ), mainPanel(fluidRow( tabPanel( 6, conditionalPanel("input.radioTest == 'simple'", plotOutput("plot2")), conditionalPanel("input.radioTest == 'plotly'", plotlyOutput("plot1")) ) ) ) ) ) server <- function(input, output, session) { iris_dt <- reactive({ subset(x = iris, Species == input$inp_species) }) output$plot1 <- output$plot1x <- renderPlotly({ Sys.sleep(2L) plot_ly( req(iris_dt()), x = ~ Petal.Width, y = ~ Sepal.Length, type = "scatter", mode = "markers" ) }) output$plot2 <- output$plot2x <- renderPlot({ Sys.sleep(2L) ggplot(req(iris_dt()), aes(x = Petal.Width, y = Sepal.Length)) + geom_point() }) } shinyApp(ui, server) ``` ### Describe the problem in detail I created a Shiny app, in which I am drawing a scatter plot on iris dataset using libraries highchart, ggplot and plotly. When I dynamically render the UI using a graphical library (such as highchart), then for the first time rendering works as expected. But when I select a different graphical library, then the cache of older graphical library renders (for a while) before the newly selected graphical library can draw. Here is the complete list of steps to reproduce this problem. Example code is shared. I am selecting the library to draw the chart dynamically using a selectInput box. Here is the problem - 1. I select a species in selectInput box and the highchart library draws a scatter plot 2. Then I select plotly in the radio button section and the rendering is done using plotly. 3. I change the species in selectInput and plotly re-renders the plot 4. Now, when I click on highchart radio button, the plot of the earlier species (from cache) is drawn for a few seconds and then the chart of the selected species is drawn.
nir4most commented 2 years ago

I haven't solved your issue, but I think I improved your reprex, by simplifying it down of library dependencies, etc.


library(shiny)
library(plotly)
library(ggplot2)

siderbar <- dashboardSidebar(
  sidebarMenu()
)

ui <- fluidPage(sidebarLayout(
      sidebarPanel(

    # Add buttons to choose the way you want to select your data
    selectInput(inputId = "inp_species", label = "Select by:",
                choices = c("setosa", "versicolor", "virginica"), selected = "setosa"),
    radioButtons(
      inputId = "radioTest", label = "Choose one:",
      choices = c(
        "Simple Plot" = "simple",
        "Plotly" = "plotly"
      ),
      inline = FALSE, selected = "simple"
    )

  ),mainPanel(
  fluidRow(
    column(
      6,
      uiOutput("tabset1Selected")
    )
  ),
  fluidRow(
    column(6, plotlyOutput("plot1x")),
    column(6, plotOutput("plot2x"))
  )))
)

shinyApp(
  ui = ui,
  server = function(input, output, session) {
    iris_dt <- reactive({
      subset(x = iris,
             Species == input$inp_species)
    })

    p1 <- reactive({
      plot_ly(req(iris_dt()), x = ~Petal.Width, y = ~Sepal.Length,type = "scatter",
              mode="markers")
    })

    output$plot1 <-  output$plot1x <- renderPlotly({
      req(p1())
    })

    p2 <- reactive({
      ggplot(req(iris_dt()), aes(x = Petal.Width, y = Sepal.Length)) +
        geom_point()
    })

    output$plot2 <- output$plot2x <- renderPlot({
      req(p2())
    })

    # The currently selected tab from the first box
    output$tabset1Selected <- renderUI({
      switch(EXPR = req(input$radioTest),
        "plotly" = plotlyOutput("plot1"),
        "simple" = plotOutput("plot2")
      )
    })
  }
)
saggrawal76 commented 2 years ago

Thanks, @nir4most. I have checked your code and the rendering has improved. The cache issue is still persistent.

ismirsehregal commented 2 years ago

I don't think this is a cache issue.

This is a matter of the output's visibility. Usually in shiny an output will only be updated if it is visible (this saves resources).

Accordingly when you switch from a freshly updated ouput to a previously hidden ouput, it is still in its old state (for a short time).

You can avoid this by using outputOptions(output, "myplot", suspendWhenHidden = FALSE)

Here is an example (also avoiding renderUI - personal preference):

library(shiny)
library(plotly)
library(ggplot2)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput(
        inputId = "inp_species",
        label = "Select by:",
        choices = c("setosa", "versicolor", "virginica"),
        selected = "setosa"
      ),
      radioButtons(
        inputId = "radioTest",
        label = "Choose one:",
        choices = c("Simple Plot" = "simple",
                    "Plotly" = "plotly"),
        inline = FALSE,
        selected = "simple"
      )
    ),
    mainPanel(fluidRow(
      column(
        6,
        conditionalPanel("input.radioTest == 'simple'", plotOutput("plot2")),
        conditionalPanel("input.radioTest == 'plotly'", plotlyOutput("plot1"))
      )
    ),
    fluidRow(
      column(6, plotlyOutput("plot1x")),
      column(6, plotOutput("plot2x"))
    ))
  )
)

server <- function(input, output, session) {
  iris_dt <- reactive({
    subset(x = iris, Species == input$inp_species)
  })

  output$plot1 <-  output$plot1x <- renderPlotly({
    # Sys.sleep(2L)
    plot_ly(
      req(iris_dt()),
      x = ~ Petal.Width,
      y = ~ Sepal.Length,
      type = "scatter",
      mode = "markers"
    )
  })

  output$plot2 <- output$plot2x <- renderPlot({
    # Sys.sleep(2L)
    ggplot(req(iris_dt()), aes(x = Petal.Width, y = Sepal.Length)) +
      geom_point()
  })

  lapply(list("plot1", "plot2"), outputOptions, x = output, suspendWhenHidden = FALSE)
}

shinyApp(ui, server)
saggrawal76 commented 2 years ago

Thanks @ismirsehregal. Using outputOptions(output, "myplot", suspendWhenHidden = FALSE) is not solving this issue. In my real app, it just takes too much time to execute all possible outputs in the background and honestly, I won't need all the rendering in the background until the user clicks on the radio button. I am looking for a more intuitive solution so that when a user switches from a freshly updated output to a previously hidden output, the old state is hidden until the new state is refreshed.

jcheng5 commented 2 years ago

@saggrawal76 Just so I understand the symptom, with the reprex you posted, the behavior I'm seeing is that the "stale" plot appears for just a fraction of a second and then is replaced by the correct plot. In your real app, do the calculations take a lot longer so the stale plot appears for a few seconds? And if that's so, does the stale plot at least become semi-transparent while Shiny is thinking?

saggrawal76 commented 2 years ago

@jcheng5 You are correct, the calculations take a few seconds and the "stale" plot appears during that time. The stale plot does not 'blur' during the waiting period. This behavior causes confusion while using the app.

ismirsehregal commented 2 years ago

@saggrawal76 - ok sorry - so the example was misleading. Maybe you can reproduce the error by calling e.g. Sys.sleep(2L) in renderPlot / renderPlotly?

saggrawal76 commented 2 years ago

@ismirsehregal - I have implemented your suggested code (without RenderUI function) and added Sys.sleep(2L) in the example code. It now exactly reproduces the error. FYI.. the same problem exists in the RenderUI version too.