Custom function to open compiled .rnw in Sumatra PDF reader? - r

Since updating to Windows 10, I am unable to compile .rnw files with the "Compile PDF" button in Rstudio if they have citations (I use the natbib package). So, I have abandoned Sweave for Knitr. Though not as convenient as the Compile PDF button, the command
knitr::knit2pdf('my_doc.rnw')
compiles PDF's with R code, Latex, and citations without error. However, unlike the Compile PDF button, it does not automatically launch the newly-compiled PDF in Sumatra, my preferred PDF previewer. My question is, could the source code for knitr::knit2pdf, namely,
function (input, output = NULL, compiler = NULL, envir = parent.frame(),
quiet = FALSE, ...)
{
out = knit(input, output = output, envir = envir, quiet = quiet)
owd = setwd(dirname(out))
on.exit(setwd(owd))
if (is.null(compiler)) {
compiler = if (grepl("\\.rst$", out))
"rst2pdf"
else "pdflatex"
}
if (identical(compiler, "rst2pdf")) {
if (tolower(file_ext(out)) != "rst")
stop("for rst2pdf compiler input must be a .rst file")
rst2pdf(basename(out), ...)
}
else {
tinytex::latexmk(basename(out), engine = compiler, ...)
}
with_ext(out, "pdf")
}
be amended to launch and update the newly-compile PDF each time I run knitr::knit2pdf? Thanks.

I would not recommend try to "amend" knit2pdf – but you can easily open your PDF in SumatraPDF after knitting. Just call:
system2("C:/Program Files/RStudio/bin/sumatra/SumatraPDF.exe", args = "my_doc.pdf", wait = FALSE, invisible = FALSE)
You may need to adjust the path to SumatraPDF.exe. SumatraPDF updates automatically when the PDF changes, so you don't need any extra effort to see changes.
If you prefer a oneliner for knitting and opening the PDF:
knit2sumatra <- function(input) {
# "input" should be the filename WITHOUT extension
knitr::knit2pdf(paste0(input, ".Rnw"))
system2(
"C:/Program Files/RStudio/bin/sumatra/SumatraPDF.exe",
args = paste0(input, ".pdf"),
wait = FALSE,
invisible = FALSE
)
}
Alternatively, just place the system2 call in a chunk (somewhere) in your RNW file. SumatraPDF will try to open the PDF too early – but as soon as compilation completes, the final PDF will be visible.

Related

R-Markdown: Rendering to PDF and appending to existing PDF

I am using RMarkdown to create a set of 6,000 letters. I am using parameters to create custom inputs into the document. I am using a loop to render the PDFs and calling it as follows:
for (i in 1:6000) {
rmarkdown::render(
input = "/template/letter.Rmd",
output_dir = "/output/",
output_file = paste0("report", i)
)
}
Now, this creates a 6,000 separate PDFs, but I would like to have it in one long document. Does anyone have any suggestions for how to use rmarkdown::render() and have it append the current PDF it generates onto the existing one? If I set output_file = report, for example, it will simply overwrite the existing PDF in the loop.
You can merge the outputs using a toolkit for pdf files. For instance, you can use the staplr package:
library(staplr)
ofiles <- c()
for (i in 1:6000) {
ofile <- rmarkdown::render(
input = "/template/letter.Rmd",
output_dir = "/output/",
output_file = paste0("report", i)
)
ofiles <- c(ofiles, ofile)
}
staple_pdf(input_files = ofiles, output_filepath = "/output/all-in-one.pdf")
In Linux, you can also merge the pdf files using pdftk:
cd /output/
pdftk report*.pdf cat output all-in-one.pdf

Shiny interactive document download button overwrites original R markdown

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.

knitr HTML output too large

