Shiny App without Shiny: a JavaScript Exercise in R Markdown

Change the number of bins of a histogram with JavaScript

Yihui Xie 2023-02-07

Please ignore the title of this post. It’s an exaggeration. In this post, I want to show an example that has the feel of a Shiny app, but it doesn’t really use Shiny or R as the backend when it runs. This example is from an answer of mine on a forum, and mainly intended for beginners who are interested in learning a little bit JavaScript, although I’m not a JavaScript expert. Below is the full R Markdown document:

---
title: "Change the number of bins of a histogram"
---

```{r, setup, echo=FALSE}
n_breaks = c(5, 10, 15, 20)
shiny::selectInput(
    'n_breaks',
    label = 'Number of bins',
    choices = n_breaks,
    selected = 5)
```

Draw all histograms and put them in a container with
the class `hist-all`.

```{r, class.chunk='hist-all'}
x = faithful$eruptions
for (i in n_breaks) {
  hist(x, breaks = seq(min(x), max(x), length.out = i + 1),
       main = paste('n = ', i))
}
```

But we hide all of those images initially with CSS.

```{css}
/* hide all images */
.hist-all img { display: none; }
/* but show the image with the class 'show' */
.hist-all .show { display: block; }
```

When the choice in the select input is changed, show
the histogram with the selected number of bins.

```{js}
(d => {
  const s = d.getElementById('n_breaks'),  // the select input
     imgs = d.querySelectorAll('.hist-all img');  // all histograms
  
  // add the class 'show' to the i-th image, where i is the
  // selected number in the select input
  function showImg() {
    const i = s.selectedIndex;
    imgs.forEach((el, k) => {
      // k is the index of the image; i is the index of the choice
      // in the select input; add the class 'show' if the two
      // indices are equal
      el.classList.toggle('show', k == i); 
    });
  }
  
  // show the initial image
  showImg();
  
  // show a different image when select input is changed
  s.onchange = showImg;
})(document);
```

You can knit the document to HTML to play with the select input, or just visit my demo. Personally I liked this example because it’s a combination of my favorite technologies: R, CSS, and JavaScript. R for drawing plots, CSS for appearance, and JavaScript for interaction. I have tried to comment the code in detail, and I hope it’s not too hard to understand. The key piece of JavaScript is el.classList.toggle().

This example could be useful when you want to present several plots one by one on a static HTML page that has no R or Shiny running behind it. But guess what? Someday you may be able to run real Shiny apps in your web browser without R or Shiny—WASM for the win!