https://maxbo.me/a-html-file-is-all-you-need.html
I've packaged this article up and released a library, @celine/celine.
It has a nicer API too. Check it out! (oV*Vo)
- Max Bo
Published May 6, 2024
Modified September 9, 2024
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:
1. Use an interactive notebook like Jupyter, RStudio, Pluto.jl or
Observable to do data exploration, analysis and visualisation,
2. Move to a publishing platform like Typst, Overleaf, pure LaTeX,
or a WYSIWYG editor to typeset their work,
3. 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.
This article takes heavy inspiration from Anton Zhiyanov's In-browser
code playgrounds, Cristobal Sciutto's Self-modifying HTML notes,
Quarto, and Observable Framework
I'll try and demonstrate these capabilities, literate programming
style.
A computer displays an open book
Cells
First, we'll steal a trick from This page is a truly naked, brutalist
html quine, and create a CSS class called echo 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 contenteditable scripts to be re-evaluated on blur by
building a clone of the script and then removing the original.
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 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 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 cell values in more complex outputs. We'll import
Observable Plot and use the counter value in a plot.
A computer displays a graph in the upward direction
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.
A computer with an open CD tray is surrounded by data
Cell status
We can also return a Promise, 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's sqlite3 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 type viewof 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 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 function mutable. 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?
[S:I will try and cram all of this into a library with some proper
documentation.:S]
[S:I initially thought it should be called incel (short for inline
cell), but I'll probably call it celine instead.:S]
I've released a library! It's called @celine/celine!
A computer terminal receives text
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
[pages]
source code (you've pretty much seen all of it already) (c) 2024