Modified
Hacker News discussion
SydJS talk
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 a HTML file can be used for all 3 of these stages, and prevent a lot of faffing around with manual processes, CLI tooling, CI steps and 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:
- Use an interactive notebook like Jupyter, RStudio, Pluto.jl or Observable to do data exploration, analysis and visualisation,
- Move to a publishing platform like Typst, Overleaf, pure LaTeX, or a WYSIWYG editor to typeset their work,
- Export to
.pdf
for distribution.
I think a HTML file can be used for all 3 of these stages, and prevent a lot of faffing around with manual processes, CLI tooling, CI steps and 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
!
We also want
(Why can't we just use
contenteditable
script
s to be re-evaluated on blur by building a clone of the script
and then removing the original.
(Why can't we just use
eval
? For one, eval
doesn't work with code with import
statements in it.)
Now we'll import the Observable standard library and the Observable runtime, and bind them to
window
.
We'll export only 2 symbols to window
, library
and cell
.
Now we'll declare a cell called
Try changing the initial counter value
counter
that emits a number every second.
The script
's id
attribute is the same as the name
parameter passed to cell
.
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 ^)
Alternatively, we can create a cell type that doesn't display its output at all.
We can use these cells to store intermediate values or datastructures. Also note that cells can be declared out of order.
We can use these cells to store intermediate values or datastructures. Also note that cells can be declared out of order.
We can use cell values in more complex outputs. We'll import Observable Plot and use the
counter
value in a plot.
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