作ったもの
Mind Map — https://sen.ltd/portfolio/mind-map/
- SVG ベースのレンダリング、自動ツリーレイアウト
- キーボードファースト: Tab で子、Enter で兄弟、Del で削除、F2 で編集、矢印でナビゲーション
- Undo / Redo(50 ステップ履歴)
- パン & ズーム
- エクスポート: SVG / PNG / Markdown / OPML / JSON
- インポート: Markdown / OPML / JSON
- localStorage 自動保存
vanilla JS、ゼロ依存、ビルド不要。node --test で 54 ケース。
シンプルなツリーレイアウト
各ノードの x = 深さ、y = 子の平均:
function layoutNode(node, depth, yOffset) {
node.x = depth * HORIZONTAL_SPACING;
if (node.children.length === 0) {
node.y = yOffset.current;
yOffset.current += VERTICAL_SPACING;
return;
}
node.children.forEach(c => layoutNode(c, depth + 1, yOffset));
node.y = (node.children[0].y + node.children.at(-1).y) / 2;
}
葉ノードから順に y を割り当て、内部ノードは子の中心に配置。Reingold-Tilford ほど厳密ではないが、数百ノードまではきれいに収まる。
SVG foreignObject でインライン編集
SVG の <text> は入力を受け付けない。<foreignObject> で HTML <input> を埋め込めば任意座標で編集可能。F2 で切り替え、Enter or blur で戻す。
キーボード主導
Tab で子追加、Enter で兄弟追加、F2 で編集、矢印でナビゲーション、Ctrl+Z で undo。e.preventDefault() を忘れると Tab で SVG からフォーカスが飛ぶ。
Markdown エクスポート
アウトライン形式は Markdown のインデントリストそのまま:
function walk(node, depth) {
lines.push(' '.repeat(depth) + '- ' + node.text);
node.children.forEach(c => walk(c, depth + 1));
}
シリーズ
100+ 公開ポートフォリオ シリーズの #58 です。
- 📦 リポジトリ: https://github.com/sen-ltd/mind-map
- 🌐 デモ: https://sen.ltd/portfolio/mind-map/
- 🏢 会社: https://sen.ltd/
