1
votes

I am looking to download an interactive R shiny document and save it to a file for other users (that do not own R or Rstudio) to view and interact with. The R shiny document runs fine, but the download button fails when I attempt to save the downloaded file somewhere. Here is my code; I used the dataset 'cars' so the errors can be reproduced:

Shiny code:

library(knitr)
library(shiny)
library(ggplot2)
library(readr)

x <- cars
x$dist_cut <- cut(x$dist, breaks = c(-1, 25, 50, 75, 100, 9999), 
                       labels = c("0-25", "26-50", "51-75", "76-100", ">100"))

t.cut <- table(x$dist_cut)

# Define UI for dataset viewer app ----
ui <- fluidPage(

  # App title ----
  titlePanel("Cars Data"),

  # Sidebar layout with a input and output definitions ----
  sidebarLayout( position = "right",

                 # Sidebar panel for inputs ----
                 sidebarPanel(

                   # Input: Selector for choosing dataset ----
                   selectInput(inputId = "dataset",
                               label = "Choose a range for dist:",
                               choices = c("show all", ">100", "76-100", "51-75", "26-50", "0-25")),
                   downloadButton('downloadReport')

                 ),

                 # Main panel for displaying outputs ----
                 mainPanel(

                   # Output: Barplot ----
                   plotOutput(outputId = "distPlot"),

                   # Output: HTML table with requested number of observations ----

                   DT::dataTableOutput("view")


                 )
  )
)

# Define server logic to summarize and view selected dataset ----
server <- function(input, output) {

  # Return the requested dataset ----
  datasetInput <- reactive({
    switch(input$dataset,
           "show all" = x,
           ">100" = x[x$dist_cut == ">100",],
           "76-100" = x[x$dist_cut == "76-100",],
           "51-75" = x[x$dist_cut == "51-75",],
           "26-50" = x[x$dist_cut == "26-50",],
           "0-25" = x[x$dist_cut == "0-25",])
  })

  # display the plot
  output$distPlot <- renderPlot({

    barplot(t.cut, beside = TRUE, col = c("green", "blue", "yellow", "orange", "red"), main = "Cars sorted by dist", ylim = c(0, 20), cex.main = 1.5, xlab = "dist", ylab = "Frequency")

  })

  # create the DT datatable
  output$view = DT::renderDataTable({
    DT::datatable(datasetInput(), filter = 'top', 
                  options = list(lengthMenu = c(5, 10, nrow(x)), pageLength = 5))

  })

  output$report <- downloadHandler(
    filename = function() paste0("report", ".html"),
    content = function(file) {
      tempReport <- file.path(tempdir(), "report.rmd")
      file.copy("report.rmd", tempReport, overwrite = TRUE)

      # Set up parameters to pass to Rmd document
      params <- list(data = x,
                     title = "Plot Title",
                     limits = c(-10,10))
      # Knit the document, passing in the `params` list, and eval it in a
      # child of the global environment (this isolates the code in the document
      # from the code in this app).
      rmarkdown::render(tempReport, output_file = file,
                        params = params,
                        envir = new.env(parent = globalenv())
      )
    }     
  )

}

# Create Shiny app ----
shinyApp(ui = ui, server = server)

Rmarkdown code:

---
output: html_document
params:
    data: NULL
    limits: NULL
    title: "Cars Data"
title: "`r params$title`"
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

```{r}
print(params$title)
barplot(table(params$x$dist_cut), beside = TRUE, col = c("green", "blue", "yellow", "orange", "red"), main = "Cars Split by dist", ylim = c(0, 20), cex.main = 1.5, xlab = "dist", ylab = "Frequency") 
library(DT) 
datatable(params$x, filter = 'top', options = list(lengthMenu = c(5, 10, nrow(x)), pageLength = 5))
```

With this code, I receive the following warnings and errors after clicking on the download button:

Warning: Error in : path for html_dependency not provided
  [No stack trace available]
Warning in min(w.l) : no non-missing arguments to min; returning Inf
Warning in max(w.r) : no non-missing arguments to max; returning -Inf
Warning in min(x) : no non-missing arguments to min; returning Inf
Warning in max(x) : no non-missing arguments to max; returning -Inf
Warning: Error in plot.window: need finite 'xlim' values

I also receive a pop-up window titled "Download Failed" stating:

Error downloading {R shiny html link} - server replied: Internal Service Error

I have spent about a week trying to debug this and I have not quite figured out what I am missing. I am new to R shiny web applications and would really like to allow non-R users to view these interactive reports to help them do their jobs. I hope this is enough information to help find a solution to my problem.

Thanks!

Update: When I save the HTML file and open it in internet explorer, I only see the title (Cars Data) at the top of the page and the sidebar panel (containing the drop-down box and the download button). In addition, I get a prompt stating "Internet Explorer restricted this webpage from running scripts or ActiveX controls" and nothing happens when I press "Allow Blocked Content." Does this have anything to do with the issue?

I apologize for not posting a reproducible example originally: this is my first stackoverflow post.

1
Thanks for posting a real minimal reproducible example of your problem! I'm pretty sure I was able to solve it now - divibisan

1 Answers

1
votes

The server replied: Internal Service Error is just a generic error that means your .Rmd wasn't able to be rendered correctly. Your real problem is likely with your .Rmd.

If you look at the stack trace error, it says:

Warning in min(w.l) : no non-missing arguments to min; returning Inf

When you click on the download button, it runs the render function on your .Rmd and tries to render it. From the look of the error, you're trying to plot something, but some or all of the objects your plot function is trying to use to make the plot can't be found, so it fails with an error.

Since the data for your plot comes from the shiny app, you have to pass it into the .Rmd. The better way to do this is to pass in the data your .Rmd needs directly as a list passed to the params= argument of rmarkdown::render.

# NOTE: downloadHandler must be assigned to the ID of your downloadButton
output$downloadReport <- downloadHandler(
    filename = function() paste0("report", ".html"),
    content = function(file) {
        tempReport <- file.path(tempdir(), "report.rmd")
        file.copy("report.rmd", tempReport, overwrite = TRUE)

        # Set up parameters to pass to Rmd document
        #  these can be any time of R objects
        #  that you want your .Rmd to have access to
        params <- list(data = x,
                       title = "Plot Title",
                       limits = c(-10,10))
        # Knit the document, passing in the `params` list, and eval it in a
        #  child of the global environment (this isolates the code in the document
        #  from the code in this app).
        rmarkdown::render(tempReport, output_file = file,
                          params = params,
                          envir = new.env(parent = globalenv())
        )
    }     
)

Now in your .Rmd you define those parameters in the header:

---
output: html_document
params:
    data: NULL
    limits: NULL
    title: "Default title"
title: "`r params$title`"
---

And then use it by accessing the params object. Once you pass a object into the .Rmd as a param, you can access it in the .Rmd using the name that you gave in the params-list. So, even though your data frame is named x in your shiny app, since you passed it in to the parameter data, you'll access it in the .Rmd using params$data.

```{r}
print(params$title)
barplot(table(params$data$dist_cut), beside = TRUE,
        col = c("green", "blue", "yellow", "orange", "red"),
        main = "Cars Split by dist", ylim = c(0, 20),
        cex.main = 1.5, xlab = "dist", ylab = "Frequency")

library(DT)
DT::datatable(params$data, filter = 'top',
              options = list(lengthMenu = c(5, 10, nrow(params$data)),
                             pageLength = 5))
```

Passing in objects explicitly through the params argument (and rendering the .rmd in a new environment) makes sure that the data you need is available to your .rmd, that it has the correct data (especially important in Shiny where the contents of your objects may change in unexpected ways due to user input) and prevents namespace collisions (where you have unexpected objects with the same name in the environment)