Modified
Reactive HTML notebooks
Before I start, why am I doing this?
I don't think HTML is being used enough as a platform for scientific publishing.
Instead, people will:
I think self-contained HTML files can be used for both of these stages, and prevent a lot of faffing around with CLI tooling, CI steps, or 3rd-party platforms.
HTML's typesetting capabilities are well documented, but its capabilities as a platform for data exploration, analysis and visualisation are not.
I'll try and demonstrate these capabilities, literate programming style.
I don't think HTML is being used enough as a platform for scientific publishing.
Instead, people will:
- First use an interactive notebook like Jupyter, RStudio, Pluto.jl or Observable to do data exploration, analysis and visualisation,
- Then move to publishing platforms like Typst, Overleaf, pure LaTeX, or a WYSIWYG editor to typeset their work.
I think self-contained HTML files can be used for both of these stages, and prevent a lot of faffing around with CLI tooling, CI steps, or 3rd-party platforms.
HTML's typesetting capabilities are well documented, but its capabilities as a platform for data exploration, analysis and visualisation are not.
I'll try and demonstrate these capabilities, literate programming style.
Cells
First, we'll steal a trick from This page is a truly naked, brutalist html quine, and create a CSS class calledecho
that will display/reflect style
and script
elements inline.
Add in a font with built-in syntax highlighting and a
contenteditable
attribute and we have a basic code editor!
I stress that this
style
element is styling itself to be visible.
Try changing
.echo
's background-color
!
Now we'll import the Observable standard library and the Observable runtime, and bind them to
window
.
We'll define helper methods cell
and observer
that wrap some of the runtime API.
Now we'll declare a cell called
counter
that emits a number every second.
The script
's id
attribute is the same as the name
parameter passed to cell
.
contenteditable
We can getcontenteditable
working by ensuring that whenever we blur a script
element, we force its re-evaluation by building a clone of it.
Try changing the initial counter value
i
above to a much bigger number, and then defocus the script
.
Now that we've created a our
We'll import Hypertext Literal and use it to format the
counter
cell, we can create other cells that depend on it.
We'll import Hypertext Literal and use it to format the
counter
value. htl
implements a full-blown HTML5 parser that performs automatic escaping and
interpolation of non-serializable values, such as event listeners, style objects, and other DOM nodes.
We can still observe the output of a cell without needing to show its definition. Just don't add the
echo
class.
This makes them useful as a rendering primitive.
(There's a hidden cell above ^)
We can use cell values in more complex outputs. We'll import Observable Plot and use the
counter
value in a plot.
We should always store data in their own
script
elements, so that they can be easily referenced by other cells. Observe that cells can be declared in any order, such that earlier cells may depend on later cells.
TeX, Markdown, Graphviz
We can return any type of DOM element from a cell.In this case, the
tex
, md
, and dot
cells return span
, table
and svg
elements respectively.
Try editing any of the following cells.
Cell status
We can also return aPromise
, or throw an Error
, from a cell.
Observable's Inspector
will apply an observablehq--running
or
observablehq--error
class to the cell's outer div
element respectively.
We'll style them appropriately:
SQLite
I've hosted the Chinook sample database on my website at https://maxbo.me/chinook.db. Now we'll use a WASM-backed SQLite client to query it.Try adding
WHERE Milliseconds < 1000000
to the SQL query!
Python
The Pyodide CPython WASM distribution includes NumPy, Pandas, Matplotlib, scikit-learn, and Scipy. We'll rebuild the plot seen above, but using Matplotlib and Python'ssqlite3
module instead.
Try editing one of the plot labels!
R
You know the drill. It's R, using WebR. I didn't figure out how to get ggplot2 rendering working, but I assume it's possible. I must disclose that this cell seems to be a bit flaky on iOS. I have not had a chance to investigate further, nor will I.Inputs
We'll create a new cell typeviewof
that works specifically with Observable Inputs.
It declares 2 reactive cells: NAME
and viewof NAME
- one for the value, and one for the DOM element itself.
To display the input above the cell, we set the cell
Wiggle the range input and see another dependent cell update. NB: The way Observable Inputs work is a bit arcane. This demo of Synchronized Inputs may shed some light.
id
to viewof NAME
.
Wiggle the range input and see another dependent cell update. NB: The way Observable Inputs work is a bit arcane. This demo of Synchronized Inputs may shed some light.
Mutability
Purely functional dataflow is great, but sometimes you just need to mutate state. We'll create a new helper functionmutable
. It registers a Mutable
- an object that yields new Generator
values when the value is mutated - in the runtime.
Try editing the initial state of the
mutable
.
Try editing the button labels.
What's next?
incel
(short for inline cell), but I'll probably call it celine
instead.I've released a library! It's called @celine/celine!
Slide infrastructure
I demo'd this article at SydJS. This is the code I used to turn the article into a slideshow.- Shift + N - Start slideshow / next slide
- Shift + B - Previous slide
- Shift + E - End slideshow