Add a Generated Table of Contents Anywhere in RMarkdown

in Blog

February 28, 2018

Posted on:
February 28, 2018
Length:
4 minute read, 686 words
Categories:
Blog
Tags:
Markdown pandoc R Tricks blogdown R Markdown
See Also:
Process Profile Pictures with magick
🧼 cleanrmd
xaringanExtra v0.6.0 — Now on CRAN!

Links: Github Gist

GitHub user @stanstrup posted a question today on the blogdown GitHub repo about manually positioning a table of contents in blogdown:

When I use toc: true in a post the toc is inserted at the very top of the post. … If you could specify the position of the toc with some keyword you could work around it.

I don’t use the academic theme for Hugo (I use a modified version of hyde), so I’m not entirely sure if I can completely solve stanstrup’s problems, but I know I’ve run into something similar recently.

And while Yihui is probably right that the effort isn’t worth it when fiddling with trivial aesthetics, I use R Markdown in enough places and have run into this a few times. Knowing that someone else out there felt the same pain was enough to push me to code up a quick solution.

The function I’ve worked up is called render_toc() and it allows you to drop in a table of contents anywhere inside an R Markdown document. This means you can use it to manually position a table of contents in:

  • A README file for your package repo
  • In a long blogdown post
  • In an overview slide in xaringan

and many more places.

Get It #

I’ve posted the function and an example document as a GitHub Gist. To use it in your document, choose one of the following:

  1. Download render_toc.R and source("render_toc.R") in your project or script

  2. Copy the function code into your RMarkdown document

  3. Source the function from GitHub using devtools:

    devtools::source_gist("c83e078bf8c81b035e32c3fc0cf04ee8", 
                          filename = 'render_toc.R')
    

Use It #

I included an example file in the GitHub Gist. Essentially, you just need to source render_toc.R somewhere (such as a setup chunk) and then call it in the document where you want to render the table of contents.

The output will just be a markdown list, so if you want to give the table of contents it’s own header, you’ll have to include that in the document.

Here’s what a simple R Markdown document would look like.

```{r setup, include=FALSE} 
knitr::opts_chunk$set(echo = TRUE)
devtools::source_gist("c83e078bf8c81b035e32c3fc0cf04ee8", 
                      filename = 'render_toc.R')
```

## Table of Contents

```{r toc, echo=FALSE} 
render_toc("blogdown-toc-example.Rmd")
```

# Writing

## R Markdown

This is an R Markdown document...

```{r cars} 
# This is not a header
summary(cars)
```

## Regular Code

```r
# Regular markdown code (not run)
plot(pressure)
```

# Plots

## Including Plots {#plots-are-here .class-foo}

You can also embed plots, for example:

```{r pressure, echo=FALSE} 
plot(pressure)
```

which outputs as this document (click to view image).

Behind the Scenes #

The function simply reads through the lines of the RMarkdown document and strips out any code blocks. The supported code fencing style is three or more ` characters in a row.

Then I extract the headers, which must be in the hashtag-style to work. In other words headers like this

## A Nice Header

work well, while headers like these won’t be processed

A Not So Nice Header
====================

The function creates the header anchor if not manually specified – see the pandoc header identifiers help page for more information – or uses the identifier if it is included.

The example above would link to #a-nice-header and the example below links to #my-shortcut

## An Overly Wordy Header Title {#my-shortcut}

Any headers with a higher depth than the toc_depth parameter (default is 3) are discarded. Also any initial headers prior to the first base level header with higher levels (say ### when the base level is ##) are discarded as well.

Finally, if toc_header_name is set, the header with that name is discarded so that the TOC itself isn’t included in the TOC.

The end result is a simple markdown list that can be rendered anywhere!

devtools::source_gist("c83e078bf8c81b035e32c3fc0cf04ee8", 
                      filename = 'render_toc.R')

# `this_post` is set in the setup chunk, 
# points to the Rmd file for this post
render_toc(this_post)

Which, underneath, is just markdown.

- [Get It](#get-it)
- [Use It](#use-it)
- [Behind the Scenes](#behind-the-scenes)

Let me know on twitter @grrrck if you found this helpful or run into any issues!