Bokeh, Bokehjs, and Observablehq

A venture out of Jupyter’s orbit

Jeremy Teitelbaum
Towards Data Science

--

Jupiter’s Northern Latitudes from Juno, November 2019 (photo credit courtesy NASA/JPL-Caltech)

The Bokeh visualization library has become one of my favorite tools for displaying data while working with python in the jupyter notebook. Bokeh is powerful, easy to use, has accessible interactive features, and produces beautiful graphs. As I’ve worked with Bokeh over the past months, however, and learned a bit more about its internals, I’ve come to realize that the python API for Bokeh in jupyter is just a small part of the entire Bokeh package. In addition to that API, Bokeh includes a server package and a javascript library called bokehjs. In fact, Bokeh’s python “plotting” package doesn’t do any plotting at all; rather, it is a language for describing plots that get serialized into a json package and passed to bokehjs for rendering in the browser. As I experimented with adding more interactivity to my plots, it gradually became clear to me that knowing some javascript — which I didn’t — and having a clearer understanding of bokehjs would let me do a lot more with Bokeh.

Meanwhile, for entirely different reasons, I came across Observablehq. Observablehq is created by a team led by Mike Bostock, the developer of the javascript D3 visualization package. At first glance, it looks very much like a cloud-hosted jupyter notebook based on javascript. Given my goals of exploring bokehjs and learning some javascript, I naively thought Observablehq was the perfect tool for me. Well, it’s not so simple, because Observablehq isn’t just a javascript version of the jupyter notebook, it’s something quite different, and quite beautiful in its own way; and bokehjs isn’t a completely natural fit for the Observablehq world. Still, I learned a lot about both bokehjs and Observablehq in trying to bring these worlds together, and I see a lot of potential for further development. Here are a brief progress report and some tips if you’d like to take this journey as well.

Observablehq isn’t Jupyter for Javascript

As I mentioned above, when I looked at the Observablehq user interface, my first reaction was this is just Jupyter for javascript!

An animated gif of creating a new Observable notebook

As the little animation above shows, Observable has notebooks, with cells, and you enter javascript (or markdown) into the cells; hit shift-enter, and the cell gets evaluated. Sounds like Jupyter, right? But Observable notebooks are profoundly different — each cell has a value, and the cells are assembled together into a graph based on references. When the value of one cell changes, all cells that depend on that cell are re-evaluated. It’s sort of like a spreadsheet of little javascript programs. This design means that Observable notebooks support a high degree of interactivity in a natural way far beyond the ability of jupyter notebooks. If you’re intrigued, your best option is to read the excellent articles at the Observablehq site. Beyond the introductory articles, check out in particular:

Loading Bokehjs in Observable

The first step in experimenting with the bokehjs in an observable notebook is to get the library loaded. For this step, I had help from Bryan Chen’s Hello, Bokehjs notebook. Putting the following code in an observable notebook cell, and hitting Shift-Enter, does the trick:

Loading the Bokehjs Library into Observablehq

Without spending too much time on the details, it is worth pointing out that the code that loads Bokeh is enclosed in braces so that it gets executed as a unit. As I mentioned earlier, each cell in an observable notebook is like a self-contained javascript program, and the cells are executed and re-executed depending on the dependency graph among their references. The crucial require statements in this code act via side effects, rather than by returning a value. That means Observable doesn’t understand the dependencies among those statements; if put in separate cells, they could be executed in any order. More generally, Observable isn’t set up to deal with functions that act via side effects, and one needs to be careful using them.

This particular cell is a viewof construct, and its effect is to assign the variable Bokeh the reference to window.Bokeh where the bokehjs javascript library is attached, while displaying the contents of the message variable which is an html string indicating what’s going on.

You can save yourself some typing and, instead of including the code above, take advantage of Observable’s ability to import cells across notebooks and just use:

import {Bokeh} from "@jeremy9959/bokeh-experiments"

Plotting with Bokehjs

Now that we have the library loaded, let’s draw a plot. I’ll follow the example of a hierarchical bar chart from the bokehjs distribution.

Bokehjs Fruit Plot Example

You can look directly at the observable notebook where I draw this plot. The first part of the notebook just sets up the data by creating cells corresponding to the fruits and years data, well as the corresponding year by year counts. For example, the year by year counts are stored in the variable data which is declared directly:

// help the parser out by putting {} in ()
data = ({
2015: [2,1,4,3,2,4] ,
2016:[5,3,3,2,4,6],
2017: [ 3,2,4,4,5,3],
})

Notice that the braces used in javascript explicit object creation need parentheses to help the observable parser out.

The Bokeh code to create the plot is taken directly from the file in the bokehjs distribution (though I made the plot a bit wider):

Finally, we render the plot into a cell in the observable notebook using Bokehjs’s embed function.

The result is the plot I’ve drawn above.

So What?

So far, this is a bit underwhelming, since we could have drawn the same plot in a jupyter notebook using the python API with no trouble at all. To illustrate why this approach is interesting, let me point out two major benefits we get by working in observable.

  1. We can inspect the javascript objects. One of the reasons I was interested in Observable in the first place is because I’m trying to learn javascript and trying to understand the inner workings of bokehjs. It’s certainly possible to use the browser’s javascript console to poke around the internals of bokehjs, but observable gives an elegant interface. For example, in producing the plot we constructed a Figure object called P. The image below shows what Observable can reveal about this figure, and using the little triangles you can open up the object and probe its inner structure. (Note: it’s important to load the non-minified bokehjs package if you want to do this, or the class ID’s will be incomprehensible in this output.)

2. Observable is interactive! What’s really different, and interesting, about doing this in Observable is that it’s interactive. If I go up to the cell where the variable data is defined, and change the numbers, as soon as I enter the cell the graph gets updated:

This is because Observable’s execution graph knows that the fruit plot depends on the data variable, and when that variable changes, the plot gets recomputed.

Incidentally, another feature of Observable is that since the execution order isn’t tied to the physical ordering of the cells in the document, I was able to move the graph right up next to the data cell so I can see clearly what was going on.

This is only a tiny taste of the level of interactivity that’s possible in Observable — it’s very easy, for example, to add widgets and even do animations right in the notebook. This post isn’t the place to get into that, but there are lots of beautiful examples on the Observable home page.

Remarks upon return to Jupyter Orbit

In closing, I think it’s important to point out that there are more natural ways to plot in Observable than using Bokeh. In particular, there is a tightly integrated API for using Vega, and the very powerful D3 package is practically built in to Observable. But for someone like me, who is comfortable with the python interface to bokeh and wants to learn more about bokehjs — especially considering that, while the python API is extensively and meticulously documented, the bokehjs API is basically a black box — Observable offers a fun opportunity. There are lots more things to try and I look forward to further ventures beyond the orbit of Jupyter.

--

--