【问题标题】:How to download graphs which are dynamic in R Shiny?如何下载 R Shiny 中的动态图形?
【发布时间】:2020-10-04 06:53:21
【问题描述】:

在选项卡中的 Shiny Dashboard 中,我根据复选框输入的选择,在另一个下方绘制图表。当相应地选中复选框时,图表将在另一个下方显示。 请在下面找到我使用的代码。

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)

我现在要做的是“想要下载这些图”,这些图是根据用户复选框输入动态生成的。如果用户生成了 1 个图表,我想下载它。如果用户生成了 3 个图形,那么我想将所有生成的图形下载到一个 jpeg 文件中。

我尝试使用downloadHandler,但不幸的是我非常非常不成功。

在这种情况下我面临的问题是,由于图表在自然界中是动态的,我无法在 downloadHandler 中存储或编写代码。图表的动态特性使其变得困难。

有人可以建议我如何克服这个问题。

【问题讨论】:

  • @Waldi 感谢您的评论。是的,我看过了。在这里,我面临的挑战是关于图表的反应/动态性质。由于它涉及观察功能,我不确定如何保存其输出,然后使用下载处理程序下载它们。

标签: r shiny download shinydashboard


【解决方案1】:

Shiny Modules [*] 在这里很可能。

注意。我没有完全理解您对动态checkboxGroup 的尝试,因此我将其替换为静态的。另外,我不太清楚您要特别绘制的什么。然而,无论如何这对于手头的问题并不重要,可以描述如下

在一个文件中下载动态数量的图形。

所以我们开始吧,下面的解释。

设置

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)
)

闪亮的模块

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)
}

主应用

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)

说明

(链接的文档深入描述了模块在做什么,因此我将重点放在我使用它们,而不是它们一般如何工作。)

  1. 我们创建了一个模块来为我们进行绘图。
  2. 该模块会创建一个反应式来生成绘图。
  3. 此反应式使用了两次:一次在 renderPlot 函数中用于渲染绘图,一次作为模块的返回参数。
  4. 在主应用程序中,我们跟踪所有创建的模块 (all_plots),通过这些模块我们可以与模型通信,尤其是检索绘图。
  5. 为了绘制绘图,我们监听checkboxGroup,每当有变化时,我们会动态删除所有绘图,重新添加并更新all_plots,通过它我们可以在最后一步检索@的绘图987654330@.
  6. downloadHandler 中,我们循环遍历所有绘图并使用gridExtra::marrange 通过ggsave 将所有ggplots 放入一个文件中。

[*] 请注意,我仍然使用旧的callModule 语法,因为我还没有升级闪亮。

【讨论】:

    【解决方案2】:

    我不得不调整您的数据,因为每年的 product_desc 都不清楚。我将其定义为Product_desc = c("X", "Y", "Z", "X", "Y", "Z", "X", "Y", "Z") 然后定义了一个反应式数据框。接下来,您需要创建一个要保存的对象。最后,您需要放置下载按钮。下载处理程序将让您下载。您可以通过更改构面的显示方式来进一步增强它。

    以下代码生成所需的输出:

    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)
    

    你会得到以下输出:

    【讨论】:

    • 非常感谢您的回复。我有一个疑问,而不是将图形数据再次保存在变量 mydf() 中。有没有办法可以直接做到这一点?
    • 我不确定。请注意,mydf() 是基于用户选择的年份的临时反应数据框。如果不是这样,您必须使情节具有反应性 - 就像您的 graph 一样。在这种情况下,让下载按钮工作可能会更难。
    • 你对mydf()有什么疑问?性能,不必要的代码?我没有看到任何问题,...
    猜你喜欢
    • 1970-01-01
    • 2018-11-14
    • 1970-01-01
    • 2019-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-23
    • 2014-04-19
    相关资源
    最近更新 更多