Creating dynamic tabs in Rmarkdown - r

In Rmarkdown, it's possible to create tabs, for example:
---
output: html_document
---
# Tabs {.tabset}
## Tab 1
foo
## Tab 2
bar
I'm wondering if it's possible to create an arbitrary number of tags? How can I create a tab programatically?
The following code is a poor attempt to do this, but it results in a heading instead of a tab.
---
output: html_document
---
# Tabs {.tabset}
```{r echo=FALSE}
shiny::tags$h2("Tab 1")
```
foo
## Tab 2
bar
Solution
Thanks to #GGamba for providing a great solution. I needed to go one step further and be able to add tabs as part of a loop, so I needed to make two changes. First of all, I used this code to dynamically add tabs (the only difference here is that I force the evaluation of hrefCode inside the timeout because otherwise all timeouts called together will use the same value)
(function(hrefCode){setTimeout(function(){
var tabContent = document.createElement('div');
var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
tabContent.setAttribute('id', 'tab-' + hrefCode);
tabContent.setAttribute('class', 'tab-pane')
tabContent.innerHTML = '", gsub('\n', '', Panel, fixed = TRUE), "';
tabContainerTarget.appendChild(tabContent);
}, 100);
})(hrefCode);
Secondly, to add tabs in a loop, you can do something like this:
tabsToAdd <- list("tab3" = "hello", "tab4" = "world")
shiny::tagList(lapply(names(tabsToAdd), function(x) {
addToTabset(title = x, tabsetId = 'tbSet1',
tabPanel(x, tabsToAdd[[x]]))
}))

