Observable Notebook представляет собой интерактивную среду разработки, ориентированную на визуализацию данных, исследовательское программирование и быстрое прототипирование. Платформа особенно тесно связана с экосистемой D3.js, поскольку её создатель — Майк Босток, автор библиотеки D3.
Главное отличие Observable от традиционных редакторов JavaScript заключается в реактивной модели вычислений. Код выполняется не сверху вниз, а как сеть зависимостей между ячейками.
Каждая ячейка notebook:
Простейший пример:
width = 800
height = width / 2
area = width * height
При изменении width автоматически пересчитываются
height и area.
Такой подход особенно полезен для визуализации данных, поскольку графики мгновенно обновляются при изменении параметров, фильтров или входных данных.
Notebook состоит из независимых ячеек.
Типы ячеек:
Пример markdown-ячейки:
# График продаж
Пример HTML:
html`<div class="container"></div>`
Пример вычисляемой ячейки:
data = [
{x: 10, y: 20},
{x: 30, y: 50}
]
Каждая переменная становится доступной глобально внутри notebook.
В Observable D3 обычно уже доступен через встроенный импорт:
d3 = require("d3@7")
Современный вариант через ES Modules:
import * as d3 from "d3"
После импорта доступны все модули библиотеки:
d3-selectiond3-scaled3-axisd3-shaped3-arrayd3-forced3-geod3-transitionObservable автоматически кэширует зависимости и управляет загрузкой модулей.
В классическом JavaScript SVG обычно создаётся через DOM API или append-цепочки. В Observable распространён подход с возвратом готового DOM-узла из ячейки.
Пример:
svg = d3.create("svg")
.attr("width", 600)
.attr("height", 400)
svg.node()
Метод d3.create() создаёт изолированный элемент, который
затем отображается notebook-средой.
Одно из ключевых преимуществ Observable — автоматическое обновление визуализаций.
Пример:
width = 800
svg = d3.create("svg")
.attr("width", width)
.attr("height", 200)
svg.append("rect")
.attr("width", width)
.attr("height", 100)
svg.node()
При изменении width SVG пересоздаётся автоматически.
В традиционном приложении пришлось бы:
Observable выполняет эти задачи автоматически.
Ключевая особенность Observable — механизм viewof.
Он позволяет связывать пользовательский интерфейс с реактивными данными.
Пример:
viewof radius = Inputs.range([10, 100], {
value: 40,
step: 1,
label: "Радиус"
})
Значение доступно через переменную:
radius
Теперь можно использовать её в визуализации:
svg = d3.create("svg")
.attr("width", 300)
.attr("height", 300)
svg.append("circle")
.attr("cx", 150)
.attr("cy", 150)
.attr("r", radius)
svg.node()
Ползунок мгновенно изменяет размер круга.
Observable содержит встроенную библиотеку Inputs.
Основные элементы:
| Компонент | Назначение |
|---|---|
Inputs.range() |
Ползунок |
Inputs.select() |
Выпадающий список |
Inputs.checkbox() |
Флажки |
Inputs.toggle() |
Переключатель |
Inputs.button() |
Кнопка |
Inputs.text() |
Текстовый ввод |
Inputs.table() |
Таблица |
Inputs.search() |
Поиск |
Пример списка:
viewof color = Inputs.select(
["red", "green", "blue"],
{label: "Цвет"}
)
Использование:
svg = d3.create("svg")
.attr("width", 200)
.attr("height", 200)
svg.append("rect")
.attr("width", 100)
.attr("height", 100)
.attr("fill", color)
svg.node()
Observable упрощает загрузку данных.
data = FileAttachment("sales.csv").csv()
data = FileAttachment("data.json").json()
text = FileAttachment("notes.txt").text()
img = FileAttachment("map.png").image()
После загрузки данные становятся реактивными.
Observable хорошо подходит для исследовательского анализа данных.
Пример группировки:
grouped = d3.group(data, d => d.category)
Агрегация:
totals = d3.rollup(
data,
v => d3.sum(v, d => d.value),
d => d.category
)
Сортировка:
sorted = data.slice().sort((a, b) => b.value - a.value)
Фильтрация:
filtered = data.filter(d => d.value > 100)
Каждая трансформация автоматически обновляет графики.
Observable идеально подходит для экспериментирования с масштабами D3.
Линейная шкала:
x = d3.scaleLinear()
.domain([0, 100])
.range([0, 500])
Категориальная шкала:
color = d3.scaleOrdinal()
.domain(["A", "B", "C"])
.range(["red", "green", "blue"])
Временная шкала:
x = d3.scaleTime()
.domain([
new Date("2024-01-01"),
new Date("2024-12-31")
])
.range([0, 700])
Observable позволяет мгновенно проверять влияние изменений на визуализацию.
Пример оси X:
svg.append("g")
.attr("transform", "translate(0,300)")
.call(d3.axisBottom(x))
Ось Y:
svg.append("g")
.call(d3.axisLeft(y))
Реактивность Observable особенно полезна при настройке:
Полный пример:
svg = {
const width = 700
const height = 400
const margin = 40
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
const x = d3.scaleLinear()
.domain(d3.extent(data, d => d.x))
.range([margin, width - margin])
const y = d3.scaleLinear()
.domain(d3.extent(data, d => d.y))
.range([height - margin, margin])
const line = d3.line()
.x(d => x(d.x))
.y(d => y(d.y))
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 2)
.attr("d", line)
svg.append("g")
.attr("transform", `translate(0,${height - margin})`)
.call(d3.axisBottom(x))
svg.append("g")
.attr("transform", `translate(${margin},0)`)
.call(d3.axisLeft(y))
return svg.node()
}
Блоковая форма { ... } позволяет использовать локальные
переменные внутри ячейки.
Observable прекрасно подходит для исследования анимаций.
Пример:
svg.select("circle")
.transition()
.duration(1000)
.attr("r", 80)
Реактивная модель позволяет создавать управляемые анимации:
viewof duration = Inputs.range([100, 5000], {
value: 1000,
label: "Длительность"
})
Использование:
.transition()
.duration(duration)
Observable поддерживает async/await напрямую в
ячейках.
Пример:
data = await d3.json("https://example.com/data.json")
Также поддерживаются:
Observable автоматически ожидает завершения промисов.
Особая возможность Observable — поддержка генераторов.
Пример:
counter = {
let i = 0
while (true) {
yield i++
await Promises.delay(1000)
}
}
Значение обновляется каждую секунду.
Использование в графике:
text = html`<div>${counter}</div>`
Генераторы часто применяются для:
По умолчанию Observable ориентирован на декларативную реактивность. Однако иногда требуется изменяемое состояние.
Для этого используется Mutable.
Пример:
mutable count = 0
Изменение:
mutable count++
Получение значения:
count
Такой механизм полезен при:
Observable поддерживает импорт ячеек из других notebook.
Пример:
import {chart} from "@user/charts"
Импорт конкретной функции:
import {legend} from "@d3/color-legend"
Преимущества:
Observable активно использует библиотеку Observable Plot — высокоуровневую надстройку над D3.
Пример:
Plot.plot({
marks: [
Plot.barY(data, {
x: "category",
y: "value"
})
]
})
Plot упрощает:
При необходимости всегда можно перейти к низкоуровневому D3.
Observable позволяет напрямую возвращать DOM-узлы.
Пример:
element = {
const div = document.createElement("div")
div.textContent = "Hello"
return div
}
Либо через tagged templates:
html`
<div class="card">
<h2>Статистика</h2>
</div>
`
Observable поддерживает обе модели рендеринга.
Подходит для:
Подходит для:
Пример canvas:
canvas = {
const canvas = document.createElement("canvas")
canvas.width = 800
canvas.height = 400
const context = canvas.getContext("2d")
context.fillStyle = "steelblue"
context.fillRect(10, 10, 200, 100)
return canvas
}
Observable особенно популярен для force-directed графов.
Пример:
simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(400, 300))
.force("link", d3.forceLink(links))
Реактивная среда позволяет:
Observable существенно упрощает debugging.
Можно:
Пример:
x.domain()
или:
data.slice(0, 10)
Каждая ячейка служит отдельной точкой анализа.
Observable поддерживает публикацию notebook как веб-страницы.
Возможности:
Notebook может выступать одновременно:
Observable-проекты можно переносить в обычные JavaScript-приложения.
Распространённые подходы:
Пример runtime:
import {Runtime, Inspector} from "@observablehq/runtime"
Observable Runtime позволяет запускать notebook вне платформы Observable.
Пример:
const runtime = new Runtime()
runtime.module(notebook, Inspector.into("#app"))
Это делает возможным:
Observable особенно эффективен при разработке D3-проектов благодаря нескольким особенностям:
Автоматическая синхронизация зависимостей избавляет от ручного управления состоянием.
UI-контролы встроены непосредственно в среду.
Каждая ячейка независима.
Notebook идеально подходит для data exploration.
Результат легко превратить в интерактивную документацию.
Изменения сразу отражаются на графиках.
Можно быстро сравнивать:
Несмотря на преимущества, существуют ограничения.
Notebook-архитектура ориентирована прежде всего на исследование и прототипирование.
При сложных взаимозависимостях возможны неожиданные реактивные обновления.
Notebook менее удобен для:
Часто требуется перенос кода в:
Наиболее распространённый сценарий:
Observable особенно ценен на этапах исследования и проектирования визуализации, где скорость итераций важнее строгой архитектуры приложения.