This R Markdown document is a quick example of how interactive html output can be automated and organized. I’ll use the spotifyr package to pull in some simple data and make quick charts as an example for creating tabs and pills, using child docs, and utilizing Markdown to generate content programmatically.


Creating Tabs

The addition of {.tabset} tells markdown to apply the predefined tabset class to downstream sub-headers. In this example we start with a blank level one header (#) above. The following level two headers (##) are then interpreted as tabs.

# {.tabset} 

## Creating Tabs 
## Adding Pills
## Using Child Docs
## Programmatic Approach

Because the output of this R Markdown document is an .html file, css (and html) can be used to customize any part of the report. For example, I’ve added some css to customize the appearance of the tabs in this example. The stylesheet is saved as styles.css in the extras/ folder of this project and applied very simply in the options.

---
title: "Automating Interactive R Markdown Reports"
output:
  html_document:
  css: extras/styles.css
---

Adding Pills

Time to add some pills. Let’s pull in the first five songs from the Jagged Little Pill album by Alanis Morissette as an example.

tracks <- spotifyr::get_artist_audio_features('alanis morissette') %>% 
  filter(album_name == 'Jagged Little Pill') %>% 
  select(track_name) %>% 
  unique() %>% 
  head(5) %>% 
  pull()

Creating pills works very similar to tabs. Adding {.tabset .tabset-pills} instead of {.tabset} to a header will prompt markdown to apply to default tabset-pills class to downstream headers. Here’s what it looks like in this example.

### `r paste(tracks[1])`
The 1st track of the Jagged Little Pill album is: `r paste(tracks[1])`
  
### `r paste(tracks[2])`
The 2nd track of the Jagged Little Pill album is: `r paste(tracks[2])`
  
### `r paste(tracks[3])`
The 3rd track of the Jagged Little Pill album is: `r paste(tracks[3])`
  
### `r paste(tracks[4])`
The 4th track of the Jagged Little Pill album is: `r paste(tracks[4])`
  
### `r paste(tracks[5])`
The 5th track of the Jagged Little Pill album is: `r paste(tracks[5])`

Which produces:

All I Really Want

The 1st track of the Jagged Little Pill album is: All I Really Want

You Oughta Know

The 2nd track of the Jagged Little Pill album is: You Oughta Know

Perfect

The 3rd track of the Jagged Little Pill album is: Perfect

Hand in My Pocket

The 4th track of the Jagged Little Pill album is: Hand in My Pocket

Right Through You

The 5th track of the Jagged Little Pill album is: Right Through You

Using Child Docs

Another method to keep all your code organized is to use child documents. Here is how the parent .Rmd file of this markdown document is set up. Notice that the {.tabset} called in the parent .Rmd flows downstream to child .Rmd files.

---
title: "Automating Interactive R Markdown Reports"
output:
  html_document:
    css: extras/styles.css
    includes:
      in_header: extras/header-logo.html
---

```{r, include=FALSE, echo=FALSE}
sys.source("extras/setup.R", envir = knitr::knit_global())
```
This R Markdown document is a quick example of how interactive html output can be automated and organized. I'll use the spotifyr package to pull in some simple data and make quick charts as an example for creating tabs and pills, using child docs, and utilizing Markdown to generate content programmatically.  
***

# {.tabset}

```{r, child=c('tab-1-tabs/tabs.Rmd', 'tab-2-pills/pills.Rmd', 'tab-3-child/child.Rmd', 'tab-4-auto/auto.Rmd', 'tab-5-demo/demo.Rmd')}
```

Also notice the sys.source("extras/setup.R", envir = knitr::knit_global()) command. This can be used to load packages, authenticate the spotifyr api connection, set knitr options, etc. all in one place and applied globally.

library(tidyverse)
library(ggplot2)
library(patchwork)
library(spotifyr)
library(ggthemes)
library(flextable)
options(knitr.duplicate.label = "allow")
access_token <- get_spotify_access_token()

Automation

For repetitive tasks cat() can be used with results='asis' to generate text that will be evaluated and knit as markdown code. For example, looking at the Jagged Little Pill album names again…

tracks <- spotifyr::get_artist_audio_features('alanis morissette') %>% 
  filter(album_name == 'Jagged Little Pill') %>% 
  select(track_name) %>% 
  unique() %>% 
  head(5) %>% 
  pull()
for (i in 1:length(tracks)) {
  cat('\n\n### ', tracks[[i]], '\n\n')
  print(paste0('The ', scales::ordinal(i), ' track of the Jagged Little Pill album is:', tracks[[i]]))
}

Produces:

All I Really Want

The 1st track of the Jagged Little Pill album is: All I Really Want

You Oughta Know

The 2nd track of the Jagged Little Pill album is: You Oughta Know

Perfect

The 3rd track of the Jagged Little Pill album is: Perfect

Hand in My Pocket

The 4th track of the Jagged Little Pill album is: Hand in My Pocket

Right Through You

The 5th track of the Jagged Little Pill album is: Right Through You

Demo

Pull in Some Data

studio_albums <- c("Parachutes",
                   "A Rush of Blood to the Head",
                   "X&Y",
                   "Viva La Vida or Death and All His Friends",
                   "Mylo Xyloto",
                   "Ghost Stories",
                   "A Head Full of Dreams",
                   "Everyday Life",
                   "Music of the Spheres")

df <- get_artist_audio_features('coldplay') %>% 
  filter(album_name %in% studio_albums == TRUE) 

artist_df <- df %>% 
  mutate(level = paste0("Artist: ", artist_name),
         year = 01) %>% 
  group_by(level) %>% 
  transmute_at(c("year",
                 "tempo",
                 "valence",
                 "energy",
                 "danceability",
                 "loudness",
                 "liveness",
                 "instrumentalness",
                 "acousticness",
                 "speechiness"), 
               mean, na.rm = TRUE) %>% 
  ungroup() %>% 
  unique()

album_df <- df %>% 
  mutate(level = paste0("Album: ", album_name, " (", album_release_year, ")"),
         year = album_release_year) %>% 
  group_by(level,year) %>% 
  transmute_at(c("tempo",
                 "valence",
                 "energy",
                 "danceability",
                 "loudness",
                 "liveness",
                 "instrumentalness",
                 "acousticness",
                 "speechiness"), 
               mean, na.rm = TRUE) %>% 
  ungroup() %>% 
  unique() 

yellow_df <- df %>% 
  filter(track_name == "Yellow") %>% 
  mutate(level = paste0("Track: ", track_name),
         year = 00) %>% 
  group_by(level, year) %>% 
  transmute_at(c("tempo",
                 "valence",
                 "energy",
                 "danceability",
                 "loudness",
                 "liveness",
                 "instrumentalness",
                 "acousticness",
                 "speechiness"), 
               mean, na.rm = TRUE) %>% 
  ungroup() %>% 
  unique()

df <- yellow_df %>% 
  union(artist_df) %>% 
  union(album_df) %>% 
  mutate(level = fct_reorder(level, desc(as.numeric(year))))

df %>% 
  mutate(across(where(is.numeric),
            number_format)) %>% 
  flextable() %>% 
  fontsize(size = 8, part = "all")

Create a Plotting Function

plotVar <- function(var_string) {
  
  ggplot(df, aes(x = level, y = .data[[var_string]], fill = level)) +
    geom_bar(stat = "identity") +
    coord_flip() +
    ggtitle(var_string) +
    ggthemes::theme_fivethirtyeight(base_size = 10) +
    theme(panel.background = element_rect(fill = "white")) +
    theme(plot.background = element_rect(fill = "white")) +
    theme(legend.position="none")  
}


features <- colnames(df)[!colnames(df) %in% c("level","year")]

pp <- features %>% 
  map(~ plotVar(.x))

Knit Automated Plots

With the chunk option results = 'asis', you can write out text as raw Markdown content, which can also be mixed with plots.

for (i in 1:length(features)) {
  cat('\n\n#### ', features[[i]], '  \n\n')
   print(pp[[i]])
}

tempo

valence

energy

danceability

loudness

liveness

instrumentalness

acousticness

speechiness