Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datatables generated in a loop in .Rmd file? #67

Closed
ldecicco-USGS opened this issue May 6, 2015 · 21 comments
Closed

datatables generated in a loop in .Rmd file? #67

ldecicco-USGS opened this issue May 6, 2015 · 21 comments
Labels

Comments

@ldecicco-USGS
Copy link

I'm trying to generate a set of datatables from a loop within an rmarkdown file. Something like:

```{r results='asis'}
for(i in 1:2){
  df <- data.frame(a="a", b=i)
  print(datatable(df))
}

While the straight R produces 2 tables, no tables show up in the 'Knit' html document.

So, this works for 1 table:

```{r, results='asis'}
# for(i in 1:2){
  df <- data.frame(a="a", b=i)
  datatable(df)
#   print(datatable(df))
# }

But I can't loop through and make several tables. Is there a print-like function I'm missing? Thanks for the awesome package!

@kkrismer
Copy link

I have the same problem. Couldn't find a solution so far. :(
Help is highly appreciated.

@StevenMMortimer
Copy link

@ldecicco-USGS @kkrismer For some reason you can only generate one datatable per chunk. To workaround that limitation you should dynamically create one chunk per datable that you want to display. Dynamically creating rmarkdown chunks was solved on stackoverflow (http://stackoverflow.com/questions/21729415/generate-dynamic-r-markdown-blocks).

Here is a gist that I think achieves your desired result: https://gist.github.com/ReportMort/9ccb544a337fd1778179

To test the datatable functionality for searching, pagelength, paging, etc still works I created another gist that splits the iris dataset into 3 parts (one for each species). The code generates 3 chunks to display a datatable for each part of the data set. Those datatable options seem to work fine, so I think this solution will work.
https://gist.github.com/ReportMort/e54ec050d97d79996189

@yihui
Copy link
Member

yihui commented May 18, 2015

Thanks for the solution. That is certainly one way to go. I have another solution proposed at ramnathv/htmlwidgets#110 I'll let you know once the proposal is accepted.

@garyfeng
Copy link

My work around. Haven't tested with datatable but works with another htmlWidget, DiagrammeR.

Say you have a data frame called grfDf with DiagrammeR graph objects in the column graph. The following is all you need to plot all the graphs in Rmd : r require(DiagrammeR); renderHtmlWidgetList(grfDf$graph, render_graph). See the code for caveats.

```
require(knitr)

#' Render a list of htmlWidgets using various tricks
#'
#' @param widgetList A list of htmlWidget objects to be rendered
#' @param renderFunction The function to render individual widgets. It can be either a name
#'   of the rendering function, e.g., "render_graph" in DiagrammeR, or the actual function to
#'   be passed to this call.
#' @return The knitted string. This is to be included in the output by using `r renderHtmlWidgetList(...)`;
#' @details This is a collection of various tricks. See the URL citations in the code.
#'   Note that this code does alliterate global variables starting with "renderHtmlWidgetList_".
#'   You may want to delete them using rm(list = ls(pattern="renderHtmlWidgetList_*")).
#' @examples Inlcude the following in the Rmd directly
#'   `r require(DiagrammeR); renderHtmlWidgetList(grfDf$graph, render_graph)`
#'
#' @export

renderHtmlWidgetList <- function(widgetList, renderFunction){
  # error checking
  stopifnot(is.list(widgetList))
  # handles if the renderFunction is actually a function
  # http://stackoverflow.com/questions/10520772/in-r-how-to-get-an-objects-name-after-it-is-sent-to-a-function
  if(is.function(renderFunction)) {
    # convert back to string, because we need to knit it later
    renderFunction <- deparse(substitute(renderFunction))
  }
  stopifnot(is.character(renderFunction) & length(renderFunction)==1)
  stopifnot(exists(renderFunction, mode = "function"))
  # inject global vars; make sure we have a unique global var name
  gVarName<- paste0("renderHtmlWidgetList_", sample(1:10000, 1))
  while (exists(gVarName)) {
    gVarName<- paste0("renderHtmlWidgetList_", sample(1:10000, 1))
  }
  # assigning widgetList to a global temp var
  # http://stackoverflow.com/questions/5510966/create-a-variable-name-with-paste-in-r
  assign(gVarName, widgetList, envir = .GlobalEnv)
  # solution from https://gist.github.com/ReportMort/9ccb544a337fd1778179
  out <- NULL
  knitPrefix <- "\n```{r results='asis', cache=FALSE, echo=FALSE}\n\n"
  knitSuffix <- "\n\n```"
  for (i in 1:length(widgetList)) {
    knit_expanded <- paste0(knitPrefix, renderFunction, "(", gVarName, "[[", i, "]])")
    out = c(out, knit_expanded)
  }
  #invisible(out)
  paste(knitr::knit(text = out), collapse = '\n')
}
```

@yihui
Copy link
Member

yihui commented Feb 5, 2016

Turns out that the solution is fairly simple -- just use tagList(). An example:

```{r}
library(DT)
# split iris by Species and generate a datatable for each species
htmltools::tagList(
  lapply(split(iris, iris[, 5]), datatable)
)
```

@stevekm
Copy link

stevekm commented Jun 1, 2017

@yihui that works, but only in the simple case of printing several datatable's alone. If you have more complicated requirements, it does not seem to work. For example:

```{r, results='asis'}
library("VennDiagram")
library("DT")
# set up data
car_list <- list()
for(i in seq_along(unique(mtcars[["gear"]]))){
    gears <- as.character(unique(mtcars[["gear"]])[i])
    car_list[[gears]] <- list()
    gears_df <- mtcars[mtcars[["gear"]] == gears , ]
    car_list[[gears]][["data"]] <- gears_df
    carb_2 <- rownames(gears_df[gears_df[["carb"]] == 2 , ])
    carb_4 <- rownames(gears_df[gears_df[["carb"]] == 4 , ])
    carb_list <- list("carb_2" = carb_2, "carb_4" = carb_4)
    venn_plot <- venn.diagram(x = carb_list, filename = NULL, main = gears) # grid.draw(venn_plot)
    car_list[[gears]][["venn"]] <- venn_plot
}

