rstudio / DT

R Interface to the jQuery Plug-in DataTables
https://rstudio.github.io/DT/
Other
598 stars 180 forks source link

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

Closed ldecicco-USGS closed 8 years ago

ldecicco-USGS commented 9 years ago

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 commented 9 years ago

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

StevenMMortimer commented 9 years ago

@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 commented 9 years ago

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

garyfeng commented 9 years ago

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 commented 8 years ago

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 commented 7 years ago

@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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

```{r, results='asis'}
# your code
RomanBi commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

@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())
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")
}


<img width="816" alt="image" src="https://user-images.githubusercontent.com/8368933/46360900-f6a5e600-c69e-11e8-8821-7b6cc326eb41.png">
RomanBi commented 6 years ago

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

jancrichter commented 5 years ago

@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 commented 5 years ago

A minimal example will help...

jancrichter commented 5 years ago

Just the exact code from above. It leads to knit_print() calling webshot::webshot(). Before installing plotly it simply printed the datatable into the knitted htm.

Am Do., 17. Jan. 2019, 16:00 hat Xianying Tan notifications@github.com geschrieben:

A minimal example will help...

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/rstudio/DT/issues/67#issuecomment-455201987, or mute the thread https://github.com/notifications/unsubscribe-auth/AmjZSzvFltmp3Ta2IFoH3upajzyp_-FLks5vEJABgaJpZM4ERnBP .

shrektan commented 5 years ago

It works on my computer after plotly being installed.

Lucius-Cesar commented 3 years ago

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 commented 2 years ago
# You need this code to conduct the magic dependences attaching...
DT::datatable(matrix())

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