How to download graphs which are dynamic in R Shiny?

拜拜、爱过 提交于 2021-01-28 04:25:08

问题


In Shiny Dashboard in a Tab I am plotting graphs one below the another, based on the selection of checkbox inputs. When the check boxes are selected accordingly the graphs will get displayed one below the another. Kindly find the code below which i used.

library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(dplyr)
d <-
  data.frame(
    year = c(1995, 1995, 1995, 1996, 1996, 1996, 1997, 1997, 1997),
    Product_Name = c(
      "Table",
      "Chair",
      "Bed",
      "Table",
      "Chair",
      "Bed",
      "Table",
      "Chair",
      "Bed"
    ),
    Product_desc = c("X", "X", "X", "Y", "Y", "Y", "Z", "Z", "Z"),
    Cost = c(1, 2, 3, 4, 2, 3, 4, 5, 6)
  )

ui <- shinyUI(fluidPage(
  useShinydashboard(),
  tabPanel(
    "Plot",
    sidebarLayout(
      sidebarPanel(
        radioButtons(
          "Choose",
          "Choose One",
          c("Year" = "p", "Numbers" = "l")
        ),
        uiOutput('checkbox'),
        #width = 2,
        position = "bottom"),
      mainPanel(uiOutput("graph"),
                uiOutput("graph_1"))
      
    )
  )
))

server <- function(input, output, session) {
  
  z_1 <- reactiveValues(years = NULL)
  z_2 <- reactiveValues(numbers = NULL)
  
  observeEvent(input$X, {
    z_1$years <- input$X
  })
  
  observeEvent(input$X_1, {
    z_2$numbers <- input$X_1
  })
  
  output$checkbox <- renderUI({
    if (input$Choose == "p") {
      checkboxGroupInput("X",
                         "year",
                         choices = (unique(d$year)),selected = z_1$years)
      
    } else{
      checkboxGroupInput("X_1",
                         "Numbers",
                         choices = c("1","2","3","4"), ,selected = z_2$numbers)
    }
    
  })
  
  output$graph <- renderUI({
    ntabs = length(input$X)
    if(input$Choose == "p"){
    myTabs = lapply(seq_len(ntabs), function(i) {
      
      fluidRow(plotOutput(paste0("plot", i)))
    })
    }else return(NULL)
  })
  
  
  output$graph_1 <- renderUI({
    ntabs = length(input$X_1)
    if(input$Choose == "l"){
    myTabs = lapply(seq_len(ntabs), function(i) {
      
      fluidRow(plotOutput(paste0("plot_1", i)))
    })
    }else return(NULL)
  })
  
  
  observe (lapply(length(input$X), function(i) {
    output[[paste0("plot", i)]] <- renderPlot({
      if (length(input$X) > 0) {
        d %>%
          ggplot(aes(Product_Name, Cost)) +
          geom_col(aes(fill = Product_desc),
                   position = position_dodge(preserve = "single")) +
          facet_wrap( ~ input$X[i],
                      scales = "free_x",
                      strip.position = "bottom") +
          theme(strip.placement = "outside") +
          theme_bw()
      }
    })
    
  }))
  
  
  observe (lapply(length(input$X_1), function(i) {
    output[[paste0("plot_1", i)]] <- renderPlot({
      if (length(input$X_1) > 0) {
        d %>%
          ggplot(aes(Product_Name, Cost)) +
          theme(strip.placement = "outside") +
          theme_bw()
      }
    })
    
  }))
  
}

shinyApp(ui, server)

What I am trying to do now is I "Wanted to download these plots" which are getting dynamically generated based on the user check box input. If the user had generated 1 graph I wanted to download it. If the user had generated 3 graphs then i want to download all the generated graphs in one single jpeg file.

I tried using downloadHandler, but unfortunately i was very very unsuccessful in it.

The issue which I am facing in this case is as the graphs are dynamic in Nature I am not able to store or write a code in the downloadHandler. The dynamic Nature of the Graph is making it difficult.

Can someone please suggest me how to overcome this.


回答1:


Shiny Modules [*] would be a neat possibility here.

Note. I did not fully understand what you tried with your dynamic checkboxGroup, so I replaced it by a static one. Also I was not quite clear what you want to plot in particular. This is however anyways not crucial to the problem at hand, which can be described as follows

