はじめに
Obsidianのグラフビューには「グループ」機能があり、path: や tag: で指定した条件に合うノートに色をつけることができます。
しかし、色グループに属さないノートはすべてデフォルトの黒色のまま。Vault内のノートが増えるにつれ、グラフビューは「色のついた島」と「黒色の海」に分かれてしまい、全体の構造が直感的に把握しにくくなります。
そこで作ったプラグインが Graph Color Propagation です。
色グループに属するノートから、リンクで繋がった未着色のノートへ、距離に応じて色を自動で伝播させる Obsidian プラグインです。
どんなことができるか
色の自動伝播
グラフビュー設定でいつも通り色グループを定義するだけで、グループに属さないノートにもリンク距離に応じた色が自動的に広がります。追加のUIや設定ファイルは不要です。
例えば医学関連の知識をObsidian上でまとめているとき、私は「循環器」や「小児科」といった分野ごとにフォルダを分けていますが、病名などの用語に関しては複数分野にまたがることがあります。
こうしたケースで分類に迷わないように、私は分野を問わず、すべての用語を「用語」フォルダに放り込むことにしています。
フォルダ構成の一例
ここで、グラフビューを開いて分野ごとにノートを色分けしてみます。
例えば「循環器」フォルダを赤色に設定すると、その配下のノートがすべて赤色で表示されます。あらゆる分野の用語を雑多に放り込んだ「用語」フォルダについては色の設定のしようがないため、未設定のままにしておきます。
グラフビューの設定モーダル
すると、大量の用語ノートがデフォルト色の黒色で表示されます。
Before: 黒色のノートが大量に残るグラフ
この運用で特に困ったことはありませんが、せっかくなら各用語がどの分野にまたがっているのか、色を混ぜることで可視化してみたいと思いました。
例えば小児の心臓病である「心室中隔欠損症」の用語ノートは、「循環器」の赤色と「小児科」の黄色が混ざった橙色にしたいわけです。そこでこのプラグインを使用すると、両者の色が伝播して混ざり合い、橙色に表示されます。
After: 色が伝播して全体が色分けされたグラフ
これで各用語がどの分野にどの程度近いのか、グラフビューから可視化することができました。
医学の勉強はほんの一例ですが、他にも、新しいアイデアやメモを「分類はせずにとりあえず投げておく一時置き場」であるinboxフォルダに放り込んでおくことがあるかもしれません。こうしたinbox内のノートについても、本プラグインを使うことで、他の色付きノートとの関連性から自動で色付けを行うことができます。
リアルタイム同期
グラフビューのグループ設定で色を変更すると、ボタン操作なしで即座にグラフに反映されます。
伝播パラメータの調整
| 設定項目 | 説明 | デフォルト |
|---|---|---|
| Auto-apply | グラフビュー表示時やグループ設定変更時に自動で色を適用 | ON |
| Decay factor | 1ホップごとの色の減衰係数(0〜1) | 0.6 |
| Max hops | 色付きノードの探索上限ホップ数 | 3 |
| Min influence | この値以下の影響度は無視 | 0.05 |
Decay factor を大きくすると遠くまで色が広がり、小さくすると近くのノートだけが色付きになります。
伝播パラメータの設定画面
技術的な仕組み
アーキテクチャ概要
.obsidian/graph.json(色グループ設定)
│ getGraphGroups()
▼
[{ query: "path:..." | "tag:...", color: "#rrggbb" }]
│ getNodeGroup() でノートごとに色を判定
▼
nodeMap: Map<path, { color, links }>
│ vault.getMarkdownFiles() + metadataCache から構築
▼
BFS 色伝播(未着色ノードごとに maxHops まで探索)
│ weight = decayFactor ^ hopDistance
▼
propagated: Map<path, {r, g, b}>(加重平均で最終色を決定)
│
▼
renderer.nodeLookup に直接適用 + renderCallback フック
BFS による色伝播アルゴリズム
色を持たない各ノートを起点に、幅優先探索(BFS)で隣接ノートを辿ります。
// 色を持たないノードごとにBFSを実行
for (const [path, node] of nodeMap) {
if (node.color) continue; // 色付きノードはスキップ
const visited = new Set([path]);
const queue = [{ p: path, hop: 0 }];
const influences = [];
while (queue.length > 0) {
const { p, hop } = queue.shift();
if (hop >= settings.maxHops) continue;
for (const neighbor of nodeMap.get(p)?.links ?? []) {
if (visited.has(neighbor)) continue;
visited.add(neighbor);
const neighborNode = nodeMap.get(neighbor);
if (neighborNode?.color) {
// 色源を発見 → 距離に応じた重みで記録
const weight = Math.pow(settings.decayFactor, hop + 1);
if (weight >= settings.minInfluence) {
influences.push({ ...hexToRgb(neighborNode.color), weight });
}
// 色付きノードの先は探索しない(境界として機能)
} else {
queue.push({ p: neighbor, hop: hop + 1 });
}
}
}
// 重み付き平均で最終色を決定
if (influences.length > 0) {
const totalWeight = influences.reduce((s, i) => s + i.weight, 0);
const r = influences.reduce((s, i) => s + i.r * i.weight, 0) / totalWeight;
const g = influences.reduce((s, i) => s + i.g * i.weight, 0) / totalWeight;
const b = influences.reduce((s, i) => s + i.b * i.weight, 0) / totalWeight;
propagated.set(path, { r, g, b });
}
}
ポイントは以下の3つです:
-
指数減衰:
decayFactor ^ hopDistanceで遠いノードほど影響力が弱くなる - 色の境界: 色付きノードは「壁」として機能し、その先へは探索しない。これにより異なる色のグループが混ざりすぎることを防ぐ
- 加重平均ブレンド: 複数の色源から影響を受けるノートは、重み付き平均で自然にブレンドされた色になる
renderCallback フックによる描画の永続化
Obsidianのグラフビューは毎フレーム renderer.renderCallback を呼び出して描画を更新しています。単純にノードの色を変えただけでは、次のフレームで元に戻されてしまいます。
そこで、元の renderCallback をラップし、毎フレームの描画後に伝播色を上書きする仕組みを採用しました。
// 元のコールバックを保存
renderer._originalRenderCallback = renderer.renderCallback;
// 毎フレーム実行されるフックに差し替え
renderer.renderCallback = () => {
// まず元の描画処理を実行
renderer._originalRenderCallback();
// その後、伝播色で上書き
for (const [nodePath, nodeData] of Object.entries(renderer.nodeLookup)) {
if (!renderer._propagatedColors.has(nodePath)) continue;
const { r, g, b } = renderer._propagatedColors.get(nodePath);
const colorInt = (Math.round(r) << 16) | (Math.round(g) << 8) | Math.round(b);
if (nodeData?.circle) nodeData.circle.tint = colorInt;
// ... Obsidianバージョンに応じたフォールバック
}
};
onunload() では必ず元の renderCallback を復元するようにしています。ここを怠るとプラグイン無効化後もフックが残り続け、メモリリークや他プラグインとの競合の原因になります。
graph.json の変更検知(3段構え)
.obsidian/graph.json はObsidianの内部で vault.adapter を通じて書き込まれるため、通常の vault.on("modify") イベントが発火しないことがあります。
そこで本プラグインでは3つの検知方法を併用しています:
| 方法 | 信頼性 | 役割 |
|---|---|---|
setInterval による500msポーリング |
◎ 最も確実 | JSON内容の差分比較で変更を検知 |
vault.on("modify") |
△ 環境依存 | 発火すればラッキーの保険 |
workspace.on("layout-change") |
○ 確実 | グラフビューが新しく開かれた時の初期適用 |
技術スタック
意図的にゼロ依存で構築しました。
- 言語: 素のCommonJS JavaScript(TypeScript不使用、バンドラー不使用)
-
外部依存: なし(
obsidianモジュールは実行時にObsidianから注入) -
ファイル構成:
main.js1ファイルのみ
ゼロ依存にしている理由は、Obsidianコミュニティプラグインの審査プロセスで攻撃表面を最小化するためです。サードパーティパッケージを含まないことで、レビュアーの負担を減らし、ユーザーが安心してインストールできるようにしています。
インストール方法
コミュニティプラグインから
- 設定 → コミュニティプラグイン → 閲覧 を開く
- 「Graph Color Propagation」を検索
- インストール → 有効化
手動インストール
-
最新リリース から
main.jsとmanifest.jsonをダウンロード -
<あなたのVault>/.obsidian/plugins/graph-color-propagation/にコピー - Obsidianを再起動し、設定 → コミュニティプラグイン で有効化
コミュニティプラグインへの提出についての所感
本プラグインは2026年8月にObsidianのコミュニティプラグインディレクトリに提出し、審査を通過して公開済みです。
提出プロセスについての所感:
- 以前は
obsidian-releasesリポジトリへのGitHub PRで提出していたようですが、2026年5月以降は Obsidian Community の Developer Dashboard に移行しています - GitHubアカウントでサインインし、リポジトリURLを入力するだけで提出できるようになりました
- 自動セキュリティレビューが行われているようです。提出から公開まではスムーズでした
既知の制限
-
path:とtag:クエリのみ対応。複合クエリ(AND/OR等)は未対応 - デスクトップ版のみ(グラフビューレンダラーの内部APIに依存)
- 伝播色は
graph.jsonに書き戻されない(表示上のオーバーレイのみ)
おわりに
Obsidianのグラフビューを「黒色の海」から「色分けされた地図」に変えたい、という個人的なニーズから生まれたプラグインです。
Obsidianプラグイン開発は、TypeScriptやバンドラーを使わなくても素のJavaScript 1ファイルで始められるのが魅力的でした。内部APIに依存する部分はありますが、グラフの色を動的に変えるにはこれが唯一の方法なので、バージョン互換性を意識しつつ複数のプロパティ名にフォールバックする設計にしています。
バグ報告や機能要望は GitHub Issues へお気軽にどうぞ。
リポジトリ: https://github.com/Tacitustus/obsidian-graph-color-propagation
ライセンス: MIT