How to Add Code Folding to Output Chunks in Rmarkdown HTML Documents

How to add code folding to output chunks in rmarkdown html documents

TOC:

  1. Full control over which chunks should be folded

  2. Fold all chunks that contain more than one line of code/output


1. Full control over which chunks should be folded

I wanted to have the same functionality as well and did the following:

I created a JavaScript that looks as follows:

$(document).ready(function() {

$chunks = $('.fold');

$chunks.each(function () {

// add button to source code chunks
if ( $(this).hasClass('s') ) {
$('pre.r', this).prepend("<div class=\"showopt\">Show Source</div><br style=\"line-height:22px;\"/>");
$('pre.r', this).children('code').attr('class', 'folded');
}

// add button to output chunks
if ( $(this).hasClass('o') ) {
$('pre:not(.r)', this).has('code').prepend("<div class=\"showopt\">Show Output</div><br style=\"line-height:22px;\"/>");
$('pre:not(.r)', this).children('code:not(r)').addClass('folded');

// add button to plots
$(this).find('img').wrap('<pre class=\"plot\"></pre>');
$('pre.plot', this).prepend("<div class=\"showopt\">Show Plot</div><br style=\"line-height:22px;\"/>");
$('pre.plot', this).children('img').addClass('folded');

}
});

// hide all chunks when document is loaded
$('.folded').css('display', 'none')

// function to toggle the visibility
$('.showopt').click(function() {
var label = $(this).html();
if (label.indexOf("Show") >= 0) {
$(this).html(label.replace("Show", "Hide"));
} else {
$(this).html(label.replace("Hide", "Show"));
}
$(this).siblings('code, img').slideToggle('fast', 'swing');
});
});

Since I am no JS crack it might not be perfect, but it does what it is supposed to.
Include it in your Rmd file:

<script src="js/hideOutput.js"></script>

I also wrote some CSS definitions to style the button:

.showopt {
background-color: #004c93;
color: #FFFFFF;
width: 100px;
height: 20px;
text-align: center;
vertical-align: middle !important;
float: right;
font-family: sans-serif;
border-radius: 8px;
}

.showopt:hover {
background-color: #dfe4f2;
color: #004c93;
}

pre.plot {
background-color: white !important;
}

After including both, the JS file and the stylesheet, you can hide chunks by wrapping a div container around them with one of the following classes:

Hide output only

<div class="fold o">
```{r}
...
```
</div>

Hide source code

<div class="fold s">
```{r}
...
```
</div>

Hide both

<div class="fold s o">
```{r}
...
```
</div>

The script detects the type of each chunk (e.g. source code, text output or plot output) and labels the buttons accordingly.

The result looks like this:

Sample Image

Sample Image


2. Fold all chunks that contain more than one line of code/output

Here is a version of the script that adds the folding feature to all chunks that are longer than one line:

$(document).ready(function() {
$plots = $('img.plot');
$chunks = $('pre').has('code');
$chunks = $chunks.filter(function(idx) {
return $(this).children('code').outerHeight(false) > parseInt($(this).css('line-height'));
});

$chunks.each(function () {
if($(this).hasClass('r')) {
$(this).append("<div class=\"showopt\">Show Source</div><br style=\"line-height:22px;\"/>");
} else {
$(this).append("<div class=\"showopt\">Show Output</div><br style=\"line-height:22px;\"/>");
}
});

$plots.each(function () {
$(this).wrap('<pre class=\"plot\"></pre>');
$(this).parent('pre.plot').prepend("<div class=\"showopt\">Show Plot</div><br style=\"line-height:22px;\"/>");
});

// hide all chunks when document is loaded
$chunks.children('code').toggle();
$('pre.plot').children('img').toggle();
// function to toggle the visibility
$('.showopt').click(function() {
var label = $(this).html();
if (label.indexOf("Show") >= 0) {
$(this).html(label.replace("Show", "Hide"));
} else {
$(this).html(label.replace("Hide", "Show"));
}
$(this).siblings('code, img').slideToggle('fast', 'swing');
});
});

Just include it with <script src="js/hideAll.js"></script> and you don't need to wrap div containers around your code chunks.
One thing you have to add in your Rmd document though is the following global chunk option:

```{r, echo = F}
knitr::opts_chunk$set(out.extra = 'class="plot"')
```

It is needed to identify graphical output.

Code folding in rmarkdown: can't get div wrapper to work

Not quite sure why the ```{js} isn't working but if you replace it with the <script> and <style> tags it seems to work as expected.

---
title: "Toy markdown doc for js"
author: "Carrie Perkins"
date: "1/10/2021"
output: html_document
---


<script>
$(document).ready(function() {

$chunks = $('.fold');

$chunks.each(function () {

// add button to source code chunks
if ( $(this).hasClass('s') ) {
$('pre.r', this).prepend("<div class=\"showopt\">Show Source</div><br style=\"line-height:22px;\"/>");
$('pre.r', this).children('code').attr('class', 'folded');
}

// add button to output chunks
if ( $(this).hasClass('o') ) {
$('pre:not(.r)', this).has('code').prepend("<div class=\"showopt\">Show Output</div><br style=\"line-height:22px;\"/>");
$('pre:not(.r)', this).children('code:not(r)').addClass('folded');

// add button to plots
$(this).find('img').wrap('<pre class=\"plot\"></pre>');
$('pre.plot', this).prepend("<div class=\"showopt\">Show Plot</div><br style=\"line-height:22px;\"/>");
$('pre.plot', this).children('img').addClass('folded');

}
});

// hide all chunks when document is loaded
$('.folded').css('display', 'none')

// function to toggle the visibility
$('.showopt').click(function() {
var label = $(this).html();
if (label.indexOf("Show") >= 0) {
$(this).html(label.replace("Show", "Hide"));
} else {
$(this).html(label.replace("Hide", "Show"));
}
$(this).siblings('code, img').slideToggle('fast', 'swing');
});
});
</script>


<style>
.showopt {
background-color: #004c93;
color: #FFFFFF;
width: 100px;
height: 20px;
text-align: center;
vertical-align: middle !important;
float: right;
font-family: sans-serif;
border-radius: 8px;
}

.showopt:hover {
background-color: #dfe4f2;
color: #004c93;
}

pre.plot {
background-color: white !important;
}
</style>


<div class="fold s o">

```{r cars}
summary(cars)
```

</div>

Sample Image

Having said all that though I would also recommend looking into using the inbuilt code folding option to see if that can do what you need rather than relying on custom JS code:
https://bookdown.org/yihui/rmarkdown/html-document.html#code-folding

Code folding in bookdown

Global Hide/Show button for the entire page

To use @Yihui's hint for a button that fold all code in the html output, you need to paste the following code in an external file (I named it header.html here):

Edit: I modified function toggle_R so that the button shows Hide Global or Show Global when clicking on it.

<script type="text/javascript">

// toggle visibility of R source blocks in R Markdown output
function toggle_R() {
var x = document.getElementsByClassName('r');
if (x.length == 0) return;
function toggle_vis(o) {
var d = o.style.display;
o.style.display = (d == 'block' || d == '') ? 'none':'block';
}

for (i = 0; i < x.length; i++) {
var y = x[i];
if (y.tagName.toLowerCase() === 'pre') toggle_vis(y);
}

var elem = document.getElementById("myButton1");
if (elem.value === "Hide Global") elem.value = "Show Global";
else elem.value = "Hide Global";
}

document.write('<input onclick="toggle_R();" type="button" value="Hide Global" id="myButton1" style="position: absolute; top: 10%; right: 2%; z-index: 200"></input>')

</script>

In this script, you are able to modify the position and css code associated to the button directly with the style options or add it in your css file. I had to set the z-index at a high value to be sure it appears over other divisions.

Note that this javascript code only fold R code called with echo=TRUE, which is attributed a class="r" in the html. This is defined by command var x = document.getElementsByClassName('r');

Then, you call this file in the YAML header of your rmarkdown script, as in the example below:

---
title: "Toggle R code"
author: "StatnMap"
date: '`r format(Sys.time(), "%d %B, %Y")`'
output:
bookdown::html_document2:
includes:
in_header: header.html
bookdown::gitbook:
includes:
in_header: header.html
---

Stackoverflow question
<https://stackoverflow.com/questions/45360998/code-folding-in-bookdown>

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

## R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.

When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:

```{r cars}
summary(cars)
```

New Edit: Local Hide/show button for each chunk

I finally found the solution !

While looking at the code folding behavior for normal html output (no bookdown), I was able to add it to bookdown. The main javascript function needs to find .sourceCode class divisions to work with bookdown. However, this also requires complementary javascript functions of bootstrap, but not all. This works with gitbook and html_document2.

Here are the steps:

  1. Create a js folder in the same directory than your Rmd file
  2. Download javascript functions transition.js and collapse.js here for instance: https://github.com/twbs/bootstrap/tree/v3.3.7/js and store them in your js folder
  3. Create a new file in the js folder called codefolding.js with the following code. This is the same as for rmarkdown code_folding option but with pre.sourceCode added to find R code chunks:

codefolding.js code:

window.initializeCodeFolding = function(show) {

// handlers for show-all and hide all
$("#rmd-show-all-code").click(function() {
$('div.r-code-collapse').each(function() {
$(this).collapse('show');
});
});
$("#rmd-hide-all-code").click(function() {
$('div.r-code-collapse').each(function() {
$(this).collapse('hide');
});
});

// index for unique code element ids
var currentIndex = 1;

// select all R code blocks
var rCodeBlocks = $('pre.sourceCode, pre.r, pre.python, pre.bash, pre.sql, pre.cpp, pre.stan');
rCodeBlocks.each(function() {

// create a collapsable div to wrap the code in
var div = $('<div class="collapse r-code-collapse"></div>');
if (show)
div.addClass('in');
var id = 'rcode-643E0F36' + currentIndex++;
div.attr('id', id);
$(this).before(div);
$(this).detach().appendTo(div);

// add a show code button right above
var showCodeText = $('<span>' + (show ? 'Hide' : 'Code') + '</span>');
var showCodeButton = $('<button type="button" class="btn btn-default btn-xs code-folding-btn pull-right"></button>');
showCodeButton.append(showCodeText);
showCodeButton
.attr('data-toggle', 'collapse')
.attr('data-target', '#' + id)
.attr('aria-expanded', show)
.attr('aria-controls', id);

var buttonRow = $('<div class="row"></div>');
var buttonCol = $('<div class="col-md-12"></div>');

buttonCol.append(showCodeButton);
buttonRow.append(buttonCol);

div.before(buttonRow);

// update state of button on show/hide
div.on('hidden.bs.collapse', function () {
showCodeText.text('Code');
});
div.on('show.bs.collapse', function () {
showCodeText.text('Hide');
});
});

}

  1. In the following rmarkdown script, all three functions are read and included as is in the header, so that the js folder in not useful for the final document itself. When reading the js functions, I also added the option to show code blocks by default, but you can choose to hide them with hide.

rmarkdown code:

---
title: "Toggle R code"
author: "StatnMap"
date: '`r format(Sys.time(), "%d %B, %Y")`'
output:
bookdown::html_document2:
includes:
in_header: header.html
bookdown::gitbook:
includes:
in_header: header.html
---

Stackoverflow question
<https://stackoverflow.com/questions/45360998/code-folding-in-bookdown>


```{r setup, include=FALSE}
# Add a common class name for every chunks
knitr::opts_chunk$set(
echo = TRUE)
```
```{r htmlTemp3, echo=FALSE, eval=TRUE}
codejs <- readr::read_lines("js/codefolding.js")
collapsejs <- readr::read_lines("js/collapse.js")
transitionjs <- readr::read_lines("js/transition.js")