I have been using rmarkdown/knitr's knit to html capability to generate html code for some blogs. I've found it extremely helpful and convenient, but have been running into some problems lately with file size.
When I knit a script that has graphics that use shapefiles or ggmap images, the html file gets too big for the blog host to make sense of it (I've tried with both blogger and wordpress). I believe this has to do with the relatively large data.frames/files that are the shapefiles/ggmap being put into html form. Is there anything I can do to get a smaller html file that can be parsed by a blog host?
For reference, the html output from an rmarkdown script with one graphic using a ggmap layer, a layer of shapefiles and some data is 1.90MB, which is too big for blogger or wordpress to handle in html input. Thanks for any ideas.
Below are 3 different options to help you reduce the file size of HTML files with encoded images.
1. Optimize an existing HTML file
You can run this Python script on an existing HTML file. The script will:
decode the base64 encoded images
run pngquant to optimize the images
re-encode the optimized images as base64
Usage:
python optimize_html.py infile.html
It writes output to infile-optimized.html.
2. Use the built-in knitr hook for optimizing PNG images
knitr 1.15 includes a hook called hook_optipng that will run the optipng program on generated PNG files to reduce file size.
Here is a .Rmd example (taken from: knitr-examples/035-optipng.Rmd):
# 035-optipng.Rmd
This demo shows you how to optimize PNG images with `optipng`.
```{r setup}
library(knitr)
knit_hooks$set(optipng = hook_optipng)
```
Now we set the chunk option `optipng` to a non-`NULL` value,
e.g. `optipng=''`, to activate the hook. This string is passed to
`optipng`, so you can use `optipng='-o7'` to optimize more heavily.
```{r use-optipng, optipng=''}
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
```
3. Write your own knitr hook for any image optimizer
Writing your own hook is also quite easy, so I wrote a hook that calls the pngquant program. I find that pngquant runs faster, and the output files are smaller and look better.
Here is a .R example that defines and uses hook_pngquant (taken from this gist).
#' ---
#' title: "pngquant demo"
#' author: "Kamil Slowikowski"
#' date: "`r Sys.Date()`"
#' output:
#' html_document:
#' self_contained: true
#' ---
#+ setup, include=FALSE
library(knitr)
# Functions taken from knitr/R/utils.R
all_figs = function(options, ext = options$fig.ext, num = options$fig.num) {
fig_path(ext, options, number = seq_len(num))
}
in_dir = function(dir, expr) {
if (!is.null(dir)) {
owd = setwd(dir); on.exit(setwd(owd))
}
wd1 = getwd()
res = expr
wd2 = getwd()
if (wd1 != wd2) warning(
'You changed the working directory to ', wd2, ' (probably via setwd()). ',
'It will be restored to ', wd1, '. See the Note section in ?knitr::knit'
)
res
}
is_windows = function() .Platform$OS.type == 'windows'
in_base_dir = function(expr) {
d = opts_knit$get('base.dir')
if (is.character(d) && !file_test('-d', d)) dir.create(d, recursive = TRUE)
in_dir(d, expr)
}
# Here is the code you can modify to use any image optimizer.
hook_pngquant <- function(before, options, envir) {
if (before)
return()
ext = tolower(options$fig.ext)
if (ext != "png") {
warning("this hook only works with PNG")
return()
}
if (!nzchar(Sys.which("pngquant"))) {
warning("cannot find pngquant; please install and put it in PATH")
return()
}
paths = all_figs(options, ext)
in_base_dir(lapply(paths, function(x) {
message("optimizing ", x)
cmd = paste(
"pngquant",
if (is.character(options$pngquant)) options$pngquant,
shQuote(x)
)
message(cmd)
(if (is_windows())
shell
else system)(cmd)
x_opt = sub("\\.png$", "-fs8.png", x)
file.rename(x_opt, x)
}))
return()
}
# Enable this hook in this R script.
knit_hooks$set(
pngquant = hook_pngquant
)
#' Here we set the chunk option `pngquant='--speed=1 --quality=0-50'`,
#' which activates the hook.
#+ use-pngquant, pngquant='--speed=1 --quality=0-50'
library(methods)
library(ggplot2)
set.seed(123)
qplot(rnorm(1e3), rnorm(1e3))
I prefer to write my reports in R scripts (.R) instead of R markdown documents (.Rmd). See http://yihui.name/knitr/demo/stitch/ for more information on how to do that.
One thing you could do would be to not use embedded image and other resources. To achieve this, you can set the self_contained option in the YAML header for your document to false, e.g.:
---
output:
html_document:
self_contained: false
---
More info here: http://rmarkdown.rstudio.com/html_document_format.html

knit2html put .md in specified path

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

R markdown file: include help information

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
---

Resources