Download a dynamic amount of figures in one file.

So here we go, explanation below.

Setup

library(shiny)
library(dplyr)
library(gridExtra)

d <- data.frame(
   year         = c(1995, 1995, 1995, 1996, 1996, 1996, 1997, 1997, 1997),
   Product_Name = c("Table", "Chair", "Bed", "Table", "Chair", "Bed", "Table",
                    "Chair", "Bed"),
   Product_desc = rep(LETTERS[24:26], each = 3),
   Cost         = c(1, 2, 3, 4, 2, 3, 4, 5, 6)
)

Shiny Modules

plot_ui <- function(id) {
   ns <- NS(id)
   plotOutput(ns("graph"))
}

plot_server <- function(input, output, session, my_data, graph_type) {
   
   get_graph <- reactive({
      base_plot <- ggplot(my_data,
                          aes(Product_Name, Cost)) +
         theme(strip.placement = "outside") +
         theme_bw()
      if (graph_type() == "b") {
         res <- base_plot +
            geom_col(aes(fill = Product_desc),
                     position = position_dodge(preserve = "single")) +
            facet_wrap(~year)
      } else if (graph_type() == "p") {
         res <- base_plot +
            geom_point()
      }
      res
   })
   
   output$graph <- renderPlot({
      get_graph()
   })
   
   list(graph = get_graph)
}

Main App

ui <- fluidPage(
   titlePanel("Modules to the Rescue!"),
   sidebarLayout(
      sidebarPanel(
         radioButtons(
            "type",
            "Graph Type",
            c(Bars = "b", Points = "p")
         ),
         checkboxGroupInput("selector",
                            "Year",
                            choices = unique(d$year)),
         downloadButton("download", "Download Graphs")
         ),
      mainPanel(div(id = "container", div("test content")))
   )
)

server <- function(input, output, session) {

   ## store active plot handlers
   all_plots <- reactiveVal()
   
   ## counter to ensure unique ids for the module uis
   cnt <- reactiveVal(0)
   
   ## when we change selector draw plots anew
   observe({
      ## remove all existing plots
      removeUI("#container *", immediate = TRUE, multiple = TRUE)
      ## for each selection create a new plot
      ## SIDE EFFECT: create the UI
      handlers <- lapply(input$selector, function(x) {
         cnt(isolate(cnt()) + 1)
         my_dat <- d %>%
            dplyr::filter(year == x)
         new_id <- paste("plot", isolate(cnt()))
         insertUI("#container", ui = plot_ui(new_id))
         callModule(plot_server, new_id, 
                    my_data = my_dat, 
                    graph_type = reactive(input$type))
      })
      all_plots(handlers)
   })
   
   output$download <- downloadHandler(
      filename = function() {
         paste0("plots-", Sys.Date(), ".png")
      }, content = function(file) {
         my_plots <- all_plots()
         ggsave(file,
                plot = marrangeGrob(lapply(my_plots, function(handle) handle$graph()),
                                    ncol = 1, nrow = length(my_plots)))
      }
   )
}

shinyApp(ui, server)

Explanation

(The linked document describes in depth what modules are doing so I focus on I used them, rather on how they work in general.)

  1. We create a module whihc does the plotting for us.
  2. The module creates a reactive which produces the plot.
  3. This reactive is used twice: once in the renderPlot function to render the plot, and once as a return parameter of the module.
  4. In the main app, we keep track about all created modules (all_plots), through which we can communicate with the model and in particular to retrieve the plot.
  5. To draw the plots, we listen to the checkboxGroup and whenever there is a change we dynamically remove all plots, and add them afresh and update all_plots through which we can in the last step retrieve the plots for the downloadHandler.
  6. In the downloadHandler we loop through all plots and use gridExtra::marrange to put all of the ggplots into one file via ggsave.

[*] Note that I still use the old callModule syntax as I have noi yet upgraded shiny.




回答2:


I had to adjust your data as product_desc was not clearly available for each year. I defined it as Product_desc = c("X", "Y", "Z", "X", "Y", "Z", "X", "Y", "Z") Then a reactive dataframe was defined. Next you need to create an object to save. Lastly, you need to place download buttons. Download handler will let you download. You can enhance it further by changing how facets are displayed.

The following code generates the required output:

ui <- shinyUI(fluidPage(
  useShinydashboard(),
  tabPanel(
    "Plot",
    sidebarLayout(
      sidebarPanel(
        uiOutput('checkbox'),
        #width = 2,
        position = "bottom"),
      mainPanel(#uiOutput("graph"),
                plotOutput("mygraph"),
                #DT::dataTableOutput("testtable"),
                uiOutput("saveplotsbtn")
                )

    )
  )
))

server <- function(input, output, session) {
  session_store <- reactiveValues() 
  output$checkbox <- renderUI({
    checkboxGroupInput("year", "year", choices = (unique(d$year)))
  })

  output$graph <- renderUI({
    # create tabPanel with datatable in it
    req(input$year)
    tabPanel("Plots",
             fluidRow(lapply(as.list(paste0("plot", seq_along(input$year))), plotOutput)))

  })

  observe(lapply(length(input$year), function(i) {
    #because expressions are evaluated at app init
    #print("I am in Render")
    output[[paste0("plot", i)]] <- renderPlot({
      #print ("bbb")
      if (length(input$year) > 0) {
        d %>%
          ggplot(aes(Product_Name, Cost)) +
          geom_col(aes(fill = Product_desc),
                   position = position_dodge(preserve = "single")) +
          facet_wrap( ~ input$year[i],
                      scales = "free_x",
                      strip.position = "bottom") +
          theme(strip.placement = "outside") +
          theme_bw()
      }
    })

  }))

  output$saveplotsbtn <-  renderUI({
    tagList(
      div(style="display: block; height: 20px; width: 5px;",HTML("<br>")),
      div(style="display: inline; padding: 50px; color: #ad1d28; font-size: 28px ; width: 190px;",HTML("Save Graph as <br>")),
      div(style="display: block; padding: 5px 350px 15px 50px ;",
          downloadBttn("savePDF",
                       HTML(" PDF"),
                       style = "fill",
                       color = "danger",
                       size = "lg",
                       block = TRUE,
                       no_outline = TRUE
          ) ),
      div(style="display: block; width: 5px;",HTML("<br>")),
      div(style="display: block; padding: 5px 350px 15px 50px;",
          downloadBttn("savePNG",
                       label= " PNG",
                       style = "fill",
                       color = "warning",
                       size = "lg",
                       block = TRUE,
                       no_outline = TRUE
          ) )
    )
  })

  mydf <- eventReactive(input$year ,{
    req(input$year)
    data <- d[d$year %in% input$year,]
    data
  })

  output$testtable <- DT::renderDataTable(
    mydf(),
    class = "display nowrap compact", 
    options = list(  # options
      scrollX = TRUE # allow user to scroll wide tables horizontally
    )
  )

  output$mygraph <- renderPlot({
    if(is.null(mydf())){
      myplot <- NULL
    }
    else{
      myplot <- ggplot(data=mydf(), aes(Product_Name, Cost, fill = Product_desc)) +
        geom_bar(#aes(fill = factor(Product_desc)),
                 stat = "identity" , # position = "dodge",
                 position = position_dodge(preserve = "single")) +
        facet_wrap( ~ year,
                    scales = "free_x",
                    strip.position = "bottom") +
        theme(strip.placement = "outside") +
        theme_bw()
    }
    session_store$plt <- myplot
    session_store$plt
  })

  output$savePNG <- downloadHandler(
    filename = function(){
      paste0('myplot', Sys.Date(), '.png', sep='')
    },

    content = function(file) {
      ggsave(file, plot = session_store$plt, width = 6, height = 5, dpi = 100, units = "in",
             device="png", path=input$file$datapath)
    }
  )

  output$savePDF <- downloadHandler(
    filename = function(){
      paste0('myplot', Sys.Date(), '.pdf', sep='')
    },

    content = function(file) {
      ggsave(file, plot = session_store$plt, width = 6, height = 5, dpi = 100, units = "in",
             device="pdf", path=input$file$datapath)
    }
  )

}

shinyApp(ui, server)

You get the following output:



来源:https://stackoverflow.com/questions/62378430/how-to-download-graphs-which-are-dynamic-in-r-shiny

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!