Just wondering if there are tricks/ways in which I could cache the plots being generated through our shiny app.
Background:
We are doing somewhat compute intensive computations which finally result in a plot. I am already caching(using memoise) the computations done, globally in shiny but it still takes about .75 seconds to render a plot. I was just wondering if we can decrease that time by removing the time it takes to render an image and if there are slick ways of already doing it.
More details:
I am using grid to create the plot(heatmap in this case. Ideally would like the caching to be disk based as storing plots in memory wont scale up.
Thanks! -Abhi
Caching of images created with renderPlot()/plotOutput()
is supported since shiny 1.2.0.
release notes: https://shiny.rstudio.com/reference/shiny/1.2.0/upgrade.html
function documentation https://shiny.rstudio.com/reference/shiny/1.2.0/renderCachedPlot.html.
The solution below behaves similar to the following usage of renderCachedPlot()
.
output$plot <- renderCachedPlot(
expr = {
histfaithful(bins = input$bins, col = input$col)
},
cache = diskCache()
)
renderCachedPlot()
allows caching in memory and on disk with sensible defaults. The rules for generating hash keys can be customized and by default digest::digest()
is used for all reactive expressions that appear in expr
.
The solution below demonstrates how a subset of these features (caching on disk) can be implemented with a shiny module. The basic strategy is to use
digest::digest()
to create cache keys based on arguments sent to a plot functiondo.call()
to pass the arguments to the plot function unless the key created from digest()
signifies that the image is already cachedgrDevices::png()
to capture an image from the call to do.call()
and add it to the cache shiny::renderImage()
to serve images from the cacheAlthough both answers to this question are very good, I wanted do add another one using shiny modules. The following module takes a plotfunction and a reactive version of it's arguments as inputs. In the end do.call(plotfun, args())
is used to create the plot.
library(shiny)
cachePlot <- function(input, output, session, plotfun, args, width = 480, height = 480,
dir = tempdir(), prefix = "cachedPlot", deleteonexit = TRUE){
hash <- function(args) digest::digest(args)
output$plot <- renderImage({
args <- args()
if (!is.list(args)) args <- list(args)
imgpath <- file.path(dir, paste0(prefix, "-", hash(args), ".png"))
if(!file.exists(imgpath)){
png(imgpath, width = width, height = height)
do.call(plotfun, args)
dev.off()
}
list(src = imgpath)
}, deleteFile = FALSE)
if (deleteonexit) session$onSessionEnded(function(){
imgfiles <- list.files(dir, pattern = prefix, full.names = TRUE)
file.remove(imgfiles)
})
}
cachePlotUI <- function(id){
ns <- NS(id)
imageOutput(ns("plot"))
}
As we can see, the module deletes the image files created if needed and gives the option to use a custom caching-directory in case persistent caching is needed (as it is in my actual usecase).
For a usage example, I'll use the hist(faithful[, 2])
example just like Stedy.
histfaithful <- function(bins, col){
message("calling histfaithful with args ", bins, " and ", col)
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = bins + 1)
hist(x, breaks = bins, col = col, border = 'white')
}
shinyApp(
ui = fluidPage(
inputPanel(
sliderInput("bins", "bins", 5, 30, 10, 1),
selectInput("col", "color", c("blue", "red"))
),
cachePlotUI("cachedPlot")
),
server = function(input, output, session){
callModule(
cachePlot, "cachedPlot", histfaithful,
args = reactive(list(bins = input$bins, col = input$col))
)
}
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With