Welcome to Observable! This notebook gives a quick but technical overview of Observable’s features. For a slightly longer introduction, see Introduction to Notebooks and introductory tutorial series. If you want to watch rather than read, we’ve also posted a short video introduction!
An Observable notebook is a sequence of cells. Each cell is a snippet of JavaScript. You can see (and edit!) the code for any cell by clicking the caret <svg width="16" height="16" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none"><polyline points="10 18 16 12 10 6"></polyline></svg> in the left margin.
2 * 3 * 7
{ let sum = 0; for (let i = 0; i <= 100; ++i) { sum += i; } return sum; }
Cells can have names. This allows a cell’s value to be referenced by other cells.
color = "red"
`My favorite color is ${color}.`
A cell referencing another cell is re-evaluated automatically when the referenced value changes. Try editing the definition of color above and shift-return to re-evaluate.
Cells can generate DOM (HTML, SVG, Canvas, WebGL, etc.). You can use the standard DOM API like document.createElement, or use the built-in html tagged template literal:
html`<span style="background:yellow;"> My favorite language is <i>HTML</i>. </span>`
There’s a Markdown tagged template literal, too. (This notebook is written in Markdown.)
My favorite language is Markdown.
DOM can be made reactive simply by referring to other cells. The next cell refers to color. (Try editing the definition of color above.)
html`My favorite color is <i style="background:${color};">${color}</i>.`
Sometimes you need to load data from a remote server, or compute something expensive in a web worker. For that, cells can be defined asynchronously using promises:
status = new Promise(resolve => { setTimeout(() => { resolve({resolved: new Date}); }, 2000); })
A cell that refers to a promise cell sees the value when it is resolved; this implicit await means that referencing cells don’t care whether the value is synchronous or not. Edit the status cell above to see the cell below update after two seconds.
status
Promises are also useful for loading libraries from npm. Below, require returns a promise that resolves to the d3-fetch library:
require
d3 = require("d3-fetch@1")
If you prefer, you can use async and await explicitly:
countries = (await d3.tsv("https://unpkg.com/world-atlas@1/world/110m.tsv")) .sort((a, b) => b.pop_est - a.pop_est) // Sort by descending estimated population. .slice(0, 10) // Take the top ten.
Cells can be defined as generators; a value is yielded up to sixty times a second.
i = { let i = 0; while (true) { yield ++i; } }
`The current value of i is ${i}.`
Any cell that refers to a generator cell sees its current value; the referencing cell is re-evaluated whenever the generator yields a new value. As you might guess, a generator can yield promises for async iteration; referencing cells see the current resolved value.
date = { while (true) { yield new Promise(resolve => { setTimeout(() => resolve(new Date), 1000); }); } }
Combining these primitives—promises, generators and DOM—you can build custom user interfaces. Here’s a slider and a generator that yields the slider’s value:
slider = html`<input type=range>`
sliderValue = Generators.input(slider)
Generators.input returns a generator that yields promises. The promise resolves whenever the associated input element emits an input event. You don’t need to implement that generator by hand, though. There’s a builtin viewof operator which exposes the current value of a given input element:
viewof value = html`<input type=range>`
value
You can import cells from other notebooks. To demonstrate how custom user interfaces can expose arbitrary values to other cells, here’s a brushable scatterplot of cars showing the inverse relationship between horsepower and fuel efficiency.
import {viewof selection as cars} from "@mbostock/d3-brushable-scatterplot"
viewof cars
cars