R Shiny应用PDF导出及appshot等待数据加载问题咨询
Hey there! Let's break down how to tackle your R Shiny PDF export requirements and fix that appshot loading delay issue. I'll walk you through each step with practical code examples.
First, make sure you have the right packages installed—we'll use webshot2 (the updated, more reliable version of webshot), shiny, htmltools, plus whatever you're using for tables (like DT) and charts (like ggplot2):
install.packages(c("shiny", "webshot2", "htmltools", "DT", "ggplot2"))
Let's add export buttons to your UI and handle each case in the server.
2.1 Export Full Page to PDF (Chrome-like Scaling)
This mimics Chrome's "Save as PDF" with zoom—we'll use webshot() to capture the entire app page, with a delay to cover your 35-40s data load.
In your UI, add the button:
actionButton("export_full", "Export Full Page as PDF")
In the server, add the trigger:
observeEvent(input$export_full, { # Get the app's URL (works for local runs; adjust if deployed to a server) app_url <- getShinyOption("appURL") # Capture the page with sufficient delay, high resolution webshot( app_url, "full_page.pdf", delay = 45, # Wait 45s to ensure data finishes loading vwidth = 1920, # Simulate a wide screen vheight = 1080, # Match standard screen height scale = 2 # Double resolution for crisp, zoomed-in PDF ) showNotification("Full page PDF exported successfully!", type = "message") })
2.2 Export Only mainPanel (With Scaling)
First, give your mainPanel an ID so we can target its content. Then we'll save its rendered HTML to a temporary file, then convert it to PDF.
Update your UI's mainPanel:
mainPanel( id = "main_content", # Your 2 tables and 5 charts go here (inside a tabsetPanel if needed) )
Add the export button in UI:
actionButton("export_main", "Export Main Panel as PDF")
Server logic:
observeEvent(input$export_main, { # Grab the mainPanel's fully rendered HTML content main_html <- htmltools::tagList(input$main_content) # Save to a temporary HTML file temp_html <- tempfile(fileext = ".html") htmltools::save_html(main_html, temp_html) # Convert to PDF with scaling webshot( temp_html, "main_panel.pdf", delay = 5, # Short delay to ensure dynamic content is fully rendered scale = 2 # High resolution for clear output ) showNotification("Main panel PDF exported successfully!", type = "message") })
2.3 Export All mainPanel Tabs to a Single PDF
First, give your tabsetPanel an ID, and each individual tabPanel an ID too. We'll switch to each tab, capture its content, combine them with page breaks, then export.
Update your UI's tabset:
tabsetPanel( id = "tabset_main", tabPanel("Tab 1", id = "tab_tab1", # Your tab content), tabPanel("Tab 2", id = "tab_tab2", # Your tab content), tabPanel("Tab 3", id = "tab_tab3", # Your tab content), tabPanel("Tab 4", id = "tab_tab4", # Your tab content), tabPanel("Tab 5", id = "tab_tab5", # Your tab content) )
Add the export button:
actionButton("export_tabs", "Export All Tabs as Single PDF")
Server logic:
observeEvent(input$export_tabs, { # List your tab names (match the tabPanel labels exactly) tab_names <- c("Tab 1", "Tab 2", "Tab 3", "Tab 4", "Tab 5") # Capture content for each tab tab_contents <- lapply(tab_names, function(tab_name) { # Switch to the tab to ensure its content is rendered updateTabsetPanel(session, "tabset_main", selected = tab_name) Sys.sleep(2) # Wait 2s for dynamic charts/tables to load fully # Grab the tab's content using its ID tab_id <- paste0("tab_", gsub(" ", "_", tab_name)) htmltools::tagList(input[[tab_id]]) }) # Combine tabs with page breaks between them combined_html <- htmltools::tagList( lapply(seq_along(tab_contents), function(i) { if (i > 1) { # Add a page break before every tab except the first htmltools::tags$div(style = "page-break-after: always;") } tab_contents[[i]] }) ) # Save combined content to a temporary HTML file temp_html <- tempfile(fileext = ".html") htmltools::save_html(combined_html, temp_html) # Export to PDF webshot( temp_html, "all_tabs.pdf", delay = 3, scale = 2 ) showNotification("All tabs PDF exported successfully!", type = "message") })
Instead of relying on a fixed delay, use the wait_for parameter to wait until a specific element (like your first table or chart) loads. This is far more reliable than guessing timings.
For example, if your first table has the ID table1, use this code:
# Trigger screenshot (could be tied to a button or run manually) appshot( session, # Pass the Shiny session object "app_screenshot.png", wait_for = "#table1", # Wait until the table with ID 'table1' exists timeout = 60, # Timeout after 60s to avoid hanging indefinitely scale = 2 # High resolution screenshot )
If you want to tie this to a button, add the button in UI:
actionButton("take_screenshot", "Take App Screenshot")
Then in server:
observeEvent(input$take_screenshot, { appshot( session, "app_screenshot.png", wait_for = "#table1", timeout = 60, scale = 2 ) showNotification("Screenshot taken successfully!", type = "message") })
Here's a complete working example that ties all these pieces together, with simulated 35-40s data loading delay to mimic your database setup:
library(shiny) library(webshot2) library(htmltools) library(DT) library(ggplot2) ui <- fluidPage( titlePanel("PDF Export Demo"), sidebarPanel( sliderInput("n", "Sample Size", min = 100, max = 1000, value = 500) ), mainPanel( id = "main_content", tabsetPanel( id = "tabset_main", tabPanel("Table 1", id = "tab_table1", DTOutput("table1")), tabPanel("Table 2", id = "tab_table2", DTOutput("table2")), tabPanel("Plot 1", id = "tab_plot1", plotOutput("plot1")), tabPanel("Plot 2", id = "tab_plot2", plotOutput("plot2")), tabPanel("Plot 3", id = "tab_plot3", plotOutput("plot3")) # Add 2 more tabs to reach your 5 total if needed ), br(), actionButton("export_full", "Export Full Page as PDF"), actionButton("export_main", "Export Main Panel as PDF"), actionButton("export_tabs", "Export All Tabs as Single PDF"), actionButton("take_screenshot", "Take App Screenshot") ) ) server <- function(input, output, session) { # Simulate 35-40s data load delay data <- reactive({ Sys.sleep(sample(35:40, 1)) data.frame( x = rnorm(input$n), y = rnorm(input$n), group = sample(letters[1:5], input$n, replace = TRUE) ) }) # Render tables and plots output$table1 <- renderDT(data()) output$table2 <- renderDT(data()[, c("x", "group")]) output$plot1 <- renderPlot(ggplot(data(), aes(x, y)) + geom_point()) output$plot2 <- renderPlot(ggplot(data(), aes(x)) + geom_histogram()) output$plot3 <- renderPlot(ggplot(data(), aes(group, x)) + geom_boxplot()) # Full page export observeEvent(input$export_full, { app_url <- getShinyOption("appURL") webshot(app_url, "full_page.pdf", delay = 45, vwidth = 1920, vheight = 1080, scale = 2) showNotification("Full page PDF ready!", type = "message") }) # Main panel export observeEvent(input$export_main, { main_html <- tagList(input$main_content) temp_html <- tempfile(fileext = ".html") save_html(main_html, temp_html) webshot(temp_html, "main_panel.pdf", delay = 5, scale = 2) showNotification("Main panel PDF ready!", type = "message") }) # All tabs export observeEvent(input$export_tabs, { tab_names <- c("Table 1", "Table 2", "Plot 1", "Plot 2", "Plot 3") tab_contents <- lapply(tab_names, function(tab_name) { updateTabsetPanel(session, "tabset_main", selected = tab_name) Sys.sleep(2) tab_id <- paste0("tab_", gsub(" ", "_", tab_name)) tagList(input[[tab_id]]) }) combined_html <- tagList( lapply(seq_along(tab_contents), function(i) { if (i > 1) tags$div(style = "page-break-after: always;") tab_contents[[i]] }) ) temp_html <- tempfile(fileext = ".html") save_html(combined_html, temp_html) webshot(temp_html, "all_tabs.pdf", delay = 3, scale = 2) showNotification("All tabs PDF ready!", type = "message") }) # Screenshot with wait for data load observeEvent(input$take_screenshot, { appshot(session, "app_screenshot.png", wait_for = "#table1", timeout = 60, scale = 2) showNotification("Screenshot ready!", type = "message") }) } shinyApp(ui, server)
内容的提问来源于stack exchange,提问作者sumanta das

