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

Widget list #110

Closed
wants to merge 4 commits into from
Closed

Widget list #110

wants to merge 4 commits into from

Conversation

yihui
Copy link
Contributor

@yihui yihui commented May 18, 2015

I have been asked multiple times how to render widgets in a loop in R Markdown (e.g. rstudio/DT#67). We cannot just do for (i in 1:n) print(widget[i]) (even knit_print(widget[i]) won't work), because the metadata about HTML dependencies is not recorded. The metadata is stored only when a top-level R expression that renders a widget is printed. This PR brings a top-level object widgetList(), and all its widget elements will be rendered (with their metadata correctly recorded).

Note this PR requires knitr >= 1.10.9. rstudio/htmltools#29 will enhance this PR, but it is not essential.

I'm totally fine if this PR is accepted after htmlwidgets 0.4 is released to CRAN.

An example (works in both R console and R Markdown):

devtools::install_github('yihui/htmlwidgets@feature/widget-list')

library(DT)
b1 = datatable(iris)
b2 = datatable(mtcars)

library(leaflet)
b3 = leaflet() %>% addTiles() %>% addMarkers(lat = rnorm(100), lng = rnorm(100))

library(htmltools)
htmlwidgets::widgetList(
  h1('A Table for iris'), b1,
  h1('A Table for mtcars'), b2,
  h1('A Map'), b3
)

yihui added a commit to yihui/knitr that referenced this pull request May 18, 2015
@jjallaire
Copy link
Collaborator

This is excellent! I will wait until after CRAN release to take it though as it touches enough code to warrant some field testing.

@yihui
Copy link
Contributor Author

yihui commented Feb 4, 2016

@jjallaire Is there a chance this PR can be merged? I'm asking because I just saw this issue yet another time: http://stackoverflow.com/q/35193612/559676

@ramnathv
Copy link
Owner

ramnathv commented Feb 4, 2016

@yihui I can test this over the weekend and merge it in. If @jjallaire or @jcheng5 can get to it before, then that's fine as well. I think the only piece of code that will touch existing widgets is the refactoring out the viewer function. So, this should be pretty quick.

@yihui
Copy link
Contributor Author

yihui commented Feb 4, 2016

You are right. Everything else is new and should be fairly safe except the refactoring of the viewer function.

@johnmous
Copy link

johnmous commented Feb 5, 2016

This particular issue has been bothering me for quite some time. Good to see someone is working on it guys. Please let us know when it is fixed and what exactly we should update for the fix to work.

@jcheng5
Copy link
Collaborator

jcheng5 commented Feb 5, 2016

I don't think this is needed--you can just use htmltools::tagList. You can even put a regular list in a tagList so you could do tagList(lapply(1:10, function() { leaflet() })) or whatever.

@timelyportfolio
Copy link
Collaborator

tagList has been the way I have handled this. I agree with @jcheng5 in that I'm not sure what benefit we get from widgetList.

@jcheng5
Copy link
Collaborator

jcheng5 commented Feb 5, 2016

Oh, my example doesn't work at the console unless you also wrap the tagList with htmltools::browsable. If it's desirable to introduce a new function that is browsable by default, I recommend you just have widgetList wrap tagList/browsable.

@yihui
Copy link
Contributor Author

yihui commented Feb 5, 2016

Darn it. What was I thinking about?... tagList() is such an obvious solution.

@yihui yihui closed this Feb 5, 2016
@jcheng5
Copy link
Collaborator

jcheng5 commented Feb 5, 2016

Clearly not that obvious if you keep getting asked about it by users... 😐

yihui added a commit to yihui/knitr that referenced this pull request Feb 6, 2016
Just use htmltools::tagList()!

This reverts commit 7ba4253.
@johnmous
Copy link

It's now working for me and the interactive plots are functioning in the browser. There are, however, two issues,s till.

First, to make it work I have to manually add the javascript source location at the top of the HTML document, because it doesn't to it for me automatically. So I have to copy-paste something like
<script src="../lib/htmlwidgets-0.5/htmlwidgets.js"></script> into the HTML

Second, the x-axis label of my plots is gone.

Any chance someone has a solution, or a tip where to look for a solution? As you may imagine, I m a beginner with both markdown and HTML (but have adequate experience with R)

@ramnathv
Copy link
Owner

You shouldn't have to do any of that. Can you share your code here so that we can try to reproduce and diagnose the source of the problem.

@jrowen
Copy link

jrowen commented Feb 19, 2016

I also ran across this issue today and am glad to have found thehtmltools::tagList solution. FWIW, I did like the widgetList approach, as using tagList is not obvious to most users.

@vrybkin
Copy link

vrybkin commented May 3, 2016

Hi,

Is there a way to add knitrformatting, such as the tabsetoption, into the tagListapproach? I am trying to dynamically create and loop through chunks to create an automated knitr, but I am still having trouble. Below is some example code of what I am thinking, but it does not quite work. What I am trying to do is create 10 tabs, each with a copy of the plot generated from plot_list. What happens right now is all of the plots go into the last tab. In practice, plot_list would have different plots/tables.

#' ---
#' title: htmltools::tagList test
#' output:
#'    html_document
#' ---

#' # {.tabset}
#+ results='asis', echo=FALSE
library(plotly)
library(printr)

plot_list = lapply(1:10, 
                   function(i){ 
                     as.widget(plot_ly(iris, 
                                       x = iris[["Sepal.Length"]],
                                       y = iris[["Sepal.Width"]], 
                                       mode = "markers")) 
                    } 
                  )

htmltools::tagList( lapply(1:10, 
                            function(i) {
                              pandoc.header(paste0("Tab",i,' {.tabset}'), 2)
                              plot_list[[i]]
                            } 
                          )
                   )

# rmarkdown::render("your_path/htmltoolsTagList_test.r")

Before, I was successfully doing something like this with nested for-loops, but once I tried using figures with HTML dependencies, such as DT, plotly, or rbokeh, the figures of course do not render as they are no longer top level expressions. Is it possible in knitrto loop like this?

A follow up question I have is: suppose I wanted to nest these tabs into another set of tabs created the same way, is that possible? What I mean to ask is, can I nest tabs dynamically using a method like this, analogous to a nested for-loop?

I am still learning how to use knitr, and would appreciate any help!
Thank you!

@timelyportfolio
Copy link
Collaborator

Good question, and I think others will be helped by this discussion. It might be easiest to start by building something like what you propose from scratch without the aid of rmarkdown.

manually build

# https://github.com/ramnathv/htmlwidgets/pull/110#issuecomment-216562703

library(plotly)
library(htmltools)
library(markdown)
library(shiny)

browsable(
  attachDependencies(
    tagList(
      tags$div(
        class="tabs",
        tags$ul(
          class="nav nav-tabs",
          role="tablist",
          tags$li(
            tags$a(
              "data-toggle"="tab",
              href="#tab-1",
              "Iris"
            )
          ),
          tags$li(
            tags$a(
              "data-toggle"="tab",
              href="#tab-2",
              "Cars"
            )
          )
        ),
        tags$div(
          class="tab-content",
          tags$div(
            class="tab-pane active",
            id="tab-1",
            as.widget(
              plot_ly(
                iris,
                x = iris[["Sepal.Length"]],
                y = iris[["Sepal.Width"]], 
                mode = "markers"
              )
            )
          ),
          tags$div(
            class="tab-pane",
            id="tab-2",
            as.widget(
              plot_ly(
                cars,
                x = speed,
                y = dist, 
                mode = "markers"
              )
            )
          )
        )
      )
    ),
    # attach dependencies
    #  see https://github.com/rstudio/rmarkdown/blob/master/R/html_document.R#L235
    list(
      rmarkdown::html_dependency_jquery(),
      shiny::bootstrapLib()
    )
  )
)

in rmarkdown

There is probably a better way to make this work, but until someone sets me straight, we can take the approach from above and use it in rmarkdown. Unfortunately, this is still very manual. For more reference, here is the code that RStudio uses to build tabsets.

    ---
    title: "tabs and htmlwidgets"
    author: "Kent Russell"
    date: "May 3, 2016"
    output: html_document
    ---

    ```{r echo=FALSE, message=FALSE, warning=FALSE}
    library(plotly)
    library(htmltools)
    library(magrittr)

    # make a named list of plots for demonstration
    #  the names will be the titles for the tabs
    plots <- list(
      "iris" = plot_ly(
        iris,
        x = iris[["Sepal.Length"]],
        y = iris[["Sepal.Width"]], 
        mode = "markers"
      ),
      "cars" = plot_ly(
        cars,
        x = speed,
        y = dist, 
        mode = "markers"
      )
    )

    # create our top-level div for our tabs
    tags$div(
      # create the tabs with titles as a ul with li/a
      tags$ul(
        class="nav nav-tabs",
        role="tablist",
        lapply(
          names(plots),
          function(p){
            tags$li(
              tags$a(
                "data-toggle"="tab",
                href=paste0("#tab-",p),
                p
              )
            )
          }
        )
      ),
      # fill the tabs with our plotly plots
      tags$div(
        class="tab-content",
        lapply(
          names(plots),
          function(p){
             tags$div(
              #  make the first tabpane active
              class=ifelse(p==names(plots)[1],"tab-pane active","tab-pane"),
              #  id will need to match the id provided to the a href above
              id=paste0("tab-",p),
              as.widget(plots[[p]])
            )
          }
        )
      )
    ) %>%
      # attach the necessary dependencies
      #  since we are manually doing what rmarkdown magically does for us
      attachDependencies(
        list(
          rmarkdown::html_dependency_jquery(),
          shiny::bootstrapLib()
        )
      )
    ```

@xliangisu
Copy link

@yihui Hi! I am facing the same problem as you mentioned in this example: printing a series of data tables which created from a loop in R markdown.
So I tried the example in 1L in both R console and R markdown.
It worked well in R console and I can find that the two tables and one map were showed in the same "Viewer" window.
But in R markdown(knit HTML), I cannot get the tables and map.

I am wondering if it is the problem of the package version. So I also tried to download htmlwidgets from CRAN and "install_github('ramnathv/htmlwidgets')". Unfortunately, I still didn't get the tables and map in R markdown.

Could you please help me with it?
Thank you.

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

Successfully merging this pull request may close these issues.

9 participants