knitr::opts_chunk$set(echo = FALSE)

Note: This post was originally written by Radovan Miletic and copied here on April 18, 2022 - see the original post here for a potentially updated version.

Image from https://www.wikigallery.org/wiki/painting_366127/William-Harris-Weatherhead/Crumbs-From-A-Poor-Man's-Table. William Harris Weatherhead (British 1843-1903), CRUMBS FROM A POOR MAN'S TABLE{target="_blank" width=70% .external}

Crumbs from a poor man's table (of contents)

You may find the crumbs here{target="_blank"}: toc-styles.css and the-toc-in-distill.Rmd. Hope they will be inspirational to someone.

Judging by the {distill{target="_blank"}} issues 16{target="_blank"}, 341{target="_blank"}, 366{target="_blank"}, 420{target="_blank"}, and the Stack Overflow question{target="_blank"} it seems that there is a genuine need for some kind of a "floating TOC on the side" in {distill}.

In his comment{target="_blank"}, J.J. Allaire summarizes the problem:
- any change in {distill} should be linked to distll.pub{target="_blank"} web framework,
- floating TOC on the side would be tricky to implement due to specific layout "figures that extend into the sidebar (quite common w/ distill)".

An optimal solution can be found within Quarto{target="_blank"} publishing system. You may try scrolling up and down and observe the collapsible sidebar navigation. HTML documents rendered with Quarto use Bootstrap 5 by default, which is not the case with distill.pub.

While we wait for a solution, you may feel tempted to try "a poor man's TOC" that I came up with for my use case (applied to single article).
Let me know how it goes!

Floating TOC in fixed position

xaringanExtra::use_panelset()

::::: {.panelset} ::: {.panel}

YAML output

You may include a toc-styles.css file in the post directory and add a reference to it in YAML output options: css: toc-styles.css.

title: "Post title"
description: |
  Description
author:
  - first_name: "R"
    last_name: "M"
output:
  distill::distill_article:
    toc: true
    toc_depth: 6
    toc_float: true
    css: toc-styles.css
    self_contained: false

:::

::: {.panel}

CSS elements

If you want to place the TOC below the figures, you need to disable the z-index{target="_blank"}.

/* This hack is needed to temporary disable the predefined layout style */
d-article {
    contain: none;
  }

/* Sidebar Navigation */
#TOC {
  opacity: 1;
  position: fixed;
  left: calc(7%);
  top: calc(5.5%);
  /* width: 20%; */
  max-width: 260px;
  max-height: 85%;
  overflow-y: auto;
  background: white;            /* Optional, remove to enable the blur filter (just for fun). */
  backdrop-filter: blur(10px);  /* Optional, wouldn't not work with Firefox browser. */
  padding: 10px;                /* Optional */
  /* border-right: 1px solid rgba(0, 0, 0, 0.1); */
  border: 1px solid rgba(0, 0, 0, 0.1);
  /* border-radius: 1px; */
  transition: all 0.5s;
  z-index: 999;                 /* Optional */
  }

/* Hide the TOC when resized to mobile or tablet:  480px, 768px, 900px */
@media screen and (max-width: 1000px) {
#TOC {
    position: relative;
    left: 0;
    top: 0;
    max-width: none;
    max-height: none;
    overflow-y: auto;
    border: none;
    background: none; }
  }

::: :::::

Toggle (show/hide) button

::::: {.panelset} ::: {.panel}

CSS elements

You may disable the hover effect, if preferred.

.d-contents nav h3 {
    text-indent: 25px;
  }

#TOC.hide { 
  opacity: 0;
  transition: visibility 0s linear 0.5s, opacity 0.5s;
  }

/* Optional, remove to disable the hover effect */
#TOC:hover {
  opacity: 1;
  transition: all 0.5s;
  }

/* Hide the TOC when resized to mobile or tablet:  480px, 768px, 900px */
@media screen and (max-width: 1000px) {
#TOC {
    }
    .d-contents nav h3 {
      text-indent: 0; }
    input[type="button" i] {  
      display: none; }
  }

:::

::: {.panel}

JAVASCRIPT elements

function toggle () {
  document.getElementById("TOC").classList.toggle("hide");
};

:::

::: {.panel}

HTML elements

<input type="button" class="d-article-with-toc" id="TOC" value="&#x2630" 
title="Toggle (Hide/Show) Table of Contents" 
onclick="toggle()" style="padding:7px; border: 0px;"/>

:::
:::::

Scrolling Active States

It was Rich Pauloo (author of Stack Overflow question mentioned above) who pointed out to a scrolling active state implementation, "such that the TOC responds to the header the user is currently on".

All credits for "scrolling active state" lightweight solution goes to Bramus Van Damme (see his tutorial{target="_blank"}) and Chris Coyier (his tutorial{target="_blank"}) and to the powerful IntersectionObserver{target="_blank"}.

I just made slight changes needed to track all headings that have an id applied.
Please note that IntersectionObserver will only observe changes in document's viewport, in our case - headings currently being displayed on the screen.

::::: {.panelset} ::: {.panel}

CSS elements

/* ScrollSpy active styles (see JAVASCRIPT tab for activation) */
.d-contents nav ul li.active > a { 
    font-weight: bold;  
    /* border-left: 1px solid #2780e3; */
    color: #0f2e3d !important;
  }

/* 💡 This small transition makes setting of the active state smooth */
.d-contents nav a {
      transition: all 0.1s ease-in-out;
  }

:::

::: {.panel}

JAVASCRIPT elements

window.addEventListener('DOMContentLoaded', () => {

  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      const id = entry.target.getAttribute('id');
      if (entry.intersectionRatio > 0) {
        document.querySelector(`[href="#${id}"]`).parentElement.classList.add('active');
      } else {
        document.querySelector(`[href="#${id}"]`).parentElement.classList.remove('active');
      }
    });
  });

  // Track all headings that have an `id` applied
  document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').forEach((h1, h2, h3, h4, h5, h6) => {
    observer.observe(h1, h2, h3, h4, h5, h6);
  });

});

::: :::::

Layouts

Default layout

l-body
library(ggplot2)
ggplot(diamonds, aes(carat, price)) + geom_smooth() +
  facet_grid(~ cut)

Wider layouts

l-body-outset
library(ggplot2)
ggplot(diamonds, aes(carat, price)) + geom_smooth() +
  facet_grid(~ cut)
l-page
library(ggplot2)
ggplot(diamonds, aes(carat, price)) + geom_smooth() +
  facet_grid(~ cut)

Tables with Knitr kable

library(knitr)
kable(head(mtcars))

Paged tables

library(rmarkdown)
paged_table(mtcars)
library(rmarkdown)
paged_table(mtcars)

Full screen layout

l-screen-inset shaded
library(leaflet)
leaflet() %>%
  addTiles() %>%  # Add default OpenStreetMap map tiles
  addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")
l-screen-inset
library(leaflet)
leaflet() %>%
  addTiles() %>%  # Add default OpenStreetMap map tiles
  addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")
l-screen
library(leaflet)
leaflet() %>%
  addTiles() %>%  # Add default OpenStreetMap map tiles
  addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")

Heading h1

Heading h2

Heading h3

Heading h4

Heading h5
Heading h6


jhelvy/distillery documentation built on Feb. 22, 2023, 2:11 p.m.