I am trying to download a already plotted ggplot
image without replotting it again. All the solutions I found (e.g. Save plots made in a shiny app) calling the function to create the figure again when they download the image.
Is there a workaround? My images are very complex and take some time to create.
Asked
Active
Viewed 914 times
0
2 Answers
2
This is an old thread. But I also needed to figure out how to avoid replotting in Shiny. My application generates many plots and calling last_plot()
in ggsave()
does not help much.
I figured it out. Indeed, since we already have an image in our application - we can just save that image. Right-click and select "Save Image" in browser. Although, this is not great - a download button and automation for downloading many plots would be nice to have.
To do that:
- I used javascript to get image URI (use
library(shinyjs)
, calluseShinyjs()
in theui
section of the app and callrunjs()
in theserver
section); - Javascript for some reason will not work inside
downloadHandler
. So, I had to makeactionButton
that runs the javascript and also simulates a.click()
ondownloadButton
. I learned this trick here: link - Use
library(magick)
for image decoding from URI and saving. This is the link how to do that: link
Here is the example code:
library(shiny)
library(magick)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
#visibale action button - this button simulates click on hidden download button
actionButton("save_myPlot", "Download", icon = icon("download")),
#hidden download button
downloadButton("save_myPlot_hidden", style = "visibility: hidden;"),
# plot
plotOutput("myPlot")
)
server <- function(input, output, session) {
#get the plot image URI and simulate click on download button
observeEvent(input$save_myPlot, {
shinyjs::runjs(
"
var p_src = document.getElementById('myPlot').childNodes[0].src;
Shiny.setInputValue('plot_src', p_src);
document.getElementById('save_myPlot_hidden').click();
"
)
})
# downaload handler - save the image
output$save_myPlot_hidden <- downloadHandler(
filename = function() {
paste0("plot_", Sys.Date(), ".png") },
content = function(file) {
# get image code from URI
plot_src <- gsub("^data.*base64,", "", input$plot_src)
# decode the image code into the image
plot_image <- image_read(base64_decode(plot_src))
# save the image
image_write(plot_image, file)
})
# plot
output$myPlot <- renderPlot(
plot(rnorm(5), rnorm(5))
)
}
shinyApp(ui = ui, server = server)

Pavel Khokhlov
- 160
- 1
- 8
1
Use the ggplot2::last_plot
function:
library(shiny)
library(ggplot2)
k <- 0
runApp(list(
ui = fluidPage(
plotOutput("fooplot"),
textOutput("fook"),
downloadButton('foo')
),
server = function(input, output) {
plotInput = function() {
k <<- k + 1
qplot(speed, dist, data = cars)
}
output$fooplot <- renderPlot({
plotInput()
})
output$fook <- renderPrint({
k
})
output$foo = downloadHandler(
filename = 'test.png',
content = function(file) {
device <- function(..., width, height) {
grDevices::png(..., width = width, height = height,
res = 300, units = "in")
}
ggsave("myplot.png", plot = last_plot(), device = device)
})
}
))
Forgive the use of the global assignment, just including to show that the plotInput
is not called twice.

mlegge
- 6,763
- 3
- 40
- 67