repr
repr copied to clipboard
viz. with rbokeh does not work
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
(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:
- 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.
- IRkernel needs to support comms, and rbokeh needs to be adapted to use them.
I have the impression that this issue is common to all of the family of htmlwidgets.
I've just tried networkD3 with similar results.
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.
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...
@ramnathv @timelyportfolio could you kindly give a piece of advice?
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.
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?
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.
@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
).
@takluyver. The iframe
trick works. Directly calling display_html(file = "")
does not work, which I think makes sense.
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.)
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.
@ramnathv I think I've missed this<iframe src="..."> trick
.
How does it work?
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.
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
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 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
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.
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 :laughing:
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))
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.
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
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 id
s, 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
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...