Exporting Text and Data table from shiny app to pdf

I built an app for work whereby it generates a list of instructions for patients on certain drugs before an operation. It displays in a datatable format and there are optional texts that can be added on. I've looked around and tried various ways of trying to export the output to pdf so we can print it out but I seem to have hit a wall so far.

library(tidyverse)
library(shiny)
library(shinythemes)
library(xtable)


insulin <- readRDS("insulin.rda")

# User Interface

ui <- fluidPage(
  
  titlePanel("Pre-operative Advice on Insulin - For Patients with Diabetes Undergoing Elective Surgery v0.1"),
  
  p("Please refer to Guideline on Shared Drive or Intranet for full guidance"),
  
  sidebarLayout(
    sidebarPanel(
      p("Patient Name and Date of Birth (Optional)"),
      
      textInput("px_name", label = "Patient Name", placeholder = "Patient Name"),
      textInput("dob", label = "Date of Birth or CHI", placeholder = "Date of Birth or CHI"),
      
      selectInput("DM", "What type of diabetes does patient have?",
                  c("Type One" = "Type 1",
                    "Type Two on Insulin" = "Type 2"),
                  selected = "Type One"),
      
      selectInput("time", "Is patient on morning or afternoon list?",
                  c("Morning List" = "AM",
                    "Afternoon List" = "PM"),
                  selected = "Morning"),
      
      checkboxGroupInput("class", "Which type(s) of insulin is patient on?",
                         c("Long and Intermediate acting",
                           "Pre-Mixed",
                           "Rapid or Short acting"))
      
      ),
    
    mainPanel(
      uiOutput("insulin_sel"),
      
      h3(textOutput(outputId = "px_name")),
      
      br(),
      
      h4(textOutput(outputId = "dob")),
      
      br(),
      
      tableOutput("table"),
      
      
    )
  )
)

server <- function(input, output){
  output$px_name <- renderText({input$px_name})
  
  output$dob <- renderText({input$dob})

  
  output$insulin_sel <- renderUI({
    
    insulin_subset <- insulin %>% filter(DM == input$DM, 
                                         Time == input$time, 
                                         Class %in% input$class)
    
    selectizeInput("name", "Type in name of insulin",
                   choices = list("Type in insulin name" = "", 
                                  "Names" = insulin_subset$Name), 
                                  selected = NULL, 
                                  multiple = TRUE,
                                  options = NULL)
  })
  
   output$table <- renderTable({
    
    insulin_subset <- insulin %>% filter(DM == input$DM, 
                                         Time == input$time, 
                                         Class %in% input$class)
    
    tab <- insulin_subset %>% filter(Name %in% input$name)
    
    xtable(tab)
    
  })
  
}

shinyApp(ui = ui, server = server)

These are the instructions from which the app is based:

