RMarkdown collapsible panel - r

As I am preparing tutorials for students, I need a way to hide content in collapsible panels which can be revealed by clicking on a button. I have got this to work using the code below. The RMarkdown file looks like this:
---
title: Collapsible Panel
output:
html_document:
theme: flatly
highlight: tango
---
<p>
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample1" role="button" aria-expanded="false" aria-controls="collapseExample1">
Click For Answer
</a>
</p>
<div class="collapse" id="collapseExample1">
<div class="card card-body">
```{r}
hist(1:10)
```
</div>
</div>
And it looks like this when rendered:
This works! I can also control if the code and/or results must be shown by controlling the chunk options.
But, this is not optimal because the code is messy and ugly with all the raw html. Copy-pasting this multiple times is not ideal. The ID used collapseExample1 needs to be unique every time this code block is used.
Is there some way to package this block into a reusable unit like a function or something? I am thinking something like an R function, where I can pass in code to be evaluated (or code that don't need to be evaluated), chunk options (eval, echo, results, etc..) and state of the panel (open/closed).
collapsible_panel(code=NULL,echo=TRUE,results="show",state="closed")
I have many unclear questions at this point. Can I run R chunks inside R chunks? Maybe I need to use child Rmd files? Do I need to write some custom javascript?

Another simple solution that would work (but without buttons and styling).
```{r, eval=FALSE}
hist(1:10)
```
<details>
<summary>Click for Answer</summary>
```{r, echo=FALSE, eval=TRUE}
hist(1:10)
```
</details>
And here are the two states:
Collapsed
Expanded

You can use multiple tabs (add {.tabset} after the header). It's very simple to generate them using r-markdown and they look almost the same as collapsible panel (of course you need to have more than one option).
Not to paste same code multiple times specify code argument in chunk options (code = readLines("code.R")). Or you can have only one panel for code and answer so you wouldn't need external document.
---
title: Collapsible Panel
output:
html_document:
theme: flatly
highlight: tango
---
# Question 1 {.tabset .tabset-fade .tabset-pills}
## Question
How does uniform distribution look like?
## Code
```{r, echo = TRUE, eval = FALSE, code = readLines("Q1.R")}
```
## Answer
```{r, echo = FALSE, eval = TRUE, code = readLines("Q1.R")}
```
Code file (Q1.R):
hist(1:10)
To not have any content and then show answer you can make first tab completely empty with:
# Question 1 {.tabset}
## <span>​</span>
## Answer
```{r, echo = FALSE, eval = TRUE, code = readLines("Q1.R")}
```

Two slightly different methods are shown. Both approaches use only HTML and CSS. Here is the full working Rmd.
---
title: Accordion
output:
html_document
---
## Method 1
This method uses button.
```{css,echo=FALSE}
button.btn.collapsed:before
{
content:'+' ;
display:block;
width:15px;
}
button.btn:before
{
content:'-' ;
display:block;
width:15px;
}
```
```{r,echo=FALSE,results='hide'}
knitr::knit_hooks$set(drop1=function(before, options, envir) {
if (before) {
paste(
'<p>',
'<button class="btn btn-primary collapsed" data-toggle="collapse" data-target="#ce1">',
'</button>',
'</p>',
'<div class="collapse" id="ce1">',
'<div class="card card-body">', sep = "\n")
} else {
paste("</div>", "</div>", sep = "\n")
}
})
```
```{r,drop1=TRUE,results="markup"}
str(iris)
```
## Method 2
This method uses a link which behaves like a button.
```{css,echo=FALSE}
[data-toggle="collapse"].collapsed .if-not-collapsed {
display: none;
}
[data-toggle="collapse"]:not(.collapsed) .if-collapsed {
display: none;
}
```
```{r,echo=FALSE,results='hide'}
knitr::knit_hooks$set(drop2=function(before, options, envir) {
if (before) {
paste(
'<p>',
'<a class="btn btn-primary collapsed" data-toggle="collapse" href="#ce2">',
'<span class="if-collapsed">+</span>',
'<span class="if-not-collapsed">-</span>',
'</a>',
'</p>',
'<div class="collapse" id="ce2">',
'<div class="card card-body">', sep = "\n")
} else {
paste("</div>", "</div>", sep = "\n")
}
})
```
```{r,drop2=TRUE,results="markup"}
str(iris)
```
Executed R chunks can be hidden in collapsible containers (collapsed by default). The containers are defined in the R chunk options using a custom knitr hook (drop1/drop2). The collapsible states of the container is controlled using a button or a link (looks like a button). Custom CSS is used to change text on the button for collapsed/open states.

Related

Pass body of Rmarkdown chunk as argument to child document

I've designed an expandable info box for my blog (which is rendered from Rmarkdown) which I want to use as a template.
At the momentan I just write more or less plain HTML in the main Rmarkdown document, as below:
<div class="info" markdown="1">
<details>
<summary class="info-header" markdown="1">
Title <i class="fas fa-info-circle"></i>
</summary>
<p class="info-details">
Here goes the body
</p>
</details>
</div>
With some CSS this renders nicely:
Ideally I would like to replace the HTML above by using some kind of child document which has a title argument to specify the title, and (and this is the tricky part) which passes the whole body of the Rmarkdown chunk to the body section of the child document. So ideally the above HTML could be written as.
```{r, child = "info_box.Rmd", title = "My Title"}
Here goes the body
```
Is this possible? I understand how to insert a child document, also with arguments, and when looking at rmarkdown::hmtl_document() I do get a glimpse of how the use real arguments like title, but can we pass the body of the Rmarkdown chunk down to the child so that it is used at a specific place? Do we need a custom output format for this and how would it look like?
Any help appreciated. I'm also open for other solutions, but it would be more than great if the ideal approach above would work.
I am giving another answer which is based on knitr output hooks and Pandoc Lua filter that also works fine for rmarkdown::html_document (and probably a better approach than the previous one).
Pass the child document path to the chunk option child_into_box and specify the box title to option box_title. One very important detail to note is that you must at least insert a comment in that chunk, otherwise for an empty chunk the redefined knitr source hook does not work.
---
title: Passing Child document Text to Info box.
output:
html_document:
pandoc_args:
- --lua-filter=box_wrap.lua
---
## Rmarkdown
Some text
```{r}
#| echo: false
library(knitr)
default_source_hook <- knit_hooks$get('source')
knit_hooks$set(
source = function(x, options) {
if(is.null(options$child_into_box))
default_source_hook(x, options)
else {
res <- knitr::knit_child(input = options$child_into_box, envir = environment(), quiet = TRUE)
div_start = paste0("::: {wrap-box=true box-title='", options$box_title, "'}")
paste0(div_start, "\n", unlist(res), "\n", "\n:::\n")
}
}
)
```
Some more text
```{r}
#| child_into_box: info_box.Rmd
#| box_title: my info test box
# comment
```
box_wrap.lua
local str = pandoc.utils.stringify
local template_info_box_p1 = [[
<div class="note" markdown="1">
<details>
<summary class="note-header" markdown="1">
%s<i class="fas fa-info-circle"></i>
</summary>
<div class="note-details">
]]
local template_info_box_p2 = [[
</div>
</details>
</div>
]]
function Div(el)
if el.attributes['wrap-box'] then
local title = str(el.attributes['box-title'])
local info_box = string.format(template_info_box_p1, title)
local info_box_rb1 = pandoc.RawBlock('html', info_box)
local info_box_rb2 = pandoc.RawBlock('html', template_info_box_p2)
el.content:insert(1, info_box_rb1)
el.content:insert(info_box_rb2)
return el
end
end
Since the output format is HTML, a possible solution based on pandoc lua filter and Javascript is as follows,
main.Rmd
---
title: Passing Child document Text to Info box.
output:
html_document:
includes:
after_body: infoBox.html
pandoc_args:
- --lua-filter=info_box.lua
---
## Rmarkdown
::: {.info-box title="My new Title" #first}
:::
::: {.add-to-info #first}
```{r}
#| child: info_box.Rmd
```
Also some random text to be inserted into that info box
:::
::: {.info-box title="More info box" #second}
:::
::: {.add-to-info #second}
```{r}
#| child: info_box.Rmd
```
Also some random text to be inserted into that info box
:::
So to add contents from child-document to the info box you need to do two thing.
Firstly, create a pandoc div (using :::) with class .info-box, give it title that you want and give a unique id (suppose, #first) so that we could use it to insert the content of a child document to this specific info box.
secondly, wrap the code chunk with the child option with another pandoc div associated with class .add-to-info and use the id of the info box to where you want the content of this child document to be.
Now following these two steps you can generate as many as info box you want.
info_box.Rmd
Lorem ipsum dolor sit amet, eget, etiam, a metus purus sit quisque elit, suscipit.
```{r}
1 + 1
```
```{r}
plot(1:10)
```
```{r}
head(mtcars)
```
(no need to use title or output for the child document, it simply contains the body text)
info_box.lua
local template_info_box_p1 = [[
<div class="info" markdown="1">
<details>
<summary class="info-header" markdown="1">
%s <i class="fas fa-info-circle"></i>
</summary>
<p class="info-details" id="%s">
]]
local template_info_box_p2 = [[
</p>
</details>
</div>
]]
function Div(el)
if el.classes:includes('info-box') then
local title = el.attributes['title']
local id = el.identifier
local info_box_p1 = string.format(template_info_box_p1, title, id)
local info_box_html_p1 = pandoc.RawBlock('html', info_box_p1)
local child_content = el.content
local info_box_html_p2 = pandoc.RawBlock('html', template_info_box_p2)
return pandoc.Div({info_box_html_p1, info_box_html_p2})
end
end
infoBox.html
<script>
function add_to_info() {
let childs = document.querySelectorAll("div.add-to-info");
let info_box = document.querySelectorAll('p.info-details');
childs.forEach(el => {
info_box.forEach(box => {
if (el.id === box.id) {
box.appendChild(el);
}
});
});
}
window.onload = add_to_info();
</script>
A portion of the rendered ouput
Note that, this output lacks the necessary CSS styles along with fontawesome icon, which you can handle accordingly using the embedded classes info-header and info-details.
I found a first answer looking at this SO post:
I could create a template info_box.Rmd like:
---
title: ""
output: rmarkdown::html_document
---
<div class="note" markdown="1">
<details>
<summary class="note-header" markdown="1">
`r title`<i class="fas fa-info-circle"></i>
</summary>
<p class="note-details">
`r body`
</p>
</details>
</div>
And then replace the intial HTML from my post with:
```{r, echo = FALSE, results='asis'}
title <- "My new Title"
body <- "The new body"
cat(
knitr::knit_child("info_box.Rmd",
envir = environment(),
quiet = TRUE)
)
```
And it works:
However, I'd still prefer a solution looking like this:
```{r, child = "info_box.Rmd", title = "My Title"}
Here goes the body
```

How can I adjust the style of the output from a .Rmd chunk?

Here is an example code chunk and it's output.
`{r example, message = F}
for (i in 1:5) {
print(i)
}
`
I would like this to render in my output file without the border box, and without the leading ## [1]. Is that possible?
This solution also removes the border box.
Use the r chunk comment option to remove the ## character.
Use cat() instead of print() to display the output without the R formatting [1]. You need to specify in cat() that you want newlines added.
One method to remove the border box would be to use css. You can use an external css file, or make a dedicated hidden chunk to specify it.
I noticed the code chunks and output chunks were both specified by the <pre> tag, with the code chunk being of class .r, and the output chunk being .hljs. These might change with different themes, but this selector worked for me. pre.hljs might work alternatively as a selector.
Below is a complete .Rmd file that can be knit to an html document
---
title: example.Rmd
output: html_document
---
```{css, echo = FALSE}
pre:not(.r) {
border: 0px;
}
```
```{r, comment = ""}
for (i in 1:5) {
cat(i, "\n")
}
```
Use the following code
{r example, message = FALSE, comment = ''}
for (i in 1:5) {
cat(i, '\n')
}

Cannot render custom blocks with cat()

As explained in this chapter of the R Markdown Cookbook, it is possible to make custom blocks in R Markdown files with this syntax (here, to center some text):
:::{.center data-latex=""}
Hello
:::
However, using cat() to render this sort of block (as explained in this chapter) does not work.
Full example:
---
output: rmarkdown::pdf_document
---
<!-- WORKS -->
:::{.center data-latex=""}
Hello
:::
<!-- DOES NOT WORK -->
```{r, results='asis', echo=FALSE}
print(
'
:::{.center data-latex=""}\n
Hello\n
:::
'
)
```
Why is the second part not rendering correctly? How to solve that?
There are two issues:
You use print() instead of cat() (even if you refer to cat() in your post
You put spaces at the beginning of each line which prevents the custom block to be rendered.
If you prefer the lines to be indented (as I do) then I would suggest make each line a separate string as I do in the second example. This also has the advantage that you could add the line breaks via the sep argument instead of having to add them manually.
Reproducible example:
---
output:
rmarkdown::pdf_document:
keep_md: true
---
<!-- WORKS -->
:::{.center data-latex=""}
Hello
:::
<!-- WORKS TOO -->
```{r, results='asis', echo=FALSE}
cat('
:::{.center data-latex=""}\n
Hello\n
:::
', sep = "\n"
)
```
<!-- WORKS TOO -->
```{r, results='asis', echo=FALSE}
cat(':::{.center data-latex=""}',
'Hello',
':::', sep = "\n"
)
```

Numbered captions on customized and reactive figure in R markdown HTML file

In recent years, it has been possible to add numbering to the figure captions on R chunks in html_document2, as outlined in Yihui Xie's online text for bookdown. However, it has been more difficult to preserve this numbering while also having custom figures output from these chunks. For example, trying to create the custom figures using CSS flexbox from Carson Sievert's online text.
There are several other threads that discuss numbering HTML Rmd figures and using hooks or CSS counters to add custom numbering. However, I could not find a solution that allowed for a custom figure as well as html_document2 numbering to be preserved.
In the example below, I want a side-by-side plotly graphic to be reactive to screen size but also have the same figure caption and numbering.
---
output:
bookdown::html_document2:
self-contained: TRUE
---
```{css, echo=FALSE}
#dualpanel {
width: 50%
}
#media screen and (max-width: 500px) {
#dualpanel {
width: 100%
}}
```
```{r chunk1, echo=FALSE, htmlcap='FIRST FIGURE CAP'}
temp <- plotly::plot_ly(mtcars, x = ~cyl, y=~mpg)
shiny::div(class = 'figure',
style = "display: flex; flex-wrap: wrap; justify-content: center",
shiny::div(temp, id = 'dualpanel'),
shiny::div(temp, id = 'dualpanel'))
```
```{r chunk2, echo=FALSE, fig.cap='SECOND FIGURE CAP'}
plot(mtcars$cyl, mtcars$mpg)
```
This creates an output like this:
In order to address this issue, it is important to know that knitr uses #fig:label in pandoc in order to create the automated numbering in the output. You can see this when you examine the markdown temporary output from the knitr process:
<div class="figure">
<img src="web_tiles_v2_files/figure-html/otherchunk-1.png" alt="SECOND FIGURE CAP" />
<p class="caption">(\#fig:SPECIFICCHUNKOFINTEREST)SECOND FIGURE CAP</p>
</div>
As such, one should be able to maintain the customized figure output by adjusting the custom hook seen in other solutions. This would look like the following, just make sure you have the right chunk label included:
```{r}
knit_hooks$set(customcap= function(before, options, envir) {
if(!before) {
paste('<p class="caption"> (\\#fig:chunk1)',options$customcap,"</p>",sep="")
}
})
```
One could also use some of the internal knitr functions to automatically grab the fig.lp and label: paste('<p class="caption">', knitr:::create_label(options$fig.lp, options$label), options$customcap,"</p>", sep="")
The entire code would look like:
---
output:
bookdown::html_document2:
self-contained: TRUE
---
```{css, echo=FALSE}
#dualpanel {
width: 50%
}
#media screen and (max-width: 500px) {
#dualpanel {
width: 100%
}}
```
```{r}
knit_hooks$set(customcap= function(before, options, envir) {
if(!before) {
paste('<p class="caption"> (\\#fig:chunk1)',options$customcap,"</p>",sep="")
}
})
```
```{r chunk1, echo=FALSE, customcap='FIRST FIGURE CAP'}
temp <- plotly::plot_ly(mtcars, x = ~cyl, y=~mpg)
shiny::div(class = 'figure',
style = "display: flex; flex-wrap: wrap; justify-content: center",
shiny::div(temp, id = 'dualpanel'),
shiny::div(temp, id = 'dualpanel'))
```
```{r chunk2, echo=FALSE, fig.cap='SECOND FIGURE CAP'}
plot(mtcars$cyl, mtcars$mpg)
```
This produces an output that has the dynamic elements and maintains labeling.

How can I align the html output of a Rmd file to the left

when I knit to html a Rmd file the output I visualize on my browser is always centered.
How can I justify everything to the left so there is no wasted space ?
By wasted space I mean there is space on the left of the TOC that is unused
EDIT
I am aware of this thread but I want to keep the TOC, what I really want is equivalent to move everything to the left so the TOC is next to the left-justified
code:
---
title: "Example SO"
output:
html_document:
toc: true
number_sections: true
code_folding: "hide"
toc_float:
collapse: false
smooth_scroll: false
---
```{r setup, echo=FALSE}
knitr::opts_chunk$set(error = TRUE,
echo = TRUE,
message = FALSE,
comment = "",
fig.align = "left"
)
```
# H1 Stuff
```{r, DT}
DT::datatable(mtcars, rownames = FALSE, options = list(pageLength = 2))
```
# H2 More stuff
Bla
output:
The reason you are seeing the "centered" behavior is because of CSS, specifically the rule:
div.main-container {
max-width: 1200px;
}
Which is being imported by something in the kniting/generation process, not sure what.
But Rmd allows you to add your own CSS rules. So you can overwrite the problematic rule with this rule taking advantage of CSS's !important:
div.main-container {
max-width: 100% !important;
}
In a file named "styles.css" located in the same directory as your Rmd file. Then reference the CSS file in your front-matter (YAML header):
title: "Example SO"
output:
html_document:
toc: true
number_sections: true
code_folding: "hide"
toc_float:
collapse: false
smooth_scroll: false
css: styles.css
You will get a result with the contents shifted all the way to the left edge of the browser.
This is one path to get to your request of "no wasted space", but there are lots of other routes if you really want left-justified but fixed width content.
If you want to "soak up the space between the TOC table and the content block you could add this to 'style.css'
.tocify {
max-width: 100% !important;
}
.toc-content {
padding-left: 10px !important;
}

Resources