There is also a simple rmarkdown solution to this problem that does not require shiny and/or custom javascript. Does not work for all kinds of R output (see below):
## Tabbed Example {.tabset}
```{r, results = 'asis'}
for (nm in unique(iris$Species)){
cat("### ", nm, "\n")
cat(knitr::knit_print(plot(iris[iris$Species == nm, ])))
cat("\n")
}
```
A more involved method, that first creates a list of raw Rmarkdown code as a list of character vectors, which are then evaluated in a separate (inline) code chunk with knitr::knit(). This works for all kinds of output, not just base plots.
## Tabbed Example ggplot {.tabset}
```{r}
library(ggplot2)
template <- c(
"### {{nm}}\n",
"```{r, echo = FALSE}\n",
"ggplot(iris[iris$Species == '{{nm}}', ], aes(x = Sepal.Length, y = Sepal.Width)) + geom_point()\n",
"```\n",
"\n"
)
plots <- lapply(
unique(iris$Species),
function(nm) knitr::knit_expand(text = template)
)
```
`r knitr::knit(text = unlist(plots))`

As far as I know what you are trying to do is not possible in rmarkdown (but I'd love to stand corrected). But of course we can implement a function to do just that.
I based my answer on this answer by #KRohde, so all the credits goes to him. I just adapted it to work in a simpler markdown document.
The answer is mostly build with JS rather than R, but as the markdown is mostly an HTML I feel JS is a better tool.
Here is the code:
---
output: html_document
---
```{r echo=FALSE, results='asis'}
library(shiny)
addToTabset <- function(title, tabsetId, Panel) {
tags$script(HTML(paste0("
/* Getting the right tabsetPanel */
var tabsetTarget = document.getElementById('", tabsetId, "');
/* Creating 6-digit tab ID and check, whether it was already assigned. */
hrefCode = Math.floor(Math.random()*100000);
/* Creating node in the navigation bar */
var navNode = document.createElement('li');
var linkNode = document.createElement('a');
linkNode.appendChild(document.createTextNode('", title, "'));
linkNode.setAttribute('data-toggle', 'tab');
linkNode.setAttribute('data-value', '", title, "');
linkNode.setAttribute('href', '#tab-' + hrefCode);
navNode.appendChild(linkNode);
tabsetTarget.appendChild(navNode);
setTimeout(function(){
var tabContent = document.createElement('div');
var tabContainerTarget = document.getElementsByClassName('tab-content')[0];
tabContent.setAttribute('id', 'tab-' + hrefCode);
tabContent.setAttribute('class', 'tab-pane')
tabContent.innerHTML = '", gsub('\n', '', Panel, fixed = T), "';
tabContainerTarget.appendChild(tabContent);
}, 100);
")
))
}
```
The code above should stay in a 'setup chunk', as it define an R function to call a JS function that mostly just add the right things to the DOM.
It can then be used when needed, passing the tabPanel title, the 'target' tabset and the normal tabPanel function.
```{r results='asis', echo=FALSE}
shiny::tabsetPanel(id = 'tbSet1',
shiny::tabPanel('Tab 1', 'foo'),
shiny::tabPanel('Tab 2', 'bar')
)
```
```{r results='asis', echo=FALSE}
addToTabset(title = 'Tab 3',
tabsetId = 'tbSet1',
tabPanel(
h1('This is a title'),
actionButton('btn',label = 'Clicky button'),
radioButtons('asd', LETTERS[1:5], LETTERS[1:5])) )
```

This also appears to be a good way to also deal with the problem of dynamic tabsets with plotly graphics at https://github.com/ropensci/plotly/issues/273
I created the plots first and saved them in a list which I then extract and display with a simple code in the template variable below. This saves me from having to enclose the large code in quotes in the template file.
However, if I try this trick a second time in the same document, it complains with a "duplicate chunk label "unnamed-chunk-1"... error and will not compile.
This appears to be "fixable" by specifying options(knitr.duplicate.label = "allow") but are there any "consequences" to allowing duplicate labels that I need to be aware of? I read https://bookdown.org/yihui/rmarkdown-cookbook/duplicate-label.html and I think I'm ok but is there a better way than allowing duplicate chunk labels?
---
title: "Tabsets for plotly graphs"
output:
html_document:
number_sections: yes
toc: yes
toc_depth: 4
---
## Tabbed Set 1 {.tabset}
```{r}
options(knitr.duplicate.label = "allow")
library(plotly)
plotlist <- plyr::dlply(iris, "Species", function(iris.part){
plot_ly(data=iris.part, x = ~Sepal.Length, y = ~Sepal.Width)
})
template <- c(
"### First {{nm}}\n",
"```{r, echo = FALSE}\n",
"plotlist[[{{nm}}]] \n",
"```\n",
"\n"
)
plots <- lapply(1:length(plotlist), function(nm) {knitr::knit_expand(text = template)})
```
`r knitr::knit(text = unlist(plots))`
## Tabbed set 2 {.tabset}
```{r}
library(plotly)
template <- c(
"### Second {{nm}}\n",
"```{r, echo = FALSE}\n",
"plotlist[[{{nm}}]] \n",
"```\n",
"\n"
)
plots <- lapply(1:length(plotlist), function(nm) {knitr::knit_expand(text = template)})
```
`r knitr::knit(text = unlist(plots))`

Related

Modify chunk options via knit_hooks

I want to define a custom chunk hook to certain r-markdown chunks, which override certain chunk options (include and eval, basically).
Use case is that I add this chunk option to a chunk, which will hide code and output per default (include = FALSE) and evaluates the chunk only if a file exists eval = <some logic>. This works if I hardcode it (cf. to chunk export_disp_mpg_by_hand but not via a custom chunk option.
But regardless of how I try it, the options are not considered.
Code
---
title: "Chunks Options"
author: "Me"
output: html_document
---
```{r setup, include = FALSE, message = FALSE}
library(ggplot2)
library(knitr)
library(here)
image_path <- "figs"
if (!dir.exists(here(image_path))) {
dir.create(here(image_path))
}
knit_hooks$set(export_fig = function(before, options, envir) {
if (before) {
fn <- gsub("^export_", "", options$label)
options$include <- FALSE
options$eval <- !file.exists(here(image_path, paste0(fn, ".png")))
### Does not work either
## opts_current$set(include = FALSE,
## eval = !file.exists(here(image_path, paste0(fn, ".png"))))
}
options
})
```
# Plot
```{r make_plot}
(pl <- ggplot(mtcars, aes(disp, mpg)) +
geom_point())
```
```{r export_disp_mpg, export_fig = TRUE}
ggsave(here(image_path, "disp_mpg.png"), pl)
```
```{r export_disp_mpg_by_hand, include = FALSE, eval = !file.exists(here(image_path, "disp_mpg.png"))}
ggsave(here(image_path, "disp_mpg.png"), pl + geom_smooth(method = "lm"))
```
Expected Behaviour
Chunk export_disp_mpg should never be shown in the rendered output and the the file it creates should only be created if it is not existing.
So close, I found the solution. In case anybody is interested, I leave the question here with the follwong working answer.
Instead of using knit_hooks, one should use opts_hooks:
```{r setup, include = FALSE}
opts_hooks$set(export_fig = function(options) {
fn <- gsub("^export_", "", options$label)
options$include <- FALSE
options$eval <- !file.exists(here(image_path, paste0(fn, ".png")))
options
})
```

R Markdown html: main and axis labels of plots as text suitable for search-function

I want to generate a R markdown html document with plots and it should be possible to jump to a certain plot by search-function (in my example there are 3 plots and I want to jump in the html-doc to the plot, where the main is "rivers").
I think, the problem is, that main and axis labels of a plot are grafical elements, like the plot itself, and not text. So the search-function doesn't work.
Of course it would be possible to add manually text before each plot, but as all my plots are generated with a for-loop, I don_t know how to do it.
is there a possibilty to include text-output in this kind of for-loop or are there other ideas, how the main or axis labels of a plot can be suitable for search-function?
thanks in advance!
---
title: "search function test"
author: "Michel Grün"
date: "last edited `r format(Sys.Date(),'%d.%m.%Y')`"
output:
html_document:
df_print: paged
---
knitr::opts_chunk$set(echo = TRUE,warning = FALSE)
df<-data.frame(x=seq(1,20),
trees=rnorm(20,4,3),
mountains=rnorm(20,6,3),
rivers=rnorm(20,4,4))
for(i in 2:length(colnames(df))){
plot(df$x,df[,i],
main=colnames(df)[i],
xlab=colnames(df)[1],
ylab=colnames(df)[i])
}
solved in another issue: https://stackoverflow.com/a/57034752/16578253
in this issue, the question is slightly different, but a solution shown there was also the solution for my problem. The idea is to create headings + outputs within a loop. As result, in the output dokument every header is followed by a plot and the header is of course suitable for search-function. It's important to use the argument results='asis' in the chunk konfiguration to allow that cat() is interpreted as Markdown syntax. Furthermore the
cat()ing tshould be surrounded by some newlines to make sure it's interpreted properly.
You can combine a svg device with a knitr hook:
---
title: "search function test"
author: "Michel Grün"
date: "last edited `r format(Sys.Date(),'%d.%m.%Y')`"
output:
html_document:
df_print: paged
---
```{r setup}
library(ggplot2)
library(knitr)
# see https://github.com/yihui/knitr/issues/754
local({
hook_plot <- knit_hooks$get("plot")
knit_hooks$set(plot = function(x, options) {
x <- paste(x, collapse = ".")
if (!grepl("\\.svg", x)) {
return(hook_plot(x, options))
}
# read the content of the svg image and write it out without <?xml ... ?>
paste(readLines(x)[-1], collapse = "\n")
})
})
opts_chunk$set(echo = TRUE, warning = FALSE, dev = "svglite")
df <- data.frame(
x = seq(1, 20),
trees = rnorm(20, 4, 3),
mountains = rnorm(20, 6, 3),
rivers = rnorm(20, 4, 4)
)
```
```{r}
for (i in 2:length(colnames(df))) {
plot(df$x, df[, i],
main =paste0(colnames(df)[i], " äöα😋"),
xlab = colnames(df)[1],
ylab = colnames(df)[i]
)
}
```

Is there a way to have the output of a function in R be an R markdown chunk?

I'm working on a project to make it easier to create flex/shiny dashboards from qualtrics surveys. I'd really like to be able to write a couple functions that would let co-workers who have less experience with R be able to make similar documents without having to know Rmarkdown syntax.
For example, if someone wanted to make a one page dashboard with a scatterplot, I'd like to be able to have them use a couple functions like (make_dashboard, make_page) etc:
make_dashboard(
title = "Qualtrics Report Dashboard",
page 1 = make_page(header = "Page 1", format = "column", render = "plot",
data = survey_data, variables = c("var1", "var2"))
)
which would then create a rmd file with this:
---
title: "Qualtrics Report Dashboard"
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: scroll
runtime: shiny
---
Page 1
=====================================
renderPlot( {
ggplot(data = survey_data, mapping = aes_string(x = var1,
y = var2)) +
geom_point() +
labs(x = get_label(get(var1, survey_data)),
y = get_label(get(var2, survey_data)))
}
)
I haven't gotten very far with trying to write these functions / implement this logic, because I'm not even sure if I'm thinking about it in the right way - is it possible to create rmarkdown chunks with functions like this?
I've looked at other posts 1 and 2 about child documents in knitr, but I don't really want every chunk to be the same, rather have the person be able to change certain aspects (e.g. type of plot, data, etc.).
Not sure if this will be useful to anyone else, but I ended up using whisker (https://github.com/edwindj/whisker), which can render strings into documents to construct an Rmd in the style of flexdashboard.
TLDR: Essentially I made functions that create strings of text matching the building blocks of flexdashboard. With whisker, you can pass in variables by encasing words in the string with two bracket parentheses and then assigning their values with a list of var_name = value for each variable in the string, e.g.
template <- "My name is {{name}}."
d <- list(name = "Emily")
cat(whisker.render(template, data = d))
print(d)
My name is Emily
I used a combination of this and the str_c from stringr to construct strings for different elements of the flexdashboard, allowing the user to input variables like title, variable for plots, etc. that then could be rendered into the string using whisker. Then, I joined all of those strings together and render it into an Rmd file. Honestly, I am not sure this is actually easier for people who don't know R to use, and I'll probably end up doing something different, but I wanted to share in case anyone is thinking about this.
Example: running the chunk below creates a file called "test_dashboard.Rmd" with the text format for a flexdashboard with a 1 input sidebar and a single page with one plot.
```
make_dashboard(title = "Test Dashboard",
sidebar = make_sidebar(sidebar_title = "here is the input",
input_type = "multi-select",
input_name = "Interesting Var #1"),
page1 = make_page(page_title = "Cool Plots!",
element_one = make_plot(plot_title = "this is my plot",
type = "bivariate",
vars = c("cool_var1",
"cool_var2"))),
fn = "test_dashboard")
```
OUTPUT:
```
---
title: Test Dashboard
output:
flexdashboard::flex_dashboard:
orientation: columns
vertical_layout: scroll
runtime: shiny
---
\```{r setup, include=FALSE}
library(flexdashboard)
library(tidytext)
library(tidyverse)
library(janitor)
library(DT)
library(gghighlight)
library(knitr)
library(shiny)
library(qualtRics)
library(curl)
library(sjlabelled)
library(naniar)
library(scales)
library(lme4)
library(MASS)
library(snakecase)
\```
\```{r global, include=FALSE}
#setting global options for table scrolling and plot theme
options(DT.options = list(scrollY="100vh"))
theme_set(theme_minimal())
#this fetches all of your survey info
surveys <- all_surveys()
#this saves the survey responses into
docusign_survey <- fetch_survey(surveyID = surveys$id[1],
verbose = TRUE,
label = TRUE,
breakout_sets = TRUE,
force_request = TRUE)
#this saves the question text into a dataframe
questions <- survey_questions(surveyID = surveys$id[1])
rename_df <- rename_variables(docusign_survey)
#this renames all of the variables
docusign_survey <- docusign_survey %>%
rename_at(as.vector(rename_df$old_name), ~ as.vector(rename_df$new_labels))
#new variables
new_var <- rename_df$new_labels
#which are multi_select?
multi_select <- rename_df %>%
filter(ms == 1) %>%
dplyr::select(new_labels)
#relabel those NAs as No
docusign_survey <- docusign_survey %>%
purrr::modify_at(multi_select$new_labels, na_to_y)
\```
Sidebar {.sidebar}
=====================================
here is the input
\```{r}
selectInput("p_var_1", label = "Interesting Var #1",
choices = new_var,
multiple = TRUE)
\```
Cool Plots!
=====================================
Column {.tabset}
-------------------------------------
### this is my plot
\```{r}
renderPlot( {
make_bivariate_plot(docusign_survey, input$cool_var1, input$cool_var2)
})
\```
```
Functions
make_dashboard()
I saved the parts that will repeat every time, probably will want to make them editable for changes in scrolling, etc. but just trying to make proof of concept at the moment.
```
make_dashboard <- function(title, sidebar, page1, fn){
load("data/top_matter.rda")
load("data/libraries.rda")
load("data/main_chunk.rda")
initial_bit <- stringr::str_c(top_matter, libraries, main_chunk, sep = "\n\n")
intermediate <- stringr::str_c(initial_bit, sidebar, sep = "\n\n")
total <- stringr::str_c(intermediate, page1, sep = "\n\n")
data <- list(title = title)
out_fn <- paste0("./", fn, ".Rmd")
writeLines(whisker.render(total, data), con = out_fn)
}
```
make_sidebar()
```
make_sidebar <- function(sidebar_title, input_type, input_name){
top_sidebar <-
'Sidebar {.sidebar}
=====================================
'
sidebar_text <- str_c(top_sidebar, sidebar_title, sep = "\n\n")
if(input_type == "multi-select"){
ms <- "TRUE"
} else {
ms <- "FALSE"
}
input_one <- make_select_input(input_name, ms)
sidebar_total <- str_c(sidebar_text, "```{r}", input_one, "```", sep = "\n\n")
return(sidebar_total)
}
```
make_page()
```
make_page <- function(page_title, element_one){
top_page <-
'{{page_title}}
=====================================
Column {.tabset}
-------------------------------------'
add_element <- stringr::str_c(top_page, element_one, sep = "\n\n")
data <- list(page_title = page_title)
page <- whisker.render(add_element, data = data)
return(page)
}
```
make_plot()
```
make_plot <- function(plot_title, type = c("univariate", "bivariate"), vars){
top_plot_piece <-' {{plot_title}}
\```{r}
renderPlot( {
'
if(type == "univariate"){
plot_piece <-
'make_univariate_plot(docusign_survey, input${{vars}})
})
\```'
total_plot <- stringr::str_c(top_plot_piece, plot_piece, sep = "\n\n")
data <- list(plot_title = plot_title,
vars = vars)
plot_chunk <- whisker.render(total_plot, data = data)
} else{
plot_piece <-
'make_bivariate_plot(docusign_survey, input${{var_1}}, input${{var_2}})
})
\```'
total_plot <- stringr::str_c(top_plot_piece, plot_piece, sep = "\n\n")
data <- list(plot_title = plot_title,
var_1 = vars[1],
var_2 = vars[2])
plot_chunk <- whisker.render(total_plot, data = data)
}
return(plot_chunk)
}
```

Is there a way to automatically add identifiers to images in R Markdown?

Because of some other javascript I'm trying to implement, I would like to be able to easily distinguish and locate images in compiled RMarkdown file. Images generated in a standard way with such chunks of code
```{r}
plot1 <- ggplot(data = mtcars, aes(x = wt, y = mpg, group = am, color = am)) +
geom_point(size = 3)
plot1
```
result in HTML like
<img src = "data:image/png..." width = 1000/>
I would like to be able to automatically add the unique identifier to each image so that every such call results in something like
<img src = "data:image/png..." id = "plot1" width = 1000/>
I could probably write this in JS but I wonder if there is a way to do this with some RMarkdown options.
You can create a chunk option hook like this:
knitr::opts_hooks$set(out.extra = function(options) {
options$out.extra <- paste0(ifelse(!is.logical(options$out.extra), options$out.extra, "")," id='", options$label, "'")
options
})
Now, if out.extra is either T or not logical, an id equal to the chunk label will be added. In case it is not logical but rather of type character, its value will be inserted before the id attribute. Consider the following example where we add a border to the plot. The id is set as well!
Full example:
---
title: "Out.Extra"
author: "Martin Schmelzer"
date: "10/9/2017"
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
knitr::opts_hooks$set(out.extra = function(options) {
options$out.extra <- paste0(ifelse(!is.logical(options$out.extra), options$out.extra, "")," id='", options$label, "'")
options
})
```
## Slide with Plot
```{r pressure, out.extra = 'style="border: red 3px solid;"'}
plot(pressure)
```
Alternatively, take a look at the package kfigr.

Show an R markdown chunk in the final output

I am writing on a presentation using Knitr, Markdown and Slidify. The slides will be partly deal with Knitr as topic which is the reason why I stumbeld upon a problem. I cannot include for example a knitr-markdown chunk to show it on the slide. It will always be interpreted on the first run even if I do something like this:
```
```{r eval = F, include = T}
```
```
How can I prevent a chunk to be interpreted and thus removed from the final output so that I can show how a chunk is structured when using Markdown and Knitr?
EDIT:
I tried the version of you #Ramnath and made up te following slides:
## Testslide 1
```{r verbatimchunk, verbatim = TRUE}
x = 1 + 1
x
```
```{r regularchunk}
x = 1 + 1
x
```
---
## Testslide 2
```{r verbatimchunk_2, verbatim = TRUE}
x = 1 + 1
x
```
* element 1
* element 2
---
## Testslide 3
* element 1
* element 2
```{r verbatimchunk_3, verbatim = TRUE}
x = 1 + 1
x
```
The first two slides work fine but the last one is the problem. If there is a bullet list before the verbatim chunk, it is interpreted as usual. So it is the same as with the first solution from #Scott. I do not understand this.
EDIT 2/3 (Working solution)
```{r echo = FALSE}
require(knitr)
hook_source_def = knit_hooks$get('source')
knit_hooks$set(source = function(x, options){
if (!is.null(options$verbatim) && options$verbatim){
opts = gsub(",\\s*verbatim\\s*=\\s*TRUE\\s*", "", options$params.src)
bef = sprintf('\n\n ```{r %s}\n', opts, "\n")
stringr::str_c(bef, paste(knitr:::indent_block(x, " "), collapse = '\n'), "\n ```\n")
} else {
hook_source_def(x, options)
}
})
```
## Testslide
* Element one
* Element two
Some text here breaks list environment:
```{r verbatim = T}
any code
```
Here is another solution that makes use of chunk hooks. The idea is that if you have a chunk with option verbatim = TRUE, it activates the hook and outputs the chunk verbatim. I have checked that it works with Slidify too.
```{r echo = FALSE}
require(knitr)
hook_source_def = knit_hooks$get('source')
knit_hooks$set(source = function(x, options){
if (!is.null(options$verbatim) && options$verbatim){
opts = gsub(",\\s*verbatim\\s*=\\s*TRUE\\s*", "", options$params.src)
bef = sprintf('\n\n ```{r %s}\n', opts, "\n")
stringr::str_c(bef, paste(knitr:::indent_block(x, " "), collapse = '\n'), "\n ```\n")
} else {
hook_source_def(x, options)
}
})
```
```{r verbatimchunk, verbatim = TRUE}
x = 1 + 1
x
```
```{r regularchunk}
x = 1 + 1
x
```
EDIT: The trick with code chunks after a list is that the list environment needs to be broken. A quick and dirty way is just to add an empty paragraph element. Alternately, you can fix the hook so that en empty paragraph is automatically added at the beginning of the code chunk.
* element 1
* element 2
<p></p>
```{r verbatimchunk_3, verbatim = TRUE}
x = 1 + 1
x
```
I think you need to add an empty string after ```{r}, and knitr will not execute the chunk, but will display it. See the example here
This on a slide works for me (where the top one executes and the bottom does not)
---
```{r}
list(5, 6, 7)
```
```{r}`r ''`
hist(rnorm(100))
5 + 6
```
---
Very late to the party, but this also seems to work:
```{r echo=FALSE, class.output="r", comment=""}
cat("```{r}\nx <- 1 + 1\nx\n```")
```
Or, equivalent but perhaps nicer to read and write:
```{r echo=FALSE, class.output="r", comment=""}
cat(paste(sep = "\n",
"```{r}",
"x <- 1 + 1",
"x",
"```"
))
```

Resources