Observable Plot
Observable HQが開発したモダンなデータビジュアライゼーションライブラリ。Grammar of Graphics概念に基づき、宣言的なチャート作成が可能。D3.jsの思想を受け継ぎつつ、より簡潔な記述を実現。
フレームワーク
Observable Framework
概要
Observable Frameworkは、データアプリケーション、ダッシュボード、レポートを作成するための静的サイトジェネレーターです。
詳細
Observable Framework(オブザーバブル フレームワーク)は、Observable社が開発したデータ駆動型アプリケーションに特化した静的サイトジェネレーターです。フロントエンドのJavaScriptと任意のバックエンド言語を組み合わせて、高性能なデータ可視化とインタラクティブなダッシュボードを構築できます。リアクティブプログラミングモデルを採用し、データの変更に応じて自動的にビジュアライゼーションが更新される仕組みを提供します。データローダーによってビルド時にデータの静的スナップショットを生成するため、瞬時に読み込まれるダッシュボードを実現します。Observable Plot、D3.js、Mosaic、Vega-Lite、DuckDBなどの強力なライブラリと統合されており、大規模データセットの処理と可視化に対応しています。MarkdownとJavaScriptを組み合わせた直感的な記述方法で、技術的なレポートから複雑なビジネスインテリジェンスアプリケーションまで幅広く活用できます。
メリット・デメリット
メリット
- 高性能: データローダーによる事前計算で瞬時に読み込まれるダッシュボード
- リアクティブ: スプレッドシートライクな自動更新で状態管理が簡単
- ポリグロット対応: JavaScript、Python、R、SQLなど複数言語をサポート
- 豊富な可視化ライブラリ: Plot、D3、Vega-Lite、Mermaidなど多数の統合ライブラリ
- 大規模データ対応: DuckDBによる効率的なクライアントサイドデータ処理
- 直感的な記述: MarkdownとJavaScriptの組み合わせで簡単開発
- ライブプレビュー: リアルタイムでの変更確認とホットリロード
デメリット
- 学習コスト: Observable特有のリアクティブモデルの習得が必要
- 静的サイト制約: ビルド時にデータが固定されるためリアルタイムデータに制限
- エコシステム: React/Vueなどと比較して相対的に小さなコミュニティ
- カスタマイズ制約: 特定のアーキテクチャに依存するため柔軟性に限界
- デバッグの複雑さ: リアクティブな依存関係のデバッグが困難な場合がある
主要リンク
- Observable Framework公式サイト
- Observable Framework ドキュメント
- Observable Framework GitHub
- Observable Cloud
- Observable Plot
- サンプルアプリケーション
書き方の例
Hello World
// 基本的なMarkdown + JavaScriptの記述
display("Hello, Observable Framework!");
// HTMLエレメントの作成
display(html`<h1>Welcome to Observable!</h1>`);
// シンプルなデータ表示
const data = [1, 2, 3, 4, 5];
display(data);
基本的なデータ可視化
// データの準備
const salesData = [
{month: "Jan", sales: 120},
{month: "Feb", sales: 180},
{month: "Mar", sales: 150},
{month: "Apr", sales: 220},
{month: "May", sales: 190}
];
// Observable Plotを使った基本的なバーチャート
Plot.plot({
marks: [
Plot.barY(salesData, {x: "month", y: "sales", fill: "steelblue"}),
Plot.ruleY([0])
],
y: {grid: true, label: "売上 (万円)"},
title: "月別売上"
})
インタラクティブなチャート
// データの読み込み
const data = FileAttachment("sales.csv").csv({typed: true});
// インタラクティブな選択機能
const selectedCategory = view(
Inputs.select(
Array.from(new Set(data.map(d => d.category))),
{label: "カテゴリを選択"}
)
);
// 選択に応じた動的チャート
Plot.plot({
marks: [
Plot.dot(
data.filter(d => d.category === selectedCategory),
{x: "date", y: "value", fill: "red", r: 4}
),
Plot.lineY(
data.filter(d => d.category === selectedCategory),
{x: "date", y: "value", stroke: "blue"}
)
],
x: {type: "utc", label: "日付"},
y: {grid: true, label: "値"},
title: `${selectedCategory} のトレンド`
})
データの読み込みと処理
// CSVファイルの読み込み
const customers = FileAttachment("customers.csv").csv({typed: true});
// JSONデータの読み込み
const config = FileAttachment("config.json").json();
// データローダーからのデータ取得
const processedData = FileAttachment("data/processed.json").json();
// データの加工と表示
const summary = customers.then(data =>
d3.rollup(
data,
v => v.length,
d => d.region
)
);
// テーブル形式での表示
Inputs.table(customers, {
columns: ["name", "region", "revenue"],
sort: "revenue",
reverse: true
})
アニメーション
// 時間ベースのアニメーション
const currentTime = Generators.now(1000); // 1秒ごとに更新
// アニメーションするデータ
const animatedData = Array.from({length: 10}, (_, i) => ({
x: i,
y: Math.sin((currentTime / 1000 + i) * 0.5) * 50 + 100
}));
// アニメーションチャート
Plot.plot({
marks: [
Plot.lineY(animatedData, {x: "x", y: "y", stroke: "blue", strokeWidth: 3}),
Plot.dot(animatedData, {x: "x", y: "y", fill: "red", r: 4})
],
y: {domain: [50, 150]},
title: "リアルタイムアニメーション"
})
複数のビューの連携
// 共有されるデータ
const salesData = FileAttachment("monthly-sales.csv").csv({typed: true});
// 地域選択コンポーネント
const selectedRegion = view(
Inputs.select(
["全体", ...Array.from(new Set(salesData.map(d => d.region)))],
{label: "地域選択", value: "全体"}
)
);
// フィルタリングされたデータ
const filteredData = selectedRegion === "全体"
? salesData
: salesData.filter(d => d.region === selectedRegion);
// トレンドチャート
const trendChart = Plot.plot({
marks: [
Plot.lineY(filteredData, {x: "month", y: "sales", stroke: "blue"}),
Plot.dot(filteredData, {x: "month", y: "sales", fill: "red"})
],
title: `${selectedRegion}の売上トレンド`
});
// サマリー表
const summaryTable = Inputs.table(
d3.rollup(
filteredData,
v => ({
totalSales: d3.sum(v, d => d.sales),
avgSales: d3.mean(v, d => d.sales),
count: v.length
}),
d => d.product
),
{columns: ["product", "totalSales", "avgSales", "count"]}
);
// レイアウト
html`
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div>${trendChart}</div>
<div>${summaryTable}</div>
</div>
`