04 Jan 2020
Observable is a web-first interactive notebook for data analysis, visualization, and exploration .
It was created by Mike Bostock, the creator of d3.js, which we discussed previously way back in 2014 . At some point, Mike stepped down from d3.js to focus on a new library called d3.express, which he presented in 2017 during the OpenVis conference  and was still in the makes. d3.express eventually got renamed to Observable.
I’ve been experimenting with Observable and in this post I’ll cover some of my learnings while trying to build two visualizations.
One of the benefits of notebooks like Observable is to co-locate code with its output. This makes it much easier to explain code because in addition to markdown comments, we can modify the code and see the results in real-time. This means that a lot of the documentation of APIs available in Observable are notebooks themselves. For these reasons, this post consists of a series of links to notebooks with high-level comments.
Why Observable - this notebook explains the motivation behind Observable and how it works at a high level:
An Observable notebook consists of reactive cells, each defining a piece of state. Rather than track dependencies between mutable values in your head, the runtime automatically extracts dependencies via static analysis and efficiently re-evaluates cells whenever things change, like a spreadsheet
Five-Minute Introduction - provides a hands-on approach of the many topics, some of which we’ll cover in more detail in this post:
[cell name] = [expression]It can be simple statements like 1 + 2, or multi-line statements like
Introduction to Mutable State - cells are read-only by default but Observable allows changing the value of a cell by declaring it
mutable. When changing the value of the cell elsewhere, the
mutable keyword also must be used.
It’s important to note that the cell is immutable, but if the cell is a reference to a value, say an
Object, then the value can be mutated. This can lead to unexpected behavior, so I created a notebook, Mutating references, to highlight this issue.
Markdown summary - is a great cheat sheet of Markdown syntax in Observable. I find the Markdown syntax in Observable verbose which is not ideal given text is so prevalent in notebooks:
To be fair, most code will be typed as cells, so this shouldn’t be too common. It supports LaTeX via KaTeX which is awesome. KaTeX on Observable contains a bunch of examples.
Introduction to Generators explains how generators can be used in Observable. The combination of generators and delays (via promises) is a great mechanism to implement a ticker, which is the base of animation. Here’s a very simple way to define a ticker in Observable (remember that Promises are awaited without extra syntax):
Introduction to Views explains what views are and how to construct one from scratch. I like this recommendation:
If there is some value that you would like the user to control in your notebook, then usually the right way to represent that value is as a view.
Views are thus convenient ways to expose parameters via buttons and knobs such as text inputs, dropdowns and checkboxes.
I ran into a problem when using checkbox with labels in it, like the cell below:
It does not yield true/false as I wanted. This notebook, Checkbox, discusses the problem and offers a solution. This is an example where great but imperfect abstractions make it hard to understand when something doesn’t work. It’s worth learning about how
viewof is implemented behind the scenes.
In one of my experiments I needed to synchronize a view and a ticker. Bostock provides a neat abstraction in his Synchronized Views notebook.
Introduction to Imports shows how easy it is to import cells from other notebooks.
It also shows that it’s possible to override the value of some of the cells in that notebook:
This is neat, because the
chart cell depends on the
data, and by allowing overriding data, it allows parameterizing
chart. In other words, we import
chart as a function as opposed to a value (the result of
Versioning. When importing a notebook we always import the most recent version, which means it could potentially break if the notebook’s contract changes. The introduction above mentions the possibility of specifying the version when importing but didn’t provide an example on how to do so.
My strategy so far has been to clone a notebook if I’m really concerned about it changing, but that means one has to manually update / re-fork if they want to sync with the upstream changes.
One natural question that crossed my mind is whether I should have written this whole post as an Observable notebook. In the end I decided to stick with an old-school static post for two reasons: