Generating markdown reports from shiny?

This tread is a continuation from the following: Generating markdown report in shiny?


I would like to bump this thread up again. The OP asked for a way to render the markdown document in the app itself, which I think is a very reasonable thing to do -- the user would be able to check that document addresses /matches their needs/inputs. I don't think any of the answers address that as the conversation steered towards producing hundreds of report files.

To the effect of showing the result in the app: My understanding is that includeHTML and includeMarkdown (http://shiny.rstudio.com/reference/shiny/1.1.0/include.html) are intended to act on static content of the app only (at least based on my attempt to pass output$whatever to them). A brute force solution might be to base::scan() the intermediate .html/.md file and pass it properly to htmlOutput(), but I hope somewhat more elegant solutions exist.

Hi @StasK ,

To make sure I understand the objective, the Rmd file will serve as the UI? If so, then the following example might help you get started.


tldr

ui.R

htmlOutput("renderedReport")

server.R

output$renderedReport <- renderUI({           
	includeMarkdown(knitr::knit('my_file.Rmd'))           
})

my_markdown_file.Rmd

## Hello, World!
some text here
..


Overview

I pulled the librarians data set from 538's data repo as an example (1). In this app, I wanted to view the areas with the most employed librarians of chosen US state.

My initial thoughts were to render the report using a shiny input(s) and to trigger the rendering of the report once a button was clicked. I did it this way as it would save on valuable computing time if the app were to be hosted on AWS or Digital Ocean.

The rendering of the rmarkdown will take place on the server side (knitr::knit('my_file.Rmd') and sent to the ui via renderUI and includeMarkdown. On the front end, the markdown output will be received using htmlOutput.

Using the shinyjs package, I smoothed the display of the content using hide and show.

ui.R

In this example, I'm using the sidebarLayout and a selectInput to filter the data by state (USA). After selection is made, the user clicks the View Report button.

sidebarPanel(

	# filter by state
	selectInput(inputId = "state",
				label = "Choose a state",
              choices = c("Choose a state", unique(librarians$prim_state)),
              multiple = FALSE),
                
 	# button
	actionButton(inputId = "report",label="View Report")
)

The mainPanel is fairly short. I added a loading... message and wrapped the Rmarkdown output to ease the rendering of the content. Additional styling can be applied to the the report using tags$style or in separate css file.

...
mainPanel(
	# show loading screen
	shinyjs::hidden(
		tags$div(id="loading",
			tags$style("#loading{position:absolute;}"),
			tags$h1("Loading..."))
	),
              
	# report
	tags$div(id="report-wrapper",
		tags$style("#report-wrapper p{font-size:14pt;}"),
		htmlOutput("renderedReport")
	)
)
...

server.R

On the server side, everything is triggered by observeEvent(input$report,{...}).

The first step is to hide the report div and show the loading message (For fun, the shinycustomloader package can be used to make cool loading screens (2)).

Next, the data is filtered based on the input. If nothing is selected, the full dataset is returned.

The markdown file is rendered and sent the UI using the following code.

output$renderedReport <- renderUI({
	includeMarkdown(knitr::knit("report_template.Rmd"))         
})

The final step is to hide the loading message and show the report.

rmarkdown file

I wrote the Rmd file as normal. In the Rmd file, I'm calling the object defined by the filtering step.

Limitations

The beauty of this method is that markdown is fairly straightforward to write and the UI can be modified fairly easy. This could be useful if you are collaborating with non-R folks that are familiar with markdown.

The downsides is that the rendering of the report can be slow. Additional YAML configurations and adjusting the shinyjs hide/show speed might help with the lag. The ggplot outputs were pretty poor. I imagine that manually styling plots via theme(..), setting the dpi, and/or css will help make the objects readable.

I didn't test interactive visualizations. I would imagine the same methods for interactive documents would apply here.

I hope this helps!


I posted the full code on github: https://github.com/davidruvolo51/shinyAppTutorials/tree/master/rmarkdown-app

Sources

  1. 538 Librarian data: https://github.com/fivethirtyeight/data/tree/master/librarians
  2. Shinycustomloader pkg: https://github.com/emitanaka/shinycustomloader
4 Likes

Thanks, David. I am sure I'll be able to figure it out using your templates.

I am trying to implement where Shiny UI inputs change the rmarkdown content displayed. I have list of Definitions, the content is styled based on Rmarkdown. Each definition is in a separate Rmarkdown file, as user selects the value in the drop-down the respective file is rendered.
But, the number of definitions grew and the rmarkdwon files increased, more or less they have same styling. I would prefer to have all the definitions into a single markdown file, and pass the shiny input value to rmarkdown and only render the specific definition.Is this possible to do ?

Hey @gadepallivs

Yes, this is possible. Parameterized reports are your friend. In your Rmarkdown template, you can define as many params as you like. For example:

---
title: "Some Title"
output: html_document
params:
    some_var: "some value"
    data: [some object]
    ..
---

You can pass objects using the params property in the rmarkdown::render() function. For example:

rmarkdown::render("path/to/template.Rmd", params = list(some_var="my new value", data = some_object))

To render your template, you can do this server side by wrapping the render function in renderUI and includeHTML.

output$someid <- renderUI({
    includeHTML(
        rmarkdown::render("path/to/template.Rmd", params = list(some_var="my new value", data = some_object))
    )
})

When your template is sent to the client side, it is wrapped in a div and some css classes are added. You can override these if you like by using:

.shiny-html-output .container-fluid,
.shiny-html-output .main-container {
    padding: 0;
    margin: 0;
}

Earlier in the thread, I linked an example app. I've updated the app to demonstrate how to use this method as well as fixed some issues with the rendering function. Here's the link to example: rmarkdown-app.

You can find more about the parameterized reports in the markdown docs here.

Hope that helps!

3 Likes