Possible bug when combining renderCachedPlot() with an actionButton()

Hello,

I am designing a shiny app using shiny rmarkdown, and I have two problems:

  1. The database backend is a little slow. Usually 2-3 seconds to generate the dataset.
  2. The resulting plot is slow to render. Anywhere from 1-5 seconds.

These aren't really long enough delays to make it unusable, but I would like to cut them out where I can. I found renderCachedPlot(), which is perfect for handling the plot delay, and I can use an actionButton() with eventReactive() to delay evaluation while a user chooses multiple inputs. However, I can't get them to work together to do both. Here is my reprex:


---
title: "Cached Plot Reprex"
date: "December 13, 2019"
output: html_document
runtime: shiny
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = F)
library(tidyverse)
```

This R Markdown document is made interactive using Shiny. Unlike the more traditional workflow of creating static reports, you can now create documents that allow your readers to change the assumptions underlying your analysis and see the results immediately. 

To learn more, see [Interactive Documents](http://rmarkdown.rstudio.com/authoring_shiny.html).

## Inputs and Outputs

You can embed Shiny inputs and outputs in your document. Outputs are automatically updated whenever inputs change.  This demonstrates how a standard R plot can be made interactive by wrapping it in the Shiny `renderPlot` function. The `selectInput` and `sliderInput` functions create the input widgets used to drive the plot.

```{r input}
inputPanel(
  numericInput("begin", label = "Start",
              min = 2, max = 20, step = 2, value = 2),
  
  numericInput("end", label = "End",
              min = 2, max = 20, step = 2, value = 2),
  
  actionButton("eval_button", "Evaluate")
)
```

```{r plot_function}
dynamic_plot <- function(begin, end) {
  ggplot(
        data.frame(
          a = seq(begin, end),
          b = seq(begin, end)
        ),
        aes(a, b)
      ) +
        geom_point() +
        labs(title = paste(begin, end, sep = ", "))
}

reactive_plot <- eventReactive(
  input$eval_button,
  {
    # Simulate a delay
    Sys.sleep(1)
    
    # Use isolate to break dependency on the inputs
    isolate(dynamic_plot(input$begin, input$end))
  }
)
```

This one only calculates after pressing the button, but it doesn't cache plots.

```{r eval_plot}
renderPlot(
  {
    # Simulate a delay
    Sys.sleep(2)
    
    # reactive_plot() is only dependent on the evaluation button
    reactive_plot()
  },
  outputArgs = list(width = "500px", height = "500px")
)
```

This one caches old plots, but it updates with every input change.

```{r cached_plot}
renderCachedPlot(
  {
    # Simulate a delay
    Sys.sleep(2)
    
    # dynamic_plot() is only dependent on whatever inputs are passed to it
    dynamic_plot(input$begin, input$end)
  },
  outputArgs = list(width = "500px", height = "500px"),
  cacheKeyExpr = {list(
    input$begin,
    input$end
  )}
)
```

This one runs when I start it, but it never invalidates, even if I press _Evaluate_.

```{r eval_cached_plot}
renderCachedPlot(
  {
    # Simulate a delay
    Sys.sleep(2)
    
    # Evaluate eval_button() to create a dependency
    input$eval_button
    
    # isolate() the inputs to break dependency
    dynamic_plot(isolate(input$begin), isolate(input$end))
  },
  outputArgs = list(width = "500px", height = "500px"),
  
  # Also isolate the inputs in the cache key
  cacheKeyExpr = {list(
    isolate(input$begin),
    isolate(input$end)
  )}
)
```

This one uses the button value in the cache invalidation, which causes it to never have a cache hit.

```{r cache_uses_eval}
renderCachedPlot(
  {
    # Simulate a delay
    Sys.sleep(2)
    
    # Evaluate eval_button() to create a dependency
    input$eval_button
    
    reactive_plot()
  },
  outputArgs = list(width = "500px", height = "500px"),
  
  # Use the button value in the cache key
  # Unfortunately, this makes the cache no longer useful
  cacheKeyExpr = {list(
    input$eval_button,
    isolate(input$begin),
    isolate(input$end)
  )}
)
```

Sorry for such a long example, but I wanted to show the different things I was able to do.

Just to be extra clear, my end goal is to have an app where users can change as many inputs as many times as they want with no reactivity until a button is clicked, but to also remember plots that have been made before. If this can't be done, I am open to writing my own caching logic and saving the plots to disk, but I would rather use the tools that are already out there. If this can only be done in a shiny app built from scratch, I am open to that too and would appreciate any guidance on converting from rmarkdown shiny to regular shiny.

Thanks!

This topic was automatically closed 54 days after the last reply. New replies are no longer allowed.