Hooks

Customizable functions to run before / after a code chunk, tweak the output, and manipulate chunk options

2017-02-03

The object knit_hooks in the knitr package is used to set hooks; the basic usage is knitr::knit_hooks$set(name = FUN) (see objects for details) where name is the name of a chunk option (can be arbitrary), and FUN is a function. There are two types of hooks: chunk hooks and output hooks. Hook functions may have different forms, depending what they are designed to do.

Chunk hooks

Chunk hooks are functions to be called before or after a code chunk when the associated chunk option is not NULL. A hook function can accept the following arguments:

hook_foo = function(before, options, envir, name, ...) {
  if (before) {
    # code to be run before a chunk
  } else {
    # code to be run after a chunk
  }
}

For example, if we set a hook for the small_mar option as:

knitr::knit_hooks$set(small_mar = function(before, ...) {
  if (before)
    par(mar = c(4, 4, .1, .1))  # smaller top/right margin
})

Then this function will be called for a chunk like this (small_mar doesn’t have to be TRUE; it can be any non-NULL value):

```{r, myplot, small_mar=TRUE}
hist(rnorm(100), main = '')  # no main title
```

In knitr, hooks can also be used to insert texts into the output. To do this, the hook function must return a character result. This feature can greatly extend the power of hooks. Take the rgl package for example: if we want to insert 3D snapshots produced in rgl into our Markdown/HTML document, we may consider this hook function (see the more sophisticated hook_rgl() in the rgl package):

knitr::knit_hooks$set(rgl = function(before, options, envir) {
  if (!before) {
    # after a chunk has been evaluated
    if (rgl.cur() == 0) return()  # no active device
    name = paste0(options$fig.path, options$label, '.png')
    rgl.snapshot(name, fmt = 'png')
    return(paste0('![rgl plot](', name, ')\n'))
  }
})

And the code chunk may look like this:

```{r, fancy-rgl, rgl=TRUE}
library(rgl)  # example taken from ?plot3d
open3d()
x = sort(rnorm(1000)); y = rnorm(1000); z = rnorm(1000) + atan2(x,y)
plot3d(x, y, z, col = rainbow(1000))
```

In the Markdown output, we will see ![rgl plot](fancy-rgl.png).

In summary:

  1. The hook can be set in knit_hooks via knit_hooks$set(name = FUN);
  2. The chunk option name should take a non-NULL value in this chunk for the hook function to run;
  3. A hook can be run before and/or after a chunk;
  4. Character results returned by hooks will be written into the output without modifications.

See 045-chunk-hook.md (source) for further examples.

Output hooks

Output hooks are used to customize and polish the raw output from chunks. There are 8 output hooks in all to deal with different types of output:

All these hooks should be of the form function(x, options) (except the inline and document hooks which only have one argument x), where x is the character string of the output, and options is a list of current chunk options. You may find more information and examples about output hooks in the R Markdown Cookbook.

Below is an example of setting the error hook to add extra formatting to any error messages in an R Markdown document.

knitr::knit_hooks$set(error = function(x, options) { 
  paste(c('\n\n:::{style="color:Crimson; background-color: SeaShell;"}',
        gsub('^## Error', '**Error**', x),
        ':::'), collapse = '\n')
})

You may test the hook with a code chunk like this:

```{r, error=TRUE}
1 + "a"
```

Unlike chunk hooks which are empty by default, output hooks all come with default values that can be reset with:

knitr::knit_hooks$restore() 

This package has set default output hooks for different parts of output and to accommodate different output formats such as LaTeX, HTML, and Jekyll. A series of functions of the form render_*() are provided to set built-in output hooks for different output formats, e.g. render_latex() and render_html(), etc. Output hooks should be set inside the document, but if the hooks are set before knitr::knit() processes the document, render_*() must be called first, where * is the output format, e.g., render_markdown() or render_html(). You can access these output hooks without setting them via the hooks_*() functions, e.g., hooks_markdown().

Below are details for these formats:

LaTeX: render_latex()

If the output file type is LaTeX, default hooks will put most output in the verbatim environment, and numeric inline output will be formatted in scientific notation (see output demo for details); plot and chunk hooks are more complicated:

Sweave: render_sweave()

Put source code in the Sinput environment, output in the Soutput environment and the whole chunk in the Schunk environment. The style file Sweave.sty is required to use this theme, or at least these three environments have to be defined.

Listings: render_listings()

Similar to Sweave, and Sweavel.sty is used instead.

HTML: render_html()

To write output into an HTML file, the hooks will be automatically adjusted. Basically the output from chunks is put in div layers with classes, e.g. source code is in <div class="knitr source"></div>; the whole chunk output is in <pre></pre>; inline output is in <code class="knitr inline"></code>.

Markdown: render_markdown()

The source code and output will be indented by 4 spaces. For GitHub Flavored Markdown, the source code is put in between ```r and ```; output is between ``` and ```.

Jekyll: render_jekyll()

I need to build this site so I also set up some hooks especially for Jekyll, and they are actually quite simple: R source code is put in a highlight environment with the language set to r, and the rest of output belongs to the highlight environment with the text language (nearly no highlighting at all). Currently plots are written out according to the syntax of Markdown.

reStructuredText: render_rst()

Code is put after :: and indented by 4 spaces, or in the sourcecode directive.

Option hooks

Sometimes you may want to change certain chunk options according to the values of other chunk options, and you may use the object opts_hooks to set up an option hook to do it. An option hook is executed when a corresponding chunk option is not NULL. For example, we can tweak the fig.width option so that it is always no smaller than fig.height:

knitr::opts_hooks$set(fig.width = function(options) {
  if (options$fig.width < options$fig.height) {
    options$fig.width = options$fig.height
  }
  options
})

Because fig.width will never be NULL, this hook function is always executed before a code chunk to update its chunk options. For the code chunk below, the actual value of fig.width will be 6 instead of the initial 5 if the above option hook has been set up:

```{r fig.width = 5, fig.height = 6}
plot(1:10)
```