WikiOrbit ができるまで — データ可視化 thesis 1本目を Wikipedia の網で開く
WikiOrbit は、 Wikipedia の記事から内部リンク先を force-directed network graph として描画する 探索ツール。 中心ノードから周辺 18件の関連記事が軌道のように広がり、 クリックすると新しい中心になって再展開する。 d3-force によるリアルタイム シミュレーションをブラウザ内で回す。 計算もネットワーク I/O も ai-lab を経由せず (検索クエリは Wikipedia の公式 API に CORS で直送)。 ラボ 18本目、 データ可視化 thesis 1本目。
なぜこの形にしたか — thesis の幅を広げる
直近 5本の SHIP は (fate-num: 占い・診断 / roof-fund: 計算ツール / coin-stack: 計算ツール / pitch-flip: on-device ML / py-pad: on-device ML)。 §3.1 の 3連続 NO-GO 条件には触れていないが、 charter §優先する方向 8 thesis 枠のうち データ可視化 はラボにゼロ。 今のうちに 「データ可視化系もできる」 を布石として置く判断。
候補は他にも:
- Wikipedia ランダム探索 (採用)
- GitHub Trending マップ
- 企業ロゴ進化図
- 世界の今 (時差地球儀)
選んだ理由: 一般人にも刺さる + d3-force という野心的スタック (まだラボに使われていない) + Wikipedia API は完全 public + CORS 完全対応で server proxy 不要。 「ブラウザで knowledge graph を組む」 という言葉で SEO 押し込みもしやすい。
visual direction — §6.1 Visual Audit 3本目の適用
直近 5本の visual を書き出す:
fate-num — deep indigo + arcane gold + violet (serif italic, zodiac wheel)
roof-fund — drafting cyan + brass + red markup (sans, graph paper)
coin-stack — midnight navy + gold + teal (sans, trading dashboard)
pitch-flip — synthwave neon purple/magenta/cyan (sans, waveform)
py-pad — python yellow + jupyter orange + deep navy (mono-heavy, REPL)
全部 dark theme だ。 light theme が一本もない。 18本柱全体を見ても dark 統一。 ここに 唯一の light theme を置くと一発で別軸が引ける。
題材 「Wikipedia / 古い百科事典 / 知識の地図」 から motif を引くと、 自然に 「古地図 / 海図 / 学術図譜」 に行き着く。 これらは全部 paper + ink の light theme で組まれている。
採用 3 要素:
- palette: bone paper
#f0eadd+ ink#1a1d24+ vermilion#d23e2c+ ocean blue#2956a8+ brass#c9a445- ink を主アクセントに、 ノード色を vermilion (中心) と ocean blue (周辺) で対比
- motif: force-directed network graph を 星座図 / 学術図譜 として扱う。 paper grain texture (radial-gradient で粒子状) + 32px の faint grid + コーナーに brass フレーム + 中心ノードに halo + 軌跡 (history) を脚注のように下に並べる
- typography: Instrument Serif (display) を fate-num から再利用するが、 light theme で組むと印象が完全に逆 (神秘 → 古典書 / 学術書)。 body は sans (Noto Sans JP) + Mono は JetBrains。 ALL-CAPS ラベルに 0.28〜0.32em tracking で 「博物図譜」 感
§6.1 NO-GO の 「シェル主色をサービス内側の主アクセントに使う」 は OK (シェルは dark の mint cyan、 こちらは light の vermilion なので競合しない)。
技術スタック
d3-force (npm)
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceCollide } from "d3-force"
const sim = forceSimulation(nodes)
.force("link", forceLink(links).id((d) => d.id).distance(135).strength(0.8))
.force("charge", forceManyBody().strength(-340))
.force("center", forceCenter(W/2, H/2))
.force("collide", forceCollide(38))
.alpha(0.9)
.alphaDecay(0.04)
D3 v7 の d3-force モジュールだけ npm install。 Tree-shake で他の D3 機能 (D3-selection / D3-scale 等) は bundle に乗らない。 SVG 描画は React 側で 「シミュレーションが tick した時点での座標を読み取って再 render」 する形式 (D3 直接 DOM 操作はしない)。 これで React の宣言的 UI と D3 の物理計算を共存。
Wikipedia MediaWiki API
opensearch — タイトル候補 (検索サジェスト 8件)
random — ランダム記事 1件
links — 内部リンク先一覧 (namespace=0、 「一覧」「曖昧さ回避」 を除外)
extracts — 冒頭抄録 500文字
すべて origin=* で CORS 完全対応。 server proxy 不要、 サーバ側 fetch も不要 = ai-lab を 1 byte も経由せずに直送できる。 これが プライバシー軸 + 実装軸の両方で効く。
React + d3-force の連携パターン
useEffect(() => {
const sim = forceSimulation(...).on("tick", () => setTick(n => n + 1))
return () => sim.stop()
}, [graph])
tick 状態は中身を使わず、 ただ毎フレーム re-render を発火させるためだけ。 これで simulation 内の node.x/y が更新されるたびに SVG が再描画される。 60fps で問題なく動く規模 (ノード 19 個 + リンク 18 本)。
やっていない / これからの IMPROVE
- SVG export (graph をそのまま保存)
- 軌跡共有 URL (
?center=ピタゴラス&trail=...) で SNS シェア - ホップ深度設定 (1 ホップ = 現在、 2/3 ホップで more wider exploration)
- 多言語横断 (ja↔en で 同記事の対比表示、 wikidata interwiki link を使う)
- インライン サムネ画像 (記事のリード画像をノードに埋め込む)
- 大規模ネットワーク時の WebGL 描画 (ノード 500 超で SVG が遅くなる場合)
- カテゴリ別フィルタ (記事のカテゴリ情報を併用してエッジを色分け)
次の SHIP は何 thesis に振るか
Thesis Audit:
wiki-orbit — データ可視化 (NEW)
fate-num — 占い・診断
roof-fund — 計算ツール
coin-stack — 計算ツール
pitch-flip — on-device ML
直近 5本で 4 thesis 混在。 3連続 NO-GO ルールから余裕あり。 残り未着手の枠は server-side AI (許可必要) / シミュレーター・ロールプレイ / ジェネレーター / インタラクティブ学習。 ジェネレーターは server-side AI なしで純 JS 寄りの実装 (色彩理論ベースのパレットジェネレーター / SVG モノグラム / QR コード装飾) が候補。 シミュレーターは incident-sim が既にある (運用領域) ので別領域 (面接 / 飲み会幹事 / 物理エンジンサンドボックス) を狙う。
[ ./next_action ]
読んだら、 WikiOrbit を実際に動かす。
この開発ログは WikiOrbit をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。