So I'm trying to write an html R markdown document with interactive shiny bits that allow the user to edit a graph and then download the results to a pdf. However, there is something catastrophically wrong with the way that I'm trying to do this because as soon as the html starts, it overwrites the original markdown file with the contents of the pdf - turning it into complete gibberish right in the editor.
I doubt that I've found a completely new way to fail at R but I haven't been able to find where anybody else has had this issue. Additionally, I've looked over the shiny reference material and I'm just going in circles at this point, so any help would be greatly appreciated.
I'm using Rstudio 1.0.44, rmarkdown 1.2 and shiny 0.14.2. A small (not)working example:
---
title: "Minimum Failing Example"
author: "wittyalias"
date: "December 5, 2016"
output: html_document
runtime: shiny
---
```{r echo = FALSE}
library(ggplot2)
today <- Sys.Date()
inputPanel(downloadButton("dnld", label = "Download pdf"))
renderPlot({
# Example code from http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/
p1 <<- ggplot(ChickWeight, aes(x=Time, y=weight, colour=Diet, group=Chick)) +
geom_line() +
ggtitle("Growth curve for individual chicks")
p1
})
reactive({
fname <- paste0("Chick Weight - ", today, ".pdf")
output$dnld <- downloadHandler(filename = fname,
content = makethepdf(file))
makethepdf <- function(fname) {
pdf(fname,
width = 14,
height = 8.5)
p1
dev.off()
}
})
```
EDIT: To be clear: I want the user to be able to download multiple pages of graphs, some of which will have different formatting. The user won't be downloading just a pdf version of the markdown document.
This happens because reasons I weren't able to identify makethepdf runs with the file = [name of the file]. Insert a print(fname) to see. The download handler isn't supposed to be inside an observer though. You need to have it outside on its own. I also failed to make pdf() dev.off() combination work for some reason so here's a working version below.
output$dnld = downloadHandler(filename = paste0("Chick Weight - ", today, ".pdf"),
content = function(file){
ggsave(file, plot = p1, width = 14, height = 8.5)
})
Use tempfile() and tempdir() to create a temporary file:
output$downloadReport = downloadHandler(
filename = function() {
normalizePath(tempfile("report_", fileext = ".docx"), winslash = "/")
},
content = function(file) {
out = rmarkdown::render("./report.Rmd",
output_file = file,
output_dir = tempdir(),
output_format = "pdf_document",
intermediates_dir = tempdir(),
envir = new.env(),
params = list( fontSize = 10)
)
})
I usually use a separate .Rmd template for my downloaded reports as the layout and text are usually similar but not identical to what works in an app.
I also find using parameters is a convenient way to pass input settings from my app to my report. See this RStudio post for details
Alright, so there are a number of problems with my code, but using some of the suggestions in the other answers I've been able to work it out.
The primary problem with this little document is that content in the downloadHandler is a function, but in my code I set content equal to the result of a function call. It looks like when the shiny app is first run it compiles content, thinking that it is a function, but actually ends up calling the function. It sends file as an arguement, which doesn't seem to exist except as a base function. Calling makethepdf with just file throws an error when I use it in the console, but for whatever reason in this app it just goes with the call, apparently with file = [name of the .Rmd] (just as OganM said).
To fix, change this:
output$dnld <- downloadHandler(filename = fname,
content = makethepdf(file))
to
output$dnld <- downloadHandler(filename = fname,
content = makethepdf)
To be clear: this code does not overwrite the .Rmd file if content calls makethepdf with any argument other than file. For instance, content = makethepdf(fnm)) causes the download button to display an object not found error and content = makethepdf(fname)) causes the download button to throw an attempt to apply non-function error when pressed.
Related
I am getting crazy with this small reproducible Shiny app:
Basically 3 steps:
I have an input$text which the user can chose
The user triggers an R file create_text.R transforming this text, creating a my_text string. (IRL it is basically a download and data preparation step)
The user triggers the render of an R Markdown where my_text value is printed
My base code looks like:
app.R
library(shiny)
ui <- fluidPage(
selectInput(inputId = "text",
label = "Choose a text",
choices = c("Hello World!", "Good-bye World!")),
actionButton("create_text", "Prepare text"),
downloadButton("report", "Render markdown"))
)
server <- function(input, output) {
observeEvent(input$create_text, {
text_intermediate <- input$text
source('create_text.R')
})
output$report <- downloadHandler(
filename = "report_test.html",
content = function(file) {
rmarkdown::render(input = "report_test.Rmd",
output_file = file)
})
}
shinyApp(ui, server)
create_text.R
my_text <- paste("transfomation //", text_intermediate)
report_test.Rmd
---
title: "My title"
output: html_document
---
```{r}
my_text
```
My problem is the intermediate step (2.), probably because I am confused between environments.
If I run source('create_text.R', local = FALSE), it fails because the R file is run from an empty environment, then does not recognize text_intermediate.
# Warning: Error in paste: object 'text_intermediate' not found
On the opposite way, if I run source('create_text.R', local = TRUE), the created my_text string is not "saved" for the next of the shiny app, then the Rmd cannot be rendered since my_text is not found.
# Warning: Error in eval: object 'my_text' not found
What I have tried:
Two ugly solutions would be:
do not use an intermediate R file and have the whole code inside the app but it will make it very unreadable
or even more ugly, only use hard assigning <-- in the R file, like my_text <<- paste("transfomation //", text_intermediate)
Playing with the env argument of the render() function dit not help neither.
Lastly, starting from scratch I would have used reactive values everywhere, but both my R and Rmd files are very big and "finished", and it would be difficult to adapt the code.
Any help ?
OK, at the end I bypassed the problem with this unelegant solution:
I added a block of code after running the external R script, which stocks all created objects into the global environment. This way, those objects are callable later on in the server function. This allows to go without eventReactive(). Not very pleasant but works.
For this, I use assign within a lapply. In my example, it would be equivalent to write: my_text <<- my_text. The lapply allows to do it for all objects.
observeEvent(input$create_text, {
text_intermediate <- input$text
source('create_text.R')
lapply(X = (ls()), FUN = function(object) {assign(object, get(object), envir = globalenv())})
})
I'm running some plot code in markdown to generate a plot in a xaringan presentation. The code works but is a little long and so takes up the whole presentation slide forcing the actual plot off the edge (see img).
How can I hide the code block generating the plot?
Also how can I compress the code block with a scroll bar?
```{r}
r_exp.fun <- function(r = 0.05, N_pop = 10, t = 150)
{
N <- vector("numeric", length = t)
N[1] <- N_pop
for (i in 2:t)
{
N[i] <- N[i-1] + (N[i-1] * r)
}
return(N)
}
args_list <- list(0.045, 0.055, 0.06)
matplot(
mapply(
r_exp.fun,
r = args_list
)
,type = "l")
abline(h = list(7052, 29150, 59000))
```
The alternative is of course to save as an image but if possible I would prefer to be able keep the code as a resource for anyone with the link.
Thanks!
As alistaire already mentioned in the comments, RMarkdown has various chunk options to customize the output.
For your problem the option echo needs to be set to FALSE.
The other options (from https://rmarkdown.rstudio.com/lesson-3.html):
include = FALSE
prevents code and results from appearing in the finished file. R Markdown still runs the code in the chunk, and the results can be used by other chunks.
echo = FALSE
prevents code, but not the results from appearing in the finished file. This is a useful way to embed figures.
message = FALSE
prevents messages that are generated by code from appearing in the finished file.
warning = FALSE
prevents warnings that are generated by code from appearing in the finished.
fig.cap = "..."
adds a caption to graphical results.
I had trouble to generate a PDF file from a scatterplot created trough ggplot in a ShinyApp. I was succesful with a similar approach as the one from the answer from user juba to this stackoverflow question, but then the nearPoints() function I was using didn't work and gave an error message saying something about not being able to find the coordinfo. Then I used the ggsave option that Yihui Xie recommended, but with this strategy I'm getting files saved in the folder where my ShinyApp resides. I'm worried that if I try to use this in my ShinyApp hosted in the shinyapps.io site, there would be problems when trying to save these transient and temporal files. I also tried removing the file after the download is done but, anytime the plot is shown the file is created, so the file is created again after the copied file is downloaded. Here is only a sample (some important lines) of the code I used to allow for the download of the plot as a PDF file:
#### User Interface ----
# Show scatterplot with clicking capability
plotOutput(outputId = "scatterplot", click = "plot_click")
# Show data table where data points clicked will be shown
dataTableOutput(outputId = "datatable")
# Download button
downloadButton('dlScatPlot', 'Download plot as PDF')
# Server ----
# Wrap the creation of the scatterplot in a function so the plot can be
# downloaded as PDF
makeScatPlot <- function() {
## some code to generate a ggplot plot
}
# Create the scatterplot object the plotOutput function is expecting
output$scatterplot <- renderPlot({
# The file saved as ggsave originally will be first saved in the server, and
# then in the client side if the Download Button is used
filename <- paste('scatterPlot_', Sys.Date(), '.pdf', sep='')
ggsave(filename, makeScatPlot(), width = 11, height = 4, dpi = 300, units = "in")
makeScatPlot()
})
# Create data table showing points that have been clicked
output$datatable <- DT::renderDataTable({
rows <- nearPoints(df1, input$plot_click) %>%
select(sample_ID, compound, DOI)
DT::datatable(rows, rownames = FALSE)
})
output$dlScatPlot <- downloadHandler(
filename = function() {
paste('scatPlot_', Sys.Date(), '.pdf', sep='')
},
content = function(file) {
file.copy(paste('scatPlot_', Sys.Date(), '.pdf', sep=''), file, overwrite = TRUE)
# To avoid the accumulation of PDFs in the server
file.remove(paste('scatPlot_', Sys.Date(), '.pdf', sep=''))
}
)
I guess it can cause trouble if I upload a ShinyApp script to shinyapps.io that creates one PDF file each time the plot is rendered, right?
Instead of saving files to a specific path, you can save them as temporary file using tempfile(fileext = ".pdf"). Those files will be automatically removed once the session is over. So no need to remove them manually.
I finally came out with an obvious answer. I wasn't doing the straightforward thing that was call the ggsave in the downloadHandler call because I was using the Yihui answer directly. So, finally I just don't create the file inside the renderPlot() function, but in the downloadHandler where it rightfully should be.
# Create the scatterplot object the plotOutput function is expecting
output$scatterplot <- renderPlot({
makeScatPlot()
})
# Create the button to download the scatterplot as PDF
output$dlScatPlot <- downloadHandler(
filename = function() {
paste('scatterPlot_', Sys.Date(), '.pdf', sep='')
},
content = function(file) {
ggsave(file, makeScatPlot(), width = 11, height = 4, dpi = 300, units = "in")
}
)
Using the above code, everything (including the nearPoints call) works now :)
In this following MWE on a Windows 7 machine running R 3.12
I am trying to create an md file from an Rmd. It creates the md file in my working directory, not int he specified path. The HTML file is created in the specified path instead. How can I make the md get created in deleteMe/my.md?
## Create a minimal Rmd:
dir.create("deleteMe")
cat("\nminimal", file = "deleteMe/my.Rmd")
## knit it to md
knitr::knit2html(input = "deleteMe/my.Rmd", output = "deleteMe/my.md")
## If I set the working directory to deleteME it is correct but I'm don't like to use setwd in functions
setwd("deleteMe")
knitr::knit2html(input = "deleteMe/my.Rmd", output = "deleteMe/my.md")
I realize it's a knit2html, not knit2md, but it creates an md as well. I'm open to other functions/packages/methods if this is the wrong approach. I do not want a yaml though as this is for a README.md creation that I don't want the yaml to appear in.
Looking at the source of knit2html(), you can see that when it calls knit(), it doesn't specify output=. That means that knit() is assuming you want the .md created in the working directory (hence why it works when you change working directory).
Creating a new version of knit2html() makes it work:
knit2html2 <- function (input, output = NULL, ..., envir = parent.frame(),
text = NULL, quiet = FALSE, encoding = getOption("encoding"))
{
#Specify output path in knit()
out = knit(input, output, text = text, envir = envir, encoding = encoding,
quiet = quiet)
if (is.null(text)) {
output = knitr:::sub_ext(if (is.null(output) || is.na(output))
out
else output, "html")
markdown::markdownToHTML(out, output, encoding = encoding,
...)
invisible(output)
}
else markdown::markdownToHTML(text = out, ...)
}
It seems reasonable that output= be passed on to knit() - maybe a pull request is in order
I would like to include at the end of the R markdown documention the help page about the mtcars dataset.
In my file I included the following:
```{r}
?mtcars
```
When I compile the markdown (output is to PDF - knitr), upon processing this instruction the help page comes up in my browser but the resulting pdf lacks this section.
Is there a way I could acheive this other then copying from one place to the other?
Thank you.
We can adapt Yihui Xie's static_help function to get the html source for a given help file
static_help <- function(pkg, topic, out, links = tools::findHTMLlinks()) {
pkgRdDB = tools:::fetchRdDB(file.path(find.package(pkg), 'help', pkg))
force(links)
tools::Rd2HTML(pkgRdDB[[topic]], out, package = pkg,
Links = links, no_links = is.null(links))
}
If we write the source to a temporary file we can then read it back in and strip off the header and footer, giving you the body of the help file to include in your markdown document
```{r, echo = FALSE, results = "asis"}
static_help <- function(pkg, topic, out, links = tools::findHTMLlinks()) {
pkgRdDB = tools:::fetchRdDB(file.path(find.package(pkg), 'help', pkg))
force(links)
tools::Rd2HTML(pkgRdDB[[topic]], out, package = pkg,
Links = links, no_links = is.null(links))
}
tmp <- tempfile()
static_help("datasets", "mtcars", tmp)
out <- readLines(tmp)
headfoot <- grep("body", out)
cat(out[(headfoot[1] + 1):(headfoot[2] - 1)], sep = "\n")
```
EDIT
The above solution produced HTML output, whereas the question actually asked for PDF output. We can adapt the above to return latex output instead; this time the only-post-editing required is to switch % for \n
```{r, echo = FALSE, results = "asis"}
static_help <- function(pkg, topic, out, links = tools::findHTMLlinks()) {
pkgRdDB = tools:::fetchRdDB(file.path(find.package(pkg), 'help', pkg))
force(links)
tools::Rd2latex(pkgRdDB[[topic]], out, package = pkg,
Links = links, no_links = is.null(links))
}
tmp <- tempfile()
static_help("datasets", "mtcars", tmp)
out <- readLines(tmp)
out <- gsub("%", "\n", out, fixed = TRUE)
cat(out, sep = "\n")
```
However the .Rd files depend on Rd.sty. The simplest way to get LaTeX to find Rd.sty is to put a copy in the same directory as your .Rmd file. Then you need to define a custom template to replace the default pandoc LaTeX template. Again, the simplest solution is to put a copy of the default template in the same directory as your .Rmd file, then modify it by replacing everything between the \documentclass command and the \begin{document} command (lines 2 - 145) with the command
\usepackage{Rd}
Finally modify the metadata of your .Rmd file to use the new template
---
output:
pdf_document:
template: template.tex
---