%matplotlib inline
import random
import math
import matplotlib.pyplot as plt
import pandas as pd
In an ideal world I’d write this notebook in the tool I’m talking about and share it on my website that way. Unfortunately I haven’t been able to find an extension for Quarto (which I use to build the website) that handles marimo notebooks1, which is the tool in question.
So what’s a marimo notebook? The easiest way to think about it is as a reactive Python notebook. I’ve been using Jupyter notebooks since forever. I first taught with them in 2016 at Berkeley as a contribution to the then nascent major in Data Science there. Since then I’ve taught python programming using Jupyter notebooks as the platform.
That’s a good choice in some ways, but a bad one in others. The good is that you can introduce code in the context of working code for students to modify and get comfortable with, in a managed, predictable environment (unlike the wild west that is most student laptops). The bad reason is that Jupyter notebooks can get in a terrible tangle if you run the code cells out of sequence. Almost invariably when a student runs into problems (or for that matter I run into a problem) using a notebook, it’s because they I have been jumping back and forward running cells, forgetting that this will put the notebook into a weird state and break things.
It’s not unreasonable for students to expect a change anywhere in their code anywhere in a notebook to affect every other part of the notebook that uses affected variables and their values. In other words, for the notebook to ‘react’ to all changes. This enhanced interactivity is what marimo notebooks offer.
Jupyter notebooks
Anyway, if you are unfamiliar, this page is built from a Jupyter notebook. More accurately, most of it is, but I’ll get to that.
This being a notebook, I can mix markdown text such as this text you are reading, with code cells like this:
That cell imports some python modules that I have installed locally, and now I can write python code using them, and run it interactively (locally) in a browser (or suitable coding environment like Visual Studio Code). For example, I can define a random point class RandomPoint
and a function for generating a collection of points using the ‘blue noise’ method I described in a previous post.
class RandomPoint():
def __init__(self):
self.x = random.random()
self.y = random.random()
def distance2(self, pt):
= min(self.x - pt.x, 1 - self.x + pt.x)
dx = min(self.y - pt.y, 1 - self.y + pt.y)
dy return dx**2 + dy**2
def spaced_points(n=100):
= [RandomPoint()]
pts for i in range(1, n):
= math.ceil(math.log(1.1 * i * math.e))
n_candidates = [RandomPoint() for _ in range(n_candidates)]
p_new = [min([p.distance2(p0) for p0 in pts]) for p in p_new]
d_mins = max(enumerate(d_mins), key=lambda x: x[1])[0]
i_max
pts.append(p_new[i_max])return pd.DataFrame(data = {'x': [p.x for p in pts],
'y': [p.y for p in pts]})
And I can make a point pattern and plot it.
= spaced_points(1000)
pp = plt.scatter(x=pp.x, y=pp.y)
scatter 1)
scatter.axes.set_aspect(0, 1)
scatter.axes.set_xlim(0, 1)
scatter.axes.set_ylim( plt.show()
What you are seeing here is the output of my code parsed to produce a static web page in HTML. You can also parse notebooks to make PDFs, Word documents, and so on.
Interactive Jupyter
The experience of using Jupyter notebooks is highly interactive for the author of the notebook. That quickly leads to wanting to put together notebooks that are interactive for the end user. That’s a little bit more complicated to code, but not by much, using the ipywidgets
module.
If I wrap the plotting code above in a function that takes as inputs a point pattern pp
and the number of points n
to plot, then using components from the ipywidgets
module, I can control how many points in the pattern are displayed, so that I can see the progression of the generating function as points are added.
import ipywidgets as widgets
from ipywidgets import interactive, fixed
def plot_spaced_points(pp:pd.DataFrame, n:int):
= plt.scatter(x=pp.x[:n], y=pp.y[:n])
scatter 1)
scatter.axes.set_aspect(0, 1)
scatter.axes.set_xlim(0, 1)
scatter.axes.set_ylim(
plt.show()return None
= spaced_points(1000)
pp = widgets.IntSlider(value=100, min=10, max=pp.shape[0], step=5)
num_pts =fixed(pp), n=num_pts) interactive(plot_spaced_points, pp
And it works. For me. Locally. Here’s a video to prove it!
What’s much more difficult to arrange is hosting this interactive notebook in an online environment so that end users can interact with it like I can. For that I need to set up some kind of server infrastructure, or use an environment such a Google Colab, or CoCalc, Azure Notebooks, or SaturnCloud.
Setting up your own infrastructure is technically demanding involving scary sounding tools like Kubernetes. It’s by no means impossible, but you need good people to set things up, and security can be a concern.
The hosted environments can be a useful alternative, but they can also be limited by low power cores, or limited storage (on free tiers), or can get quite pricey quickly if you need more oomph, or want multiple users at a time (this is a common problem getting Jupyter notebooks into the lab in New Zealand universities).
Enter marimo
This is where marimo comes in. Below is a marimo notebook embedded in a HTML iframe
.2
You can drag the slider to see the number of points in the plot change. You can even edit the code blocks to see the effect of changes to the code! If you make it crash, you’ll have to relaunch the Restart kernel option in the controls dropdown menu in the upper right.
Putting it in an iframe like this isn’t ideal. If you’d just like to visit the page, then go to southosullivan.com/misc/meet-marimo/
The magic here is :WebAssembly (WASM) which my python ‘source code’ has been compiled to via the Pyodide project. Because the resulting HTML page is in WebAssembly it runs in the browser, not on a python kernel on a remote server, so it is effectively a static web page and I can even serve it on github pages. Here’s a more ambitious example from my work on tiled maps of multivariate data. Be warned it takes a little while to download (about a minute to get up and running) because it has to install a bunch of relatively chunky python modules before it will work.
As a way to build relatively lightweight dashboards or nice interactive web-app ‘explainers’ without complicated backend hosting, this seems to hold a lot of potential!