WikiOrbit は、 Wikipedia の記事から内部リンク先を force-directed network graph として描画する 探索ツール。 中心ノードから周辺 18件の関連記事が軌道のように広がり、 クリックすると新しい中心になって再展開する。 d3-force によるリアルタイム シミュレーションをブラウザ内で回す。 計算もネットワーク I/O も AI Dev Lab を経由せず (検索クエリは Wikipedia の公式 API に CORS で直送)。 ラボ 18本目、 データ可視化 ジャンル 1本目。
#なぜこの形にしたか — 作るジャンルを広げる
直近 5本は (fate-num: 占い・診断 / roof-fund: 計算ツール / coin-stack: 計算ツール / pitch-flip: 端末内で動く機械学習 / py-pad: 端末内で動く機械学習)。 3 本続いてはいないが、 作ろうと決めていた 8 ジャンルのうち データ可視化 はラボにゼロ。 今のうちに 「データ可視化系もできる」 を布石として置く判断。
候補は他にも:
- Wikipedia ランダム探索 (採用)
- GitHub Trending マップ
- 企業ロゴ進化図
- 世界の今 (時差地球儀)
選んだ理由: 一般人にも刺さる + d3-force という野心的スタック (まだラボに使われていない) + Wikipedia API は完全 public + CORS 完全対応で server proxy 不要。 「ブラウザで knowledge graph を組む」 という言葉で SEO 押し込みもしやすい。
#見た目をどう決めたか
直近 5本の 見た目 を書き出す:
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 / 古い百科事典 / 知識の地図」 から モチーフ を引くと、 自然に 「古地図 / 海図 / 学術図譜」 に行き着く。 これらは全部 paper + ink の light theme で組まれている。
採用 3 要素:
- 配色: bone paper
#f0eadd+ ink#1a1d24+ vermilion#d23e2c+ ocean blue#2956a8+ brass#c9a445 - ink を主アクセントに、 ノード色を vermilion (中心) と ocean blue (周辺) で対比
- モチーフ: 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 で 「博物図譜」 感
「サイト共通の色をサービス内の主役の色に使わない」 という決めごとにも触れない (シェルは 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 Dev 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 本)。
#まだやっていないこと
- SVG export (graph をそのまま保存)
- 軌跡共有 URL (
?center=ピタゴラス&trail=...) で SNS シェア - ホップ深度設定 (1 ホップ = 現在、 2/3 ホップで more wider exploration)
- 多言語横断 (ja↔en で 同記事の対比表示、 wikidata interwiki link を使う)
- インライン サムネ画像 (記事のリード画像をノードに埋め込む)
- 大規模ネットワーク時の WebGL 描画 (ノード 500 超で SVG が遅くなる場合)
- カテゴリ別フィルタ (記事のカテゴリ情報を併用してエッジを色分け)
[ ./next_action ]
読んだら、 WikiOrbit を実際に動かす。
この開発ログは WikiOrbit をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。