> dput(insulin)
structure(list(DM = c("Type 2", "Type 2", "Type 2", "Type 2", 
"Type 2", "Type 2", "Type 2", "Type 2", "Type 2", "Type 2", "Type 2", 
"Type 2", "Type 2", "Type 2", "Type 1", "Type 1", "Type 1", "Type 1", 
"Type 1", "Type 1", "Type 1", "Type 1", "Type 1", "Type 1", "Type 1", 
"Type 1", "Type 1", "Type 1", "Type 2", "Type 2", "Type 2", "Type 2", 
"Type 2", "Type 2", "Type 2", "Type 2", "Type 1", "Type 1", "Type 1", 
"Type 1", "Type 1", "Type 1", "Type 1", "Type 1", "Type 2", "Type 2", 
"Type 2", "Type 2", "Type 2", "Type 2", "Type 2", "Type 2", "Type 2", 
"Type 2", "Type 1", "Type 1", "Type 1", "Type 1", "Type 1", "Type 1", 
"Type 1", "Type 1", "Type 1", "Type 1"), Time = c("AM", "AM", 
"AM", "AM", "AM", "AM", "AM", "PM", "PM", "PM", "PM", "PM", "PM", 
"PM", "AM", "AM", "AM", "AM", "AM", "AM", "AM", "PM", "PM", "PM", 
"PM", "PM", "PM", "PM", "AM", "AM", "AM", "AM", "PM", "PM", "PM", 
"PM", "AM", "AM", "AM", "AM", "PM", "PM", "PM", "PM", "AM", "AM", 
"AM", "AM", "AM", "PM", "PM", "PM", "PM", "PM", "AM", "AM", "AM", 
"AM", "AM", "PM", "PM", "PM", "PM", "PM"), Class = c("Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Long and Intermediate acting", 
"Long and Intermediate acting", "Pre-Mixed", "Pre-Mixed", "Pre-Mixed", 
"Pre-Mixed", "Pre-Mixed", "Pre-Mixed", "Pre-Mixed", "Pre-Mixed", 
"Pre-Mixed", "Pre-Mixed", "Pre-Mixed", "Pre-Mixed", "Pre-Mixed", 
"Pre-Mixed", "Pre-Mixed", "Pre-Mixed", "Rapid or Short acting", 
"Rapid or Short acting", "Rapid or Short acting", "Rapid or Short acting", 
"Rapid or Short acting", "Rapid or Short acting", "Rapid or Short acting", 
"Rapid or Short acting", "Rapid or Short acting", "Rapid or Short acting", 
"Rapid or Short acting", "Rapid or Short acting", "Rapid or Short acting", 
"Rapid or Short acting", "Rapid or Short acting", "Rapid or Short acting", 
"Rapid or Short acting", "Rapid or Short acting", "Rapid or Short acting", 
"Rapid or Short acting"), Name = c("Abasaglar", "Lantus", "Levemir", 
"Toujeo", "Tresiba", "Insulatard", "Humulin I", "Abasaglar", 
"Lantus", "Levemir", "Toujeo", "Tresiba", "Insulatard", "Humulin I", 
"Abasaglar", "Lantus", "Levemir", "Toujeo", "Tresiba", "Insulatard", 
"Humulin I", "Abasaglar", "Lantus", "Levemir", "Toujeo", "Tresiba", 
"Insulatard", "Humulin I", "Humulin M3", "Novomix 30", "Insuman Comb 15/25/50", 
"Humalog Mix 25/50", "Humulin M3", "Novomix 30", "Insuman Comb 15/25/50", 
"Humalog Mix 25/50", "Humulin M3", "Novomix 30", "Insuman Comb 15/25/50", 
"Humalog Mix 25/50", "Humulin M3", "Novomix 30", "Insuman Comb 15/25/50", 
"Humalog Mix 25/50", "Novorapid/Fiasp", "Humalog", "Apidra", 
"Humulin S", "Actrapid", "Novorapid/Fiasp", "Humalog", "Apidra", 
"Humulin S", "Actrapid", "Novorapid/Fiasp", "Humalog", "Apidra", 
"Humulin S", "Actrapid", "Novorapid/Fiasp", "Humalog", "Apidra", 
"Humulin S", "Actrapid"), Plan = c("Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Usual dose at usual time", "Usual dose at usual time", "Usual dose at usual time", 
"Half usual morning dose taken with a sugary drink at 7am", "Half usual morning dose taken with a sugary drink at 7am", 
"Half usual morning dose taken with a sugary drink at 7am", "Half usual morning dose taken with a sugary drink at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a sugary drink at 7am", "Half usual morning dose taken with a sugary drink at 7am", 
"Half usual morning dose taken with a sugary drink at 7am", "Half usual morning dose taken with a sugary drink at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Omit breakfast dose", "Omit breakfast dose", "Omit breakfast dose", 
"Omit breakfast dose", "Omit breakfast dose", "Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Half usual morning dose taken with a light breakfast at 7am", 
"Omit breakfast dose", "Omit breakfast dose", "Omit breakfast dose", 
"Omit breakfast dose", "Omit breakfast dose", "Usual morning dose taken with a light breakfast at 7am, oral fluids until 11am, omit lunchtime dose", 
"Usual morning dose taken with a light breakfast at 7am, oral fluids until 11am, omit lunchtime dose", 
"Usual morning dose taken with a light breakfast at 7am, oral fluids until 11am, omit lunchtime dose", 
"Usual morning dose taken with a light breakfast at 7am, oral fluids until 11am, omit lunchtime dose", 
"Usual morning dose taken with a light breakfast at 7am, oral fluids until 11am, omit lunchtime dose"
)), row.names = c(NA, -64L), class = c("tbl_df", "tbl", "data.frame"
))

I tried using the method outlined here but seems to get the error below:

Listening on http://127.0.0.1:6239
Warning: Error in switch: EXPR must be a length 1 vector
  [No stack trace available]

Ideally I would like to have the option of pasting the Texts (Name and DOB) into the final pdf but at this point, I would be happy with finding out if it is possible to generate a pdf of the datatable.

Thanks.

Hi @lawrencelmli,

Off the top of my head, it seems like at least one approach would be to draft up a parameterized R Markdown file. Then, you can collect whatever input data you want from your shiny app, feed it into the Rmd as parameters, knit to tempdir(), and then allow the user to download the rendered PDF.

Does this seem like it aligns with what you are hoping to do?

Yes. Thanks for the reply. As I am new to this, I hadn't realised this. I think I kinda know what to do now.

Okay, great. Let me know if you need any additional help.

1 Like

This is a good tutorial: https://shiny.rstudio.com/articles/generating-reports.html

1 Like

I wish I had seen that earlier! Many thanks again.

Hi, I have a bit of code within the app that looks like this:

  observeEvent(input$ortho, {
    if(input$ortho == "yes"){
      output$ortho_tab <- renderTable({
        xtable(my_ortho_table())})
      output$ortho_text <- renderText("Additional information for patients undergoing hip and knee replacement or revision,
                                      if taking the following medications")
    }else{
      output$ortho_tab <- NULL
      output$ortho_text <- NULL
    }

Are you aware of any way where by I can generate the text ortho_text in Rmd? As far as I know, it is now possible to read an output in Rmd

Hi @lawrencelmli,

I believe you can just pass output$ortho_text as a parameter to the Rmd for rendering. For example, if you have a R Markdown file that accepts ortho_text as a parameter:

---
output: pdf_document
params:
  ortho_text: NULL
---

`r params$ortho_text`

Then inside your Shiny app when you are ready to generate your Rmd report you pass the text object along with the function call rmarkdown::render('file.Rmd', params = list(ortho_text = ouput$ortho_text))