New Component: streamlit-observable, a new way to embed Observable Notebooks

Hey all!

I made streamlit-observable, a custom component for rendering Observable notebooks into Streamlit apps. I’ve been wanting this feature in Streamlit for quite some time, so I was pretty excited when Streamlit launched custom components!

If you’re not familiar with Observable notebooks, they’re reactive client-side Javascript notebooks that are really great for making custom data visualizations, especially with D3. There’s a nice community around it, and a ton of examples that you can plug into and remix without much effort. Every notebook is comprised of cells, that each depend on each other to create a DAG of cells that you can hook into, allowing you to inject your own data, configuration, or styling into charts, maps, or animations!

streamlit-observable does most of the heavily lifting around dealing with the Observable runtime, and offers small API that allows you to redefine cells, observe cell values when they change, and configure how to render them. This was built on top of the standard Streamlit Custom Component typescript template, meaning each embed is isolated in its own iframe.

You can pip install streamlit-observable to start using it right away, and here’s the project on Github.

Here’s an example Streamlit app that shows off all the cool ways you can use streamlit-observable. If you don’t want to click there, here’s a few of my favorite examples:

Bar Chart Race with Wikipedia pageviews

import streamlit as st
from streamlit_observable import observable

@st.cache
def get_wiki_data():
    # A bunch of code...
    return df

df_wiki = get_wiki_data()
observable("Wiki Bar Chart Race", 
    notebook="d/9bbcce8f2f6834d7", 
    redefine={
        "rawData": df_wiki.to_csv(),
        "duration": 200,
        "n": 10
    }, 
    targets=["chart", "viewof keyframe", "update"],
    hide=["update"]
)

barchartrace

Voronoi Map of Trader Joe’s Locations

import streamlit as st
from streamlit_observable import observable

@st.cache
def get_trader_joes():
    # a lot of code...
    return df

df = get_trader_joes()

observable("Trader Joes Voronoi Map", 
    notebook="@mbostock/u-s-voronoi-map-o-matic", 
    targets=["map"],
    redefine={
        "data": df[["longitude", "latitude", "name"]].to_dict(orient="records")
    }
)

Drawing on a Canvas

observers = observable("Example Drawing Canvas", 
    notebook="@d3/draw-me", 
    targets=["viewof lineWidth", "viewof strokeStyle", "undo", "viewof strokes"], 
    observe=["strokes"]
)

drawme1

Selecting U.S. Counties on a Map

observers = observable("County Brush", 
    notebook="d/4f9aa5feff9761c9",
    targets=["viewof countyCodes"], 
    observe=["selectedCounties"]
)

selectedCounties = observers.get("selectedCounties")

countiesbrush

8 Likes

Finally all the D3 v6 tutorials directly in Streamlit :heart: ! Bravo :clap: I’m already having fun with the Static Embed bar chart.

Also this example app is very detailed, thanks for putting so much work into it.

Could you edit the tracker to add this awesome component :slight_smile: ?

Cheers,
Fanilo

2 Likes

Welcome back @asg017! What a flex publishing this component as your triumphant return to the forum :wink:

2 Likes

Per this comment:

Accessing webcam and microphone doesn’t work

Not entirely sure why this is the case, but if someone figures it out, I’d love to see a PR!

As part of the Components launch, we were pretty conservative about the iframe properties that we would allow. Secondarily, we have to think about how a live videocam might work, since it’s not clear when you would refresh an app or what the triggers might be. Stay tuned!

1 Like

So many amazing examples here - thanks for sharing! This is definitely the coolest thing I’ve seen in a while - and I’m functionally looking at that Trader Joes map to see possible places I could move :smiley:

3 Likes

Thanks for sharing this. Really useful. :smiley:

That is a great component that genuinely gave me a “Whoaa” reaction! Love it!

2 Likes