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

viz. with rbokeh does not work #108

Open
smartinsightsfromdata opened this issue Oct 2, 2015 · 24 comments
Open

viz. with rbokeh does not work #108

smartinsightsfromdata opened this issue Oct 2, 2015 · 24 comments

Comments

@smartinsightsfromdata
Copy link

First of all, I've just installed yesterday the new OS from Apple El Capitan - I mention this in case there is some specific issue related to it.

Alo, this assumes that IRkernel / Jyupiter works with the family of htmlwidgets. So far I've tested two and both don't work.

This is the code

library(rbokeh)
figure() %>% ly_points(cars$speed, cars$dist)

I get the following code at the UI in red, but it is not a major issue as I normally get it also in Studio

In red:

xlim not specified explicitly... calculating...
ylim not specified explicitly... calculating...

Nothing happen in the browser. This is what I get in the console:

[I 16:18:09.307 NotebookApp] Serving notebooks from local directory: /Users/e
[I 16:18:09.307 NotebookApp] 0 active kernels 
[I 16:18:09.307 NotebookApp] The IPython Notebook is running at: http://localhost:8888/
[I 16:18:09.307 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[I 16:18:21.015 NotebookApp] Creating new notebook in 
[I 16:18:21.554 NotebookApp] Kernel started: 8eeb3261-a880-4806-9c3d-6886cde73adb
> IRkernel::main()
[1] "Got unhandled msg_type:" "comm_open"              

Afterward if I do !R --version (no matter if I restart the kernel etc.):

Error in eval(expr, envir, enclos): object 'R' not found
@takluyver
Copy link
Member

(the !R --version was to be run in Python to debug the R kernel crashing on start - that's unrelated to this issue)

It looks like rbokeh is trying to open a Jupyter comm, which isn't implemented for the R kernel yet. I'd guess the Bokeh JS is doing something to detect whether it's being used in a notebook, and if so it assumes that it's valid to open a comm. @damianavila, @hafen, does that sound right?

To make it work, either:

  1. Bokeh needs to understand that being in a notebook frontend doesn't automatically mean that comms are supported by the kernel, and if they're not, it should be able to fall back to... whatever else it does when it doesn't have comms.
  2. IRkernel needs to support comms, and rbokeh needs to be adapted to use them.

@smartinsightsfromdata
Copy link
Author

I have the impression that this issue is common to all of the family of htmlwidgets.

I've just tried networkD3 with similar results.

@hafen
Copy link

hafen commented Oct 2, 2015

Enzo is correct - the rbokeh output is an htmlwidget, so it should render like any htmlwidget. I'd love to see this working in jupyter but unfortunately I don't have much expertise here.

@damianavila
Copy link

AFAIK, rbokeh does not try to use comms (but I can be wrong because I did not see the code base for a while), so this is probably related with htmlwidgets...

@smartinsightsfromdata
Copy link
Author

@ramnathv @timelyportfolio could you kindly give a piece of advice?

@ramnathv
Copy link
Contributor

ramnathv commented Oct 2, 2015

For this to work, one would have to write an R package to handle the comms in Jupyter. Rather than each widget trying to write its own comm, I think it makes sense to write such a package, and an S3 method that would invoke it in a notebook context.

@ramnathv
Copy link
Contributor

ramnathv commented Oct 2, 2015

The following code will save the widget as a local html file

library(htmlwidgets)
library(rbokeh)
x = figure() %>% ly_points(cars$speed, cars$dist)
tf = 'test.html'
saveWidget(x, file = tf, selfcontained = F)

I tried using IRdisplay::display_html to display the html file in the notebook, but was unable to get the path right. How does one refer to local files in a notebook?

@takluyver
Copy link
Member

How does htmlwidgets JS communicate with the backend in general?

The "Got unhandled msg_type:" "comm_open" in the console indicates that something on the JS side is trying to open a comm, and I was guessing that that was related, but it may not be.

@ramnathv , if you call display_html(file='...'), it should read the file in the kernel, so paths should be relative to the CWD. If you display <iframe src="...">, I think paths work relative to the directory containing the notebook, which should be the same as CWD if your code doesn't change it.

@ramnathv
Copy link
Contributor

ramnathv commented Oct 2, 2015

@takluyver currently we have only implemented communication protocols for shiny as the backend. In an R session (outside of Rstudio), it simply opens up an external browser and displays the saved HTML file. I suppose in a notebook context, we can do something different so that the file displays in the notebook (which is what I was trying to do using display_html).

@ramnathv
Copy link
Contributor

ramnathv commented Oct 2, 2015

@takluyver. The iframe trick works. Directly calling display_html(file = "") does not work, which I think makes sense.

@takluyver
Copy link
Member

Are the communications for shiny HTTP requests, websocket messages, or something else? How practical would it be to do similar communications patterns over Jupyter comms? And would packages like rbokeh need changes to run within Jupyter, or would htmlwidgets abstract all the differences away?

(display_html(file = "...") ought to read the file and display the contents, but it will drop in into the existing page in a div, which may not work if the HTML in question is for an entire page. But this is probably irrelevant now.)

@ramnathv
Copy link
Contributor

ramnathv commented Oct 2, 2015

Shiny uses websockets. I think it is very feasible for us to implement comm protocols for Jupyter in htmlwidgets, thereby abstracting things away for packages that depend on it. This was the approach I was suggesting earlier.

As for the comm protocol, we need to know how to do two things. One, how to load js/css dependencies, and two, insert the relevant html div into the notebook. I think I know how to do (2). So (1) is where we would need help.

Another point of note, is how do notebooks handle redundancies in dependencies. So let us say I have one widget that has loaded jquery 1.10.1. Suppose another widget attempts to load jquery 1.11.1, can the notebook prevent duplicates? The iframe approach will take care of this since it sandboxes the pages, but I was wondering if the notebook architecture had robust dependency resolution mechanisms.

@smartinsightsfromdata
Copy link
Author

@ramnathv I think I've missed this<iframe src="..."> trick.
How does it work?

@ramnathv
Copy link
Contributor

ramnathv commented Oct 7, 2015

Here is how you would embed a htmlwidget as an iframe.

library(htmlwidgets)
library(rbokeh)
x = figure() %>% ly_points(cars$speed, cars$dist)
tf = 'myfigure.html'
saveWidget(x, file = tf, selfcontained = F)
IRdisplay::display_html(paste("<iframe src=' ", tf, " ' width = 100% height = 400"))

Note that I used selfcontained = F, since the notebook was unable to find the version of pandoc that comes installed with RStudio. You can set it to TRUE if pandoc is seen by the notebook.

@flying-sheep
Copy link
Member

i really hate tempfiles.

let’s do this instead:

IRdisplay::display_html(toHTML(x))

and if it somehow need to be isolated, let’s use the isolated flag like we do for SVG

@smartinsightsfromdata
Copy link
Author

While @ramnathv code works (adding "/>" at the end), I don't seem to be able to make to work @flying-sheep suggestion to use:

IRdisplay::display_html(toHTML(x))

toHTML is not a function available in any of the packages in my space (htmlwidgets, rboketh etc.).
I also tried tools::toHTML and XML::toHTML but I get errors.

Which toHTML have you been using?

@flying-sheep
Copy link
Member

oh, sorry, it’s not exported. but it has an exported alias:

IRdisplay::display_html(as.tags(x))

@smartinsightsfromdata
Copy link
Author

@flying-sheep I can get to it with

IRdisplay::display_html(htmlwidgets:::as.tags.htmlwidget(x))

but I get:

Error in prepare_content(isbinary, data, file): Data needs to be a character vector

Realising that the toHTML referred to above refers to the internal function in htmlwidgets, I tried also:

IRdisplay::display_html(htmlwidgets:::toHTML(x))

But I get the same (not surprising as as.tags.htmlwidget calls toHTML)

Error in prepare_content(isbinary, data, file): Data needs to be a character vector

@flying-sheep
Copy link
Member

it’s in 0.5. update htmlwidgets and you’ll have as.tags

this should work:

display_html(as.character(as.tags(x)))

or we can do sth. smart with htmltools::renderTags:

renderTags returns a list with the following variables:

head

An HTML string that should be included in <head>.

singletons

Character vector of singleton signatures that are known after rendering.

dependencies

A list of resolved htmlDependency objects.

html

An HTML string that represents the main HTML that was rendered.

@flying-sheep
Copy link
Member

here’s a dumb, ad-hoc version that works at least for DiagrammR.

this displays how useful a dependency mechanism for jupyter notebooks would be 😆

library(DiagrammeR)
library(htmltools)
library(htmlwidgets)
library(repr)

repr_html.htmlwidget <- function(w) {
    tags <- renderTags(as.tags(w))

    deps <- ''

    for (dep in tags$dependencies) {
        if (!is.null(dep$script)) {
            f <- file.path(dep$src$file, dep$script)
            deps <- sprintf('%s\n<script>// %s\n%s</script>', deps, f, readChar(f, file.info(f)$size))
        }
        if (!is.null(dep$stylesheet)) {
            f <- file.path(dep$src$file, dep$stylesheet)
            deps <- sprintf('%s\n<style>/* %s */\n%s</style>', deps, f, readChar(f, file.info(f)$size))
        }
    }

    paste(deps, tags$html, '<script>HTMLWidgets.staticRender()</script>', sep = '\n');
}

g <- grViz("
  digraph {
    layout = twopi
    node [shape = circle]
    A -> {B C D} 
  }")

display_html(repr_html(g))

@ramnathv
Copy link
Contributor

ramnathv commented Oct 7, 2015

A dependency specification mechanism would be awesome. The wrapper function by @flying-sheep is a good start. However, it may not work always, since it simply inlines js/css assets, without paying attention to additional dependencies that these files might import. Writing a wrapper function that invokes pandoc to do the inlining and then embeds the html fragment as opposed to the whole page, should be easy to write.

I would still recommend the iframe route, in the absence of a dependency handling mechanism. It sandboxes the page and ensures that it displays exactly the way it should. One can also inline an entire iframe using the srcdoc argument, which works with most modern browsers. I have an ongoing PR in the htmlwidgets repo that adds robust iframe support, which we can take advantage of here.

@tverbeke
Copy link

The DiagrammeR example of @flying-sheep works in a fresh session if I change the last line to

IRdisplay::display_html(repr_html(g))

For the OP's question, this

library(htmlwidgets)
library(rbokeh)
x = figure() %>% ly_points(cars$speed, cars$dist)
IRdisplay::display_html(as.character(htmlwidgets:::as.tags.htmlwidget(x)))

will not display anything (but a white rectangle) with the following sessionInfo():

R version 3.2.2 (2015-08-14)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 14.04.3 LTS

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] rbokeh_0.2.3.2    htmlwidgets_0.5.1

loaded via a namespace (and not attached):
 [1] lattice_0.20-33 digest_0.6.8    IRdisplay_0.3   grid_3.2.2     
 [5] repr_0.4        jsonlite_0.9.17 magrittr_1.5    evaluate_0.8   
 [9] stringi_0.5-5   uuid_0.1-1      hexbin_1.27.1   IRkernel_0.5   
[13] tools_3.2.2     stringr_1.0.0   maps_3.0.0-2    yaml_2.1.13    
[17] base64enc_0.1-2 rzmq_0.7.7      htmltools_0.2.6

@flying-sheep
Copy link
Member

Writing a wrapper function that invokes pandoc to do the inlining and then embeds the html fragment as opposed to the whole page, should be easy to write.

very inefficient. we should either push the dependency management or code some simple hack to fix this.

maybe it can be done with a simple helper that searches for script tags with ids, e.g.: <script id="d3-3.5.6" src="*"/>, and inserts them, then calls some optional JS onload.

we should be able to chain-load or parallelly-load them, which is easy with promise code.

// event to promise
const once = (emitter, event) =>
  new Promise((resolve, reject) =>
    emitter.addEventListener(event, resolve))

function load_script(id, url) {
  const existing = document.getElementById(id)
  if (existing)
    return Promise.resolve(existing)

  const script = document.createElement('script')
  script.setAttribute('id', id)
  script.setAttribute('src', url)
  document.head.appendChild(script)
  return once(script, 'load')
}

function load_dependencies(deps, callback) {
  let promise = null
  //sequentially load all deps in the array
  if (Array.isArray(deps)) {
    promise = load_dependencies(deps.shift())
    if (deps.length > 0) promise = promise.then(load_dependencies(deps))
  //parallelly load all deps in the object
  } else {
    promise = Promise.all(Object.keys(deps).map(id => load_script(id, deps[id])))
  }
  if (callback) promise = promise.then(callback)
  return promise
}

//example: parallelly load underscore and d3,
//after both are finished, load react (makes no sense but whatever)
load_dependencies([
  {
    '_-1.8.3': 'http://...',
    'd3-3.5.6': 'http://...',
  },
  { 'react-1.4.0': 'http://' }
])

missing from above code

@jankatins
Copy link
Contributor

I think the

> IRkernel::main()
[1] "Got unhandled msg_type:" "comm_open"

comes form somethign different, I get that as well on any kernel startup... Will open a new issue for that...

@flying-sheep flying-sheep changed the title viz. with rbokeh does not work - [1] "Got unhandled msg_type:" "comm_open" viz. with rbokeh does not work Jan 27, 2016
@flying-sheep flying-sheep transferred this issue from IRkernel/IRkernel Jan 16, 2019
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

No branches or pull requests

8 participants