Add a Generated Table of Contents Anywhere in RMarkdown
in Blog
February 28, 2018
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: truein 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:
-
Download
render_toc.Randsource("render_toc.R")in your project or script -
Copy the function code into your RMarkdown document
-
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!