R programmatically created reactable chunk won't show in Rmarkdown

74 Views Asked by At

I have this dataframe

products <- c("Product1", "Product2", "Product3")
departments <- c("Dept1", "Dept2", "Dept3")
campaigns <- c("Campaign1", "Campaign2", "Campaign3", "Campaign4", "Campaign5")
agents <- c("Agent1", "Agent2", "Agent3", "Agent4", "Agent5")

combinations <- expand.grid(Product = products, 
                            Department = departments, 
                            Campaign = campaigns, 
                            Agent = agents)

grouped_data <- combinations %>%
  group_by(Product, Department, Campaign, Agent) %>%
  summarize(Total_Sales = sum(Sales), 
            Average_Sales = mean(Sales))

I'd like to print in Rmarkdown programmatically without manually creating so many sections in my document so I created this function:

render_content <- function(dataframe) {
  split_data <- split(dataframe, f = list(dataframe$Product, dataframe$Campaign))
  
  for (product in unique(dataframe$Product)) {
    cat(paste0("<h2>", product, "</h2>\n"))
    product_data <- split_data[grepl(product, names(split_data))]
    
    for (campaign in unique(dataframe$Campaign)) {
      campaign_key <- paste(product, campaign, sep = ".")
      
      if (campaign_key %in% names(product_data)) {
        cat(paste0("<h3>", campaign, "</h3>\n"))
        table_html <- reactable(product_data[[campaign_key]], 
                                defaultPageSize = 5, 
                                minRows = 0)
        cat(as.character(htmltools::as.tags(table_html)))
      }
    }
  }
}

I put this chunk to my rmarkdown document but only h2 and h3 tags are rendered, I believe it has something to do with the necessity to include tags in documents so I tried several variants of it, but nothing helps.

{r, include=FALSE}
htmltools::tagList(reactable())

{r results='asis', echo=FALSE}
render_content(grouped_data)
2

There are 2 best solutions below

2
xx0020 On BEST ANSWER

Here is a reproducible example of one approach how you can achieve your aims. It uses a child document.

The next part is your main .Rmd file.

---
title: "A title"
output: html_document
---

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

library(tidyverse)
## {reactable} used with namespace
```

# Data

```{r}
dat <- expand.grid(
  products = c(paste("prd", 1:3, sep = "_")),
  departments = c(paste("dept", 1:3, sep = "_")),
  campaigns = c(paste("camp", 1:3, sep = "_") ),
  agents = c(paste("agent", 1:3, sep = "_"))
  ) %>% 
  mutate(sales = rnorm(81, 5000, 500)) %>% 
  filter(!(products == "prd_1" & campaigns == "camp_3")) 
```

Exclude the combination of product 1 and campaign 3 since it might be another
interesting way to see that the headers/sections will be generated correctly.

# Function(s)

```{r}
func_render_content <- function(x_dat) {
  
  param_env <- new.env()
  param_env$x_dat <- x_dat
  
  for(product_i in unique(x_dat$products)) {
    cat(paste0("<h2>", product_i, "</h2>\n"))
    
    product_data <- x_dat %>% filter(products == product_i)
    
    for(campaign_i in unique(x_dat$campaigns)) {
      
      if(campaign_i %in% unique(product_data$campaigns)) {
        cat(paste0("<h3>", campaign_i, "</h3>\n"))
        
        param_env$product_i <- product_i
        param_env$campaign_i <- campaign_i
        
        cat(knitr::knit_child(
          'so-content.Rmd', 
          envir = param_env, 
          quiet = TRUE
          ))
        
        cat("\n\nThat's it on", product_i, "and", campaign_i, "end of sentence.")
      }
    }
  }
}
```

```{r}
func_render_content_your_attempt <- function(x_dat) {
  
  for (product_i in unique(x_dat$products)) {
    cat(paste0("<h2>", product_i, "</h2>\n"))
    
    product_data <- x_dat %>% filter(products == product_i)
    
    for (campaign_i in unique(x_dat$campaigns)) {
      
      if (campaign_i %in% unique(product_data$campaigns)) {
        cat(paste0("<h3>", campaign_i, "</h3>\n"))
        
        x_dat %>% 
          filter(products == product_i & campaigns == campaign_i) %>% 
          reactable::reactable(defaultPageSize = 5)
      }
    }
  }
}
```

# This works

```{r, results='asis'}
func_render_content(dat)
```

# Your attempt, does not what you want

In the sense of completeness.

```{r, results='asis'}
func_render_content_your_attempt(dat)
```

The next part is a separate .Rmd file called so-content.Rmd.

---
title: "section-content"
---

```{r, echo=FALSE}
x_dat %>% 
  filter(products == product_i & campaigns == campaign_i) %>% 
  reactable::reactable(defaultPageSize = 5)
``` 

0
stefan On

Similar to other answers (like e.g. Using ggplotly and DT from a for loop in Rmarkdown) related to plotly, DT or other htmlwidgets you could use results='asis', print your table inside the for loop using print(htmltools::tagList(...)) and as an important step make sure that the JS dependencies are included for which I call reactable(mtcars) in a separate code chunk for which I have set include=FALSE:

---
title: "Untitled"
output: html_document
date: "2024-01-20"
---

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

```{r message=FALSE}
library(tidyverse)
library(reactable)
```

```{r}
products <- c("Product1", "Product2", "Product3")
departments <- c("Dept1", "Dept2", "Dept3")
campaigns <- c("Campaign1", "Campaign2", "Campaign3", "Campaign4", "Campaign5")
agents <- c("Agent1", "Agent2", "Agent3", "Agent4", "Agent5")

combinations <- expand.grid(
  Product = products,
  Department = departments,
  Campaign = campaigns,
  Agent = agents
)

grouped_data <- combinations %>%
  mutate(Sales = 1) |>
  group_by(Product, Department, Campaign, Agent) %>%
  summarize(
    Total_Sales = sum(Sales),
    Average_Sales = mean(Sales),
    .groups = "drop"
  )
```

```{r include=FALSE}
# Init step to ensure that JS dependencies are included in the HTML doc
reactable(mtcars)
```

```{r}
render_content <- function(dataframe) {
  split_data <- split(dataframe, f = list(dataframe$Product, dataframe$Campaign))

  for (product in unique(dataframe$Product)) {
    cat(paste0("<h2>", product, "</h2>\n"))
    product_data <- split_data[grepl(product, names(split_data))]

    for (campaign in unique(dataframe$Campaign)) {
      campaign_key <- paste(product, campaign, sep = ".")

      if (campaign_key %in% names(product_data)) {
        cat(paste0("<h3>", campaign, "</h3>\n"))
        table_html <- reactable(product_data[[campaign_key]],
          defaultPageSize = 5,
          minRows = 0
        )
        print(htmltools::tagList(table_html))
      }
    }
  }
}
```

```{r results='asis', echo=FALSE}
render_content(grouped_data)
```

enter image description here