# print to HTML
cat("# Results \n \n ")
for(i in seq_along(names(car_list))){
    name <- names(car_list)[i]
    cat("\n \n")
    cat(sprintf("## Gears: %s {.tabset .tabset-pills} \n \n", name))
    cat("### Table \n \n")
    print(htmltools::tagList(datatable(car_list[[i]][["data"]])))
    cat("\n \n")
    cat("### Venn \n \n")
    grid.draw(car_list[[i]][["venn"]])
    cat("\n \n")
}

Output looks like this. If you check the source HTML and intermediary Markdown, you can see the HTML for the datatables are present, but are not displaying correctly:

screen shot 2017-06-01 at 7 09 16 pm

@RomanBi
Copy link

RomanBi commented Sep 13, 2018

Any news on that one? I am running into exactly the same problem as @stevekm does: trying to programmatically create sections using for loop and results='asis'. htmlwidgets just do not render, whereas it works using e.g. ggplot2 plots.
@yihui: is there a solution yet?

@yihui
Copy link
Member

yihui commented Sep 13, 2018

htmltools::tagList() has to be at the top level: https://yihui.name/en/2017/06/top-level-r-expressions/

Instead of ## headers, you could use, for example, htmltools::tags$h2() in tagList().

@RomanBi
Copy link

RomanBi commented Oct 2, 2018

Unfortunately this workaround does not seem to work when e.g. creating an ioslide presentation. htmltools::tags$h2() is creating the headers, htmlwidgets can be included, but there is no slide break (seems like those are not triggered when using tags?).
The solution will most likely be super simple, can you give me a hint?

@shrektan
Copy link
Collaborator

shrektan commented Oct 2, 2018

Can you be sure that you have set the results='asis' in your R chunk?

```{r, results='asis'}
# your code
```

@RomanBi
Copy link

RomanBi commented Oct 2, 2018

Hm. I thought the following should work as part of an ioslide presentation (minimum example):

---
title: "aaa"
author: "aaaaa"
date: "2018/10/2"
output: html_document
---

# Test

```{r test}
library("htmltools")
library("DT")
createDT <- function(variable){
  return(list(tags$h2(names(cars[variable])), datatable(cars[variable])))
}
htmltools::tagList(lapply(as.list(1:2), function(x) createDT(x)))
```

Spoiler: it does not work
This "solution" is mainly based on that SO question and answer: https://stackoverflow.com/questions/43792783/combine-leaflet-and-markdown-in-loop

I tried a lot of things, including a combination of results='asis' and cat("\n---\n"), but all of them failed...

@shrektan
Copy link
Collaborator

shrektan commented Oct 2, 2018

The code you provided works on my computer.

image

BTW, please preview your comment before submitting (I've cleaned last comment for you). Code with the bad format is difficult for people to read.

@RomanBi
Copy link

RomanBi commented Oct 2, 2018

It certainly works for regular markdown documents. However, it does not work for ioslide presentations as stated above. Switching to the next slide is not happening by using tags$h2().

@shrektan
Copy link
Collaborator

shrektan commented Oct 2, 2018

@RomanBi This certainly works for you.

---
title: "loop DT in ioslides"
author: "shrektan"
date: "2018/10/2"
output: ioslides_presentation
---

# Test

```{r, echo=FALSE,include = FALSE}
# You need this code to conduct the magic dependences attaching...
DT::datatable(matrix())
```

```{r test, results='asis', echo = FALSE}
for (i in 1:3) {
  nm <- colnames(iris)[i]
  cat(sprintf("\n\n## %s\n\n", nm))
  cat("\n\n")
  cat(knitr::knit_print(DT::datatable(iris, width = "100%")))
  cat("\n\n")
}
```

image

@RomanBi
Copy link

RomanBi commented Oct 4, 2018

Thank you very much, @shrektan! Works like a charm. How could I miss that? ;)

@jancrichter
Copy link

@shrektan 's solution worked perfectly. For all of 5 minutes. Since then all I did was install plotly and now knit_print() always wants to make a screenshot of my datatable and complains that phantom.js is not installed.

Any ideas anyone?

@shrektan
Copy link
Collaborator

A minimal example will help...

@jancrichter
Copy link

jancrichter commented Jan 17, 2019 via email

@shrektan
Copy link
Collaborator

It works on my computer after plotly being installed.

@Lucius-Cesar
Copy link

Lucius-Cesar commented Mar 29, 2021

The workaround
DT::matrix() + cat(knitr::knit_print(DT::datatable())) seems to be less responsive than DT::datatable()

In the case of a datatable smaller than 10 lines, it includes a lot of blank space after the table.
In the case of displaying more than 10 entries, it can disrupts the content of the rest of the page.

Screenshot from 2021-03-29 21-53-15

Any solutions to avoid this ? Thanks in advance !

EDIT: Solved adding height = "100%", width = "100%" parameters to datatable()

example:
cat(knitr::knit_print(DT::datatable(head(iris,3), height = "100%", width = "100%")))

@yoursdearboy
Copy link

# You need this code to conduct the magic dependences attaching...
DT::datatable(matrix())

Thank you @shrektan, that was the missing part for me,.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests