12 Customized reports

While the best part of Shiny apps is their interactivity, sometimes your users need to download data, images, or a static report. This section will show you how.

Figure 12.1: Reports Demo App. You can also access this app with shinyintro::app("reports_demo")or view it in a separate tab with the showcase interface.

12.1 Download Data

First, we need to add the appropriate UI to our questionnaire app. Create a new tab called "report_tab" with two downloadButton()s, one for the pets data and one for the food data.

report_tab <- tabItem(
  tabName = "report_tab",
  box(
    id = "download_box",
    title = "Downloads",
    solidHeader = TRUE,
    downloadButton("pet_data_dl", "Pets Data"),
    downloadButton("food_data_dl", "Food Data")
  )
)

Remember to add this to tabItems() in dashboardBody() and also add a corresponding menuItem() in sidebarMenu(). Run the app to make sure the UI looks like you expect before you proceed.

Now we need to add code to server() to handle the downloads. downloadButton() is a special type of output that is handled by downloadHandler(). This function takes two arguments, a function to create the filename and a function to create the content.

### pet_data_dl ----
output$pet_data_dl <- downloadHandler(
  filename = function() {
    debug_msg("pet_data_dl")
    paste0("pet-data_", Sys.Date(), ".csv")
  },
  content = function(file) {
    gs4_write_csv(v$pet_summary_data, file)
  }
)

Instead of write.csv() or readr::write_csv(), here we're using gs4_write_csv() (defined in scripts/gs4.R) because googlesheets can return list columns that cause errors when saving to CSV without some preprocessing.

12.2 Download Images

Now that we need to use the summary plot in more than one place, it doesn't make sense to build it twice. Move all of the code from the renderPlot() for output$pet_summary into a reactive() called pet_summary_plot. Then we can build the plot whenever the inputs change and refer to it anywhere as pet_summary_plot().

### pet_summary_plot ----
pet_summary_plot <- reactive({ debug_msg("pet_summary_plot")
  # code from output$pet_summary ...
})

### pet_summary ----
output$pet_summary <- renderPlot({ debug_msg("pet_summary")
    pet_summary_plot()
})

The downloadHandler() works the same as for downloading a CSV file. You can use ggsave() to write the plot.

# pet_plot_dl ----
output$pet_plot_dl <- downloadHandler(
  filename = function() {
    paste0("pet-plot_", Sys.Date(), ".png")
  },
  content = function(file) {
    ggsave(file,
           pet_summary_plot(),
           width = 7,
           height = 5)
  }
)

Add numeric inputs to the UI to let the user specify the downloaded plot width and height.

# add to report_tab ui
numericInput("plot_width", "Plot Width (inches)", 7, min = 1, max = 10)
numericInput("plot_height", "Plot Height (inches)", 5, min = 1, max = 10)

### pet_plot_dl ----
output$pet_plot_dl <- downloadHandler(
  filename = function() {
    debug_msg("pet_plot_dl")
    paste0("pet-plot_", Sys.Date(), ".png")
  },
  content = function(file) {
    ggsave(
      file,
      pet_summary_plot(),
      width = input$plot_width,
      height = input$plot_height
    )
  }
)

12.3 R Markdown

You can render an R Markdown report for users to download.

First, you need to save an R Markdown file (save the one below as reports/report.Rmd). You need to set up params for any info that you want to pass from the Shiny app in th YAML header. Here, we will dynamically update the title, data, and plot. You can set the values to NULL or another default value.

---
title: "`r params$title`"
date: "`r format(Sys.time(), '%d %B, %Y')`" 
output: html_document
params:
  title: Report
  data: NULL
  plot: NULL
---

Then you can refer to these params in the R Markdown file as params$name. If you knit the report, these will take on the default values.




```r
n_responses <- nrow(params$data)

n_sessions <- params$data$session_id %>%
  unique() %>%
  length()
```

We asked people to rate how much they like pets. We have obtained `r n_responses` responses from `r n_sessions` unique sessions.

## Summary Plot


```r
params$plot
```

The content function uses rmarkdown::render() to create the output file from Rmd. We are using html_document as the output type, but you can also render as a PDF or Word document if you have pandoc or latex installed. Include the title, data and plot as a named list to the params argument.

### pet_report_dl ----
output$pet_report_dl <- downloadHandler(
  filename = function() {
    debug_msg("pet_report_dl")
    paste0("pet-report_", Sys.Date(), ".html")
  },
  content = function(file) {
    rmarkdown::render("reports/report.Rmd",
                      output_file = file, 
                      params = list(
                        title = "Pet Report", 
                        data = v$pet_summary_data,
                        plot = pet_summary_plot()
                      ),
                      envir = new.env(),
                      intermediates_dir = tempdir())
  }
)

Setting the envir argument to new.env() makes sure that settings in your Shiny app, such as a default ggplot theme, can't affect the rendered file, but also makes it so that the R Markdown code will not have access to any objects from your app, such as input or v$summary_data, unless you pass them as parameters.

On your own computer, you don't need to set intermediates_dir = tempdir(), but you will need to do this if you want to deploy your app on a shiny server. When rmarkdown renders an Rmd file, it creates several intermediate files in the working directory and then deletes them. you have permission to write to the app's working directory on your own computer, but might not on a shiny server. tempdir() is almost always a safe place to write temporary files to.

12.5 Exercises

Food Data

Create a button that downloads the food data.

# add to report_tab ui ----
downloadButton("food_data_dl", "Food Data")

### food_data_dl ----
output$food_data_dl <- downloadHandler(
  filename = function() {
    debug_msg("food_data_dl")
    paste0("food-data_", Sys.Date(), ".csv")
  },
  content = function(file) {
    gs4_write_csv(v$food_summary_data, file)
  }
)

Food Image

Create a button that downloads the food summary plot

# add to report_tab ui ----
downloadButton("food_plot_dl", "Food Plot")

### food_summary_plot ----
food_summary_plot <- reactive({ debug_msg("food_summary_plot")
  # code from output$food_summary ...
})

### food_summary ----
output$food_summary <- renderPlot({ debug_msg("food_summary")
  food_summary_plot()
})

### food_plot_dl ----
output$food_plot_dl <- downloadHandler(
  filename = function() {
    debug_msg("food_plot_dl")
    paste0("food-plot_", Sys.Date(), ".png")
  },
  content = function(file) {
    ggsave(
      file,
      food_summary_plot(),
      width = input$plot_width,
      height = input$plot_height
    )
  }
)

Food Report

Create a downloadable report for the food data.

# add to report_tab ui ----
downloadButton("food_report_dl", "Food Report")

### food_report_dl ----
output$food_report_dl <- downloadHandler(
  filename = function() {
    debug_msg("food_report_dl")
    paste0("food-report_", Sys.Date(), ".html")
  },
  content = function(file) {
    rmarkdown::render("reports/report.Rmd",
                      output_file = file, 
                      params = list(
                        title = "Food Report", 
                        data = v$food_summary_data,
                        plot = food_summary_plot()
                      ),
                      envir = new.env(),
                      intermediates_dir = tempdir())
  }
)

12.6 Your app

What might users want to download from your app? Include data, image, or report download if that is applicable to your app.