2

I've plotted an image in shiny, and created a save button to save the image as a .png file.

server <- function(input, output) {

  output$plot <- renderPlot({
    plot(1:500,rep(1:5,100))
  })

  plotInput <- function(){
    plot(1:500,rep(1:5,100))
  }

  output$savePlot <- downloadHandler(
    filename = "file.png", 
    content = function(file) {
      png(file)
      plotInput()
      dev.off()
    }
  )
}

When I view the image in the Shiny app on my laptop screen (1366x768 resolution), the plot becomes stretched out (i.e., longer than it is wide). [note: this is what I want].

  • Note: I'm using fluidRow, so the image changes size depending on the screen I view it on.

When I save the plot, the output file is no longer horizontally long as when viewed in the app itself, but rather it's saved as a square.

Now I know I can modify the size of the output image using width, height, and units arguments in the png() function.

However, I don't know the size of my open image in my Shiny app.

So my question:

Is there a way to save an image in a Shiny app to automatically have the same "dimensions" as it does when viewed on the screen?

Note: I realize that the shape/dimensions of the graph will change when I move my app to a new monitor or device, so I'm lookign for some way to actively determine the current dimensions of the image.


Here is what my plot looks like in the Shiny app on my laptop screen:

enter image description here


Again, I'd ideally like the solution to automatically determine the current dimensions of the plot as it's displayed on my screen and apply those dimensions to my saved png output image.

theforestecologist
  • 4,667
  • 5
  • 54
  • 91

2 Answers2

2

Add the following javascript to your UI:

 tags$script("$(document).on('shiny:connected', function(event) {
var myWidth = $(window).width();
Shiny.onInputChange('shiny_width',myWidth)

});"),

  tags$script("$(document).on('shiny:connected', function(event) {
var myHeight = $(window).height();
Shiny.onInputChange('shiny_height',myHeight)

});")

Then, change your downloadHandler code as follows:

  output$savePlot <- downloadHandler(
    filename = "file.png", 
    content = function(file) {
      png(file,
          width = input$shiny_width,
          height = input$shiny_height)
      plotInput()
      dev.off()
    }
  )
kostr
  • 836
  • 7
  • 13
1

You can save an image using image URI. In this case the image is exactly the same as you see it in shiny. Also you do not need to replot inside downloadHandler. Have a look at my answer in this thread for details: link

Reposting the example code here:

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