Some Noticeable Thoughts on Observable

I have been experimenting with Observable as a means of creating explorable explainations for my Computer Graphics course. Now that my first, on barycentric coordinates, has been published, I’ll share my thoughts on my experience.

What is Observable?

Observable is an interactive notebook in the vein of IPython, Jupyter, R Studio with R Markdown and its ilk, with one major difference—it is native on the web. Thus, native Javascript and web technologies such as SVG, Canvas, and d3 can be utilized.

The core of Observable grew out of Mike Bostock‘s d3.express, which he expounds on here. Effectively, an Observable notebook is a collection of cells which dynamically manage relationships between input and outputs, creating a dynamic system. Its very much like a spreadsheet, occasionally for visualization, which I know a little about (though it is much closer to Chi’s spreadsheet work than mine): Cells are code, text, and graphics which then influence other code, text, and graphics. The technology is at its core an asychonrous state manager for a data flow network. But, looking beyond the techonology, Observable (in my mind) is a place to interactively explain or explore concepts, concepts instantiated through code.

Thoughts

This post is not an introduction to Observable; they provide a series of tutorials for that purpose. Instead, I want to focus on my thoughts about writing on the platform for others who are thinking about it. I also share some insights about the project that are not notebook specific. The entire process of inception to publication took about a week; not all of that was down the code trenches.

Javascript has come a long way

This observation is not really specific to Observable, but considering my last real work in Javascript was back in 2015 or so, the language has changed significantly. It has become a bit more Pythonic in ways (e.g., it doesn’t have true list comprehensions [they were rejected], but they can emulate the behavior easily enough). There is less of a cognitive context switch now.

Geometry in 3D is a PITA

Again, not tied to Observable, but important in this context. As I mentioned, it took me about a week to write the notebook on barycentric coordinates. Half of that, however, was spent in the math behind their derivation. There are plenty of sources for calculating the coordinates; I, however, wanted to focus on doing the calculating in 3D with a geometric slant, motivated by one of my doctoral advisors Dr. Ken Joy. Lines in 3D, however, are non-trivial to parameterize in 3-space. Ultimately, doing the calcuation in 3D was dropped as unneeded (a triangle is in a plane, after all) and too complex for what is a straightforward calculation.

A notebook is not a webpage

Though an Observable notebook is found via your web-browser, it is not a web document. You can create HTML directly or via a DOM or Markdown, but what is generated is tied to individual cells. As an example, relative links in the web-page sense do not work; named anchors are embedded in the execution model’s iframe and thus inaccessible to the greater world. You can link into a notebook, but you are linking to named cells, not anchors. Thus, I had to update my mental model—notebooks are not documents they a view upon code.

The joy of asychronous computing

While half of my time was spent trying to tidy my geometric math, the other half was spent fighting my years of sychronous thinking on an inheriently asychronous platform. Observable is built upon Promises and Generators—means of waiting for things that do not happen when you expect them. These technologies allow Observable to work, but have some consequences that are not immediately obvious.

The first example I ran into was when I was updating my barycentric values. Observable distinguishes between variable accesses that causes a dependency (i.e., forces changes to the variable to update the cell) and ones that does not; see mutables for more details. A link to a mutable variable only needs to change the cell when that varaible is explicitly changed; otherwise, an update is fired whenever something in the containing cell is changes. Thus, I ended up continuously firing update messages with the following code:

mutable alpha = ...
mutable beta =  ...
mutable gamma = 1 - alpha - beta

The third line caused a dependency on the use of alpha and beta—which I modified in the code immediately beforehand. Thus, the cell was marked continuously in need of an update. So many console.log messages. Marking both alpha and beta as mutable one the third line fixed the issue.

The second issue was more amusing if frustrating. Dragging on the points in the triangle needs to update all the associated variables; naturally, an event handler was involved. In my code, the event handler was created in the same notebook cell that instantiated the SVG graphics which needed updating:

{
    // Problem is here where I bind to cell variables a & b
    const tri = html`<svg width="${width}" height="300">
        <line class="ab" x1=${a[0]} y1=${a[1]} 
                         y1=${b[0]} y2=${b[1]} 
                         stroke="blue" /> 
        ...
    </svg>`

    // Event bound to svg element that will be replaced
    d3.select(tri).call(d3.drag().on("drag", dragging)); 
    return tri;
}

However, when a cell is updates, its contents are replaced. Thus, the <svg> element that the mousemove event was using as its frame of reference vanished between the first and second mouse events, causing wild amounts of change in the d3.event.x/y values. Because the graphics updated faster than I could notice (a new <svg> node replaced the old one with no flashing in-between), realizing what was happening took some time. Changing the cell so that the existing <svg> child elements were modified (in a separate cell) fixed my roving mouse issue.

Colors everywhere

As part of my explorable, I wanted to use color to highlight the connections between the different geometric parts and the equations. I believe this worked well, but I really should have cached the color names so I was not typing \textcolor{${d3.schemeCategory10[1]}}{\beta} everywhere.

Summary

I believe Observable is a powerful platform for the creation of interactive explainations. However, it does require updating your model of how web documents and Javascript function. Or perhaps my inner grognard is showing.