Observable Frameworkに見られる興味深いアイデア
(simonwillison.net)- Observable Frameworkは、一時的なデータ探索に強いノートブックモデルを超えて、高速に読み込まれるデータアプリ・ダッシュボード・レポートを静的サイトとしてデプロイするためのオープンソースツール
- Markdown内の
jsコードブロックとインライン式はブラウザで実行され、nowのようなリアクティブな値が変わると関連する表示も自動更新される - FrameworkはObservable Notebooksのリアクティブ性を維持しつつ、単一のMarkdownファイル、標準JavaScript、Gitフレンドリーなワークフローを提供する
Inputs、d3、Plotのようなライブラリは開発中に遅延読み込みされ、ビルド・デプロイ時には参照されたコードだけがjsdelivr CDNから自動読み込みされる- Data loaderを使うと、ビルド時に任意の言語でデータを準備してJSON・CSVのような静的ファイルとしてまとめられるため、バックエンド依存を減らしたダッシュボードのデプロイが可能
データアプリのための静的サイトジェネレーター
- Observable Frameworkは、Markdown、JavaScript、必要であれば他の言語まで組み合わせてインタラクティブなページへコンパイルする静的サイトジェネレーター
- フル機能のホットリロードサーバーが含まれており、エディタでファイルを修正して保存するとブラウザに変更が即座に反映される
- 作業が終われば、ビルドコマンドで静的ファイル一式を生成できる
- これらのファイルはサーバーにデプロイできる
npm run deployでObservableの認証済み共有プラットフォームへ直接デプロイすることも可能
Markdown内で実行されるJavaScript
- Frameworkの中核設計は、Markdown文書の中にJavaScriptを入れてインタラクティブな文書を作る方式
jsでタグ付けされたMarkdownコードブロックは、ユーザーのブラウザ上でJavaScriptとして実行される- インライン式も使用でき、
${new Date(now)}のように現在時刻を文字列で表示できる nowはepoch以降のミリ秒単位の現在時刻を提供し、継続的に更新される特殊変数nowが変わると、それを参照するセルやインライン式も一緒に更新される
- Observable NotebooksではコードとMarkdownは別々のセルに書かれるが、Frameworkではその両方が1つのテキスト文書の中に入る
- インライン式と
jsブロックでは表示方法が異なる場合がある- インライン式はJavaScriptオブジェクトのデフォルトの文字列表現を使う
jsブロックはObservableのdisplay()関数を使い、表示ルールはinspect/src/inspect.jsにある
リアクティブな実行モデルを維持
- Observable Notebooksの中核機能であるリアクティブ性は、FrameworkのJavaScript Markdown文書でも維持されている
- あるセルが変わると、そのセルに依存する他のセルが自動的に再評価される
- この方式はJupyter notebooksとの大きな違いであり、Python notebookツールのmarimoの代表的な機能でもある
- フォーム入力と組み合わせると効果が大きい
- ページに入力を追加し、その値を文書の別の部分で参照すれば、リアルタイムなインタラクションを簡単に作れる
PyPIダウンロードダッシュボードの例
- 例のダッシュボードはPythonパッケージごとのPyPIダウンロード統計を表示し、Observable Framework版は57行のMarkdown文書で構成されている
- ユーザーは
Inputs.select()でpackages配列からパッケージを選ぶInputs.select()はFrameworkに含まれるメソッドで、Observable Inputsの文書で確認できるview()関数はFrameworkで新たに追加された機能で、入力選択の変更が文書内の他のコードブロックに反映されるようにする
packageNameはconstとして定義され、ページ内の他のjsブロックで利用できる- データは
d3.json()で取得する- FrameworkではD3全体を利用できる
- URLには選択されたパッケージ名が含まれる
- データソースはDatasetteのJSON API
- SQLiteテーブルはdatasette.io/content/statsにあり、最新のPyPIパッケージ統計で1日1回更新される
- 関連するGitHub Actionsワークフローは以前のbaked dataの記事で扱われていた
- URLに
.jsonを付けるとJSONが返る- 特定パッケージの行だけを要求する
- 日付の降順で並べる
- 最大1,000行をオブジェクト配列として受け取る
- SQLiteの文字列日付は
d3.timeParse("%Y-%m-%d")でJavaScriptのDateオブジェクトに変換される - チャートはFrameworkに同梱されたObservable Plotでレンダリングされる
- パッケージ一覧はDatasetteの
/contentデータベースに対して直接SQLクエリを実行して取得する- クエリは
select package from stats group by package order by max(downloads) desc _shape=arrayfirstは結果行の最初のカラムをJSON配列として受け取るための短縮記法
- クエリは
使うコードだけを含める
- 例のダッシュボードは
Inputs、d3、Plotのような追加ライブラリを使う - 開発モードでは遅延読み込みが適用される
- セルで最初に使おうとしたときだけコードが読み込まれる
- アプリケーションをビルドしてデプロイすると、Frameworkが参照されたライブラリコードだけをjsdelivr CDNから自動読み込みする
ビルド時点のデータキャッシュ
- FrameworkのData loaderは、ダッシュボード用データをビルド時点で準備する機能
- Frameworkのダッシュボードは実行時に
fetch()やそのラッパーを使って、どこからでもデータを取得できる- Observable Notebooksも同じ方式で動作する
- この方式ではダッシュボードの性能が接続先バックエンドに左右される
- Frameworkは、デプロイ時点でダッシュボード用データを作成し、必要なデータの部分集合だけを静的ファイルとしてまとめるパターンを推奨している
- 静的データファイルは、ダッシュボードコードと同じ静的ホスティングから高速に配信できる
- Data loaderは任意のプログラミング言語で書いたスクリプト
- ビルド時にFrameworkがそのスクリプトを実行する
- スクリプトの標準出力結果をファイルとして保存する
- 例では
quakes.json.shファイルにcurl https://earthquake.usgs.gov/earthquakes/feed/…を入れる方式- ビルド時、このファイル名は出力先ファイルが
quakes.jsonで、実行するローダーが.shであることをFrameworkに知らせる
- ビルド時、このファイル名は出力先ファイルが
- 標準出力としてJSON、CSV、または有用な形式を出力できるなら、どんな技術でもデータを取得できる
Observable Notebooksとの違い
- Observable FrameworkはObservable Notebooksの多くのアイデアやコードを再利用しているが、ファイル形式と実行環境には大きな違いがある
- 既存のObservable Notebooksは、Jupyter Notebooksと比べると次の特徴を持つ
- PythonではなくJavaScriptを使う
- ノートブックエディタ自体はオープンソースではなく、observablehq.comで提供されるホスティング製品
- ノートブックは静的ファイルとしてエクスポートしてどこでも実行できるが、エディタは独自製品
- セルはリアクティブで、あるセルが変わるとそれに依存する他のセルがExcelのように自動再評価される
- リアクティブ性モデルを支えるために
viewofというカスタムキーワードを作ったため、JavaScript文法は完全に標準ではない - 編集可能なノートブックは複雑な独自ファイル形式で、Gitのようなツールと相性がよくないため、Observableは独自のバージョン管理とコラボレーションシステムを実装していた
- Observable Frameworkはこのモデルを、よりシンプルなファイル形式とオープンソースの実行環境へ移したもの
- 文書はJavaScriptブロックを含む単一のMarkdownファイル
- 依然としてリアクティブだが、どんなテキストエディタでも編集でき、Gitに入れられる
- 全体がISCライセンスのオープンソースで、編集スタック全体をローカルマシンで実行できる
- カスタム構文なしで標準JavaScriptだけを使う
Observableの方向転換
- Observable Frameworkは、Observableが従来の独自Observable Notebookエディタ中心のコラボレーションツールから、より開発者向けツールへ傾いている変化を示しているように見える
- ObservableのTwitter紹介文は “The end-to-end solution for developers who want to build and host dashboards that don’t suck”
- 2023年10月3日のInternet Archiveのスナップショットでは “Build data visualizations, dashboards, and data apps that impact your business — faster.” となっていた
- Observable Notebooksは、プラットフォームの独自性や無料アカウントの制限、特に無料の非公開ノートブックがないことによって、利用が一部制約されることがある
- Observable Plotのようなオープンソースライブラリは、すでに積極的に利用できる技術として評価されている
- Observable Frameworkは、Observable Notebooksを魅力的にしていたアイデアを、オープンソース、標準JavaScript、単一テキストファイル、静的デプロイモデルとして再実装している
1件のコメント
Hacker Newsのコメント
ある意味で Observable Framework は、Mike Bostock シネマティック・ユニバースにおける Avengers: Endgame のようなもの
d3、Observable、Observable Plot、HTL をひとまとめにし、そこへ新しいアイデアもかなり載せている
Observable にはすでに AI 統合があり、これは AI がより簡単に組み合わせて活用できるようにするラッパーのように見える。AI なしで戦略を評価していた部分は少しちぐはぐに感じた
今日、静的な Jupyter Notebook をホスティングしたり、WASM でインタラクティブに動かしたりする方法を見始めたが、ほとんどの用途では Observable Framework のほうが合っていそう
Observable の問題は、d3 のサンプルギャラリーのように見えるのに、そのコードがそのフレームワーク内で動くよう設計されていて、そのままコピー&ペーストできないこと
そもそも d3 はサンプルなしで使いやすいものでもなく、とくにバージョン間の変更に互換性がないことも多い。それでもサイトには驚くようなグラフィックがたくさんある
[0] https://observablehq.com/@d3/gallery
基本言語に十分近いので、グラフィック表示用の API を少し足す程度で、そのまま JavaScript を使えたのではと思う
ほとんどはトップレベルのセル定義を書き換える作業
[0]: https://observablehq.com/@bumbeishvili/convert-observable-co...
ObservableHQ ノートブックでは特にその不満が大きかった。優れたサンプルであると同時に、役に立たない資料にもなってしまう。ただ、今回の Framework のほうはもう少し開かれているようで、少なくともセルフホスティングはできるので様子を見ている
しかもこの記事は、新しい Observable Framework が昔の Observable ノートブックの問題の一部をなくしたという話なので、このコメントは記事とは少しずれている。今では「全部標準 JavaScript で、カスタム構文はない」という話なのだから
Framework は GitHub Pages にデプロイするのもとても簡単
手順とサンプルの GitHub Action をまとめてある: https://notes.billmill.org/programming/observable_framework/...
筆者は Framework について的確に言い当てている
Observable Framework を使って小さなインタラクティブ・プロットを作ってみたが(https://github.com/willmeyers/observable-ssta)、セットアップしてデータを描画するまでが信じられないほど簡単だった。唯一の不満は、Python データローダーが virtualenv を使うよう設定できたらよいのにという点
Python プロジェクトを作成したあと、開発サーバーを起動するときに
yarn run devの代わりにpoetry run yarn run devを実行すれば、Python は virtualenv 内で動く。この構成は、データローダーで再利用可能かつ単体テスト可能なコードをカスタム Python パッケージとして定義し、*.json.pyファイルから import して非常にシンプルに保つのにも使えるnode や npm/yarn のようなツール、そして JavaScript まで venv の中に一緒に保持される
.shデータローダーに shebang を入れて、仮想環境ディレクトリ内のbin/pythonのフルパスを指すようにできない?最近、Observable ノートブックで初めて「実運用」プロジェクトを完了した
Observable Plot、Arquero を学び、JavaScript を少し学び直し、データ生成過程である Rust ベースのシミュレーターと統合する作業も含まれていた。正直、本当に素晴らしかった。ツールを学ぶのにはかなりエネルギーが要り、データジェネレーターをパラメータ化する機能にはまだ不満もあるが、最終的なノートブックは美しく、よく動く
Markdown とリアクティブ性があることで、こうしたノートブックは本当に実用になると感じる。Jupyter のカスタムフォーマットはバージョン管理を非常に難しくし、リアクティブ性がないと反復的に設計したノートブックは、書くにはいいが読むにはつらい状態依存のごちゃごちゃになりがちだ。Quarto とその Observable 統合でも試したが、場当たり的な寄せ集めのように感じた
心から、ノートブックを書いて他人と共有することが楽しく、楽しみだと感じられた初めての体験だった。今後も荒削りな部分はあるだろうが、このプロジェクト以降はノートブック用ツールの第一候補になっている
[0]: https://living-papers.vercel.app/
ブラウザで Framework を素早く試して触ってみたいなら、Node と Python 環境を自動構成する Codespace devcontainer が用意されている。
[0]: https://github.com/dleeftink/observable-codespace
Jupyter Notebook から Observable に移るべきだろうか? それとも、両者をそうやって分けること自体が誤った構図なのだろうか?
記事の内容を言い換えると、
jsコンテンツヒントが付いたコードブロック内のものはすべて、ユーザーのブラウザで即座に実行される。コードを表示したいなら
js echoヒントを使わなければならない。後方互換性を考えると、逆のほうがよかったのではないだろうか? たとえば、ユーザーのブラウザで実行するコードはjs execのようなオプトインヒントにして、広く使われているjsヒントはそのまま残す形だ。現在の構造だと、そのレンダラーを既存アプリに統合する際、どこで実行を許可するかを別途管理しなければならないページごとに front matter で設定したり、プロジェクト設定でプロジェクト全体に適用したりできるようになる。そうすれば
js run=falseをデフォルトにして、必要な場合だけjs runでライブコードを有効にできる。ただし、私たちの主なユースケースはライブコードなので、これをデフォルトに選んだいまや言語注釈を外さない限り、Mermaid のコードブロックをそのまま表示できなくなっている
Observable Framework にハマって一晩を過ごしたが、とても良かった。
ほとんど邪魔にならず、Google Maps の履歴を詳しく可視化して探索できた。データローダー環境に関する部分はそれほど明確ではなかったが、Python は poetry 環境で実行されるので解決した。
Kotlin が好きなので Kotlin スクリプト用のデータローダーも作ってみようとしたが、荒削りな部分があった。Kotlin はスクリプトファイル名が
foo.main.ktsであることを期待する一方、Observable は実行可能な shebang ローダーにfoo.exe拡張子を期待する。そのため Kotlin スクリプトを呼び出すプロキシ exe スクリプトを作ったが、そうするとデータの自動再読み込みがトリガーされない。marimo や Jupyter と比べて少し不便なのは、データローダーとノートブックの間で変数を使う部分だ。たとえば、日付選択ビューコンポーネントでローダーが取得するデータ範囲を変えたいのだが、どうすればよいのかが明確ではない。そのため探索的分析が少し遅くなる。これがパラダイムに反しているのは分かっているが、指摘しておきたかった。結果として、探索しながらデータ加工のかなりの部分をノートブック側へ移すことになり得るが、性能の観点では理想的ではない。
最後に、データローダーを インラインで定義 できるとよいと思う。単一ファイルが好きなので、Python コードブロックを追加すると Framework がそれをファイルとして抽出してくれる、という形なら小さな QoL 改善になるはずだ。まだ初期段階だが、Framework は有望に見える。すべての Markdown ノートをここに載せて、完全な Emacs まで行かなくても org-mode のような環境を作れたらいいと思う
.shや.exeで回避せずに、新しいインタープリターをもっと簡単に登録できるようにする PR が出ている。特定のファイル拡張子に関連付けられたインタープリターを指定できるようになる予定。たとえば Kotlin 用の
.ktsが可能になる。 https://github.com/observablehq/framework/pull/935入力値でデータローダーを動かす方式は、Framework が静的データスナップショットを好むという点で少し方向性が異なる。目的は、ビルドされたサイトを自己完結的で高性能にすることだ。それでもうまく機能する手法として、データローダーで対話対象データの上位集合を Parquet ファイルとして生成し、クライアント側で DuckDB/SQL を使って可視化する部分集合を取り出す方法がある。通常は高性能だが、もちろん扱おうとする上位集合のサイズによって変わる
Observable は REST API を通じて ClickHouse と非常にうまく統合される。
例はここにある: https://observablehq.com/@stas-sl/github-issues-survival-ana...
新しい Observable Framework はまだ使っていないが、データベースをリアルタイムにクエリする似たような例を見ると面白そうだ。すべてのデータを事前に読み込んでキャッシュすることしかできない構造ではないことを願う。こうしたアプリはインタラクティブであるべきなので、理想的には SQL をライブで編集できるよう公開されているべきだ
こちらのデモではランタイムに
fetch()でデータを読み込んでいる: https://simonw.github.io/observable-framework-experiments/pa...