htmlhead <-
paste('
<script>',
paste(transitionjs, collapse = "\n"),
'</script>
<script>',
paste(collapsejs, collapse = "\n"),
'</script>
<script>',
paste(codejs, collapse = "\n"),
'</script>
<style type="text/css">
.code-folding-btn { margin-bottom: 4px; }
.row { display: flex; }
.collapse { display: none; }
.in { display:block }
</style>
<script>
$(document).ready(function () {
window.initializeCodeFolding("show" === "show");
});
</script>
', sep = "\n")

readr::write_lines(htmlhead, path = "header.html")
```

## R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.

When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:

```{r cars}
summary(cars)
```

```{r plot}
plot(cars)
```

This script shows the buttons in the Rstudio browser but does not work well. However, this is ok with firefox.

You'll see that there is a little css in this code, but of course you can modify the position and color and whatever you want on these buttons with some more css.

Edit: Combine Global and local buttons

Edit 2017-11-13: Global code-folding button well integrated with individual bloc buttons. Function toggle_R is finally not necessary, but you need to get function dropdown.js in bootstrap.

Global button is called directly in the code chunk when calling js files:

```{r htmlTemp3, echo=FALSE, eval=TRUE}
codejs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/codefolding.js")
collapsejs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/collapse.js")
transitionjs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/transition.js")
dropdownjs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/dropdown.js")

htmlhead <- c(
paste('
<script>',
paste(transitionjs, collapse = "\n"),
'</script>
<script>',
paste(collapsejs, collapse = "\n"),
'</script>
<script>',
paste(codejs, collapse = "\n"),
'</script>
<script>',
paste(dropdownjs, collapse = "\n"),
'</script>
<style type="text/css">
.code-folding-btn { margin-bottom: 4px; }
.row { display: flex; }
.collapse { display: none; }
.in { display:block }
.pull-right > .dropdown-menu {
right: 0;
left: auto;
}
.open > .dropdown-menu {
display: block;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
box-shadow: 0 6px 12px rgba(0,0,0,.175);
}
</style>
<script>
$(document).ready(function () {
window.initializeCodeFolding("show" === "show");
});
</script>
', sep = "\n"),
paste0('
<script>
document.write(\'<div class="btn-group pull-right" style="position: absolute; top: 20%; right: 2%; z-index: 200"><button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-_extension-text-contrast=""><span>Code</span> <span class="caret"></span></button><ul class="dropdown-menu" style="min-width: 50px;"><li><a id="rmd-show-all-code" href="#">Show All Code</a></li><li><a id="rmd-hide-all-code" href="#">Hide All Code</a></li></ul></div>\')
</script>
')
)

readr::write_lines(htmlhead, path = "/mnt/Data/autoentrepreneur/header.html")
```

The new global button shows a dropdown menu to choose between "show all code" or "hide all code". Using window.initializeCodeFolding("show" === "show") all codes are shown by default, whereas using window.initializeCodeFolding("show" === "hide"), all codes are hidden by default.



Related Topics



Leave a reply



Submit