Printing From Flex Dashboard

by Harrison Schramm and Aaron Berg

Shiny applications of all stripes (including flexdashboard with runtime Shiny) are revolutionary in that they put the power of R directly in the end user’s hands without needing to interact directly with the language. A common way end-users wish to interact with their data is via a dashboard that they can manipulate on the fly. Flexdashboard streamlines the process of turning an R-based analysis into a dashboard, so R users can create good-looking output for their analyses - and deploy this output to the web - with very little additional effort.

There are various ways to extract information from flexdashboard, but no good way to programatically return a printable version of the dashboard itself. This is partially by design - responsive HTML dashboards aren’t meant to be printed. There are well-documented workflows about how to share information between a web-centric output and a complementary report that renders to a printable format.

That said, there are some reasons to want a printable output that looks similar to a related HTML report. Snapshots are valuable for documentation/training materials, for embedding in emails or other reports, or for the all-too-common case of meetings with executives who like dashboards but want to consume paper-based reports. Plus, flexdashboards are an incredibly quick way to create visually appealing (read: pretty) summary output of a combination of outputs on a single page.

Both Harrison and Aaron1 have struggled with trying to print flexdashboards in their respective practices. In a recent instance, Harrison was working with a client whose end users would be carrying snapshots of the dashboard into an industrial setting where mobile devices were not desired. It was important to the client to maintain a similar look between the on-screen and paper representations of the dashboard. It was also important to make generating these snapshots an integrated part of the application; it was not sufficient to suggest that the client manually take a screenshot of their browser window. This turned out to be a blessing, because (as you will see below) it allows the creation of a web-friendly ‘tabbed’ report with a companion ‘flat’ printed rendering.

Specific Task

Our goal was to create a “printable” flexdashboard with runtime Shiny. Flexdashboard produces a beautiful HTML dashboard with very little effort. Like all R Markdown documents, it also allows Shiny objects to be embedded inside of it, so that when rendered locally or on a server, it produces a reactive web application with minimal developer effort. Like many HTML files, however, it does not print well.

The flexdashboard in question also had tabsets, which make perfect sense on a computer or mobile device, but don’t translate well to printed media. When printed, tabsets communicate that there is pertinent information hidden from the viewer; better to remove the tabset in the printed version. If the information is pertinent, it should be displayed in the printed document; if it is not, it should be removed.

PhantomJS and Headless Chrome

The approach we settled on as good enough and easy enough was to use a headless browser to take a screenshot of a non-reactive HTML document. It’s not a perfect solution: a PDF would be better than a png file, headless browsers add a code dependency, and there is some administrative overhead to create the document to print. It is, however, quick to implement, easy to understand, and robust.

Example2

The mechanics of building a screen-capture-style report from flexdashboard are neither difficult nor obvious. In general, it requires the following steps:

  1. Add a downloadButton to a flexdashboard with runtime Shiny
  2. When the download button is invoked, temporarily save non-reactive copies of any objects of interest
  3. Knit a static HTML version of the flexdashboard, using the objects saved in the prior step
  4. Use webshot::webshot or decapitated::chrome_shot to capture a .png image of the static dashboard3
  5. Deliver this version to the client using the downloadHandler function

The solution requires two separate R documents. The process is as follows:

Dynamic Flexdashboard document


---
title: "Snapshot Example"
runtime: shiny
output:
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: scroll
---

This is standard YAML header for a flexdashboard, including the command runtime: shiny to create a Shiny application from an R Markdown document. Let’s examine the next chunks:

gears = mtcars$gear %>% as.factor() %>% levels()
selectizeInput("grs", "GEARS",
               gears, selected = gears[1])
renderUI({
downloadButton("downloadFile", "Download")
})

In this chunk, we have built a selectizeInput object to choose the number of gears to select, as well as a downloadButton named ‘downloadFile’. Notice that we have wrapped the button in a renderUI() context. This is standard shiny / flexdashboard code.

mpgp = reactive({
  mtcars %>% filter(gear %in% input$grs) %>% ggplot(aes(x = hp, y = mpg)) + geom_point() + geom_smooth()
  
})
renderPlot({
  mpgp()
})

For veteran shiny programmers, this block may seem redundant. We’re creating a plot called mpgp in a reactive context and then rendering it. In a ‘standard’ shiny implementation, we would simply have wrapped the plot code in the renderPlot() context. However, as you will see below, we’re going to need to reuse the object mpgp.

output$downloadFile <- downloadHandler(filename = function() { 
return(paste('Cars', '.png', sep=''))
},
    content = function(file){
    to_save <- list(
    mpgg = mpgg(),
    mpgp = mpgp()
    )
      saveRDS(to_save, "config_data.RDS")
      rmarkdown::render("ShadowCars.Rmd")
      webshot::webshot("ShadowCars.html", file = file)
                        })

This chunk contains the secret sauce. When the downloadButton is invoked, the program saves all elements to appear in the downloaded report in a file called ‘config_data.RDS’. These elements are then used as arguments to render a shadow document, ‘ShadowCars.Rmd’. Finally, webshot::webshot converts the hidden document to a .png and delivers it to the client side.

The full dashboard looks like this:

Shadow Document

The key elements of the shadow document code are presented below:

---
title: "Snapshot Example"
output:
  flexdashboard::flex_dashboard:
    orientation: rows
---

Note that our YAML for the shadow document is similar to the dashboard, except it does not include runtime: shiny

The code blocks that render the objects created in the dynamic document are straightforward to the point of triviality; they simply reference the saved objects (which are retrieved using readRDS) and render them using whatever we choose, in this case kable() for the table and the native print() for the plot.

data <- readRDS("config_data.RDS")
data$mpgg %>% kable() 
data$mpgp %>% print() 

And of course, while mpgp was reactive in the original dashboard, it is static in the shadow doc.

The report delivered to the client is as follows:

Conclusion

In this short post, we have shown how to create a downloadable custom report by using existing tools to create a static flexdashboard. From the .png environment, the rendered image can be converted to other formats as required.


  1. Harrison Schramm is an Operations Research Analyst at CANA Advisors. Aaron Berg is a Customer Success Representative at RStudio.

  2. To recreate this minimal example for yourself, please download the files from this gist.

  3. The webshot package requires phantomJS to be installed as an external dependency, but will work on all systems. decapitated will only work on systems with versions of Chrome that can operate in headless mode.

Share Comments · · · ·