NodeEditor API 仕様書
canvas-flow のコアクラス NodeEditor(フレームワーク非依存)のリファレンスです。Lit 版 <canvas-flow-editor> は el.editor でこのインスタンスを公開しており、主要メソッドを委譲しています。
概要
NodeEditor は <canvas> 要素を受け取り、Canvas 2D でノード・コネクタの描画と、ポインタ/ホイール/キーボード/タッチ操作、選択・編集コマンド、Undo/Redo、JSON 入出力を一括して引き受けます。データは内部の Graph(editor.graph)が保持し、描画・ヒットテストは空間インデックスにより表示範囲内の要素だけを処理するため、10,000 ノード規模でも軽量に動作します。
本書で Undo 可 と付いた操作は 1 回の
undo() で戻ります。複数の操作を 1 回にまとめたいときは editor.graph.batch(() => { … }) で囲みます。options.readOnly が true のとき、編集系メソッドは何もせず null / false / 空配列を返します(クリックイベント等は発火します)。導入
同じフォルダの demo.html は、jsDelivr の CDN から
<canvas-flow-editor> を読み込むだけの 1 ファイルのデモです。ビルド不要でブラウザから開けます(CDN に届かない環境では同梱の ../src/index.js に自動でフォールバックします)。import { NodeEditor } from '@hidemikimura/canvas-flow/core'; // または '@hidemikimura/canvas-flow'
const canvas = document.querySelector('canvas.main');
const minimap = document.querySelector('canvas.minimap'); // 省略可
const editor = new NodeEditor(canvas, { minimapCanvas: minimap, theme: { lodZoom: 0.3 } });
editor.resize(800, 600); // 表示サイズ(CSS px)。ResizeObserver 等で呼び直す
editor.resizeMinimap(200, 140);
editor.graph.addNode({ id: 'a', title: 'Source', x: 40, y: 40, output: true,
items: [{ id: 'v', label: 'value', value: '10', output: true }] });
editor.graph.addNode({ id: 'b', title: 'Sink', x: 360, y: 120, input: true,
items: [{ id: 'in', label: 'in', input: true }] });
editor.graph.addEdge({ source: 'a', sourcePort: 'item:v:out', target: 'b', targetPort: 'item:in:in' });
editor.on('selection:change', ({ nodes, edges }) => console.log(nodes, edges));
editor.on('item:edit', ({ node, item, screenRect }) => { /* 入力欄を screenRect に重ねる */ });
インライン編集(ダブルクリック)はコアではイベント発火のみです。Lit 版は内蔵の入力欄を表示します。
用語と座標系
| 用語 | 意味 |
|---|---|
| ノード (Node) | タイトル付きの矩形。項目(NodeItem)を縦に並べる。高さは項目数から自動計算される |
| 項目 (NodeItem) | ノード内の 1 行。ラベルと値を持ち、左右に入力/出力ポートを持てる |
| ポート (Port) | 接続の端子。ノードのヘッダ左右と各項目の左右にある。ポートキーで識別 |
| コネクタ / エッジ (Edge) | 出力ポートから入力ポートへの接続線。常に「出力 → 入力」の向き |
| ワールド座標 | ノードの x, y が置かれている座標系。ズーム・パンの影響を受けない |
| スクリーン座標 | canvas 左上を原点とする CSS ピクセル。screen = world × zoom + (tx, ty) |
| クライアント座標 | マウスイベントの clientX / clientY。clientToWorld() で変換 |
TypeScript
type同梱の型定義
手書きの型定義を同梱しているため、追加のインストールや設定なしで型が付きます。エントリごとに
types/core.d.ts / types/lit.d.ts / types/index.d.ts が対応します。
import { NodeEditor, type Node, type EdgeType } from '@hidemikimura/canvas-flow/core';
import '@hidemikimura/canvas-flow/lit';
| 型 | 内容 |
|---|---|
NodeEditorEvents | イベント名 → detail の対応。editor.on() は名前から detail の型が決まり、未知の名前はエラーになる |
HitTestResult | hitTest() の戻り値。type で判別できるユニオン |
EdgeGeometry | BezierEdgeGeometry(制御点あり)と PolylineEdgeGeometry のユニオン。g.type === 'bezier' で絞り込む |
Node / NodeItem / Edge / PortSpec / PortKey | データモデル。NodeInput / EdgeInput は id を省略できる追加用 |
Theme / ThemePatch | ThemePatch は深い部分指定。キー名の打ち間違いはエラーになる |
CanvasFlowEditorEventMap | Lit 版の CustomEvent 名 → detail。HTMLElementTagNameMap も拡張済みで querySelector('canvas-flow-editor') に型が付く |
/core の型は lit に依存しないので、コアだけ使う場合は lit を入れずに型チェックが通ります。コンストラクタ / オプション
constructornew NodeEditor(canvas, options?)
| オプション | 型 | 既定 | 説明 |
|---|---|---|---|
theme | object | – | テーマの部分上書き(深いマージ) |
graph | Graph | 新規作成 | 既存の Graph を使う |
nodeTypes | { [type]: { style } } | {} | ノード種別ごとの既定スタイル(node.type で参照) |
minimapCanvas | HTMLCanvasElement | – | 指定するとミニマップを描画・操作する |
wheelMode | 'zoom' | 'pan' | 'zoom' | ホイールの既定動作。zoom: 通常ズーム / Shift でパン。pan: 通常パン / Ctrl・Cmd でズーム |
dragMode | 'pan' | 'select' | 'pan' | 空白を左ドラッグしたときの動作(Shift で反転) |
readOnly | boolean | false | 編集操作を無効化 |
historyLimit | number | 200 | Undo 履歴の上限 |
minNodeWidth | number | 100 | リサイズ時の最小幅 |
rules | { maxInputs?, maxOutputs?, onFull? } | 無制限 / 'reject' | 接続ルール。ポート側で上限を指定しないときの既定上限と、満杯ポートへ接続したときの挙動('reject' 拒否 / 'replace' 最古のコネクタを外して付け替え) |
edgeDeleteIcon | boolean | true | ホバー/選択中のコネクタ中央に削除アイコンを表示 |
keyboardScope | Element | canvas | この要素内を最後にポインタ操作していればキーボードショートカットを受け付ける(Lit 版はホスト要素) |
wheelGestureGap | number (ms) | 250 | この間隔以内のホイールイベントは同じスクロール操作とみなし、ズーム/パンの判定を維持する(慣性スクロール対策) |
moveSnap | number (px) | 0(無効) | ノードの移動単位。ドラッグ・矢印キー・ノード追加・JSON 追加の位置がこの単位に揃う |
focusMode | 'off' | 'connected' | 'neighbors' | 'off' | 選択ノードと同じ流れにある要素を強調し、他を薄く描く |
focusDepth | number | 1 | 'neighbors' のときに何段まで辿るか |
focusDirection | 'lineage' | 'downstream' | 'upstream' | 'both' | 'lineage' | 辿る向き。既定は起点の祖先と子孫だけ(起点を通る道筋) |
theme.edge.type | 'bezier' | 'straight' | 'step' | 'bezier' | コネクタの描画方法の既定(theme オプション経由。後から setEdgeType() で変更可) |
オプションは editor.options に保持され、wheelMode / dragMode / readOnly / edgeDeleteIcon は実行中に書き換えても次の操作から反映されます。
プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
graph | Graph | ノード・コネクタのモデル。低レベルな操作はここから行う |
viewport | Viewport | tx, ty, zoom, width, height、toWorld() / toScreen() / visibleRect() / snapshot() / restore() |
renderer | Renderer | 描画器。renderer.stats に直近フレームの { nodes, edges, ms } |
history | History | Undo 履歴。begin(label) / end() でトランザクションを明示できる |
minimap | Minimap | null | minimapCanvas を渡したときのみ |
interaction | Interaction | 入力処理。dragThreshold(既定 3px)、pointerInside |
selection | { nodes: Set<string>, edges: Set<string> } | 選択中の ID。直接変更せず 選択メソッドを使う |
selectedNodes | Node[](getter) | 選択中ノードの配列 |
hover | { node, edge, port, edgeDelete } | ホバー中の要素 ID(読み取り用) |
theme | object | 現在のテーマ(マージ済み)。変更は setTheme() で |
nodeTypes | object | ノード種別の定義 |
options | object | コンストラクタのオプション(既定値でマージ済み) |
canvas | HTMLCanvasElement | 描画先 |
canUndo / canRedo | boolean(getter) | 履歴の有無 |
nodeRenderer / edgeRenderer / overlayRenderer | function(setter) | 独自描画関数・追加描画。見た目を参照 |
見た目
methodsetTheme(patch)
テーマを部分的に上書きします。node.headerHeight / itemHeight / padding / width / edge.curvature / edge.type / edge.stepOffset はレイアウトにも反映され、空間インデックスが再構築されます。
editor.setTheme({ background: '#0f172a', node: { fill: '#1e293b', titleColor: '#f1f5f9' }, lodZoom: 0.25 });
methodsetRules(rules)
接続ルール { maxInputs, maxOutputs, onFull } を変更します(editor.graph.rules に反映)。
methodsetEdgeType(type, edgeIds?) / getter/setteredgeType / methodsetSelectedEdgeType(type)
コネクタの描画方法を変えます。type は 'bezier'(三次ベジェ曲線・既定)、'straight'(直線)、'step'(90 度にだけ曲がる階段状の折れ線)。edgeIds を省略すると全体の既定(theme.edge.type / graph.layout.edgeType)を変え、edge.type を持たないすべてのコネクタが切り替わります。edgeIds を渡すとそのコネクタの edge.type を更新します(Undo 可、JSON にも保存)。setSelectedEdgeType は選択中のコネクタだけ(選択が無ければ全体の既定)。いずれも 'edge-type:change' を発火します。
| type | 形 |
|---|---|
'bezier' | 制御点を横に max(40, |dx| × theme.edge.curvature) 出した三次ベジェ曲線 |
'straight' | 2 点を結ぶ直線 |
'step' | 前方(右側)の相手へは横中央で 1 回だけ縦に折れる。後方の相手へは theme.edge.stepOffset(既定 24px)だけ右へ突き出し → 上下の中間の高さで左へ戻り → 相手の左から入る |
editor.setEdgeType('step'); // 全体
editor.setEdgeType('straight', ['e1']); // e1 だけ
editor.graph.addEdge({ source: 'a', sourcePort: 'out', target: 'b', targetPort: 'in', type: 'step' });
ドラッグ中の仮コネクタ・ヒットテスト・削除アイコンの位置(形状の中点)・簡易表示(LOD)も同じ形に従います。'line' → 'straight'、'orthogonal' / 'smoothstep' → 'step' の別名は normalizeEdgeType() が丸めます。
methodsetFocusMode(mode, options?) / getter/setterfocusMode
選択ノードと同じ流れにある要素だけをはっきり描き、それ以外を薄くします。選択が空のときは自動的に解除されます。
対象が変わると 'focus:change'({ mode, direction, nodes, edges })が発火します。
| mode | 強調する範囲 |
|---|---|
'off'(既定) | 無効。全部を通常どおり描く |
'connected' | 進める先をすべて辿る |
'neighbors' | focusDepth 段まで(既定 1 = 次の 1 段だけ) |
| direction | 辿る向き |
|---|---|
'lineage'(既定) | 起点の祖先(上流)と子孫(下流)だけ=起点を通る道筋 |
'downstream' | 出力ポートから進める先だけ |
'upstream' | 入力ポートへ入ってくる側だけ |
'both' | 向きを問わず繋がっているもの全部 |
「その要素を通る道筋」だけを強調します。起点まで遡れるノード(祖先)と起点から進めるノード(子孫)が対象で、祖先から分かれた別の枝や、子孫へ合流してくる別の枝は入りません。
A → B → C / A → E / D → E / E → F というグラフなら、B を選ぶと A・B・C(E・F・D は薄い)、E を選ぶと A・D・E・F(B・C は薄い)、A を選ぶと A・B・C・E・F(D は薄い)。goto でも同じで、
A → B → C に加えて A が goto で D を指していても、C を選んだときに D は強調されません。前後の枝まで見たいときは 'both' を使ってください。editor.setFocusMode('connected'); // 同じ流れ全部(既定)
editor.setFocusMode('neighbors', { depth: 2 }); // 2 段まで
editor.setFocusMode('connected', { direction: 'downstream' }); // 進める先だけ
editor.focusMode = 'off';
methodfocusSet() → { nodes: Set, edges: Set } | null
現在の強調対象。無効なとき・選択が空のときは null(= 全部を通常描画)。選択とグラフの変更でキャッシュが破棄されます。
methodselectFocused(options?) → { nodes, edges } | null
いま強調表示されている要素(focusSet() の中身)をそのまま選択します。選んだ流れをまとめて移動・複製・削除したいときに使います。ツールバーの「強調を選択」ボタンと Ctrl/Cmd + Shift + A からも呼べます。
| オプション | 意味 |
|---|---|
additive | true でいまの選択に加える(既定 false = 置き換え) |
depth / direction | 強調表示の設定を使わず、この条件で辿り直して選択する |
editor.selectFocused(); // 強調されている要素で選択を置き換える
editor.selectFocused({ additive: true }); // いまの選択に加える
editor.selectFocused({ direction: 'downstream' }); // 向きを指定して辿り直す
強調表示が無効(focusMode: 'off')のときは focusDirection / focusDepth の設定どおりに繋がりを辿って選択します。選択ノードが無ければ何もせず null。実行すると 'focus:select'({ mode, nodes, edges })が発火します。選択操作なので readOnly でも使えます。
methodUndo 可selectConnected(options?) → { nodes, edges } | null
繋がっている要素を選択に加えます(まとめて移動・削除したいとき)。探索だけしたい場合は
graph.connectedTo(startIds, { depth, direction, includeStart }) を直接呼べます。
theme.focus(dimOpacity 既定 0.12 / dimOpacityLod 既定 0.25 / edgeStroke / edgeWidth)で変えられます。簡易表示(LOD)中は線が細く消えやすいため別の値を持たせています。methodregisterNodeType(type, { style })
ノード種別を登録します。スタイルの優先順位は node.style > nodeTypes[node.type].style > theme.node です。
setternodeRenderer = (ctx, node, rect, api) => boolean | void
setteredgeRenderer = (ctx, edge, geometry, api) => boolean | void
独自の描画関数を設定します(null で既定に戻す)。true を返すと既定描画を省略、それ以外は既定描画にフォールバックします。api には selected, hover, style, lod, zoom, theme, graph, roundRect(), fitText() が入ります。geometry は { type, x1, y1, x2, y2, points }(points は折れ線の頂点列。type === 'bezier' のときは制御点 c1x, c1y, c2x, c2y も入る)です。
api.lod で判断できます)。setter/getteroverlayRenderer = (ctx, api) => void
ノードとコネクタを描いたあとに 1 回だけ呼ばれる追加描画です(null で解除)。ノードの上に重ねたいもの(分析用のバーやバッジ、注釈)はここで描きます。nodeRenderer と違い既定描画を置き換えないので、縮小時の一括簡易描画もそのまま使われます。
api は { visible, lod, zoom, nodes, edges, theme, graph, roundRect(), fitText() }。nodes / edges は表示範囲内の要素(子ノードも含む)です。ワールド座標系のまま呼ばれるので、画面上で一定の大きさにしたいものは zoom で割ります。
editor.overlayRenderer = (ctx, api) => {
if (api.lod) return;
for (const node of api.nodes) {
const r = api.graph.nodeRect(node);
ctx.fillStyle = '#3b82f6';
api.roundRect(r.x + 8, r.y + r.h - 6, (r.w - 16) * 0.42, 4, 2);
ctx.fill();
}
};
demo/analytics.html にあります(項目行の選択率バー・ヘッダの離脱率バー・ヒートマップ・コネクタの太さ・ホバー詳細)。サイズ・描画
methodresize(width, height, dpr?) / resizeMinimap(width, height, dpr?)
表示サイズ(CSS px)を設定します。dpr 省略時は devicePixelRatio。コンテナのサイズが変わるたびに呼んでください。
methodrequestRender() / render()
requestRender() は次の requestAnimationFrame で 1 回だけ描画を予約します(複数回呼んでも 1 フレームにまとまる)。render() は即時描画し、'render' イベントで統計を通知します。モデルやビューの変更は内部で自動的に requestRender() されるため、通常は呼ぶ必要はありません。
座標・ヒットテスト
methodclientToWorld(clientX, clientY) → {x, y}
マウスイベントのクライアント座標をワールド座標に変換します。
methodworldRectToScreen(rect) → {x, y, w, h}
ワールド矩形を canvas 基準のスクリーン矩形に変換します(インライン編集 UI の配置などに)。
methodviewCenter() → {x, y}
表示中の画面中央のワールド座標。
methodgetPointer() → { world, screen, inside } | null
最後に観測したポインタ位置。inside はポインタが canvas 上にあるか。一度も観測していなければ null。
methodhitTest(wx, wy) → Hit
ワールド座標にある要素を返します。判定の優先順位は 削除アイコン > リサイズグリップ > ポート > ノード(ヘッダ/項目/本体)> コネクタ です。
type | 追加フィールド | 意味 |
|---|---|---|
'edge-delete' | edge | 表示中のコネクタ削除アイコン |
'resize' | node | ノード右下のリサイズグリップ |
'port' | node, port: { key, dir, itemId, x, y, visible } | 表示中のポート |
'item' | node, item | ノード内の項目行 |
'node' | node, header: boolean | ノードのヘッダまたは本体 |
'edge' | edge | コネクタ(線から約 6px 以内) |
'none' | – | 空白 |
methodedgeAt(wx, wy) → Edge | null
点に最も近いコネクタ(しきい値内)。
methodedgeDeleteIconAt(wx, wy) → Edge | null / deleteIconEdges() → Edge[]
削除アイコンの当たり判定と、現在アイコンを表示しているコネクタ一覧(ホバー中 + 選択中。readOnly / LOD 時は空)。
選択
methodselect({ nodes?, edges? }, { additive? })
ID 配列を選択します。additive: true で既存の選択に追加。'selection:change' を発火します。
methodtoggleSelect({ nodes?, edges? })
指定 ID の選択状態を反転します。
methodclearSelection() / selectAll()
methodselectInRect(rect, { additive? })
ワールド矩形に完全に含まれるノードと、その間のコネクタを選択します(範囲選択と同じ挙動)。
editor.select({ nodes: ['a', 'b'] });
editor.select({ edges: ['e1'] }, { additive: true });
console.log(editor.selectedNodes.map(n => n.title));
編集コマンド
methodUndo 可deleteSelection()
選択中のノードとコネクタを削除します。ノードに繋がるコネクタも一緒に削除され、1 回の Undo で戻ります。
methodUndo 可deleteSelectedEdges({ scope = 'between' }) → string[]
選択範囲内のコネクタだけを削除し、ノードは残します。戻り値は削除したコネクタ ID。'edges:delete' を発火します。Shift + Delete、Lit 版ツールバーの「コネクタ削除」に対応。
scope | 対象 |
|---|---|
'between'(既定) | 選択中のコネクタ + 両端とも選択中ノードに繋がるコネクタ(範囲選択の内側) |
'attached' | 選択中のコネクタ + 選択中ノードに繋がるすべてのコネクタ(範囲外へ出るものも含む) |
'selected' | 選択中のコネクタのみ |
methodselectedEdgeIds({ scope = 'between' }) → string[]
deleteSelectedEdges と同じ規則で対象になるコネクタ ID を返します(削除はしない)。
methodUndo 可removeNode(id) / removeEdge(id)
methodUndo 可duplicateSelection(offset = {x: 40, y: 40}) → { nodes, edges, idMap } | null
選択中のノードと、その内部で閉じているコネクタを複製し、複製側を選択状態にします。
methodUndo 可duplicateNode(id, offset?) → Node | null
methodcopySelection() → boolean
選択内容を内部クリップボードに保存します。キーボードの Ctrl/Cmd+C はこれに加えてシステムのクリップボードへ JSON を書き込みます。
methodUndo 可paste(at?) → { nodes, edges } | null
内部クリップボードの内容を貼り付けます。at(ワールド座標)を渡すと貼り付け内容の左上をそこに置き、省略時は元の位置から 40px ずらします。
methodUndo 可moveSelection(dx, dy)
選択中ノードをワールド座標で相対移動します(移動単位は適用しない)。
methodUndo 可nudgeSelection(stepsX, stepsY, { pixels = 1 })
選択中ノードを移動単位で動かします(矢印キーの実装)。moveSnap 未設定なら pixels × steps。設定時は先頭の選択ノードが目盛りに乗っていなければ進む方向の次の目盛りが 1 歩目になり、動かす軸だけ揃えます。
methodUndo 可resizeNode(id, width) → Node | null
ノードの幅を変更します(minNodeWidth 未満は切り上げ)。高さは項目数で決まるため変更できません。
methodUndo 可setPortVisible(nodeId, portKey, visible) → boolean
1 つのポートの表示/非表示。ポートが存在しなければ false。
methodUndo 可setPortsVisible(nodeId, visible, itemId?) → boolean
ノード(itemId 指定時はその項目)の全ポートの表示/非表示(showPorts を設定)。
addEdge() / connect() / JSON 読み込みからの接続は有効です。methodsetMoveSnap(step) / gettermoveSnap
ノードの移動単位(ワールド px)を設定・取得します。0 で無効。設定するとドラッグ中はクリックしたノードの左上が単位に揃い、他の選択ノードは相対位置を保って追従します。addNodeAt / insertJSON の snap 既定値も true になります。
methodsnapValue(v) → number / snapPoint({x, y}) → {x, y}
値・座標を移動単位に丸めます(無効時はそのまま)。
methodUndo 可snapNodes(ids?) → string[]
ノードの左上を移動単位に揃えます。ids 省略時は選択中ノード、選択が無ければ全ノード。戻り値は動かしたノード ID。
ノード追加
methodUndo 可addNodeAt(spec, options?) → Node | null
位置を指定してノードを追加し、選択状態にします。spec は Node 定義(x, y は計算で上書き)。
| オプション | 既定 | 説明 |
|---|---|---|
at: {x, y} | – | ワールド座標 |
client: {clientX, clientY} | – | クライアント座標(マウスイベントをそのまま渡せる) |
| どちらも省略時はポインタ位置。ポインタが canvas 外か未観測なら画面中央 | ||
anchor | 'center' | 位置に合わせる基準点。'center' / 'top-left' / 'header'(ヘッダ中央) |
select | true | 追加後に選択する |
snap | moveSnap > 0 | 追加位置を moveSnap(未設定なら theme.grid.size)に吸着 |
avoidOverlap | false | 既存ノードと重なる場合は右下へ moveSnap(未設定なら 24px)ずつずらす(最大 50 回) |
methodUndo 可addNodeAtPointer(spec, options?) / addNodeAtCenter(spec, options?)
それぞれ「ポインタ位置(canvas 外なら画面中央)」「画面中央」に追加する省略形です。
document.addEventListener('keydown', (e) => {
if (e.key === 'n') editor.addNodeAtPointer({ title: '新規', input: true, output: true, items: [] }, { avoidOverlap: true });
});
editor.on('canvas:dblclick', ({ x, y }) => editor.addNodeAt({ title: '新規' }, { at: { x, y }, anchor: 'header' }));
位置をそのまま使いたい場合は editor.graph.addNode(node) を使います。
子ノード
ノードに childs を持たせると、その中に子ノードが入ります。子ノードは親の項目の下に上から順に縦に積まれ、位置(x / y)と幅は親が自動計算します。入れ子は何段でも可能です。
editor.graph.addNode({
id: 'n0', title: '親ノード', x: 100, y: 50, input: true, output: true,
items: [{ id: 'p0', label: 'value', input: true, output: true }],
childs: [
{
id: 'n1', title: '子ノード', input: true, output: true,
items: [{ id: 'p1', label: 'value', input: true, output: true }],
childs: [{ id: 'n2', title: '孫ノード', input: true, output: true }],
},
],
});
| 項目 | ふるまい |
|---|---|
| 配置 | 親の項目の下に縦に並ぶ。左右は theme.node.childIndent(既定 10px)だけ内側、間隔は theme.node.childGap(既定 6px)。子の x / y / width の指定は無視される |
| 親の高さ | headerHeight + 項目 + 子ブロック。孫まで含めて自動計算される |
| 移動 | 子だけを動かすことはできない。子をドラッグすると一番外側の親ごと動く(moveNodes も同じ) |
| ポート・接続 | 子もヘッダポートと項目ポートを持ち、外のノードとも自由に接続できる。端子の丸は一番外側の親の左右の縁(portEdgeX)に並ぶ |
| 選択・ヒットテスト | 深い子ノードが優先して当たる。範囲選択(selectInRect)は親だけを選ぶ |
| 削除・複製 | 親を削除すると子孫もまとめて消える。複製(duplicateNodes)は部分木ごとコピーされ、二重にはコピーされない |
| 幅リサイズ | 子ノードは親に追従するためリサイズできない(グリップも出ない) |
| JSON | childs として入れ子で入出力される |
methodUndo 可addChild(parentId, child, index?) → Node | null
子ノードを追加します。index を渡すとその位置に挿入します(省略時は末尾)。
methodUndo 可removeChild(id, { x?, y? }) → Node | null
子を親から外して独立したノードに戻します。x / y を渡すとその位置に置きます。子ノードでなければ null。Lit 版では DOM の予約名を避けて detachChild(id, position?) という名前です。
methodUndo 可setParent(id, parentId | null, index?) → boolean
親を付け替えます。parentId に null を渡すと独立させます。自分の子孫を親にするような循環は false を返して拒否します。
methodchildrenOf(node) → Node[] / rootNodeOf(node) → Node | null
子ノードの配列(表示順)と、一番外側の親(自分が子でなければ自分自身)。引数はノードでも ID でも渡せます。さらに細かい問い合わせは Graph 側の isChild / parentOf / rootOf / depthOf / descendantIds / rootNodes を使います。
goto(ID 指定の遷移)
「この選択肢を選んだらこのノードへ」という遷移を、コネクタを引かずにノード ID で指定できます。ノードにも項目にも goto を書けて、コネクタと混ぜて使えます。
graph.addNode({
id: 'menu', title: 'メニュー', x: 300, y: 0, input: true,
items: [
{ id: 'm1', label: '料金を知りたい', goto: 'price' }, // ID だけ
{ id: 'm2', label: '使い方を知りたい', goto: { to: 'howto', label: '使い方' } }, // ラベル付き
{ id: 'm3', label: 'その他', goto: ['faq', 'agent'] }, // 複数
],
goto: 'survey', // ノード本体に書くと「この画面のあと進む先」
});
| 扱い | 内容 |
|---|---|
| 強調表示 | connectedTo() がコネクタと同じ向きのつながりとして辿るので、focusMode でも goto 先が強調される({ links: false } で除外) |
| 自動整列 | autoLayout() も層の計算に使う({ links: false } で除外) |
| 描画 | 普段は描かず、そのノードを選択したとき・強調表示で辿られたときだけ点線の矢印で表示(theme.goto) |
| JSON | そのまま保存・復元。merge / insertJSON で ID が付け替わるときは遷移先も付け替わる |
| 存在しない ID | エラーにならず exists: false として辿られない |
typeGotoSpec
'n1' / ['n1','n2'] / { to: 'n1', label?: string } / その配列。normalizeGoto(value) で [{to, label?}] に正規化できます。
methodUndo 可setGoto(nodeOrId, value, { itemId })
設定・解除(null で解除)。itemId を渡すとその項目に設定します。内部では updateNode / updateItem なので 1 回の Undo で戻ります。
methodgotoLinks(nodeOrId?) → GotoLink[] / gotoSources(nodeOrId) → GotoLink[] / gotoTargets(nodeOrId) → Node[]
gotoLinks は出ていく遷移(引数を省略するとグラフ全体)、gotoSources は「このノードを指しているリンク」、gotoTargets は存在する遷移先ノードです。GotoLink は { key, from, itemId, to, label, exists }(key は goto:<from>:<itemId|->:<index> という安定した識別子で、強調表示の links に入るのもこのキー)。
editor.setGoto('menu', 'price', { itemId: 'm1' });
editor.gotoLinks('menu'); // → [{ key, from, itemId: 'm1', to: 'price', label: null, exists: true }, ...]
editor.gotoSources('price'); // → price へ入ってくるリンク
editor.on('focus:change', ({ links }) => console.log('辿った goto', links));
graph.gotoAnchor(link) で取れます(始点は項目の行、終点は遷移先ノードのヘッダ)。独自に描きたいときは overlayRenderer と組み合わせてください。右クリックメニュー
右クリックすると、対象(ノード/項目/コネクタ/空白)に応じたメニューが出ます。ブラウザ既定のメニューは常に抑制されます。未選択のものを右クリックした場合はそれを選択してからメニューを出し、選択中のノードを右クリックしたときは複数選択を保ちます(まとめて複製・削除できる)。readOnly のときと context-menu="false" のときはメニューを出しません(イベントだけ発火)。
event'context:menu'(Lit: context-menu)
コアが出すのはこのイベントだけで、メニューの描画は Lit 版が担当します。detail は { type, node, item, edge, port, x, y, screen, client, selection, originalEvent }。type は hitTest と同じ(none / node / item / port / resize / edge / edge-delete)、x / y はワールド座標、screen は canvas 基準、client はビューポート基準です。
// 自前のメニューを出す(内蔵メニューは抑止)
el.addEventListener('context-menu', (e) => {
e.preventDefault();
myMenu.open(e.detail.client.x, e.detail.client.y, e.detail);
});
propertycontextMenuItems
内蔵メニューの項目。配列を渡すと常に同じメニュー、関数を渡すと対象ごとに組み立てられます。ctx はイベントの detail に el / editor / graph / defaultItems を足したものです。
el.contextMenuItems = (ctx) => [
...ctx.defaultItems.filter((i) => i.id !== 'export'),
{ type: 'separator' },
{ id: 'log', label: 'ID をログ出力', shortcut: 'Ctrl+L', run: (c) => console.log(c.node?.id) },
];
| 項目のフィールド | 意味 |
|---|---|
id | 項目を見分ける ID(context-menu-select の detail に入る) |
label | 表示名 |
shortcut | 右端に薄く出す表記(動作は割り当てない) |
disabled / hidden | 無効表示 / 一覧から除く |
danger | true で赤字(削除など) |
run(ctx) | 実行する処理(非同期でもよい) |
{ type: 'separator' } | 区切り線。先頭・末尾・連続したものは自動で削られる |
既定の項目は defaultContextMenuItems(ctx) として公開しています(@hidemikimura/canvas-flow / /lit から import)。対象ごとの内容は次のとおりです。
| 対象 | 項目 |
|---|---|
| ノード・項目 | 複製 / 削除 / つながりを外す / 子ノードを追加 / 強調されている要素を選択 / つながっている要素を選択 / コピー / このノードを中心に |
| 子ノード | この子ノードを複製 / この子ノードを削除 / 子ノードを親から出す(ほかはノードと同じ) |
| コネクタ | このコネクタを削除 / 曲線・直線・直角にする / 両端のノードを選択 |
| 空白 | ここにノードを追加 / 貼り付け / すべて選択 / 選択を解除 / 整列 / 全体表示 / 縮尺をリセット / JSON を書き出し |
methodcloseContextMenu() / openContextMenuAt(x, y, detail?)
閉じる・任意の位置(要素内の px)に出す。ツールバーの「…」ボタンから同じメニューを出したいときなどに使います。
part="context-menu" で上書きできます。自動整列
methodUndo 可autoLayout(options?) → string[]
階層レイアウト(Sugiyama 方式の簡略版)で並べ直します。コネクタは左から右へ流れ、層をまたぐコネクタは途中の層で場所を確保するため他のノードの上を横切りません。2 つ以上のノードを選択していればその範囲だけ、なければ全ノードが対象です。戻り値は移動対象になったノード ID。
| オプション | 既定 | 説明 |
|---|---|---|
nodeIds | 選択 or 全体 | 対象を明示 |
fit / animate | true / true | 整列後に表示を合わせる(全体なら全体表示、範囲なら中央へ) |
layerGap | 120 | 層と層の横の間隔 |
nodeGap | 40 | 同じ層のノード間の縦の間隔 |
componentGap | 80 | 連結成分の間隔 |
dummyHeight | 24 | 通過するコネクタ 1 本が確保する高さ |
iterations | 8 | 順序付け・座標調整の反復回数 |
origin: {x, y} | 対象の元の左上 | 整列後の左上位置 |
位置だけを計算したい場合は import { layeredLayout } from '@hidemikimura/canvas-flow' の layeredLayout(graph, nodeIds, options) → Map<id, {x, y}> を使います。
Undo / Redo
methodundo() → boolean / redo() → boolean
履歴を 1 段戻す/進めます。存在しなくなった要素は選択から外れます。canUndo / canRedo で可否を確認できます。
graph.batch(fn) の中、または history.begin() 〜 history.end() の間の操作は 1 項目になります。同じノード群の連続移動、同じノードの連続更新は併合されます。load() / importData({ mode: 'replace' }) で履歴は破棄されます。
editor.graph.batch(() => {
editor.graph.addNode({ id: 'x', title: 'X' });
editor.graph.addNode({ id: 'y', title: 'Y', x: 300 });
editor.graph.addEdge({ source: 'x', sourcePort: 'out', target: 'y', targetPort: 'in' });
});
editor.undo(); // 3 つまとめて消える
ビュー操作
methodzoomIn(factor = 1.2) / zoomOut(factor = 1.2) / setZoom(zoom)
画面中央を基準にズームします。範囲は viewport.minZoom(0.05)〜 maxZoom(4)。
methodresetZoom() / resetView()
resetZoom() は縮尺を 1 に戻し(中心は維持)、resetView() は縮尺 1 で原点を左上に戻します。
methodfitView(padding = 40, { animate = false })
全ノードが収まるようにズーム・位置を合わせます。
methodcenterOnNode(id, { zoom?, animate = true, duration = 300 }) → boolean
ノードが画面中央に来るようにスクロールします(既定でアニメーション)。
methodcenterOn(wx, wy, { zoom?, animate = false, duration = 300 })
ビューの変更後は 'viewport:change' が発火します。直接 editor.viewport を操作した場合は requestRender() を呼んでください。
検索
methodsearch(query) → Node[]
タイトル・ID・項目ラベル・項目値を部分一致(大文字小文字無視)で検索します。query は文字列、RegExp、または (node) => boolean。
methodsearchAndFocus(query, index = 0) → Node[]
検索して index 番目の一致(循環)を選択し、中央へアニメーションで移動します。
JSON 入出力
methodexportData({ selectionOnly = false, includeViewport = true }) → object
JSON フォーマットのオブジェクトを返します。selectionOnly で選択中のノードとその間のコネクタだけを出力(このとき viewport は含まれない)。'export' イベントを発火します。
methodexportJSON({ pretty = true, ...exportDataOptions }) → string
methoddownloadJSON(filename = 'canvas-flow.json', options?)
ブラウザでファイルとしてダウンロードさせます。
methodtoJSON() → object
JSON.stringify(editor) で使われます。exportData() と同じ形式(イベントは発火しない)。
methodimportData(input, options?) → { ok: true, mode, nodes, edges, warnings } | { ok: false, errors }
JSON 文字列またはオブジェクトを検証して読み込みます。importJSON() は別名です。
| オプション | 既定 | 説明 |
|---|---|---|
mode | 'replace' | 'replace': 内容を置き換え(履歴は破棄) / 'merge': 既存に追加。衝突する ID は付け替え、Undo 可 |
at: {x, y} | – | merge 時の配置位置。基準は anchor で選ぶ |
anchor | 'top-left' | 'origin': JSON の座標を at からの相対位置として扱う / 'top-left': ノード群の左上を at に / 'center': 中心を at に |
offset: {x, y} | – | merge 時に加える位置オフセット |
select | true | merge 後に読み込んだ要素を選択 |
restoreViewport | true | replace 時に viewport を復元 |
fitView | false | 読み込み後に全体表示 |
検証で構文エラー・構造エラー・未対応 format/version は errors、存在しないノード/ポートを指すコネクタは除外して warnings に記録します。成功時は 'import' イベントを発火します。
methodUndo 可insertJSON(input, options?) → result
複数のノード・コネクタを含む JSON を指定位置に追加します(既存の内容は残す)。JSON 内のノード座標は追加位置からの相対位置として扱われ(既定 anchor: 'origin')、(0, 0) のノードが追加位置にそのまま置かれます。衝突する ID は付け替え、コネクタの参照も追従します。戻り値は importData と同じで、成功時に 'insert' イベントを発火します。
| オプション | 既定 | 説明 |
|---|---|---|
at: {x, y} / client: {clientX, clientY} | ポインタ位置 | 追加位置。省略時はポインタ位置(canvas 外・未観測なら画面中央) |
anchor | 'origin' | 'origin' / 'top-left' / 'center' |
select | true | 追加した要素を選択 |
snap | moveSnap > 0 | 追加位置を moveSnap(未設定なら theme.grid.size)に吸着 |
const template = { nodes: [{ id: 'a', title: 'A', x: 0, y: 0, output: true }, { id: 'b', title: 'B', x: 300, y: 80, input: true }],
edges: [{ source: 'a', sourcePort: 'out', target: 'b', targetPort: 'in' }] };
editor.insertJSON(template, { at: { x: 500, y: 200 } }); // A → (500,200), B → (800,280)
methodUndo 可insertJSONAtPointer(input, options?)
insertJSON をポインタ位置で実行します(canvas 外なら画面中央)。
methodimportFromFile(file | string, options?) → Promise
methodopenImportDialog(options?) → Promise<result | null>
ファイル選択ダイアログを開いて読み込みます。キャンセル時は null。
methodload(data)
検証なしで内容を置き換えます({ nodes, edges, viewport? })。信頼できるデータ向け。
ライフサイクル
methodon(type, handler) → unsubscribe / off(type, handler)
イベント購読。戻り値の関数で解除できます。
methoddestroy()
イベントリスナ(canvas / document)とアニメーションを解除します。canvas を DOM から外す前に呼んでください。
イベント一覧
コアは editor.on(type, handler)、Lit 版は CustomEvent(名前は kebab-case、内容は event.detail)です。
event状態・モデルの変化
| コア | Lit | payload | タイミング |
|---|---|---|---|
selection:change | selection-change | { nodes: string[], edges: string[] } | 選択が変わった |
viewport:change | viewport-change | { tx, ty, zoom } | パン/ズームの操作が終わった(ホイールは 120ms 間引き) |
graph:change | graph-change | – | モデルが変わった(batch 内はまとめて 1 回) |
node:add / node:remove / node:change | node-add / node-remove / node-change | Node | ノードの追加/削除/プロパティ変更 |
nodes:move | nodes-move | { ids, dx, dy } | 移動のたび(ドラッグ中も) |
nodes:move:end | nodes-move-end | { ids, dx, dy, start } | ドラッグ移動の終了 |
node:resize:end | node-resize-end | { id, from, to } | リサイズの終了 |
edge:add / edge:remove / edge:change | edge-add / … | Edge | コネクタの追加/削除/変更 |
history:change | history-change | { canUndo, canRedo } | 履歴の状態が変わった |
import / export | import / export | { mode, nodes, edges, warnings } / { data, selectionOnly } | JSON 入出力 |
layout | layout | { nodes: string[] } | 自動整列を適用した |
edge-type:change | edge-type-change | { type, edges: string[] | null } | コネクタの描画方法を変えた(edges が null なら全体の既定) |
focus:change | focus-change | { mode, direction, nodes: string[], edges: string[], links: string[] } | 強調表示の対象が変わった(links は辿った goto のキー) |
focus:select | focus-select | { mode, nodes: string[], edges: string[] } | 強調表示されている要素を選択した(selectFocused) |
context:menu | context-menu | { type, node, item, edge, port, x, y, screen, client, selection, originalEvent } | 右クリック。Lit 側の context-menu は preventDefault() で内蔵メニューを抑止できる |
| — | context-menu-select | { id, item, target } | 右クリックメニューの項目を実行した(Lit のみ) |
edges:delete | edges-delete | { ids: string[], scope } | 選択範囲内のコネクタだけを削除した |
insert | insert | { at, anchor, nodes, edges } | insertJSON で JSON を指定位置に追加した |
render | render | { nodes, edges, ms } | 描画後の統計 |
eventユーザー操作
| コア | Lit | payload | タイミング |
|---|---|---|---|
node:click | node-click | { node, item, header, port, …共通 } | ノードのどこかをクリック(ドラッグせずに離した) |
item:click | item-click | { node, item, …共通 } | 項目をクリック(続けて node:click も発火) |
edge:click | edge-click | { edge, …共通 } | コネクタをクリック |
canvas:click | canvas-click | { …共通 } | 空白をクリック |
node:edit | node-edit | { node, rect, screenRect } | ヘッダをダブルクリック(Lit 版は preventDefault() で内蔵編集を抑止) |
item:edit | item-edit | { node, item, rect, screenRect } | 項目をダブルクリック |
canvas:dblclick | canvas-dblclick | { x, y } | 空白をダブルクリック |
edge:dblclick | edge-dblclick | { edge, at } | コネクタをダブルクリック |
connect:cancel | connect-cancel | { from: { node, port }, at } | コネクタのドラッグを空白で離した |
connect:rejected | connect-rejected | { node, port, reason } | 満杯のポートからコネクタを引こうとした |
edge:delete-icon | edge-delete-icon | { edge } | 削除アイコンのクリックでコネクタを削除した |
クリック系の共通 payload: x, y(ワールド), screen: {x, y}, button, shiftKey, ctrlKey, metaKey, altKey, pointerType, originalEvent。選択の更新はクリックイベントより前に行われ、readOnly でも発火します。
Node / NodeItem
typeNode
| フィールド | 型 | 説明 |
|---|---|---|
id | string | 一意な ID(省略時は自動生成) |
type | string? | ノード種別。nodeTypes のスタイルを適用 |
title | string | ヘッダに表示(既定 'Node') |
x, y | number | 左上のワールド座標 |
width | number? | 幅。省略時は theme.node.width(200) |
input / output | PortSpec | ヘッダ左/右のポート |
items | NodeItem[] | 項目。高さは headerHeight + items.length × itemHeight + padding |
childs | Node[]? | 子ノード。項目の下に縦に並ぶ。位置と幅は親が自動計算(子の x / y / width は無視される)。何段でも入れ子にできる |
parent | string? | 子ノードにだけ入る親の ID(読み取り専用。変更は setParent()) |
goto | GotoSpec? | コネクタを使わない ID 指定の遷移先。'n1' / ['n1','n2'] / {to:'n1', label:'戻る'} |
showPorts | boolean? | false で全ポートの丸を非表示 |
style | object? | theme.node.* の上書き |
data | any? | 任意データ(そのまま JSON に保存) |
typeNodeItem
| フィールド | 型 | 説明 |
|---|---|---|
id | string | ノード内で一意(省略時は自動生成) |
label | string | 左側に表示 |
value | string? | 右側に表示。あればダブルクリックで値を、無ければラベルを編集 |
input / output | PortSpec | 行の左/右のポート |
showPorts | boolean? | false でこの項目のポートを非表示 |
goto | GotoSpec? | この項目が選ばれたときの遷移先(ID 指定) |
PortSpec / ポートキー
typePortSpec = false | true | number | { max?: number, visible?: boolean }
| 値 | 意味 |
|---|---|
false / 省略 | ポート無し(接続もできない) |
true | ポート有り。上限は rules.maxInputs / maxOutputs(既定 無制限) |
数値 n | 最大 n 本 |
{ max, visible } | max 省略で既定上限。visible: false で丸を描かない(接続は有効) |
output の上限は「そのポートから開始できるコネクタ数」、input は「そのポートに終了できるコネクタ数」です。上限に達したポートは theme.port.fullFill(橙)で描かれます。
typeポートキー
| キー | 場所 |
|---|---|
'in' / 'out' | ノードヘッダの左/右 |
'item:<itemId>:in' / 'item:<itemId>:out' | 項目の左/右 |
portKey(itemId, dir) / parsePortKey(key)(canvas-flow から export)で生成・分解できます。
Edge
typeEdge
| フィールド | 型 | 説明 |
|---|---|---|
id | string | 一意な ID(省略時は自動生成) |
source / sourcePort | string | 出力側のノード ID とポートキー(out 系) |
target / targetPort | string | 入力側のノード ID とポートキー(in 系) |
style | object? | theme.edge.* の上書き |
type | 'bezier' | 'straight' | 'step'? | 描画方法。省略時は全体の既定(theme.edge.type) |
data | any? | 任意データ |
向きが逆(in → out)で渡された場合、addEdge() が正規化します。同一ノード間・同じポート組の重複・存在しないポート・上限超過は追加されず null を返します。
JSON フォーマット
{
"format": "canvas-flow",
"version": 1,
"nodes": [ { "id": "a", "title": "A", "x": 0, "y": 0, "output": true, "items": [],
"childs": [ { "id": "a1", "title": "子", "input": true, "items": [] } ] } ],
"edges": [ { "id": "e1", "source": "a", "sourcePort": "out", "target": "b", "targetPort": "in" } ],
"viewport": { "tx": 0, "ty": 0, "zoom": 1 }
}
format / version / viewport は省略可能で、プレーンな { nodes, edges } も読み込めます。未対応の format や version > 1 はエラーになります。
子ノードは nodes の中に childs として入れ子で書き出されます(トップレベルの nodes に並ぶのは親を持たないノードだけ)。子ノードの座標と幅は親が決めるため出力されず、読み込み時に指定されていても無視されます。edges は入れ子にならず、子ノードの ID をそのまま参照します。
Graph(editor.graph)
モデル操作の中心です。変更系はすべて Undo 可(load / clear を除く)。
| メソッド | 説明 |
|---|---|
addNode(node, { parent?, index? }) → Node | ノードを追加(ID 重複は例外)。node.childs があれば子ノードもまとめて追加 |
getNode(id) / getEdge(id) | 取得 |
updateNode(id, patch) | プロパティを部分更新(title, x, y, width, items, style, data…) |
updateItem(nodeId, itemId, patch) / addItem(nodeId, item, index?) / removeItem(nodeId, itemId) | 項目の更新・追加・削除(削除時はその項目のコネクタも削除) |
moveNodes(ids, dx, dy) | 複数ノードを相対移動(子ノードの ID を渡すと一番外側の親ごと動く) |
removeNode(id) / removeNodes(ids) | 接続コネクタと子孫ノードごと削除 |
childrenOf(node) / isChild(node) / parentOf(node) / rootOf(node) / depthOf(node) / descendantIds(node, { includeSelf? }) / rootNodes() | 親子関係の問い合わせ(引数はノードでも ID でも可) |
addChild(parentId, child, index?) / removeChild(id, { x?, y? }) / setParent(id, parentId | null, index?) | 子ノードの追加・切り離し・付け替え(循環は setParent が false を返して拒否) |
relayoutChildren(node, { reindex? }) / nodeWidthOfChild(parent) / portEdgeX(node) | 子ノードの再配置(通常は自動)・子の幅・ポートを描く左右の X |
addEdge(edge) → Edge | null | 検証して追加(常に拒否モード) |
connect(source, sourcePort, target, targetPort, { replace? }) → Edge | null | rules.onFull に従って接続(満杯なら付け替え) |
canConnect(...) → boolean / connectError(...) → string | null | 接続可否。理由: same-node / invalid-port / missing-port / duplicate / source-full / target-full |
updateEdge(id, patch) / removeEdge(id) / removeEdges(ids) | コネクタの更新・削除 |
edgesOf(nodeId) → Edge[] | ノードに接続しているコネクタ |
connectedTo(startIds, { depth, direction, includeStart, links }) → { nodes: Set, edges: Set, links: Set } | 起点から辿れるノード・コネクタ・goto(強調表示・まとめ選択用)。direction は 'lineage' | 'downstream' | 'upstream' | 'both'(この API の既定は 'both')。links: false で goto を辿らない |
gotoLinks(node?) / gotoSources(node) / gotoTargets(node) / setGoto(node, value, { itemId }) / gotoAnchor(link) | goto の一覧・逆引き・設定・点線の始点終点 |
portSpec(nodeId, key) / portEdges(nodeId, key) / portCapacity(nodeId, key) → { count, max, full } | ポートの定義・接続・残量 |
portVisible(nodeId, key) / setPortVisible(...) / setPortsVisible(...) | ポートの表示状態 |
nodeRect(node) / nodeWidth(node) / nodeHeight(node) | 矩形・寸法(高さはヘッダ+項目+子ノード) |
nodePorts(node, { visibleOnly? }) / portPosition(nodeId, key) / itemRect(node, itemId) | ポート位置・項目矩形 |
edgeGeometry(edge) / edgePoint(edge, t = 0.5) / edgeRect(edge) / edgeType(edge) | 形状({ type, x1, y1, x2, y2, points, c1x… })・形状上の点(折れ線は道のり比)・外接矩形・描画方法 |
nodesInRect(rect) / edgesInRect(rect) / nodesFullyInRect(rect) | 空間インデックスによる範囲問い合わせ |
bounds() → rect | null | 全ノードを囲む矩形 |
duplicateNodes(ids, offset) → { nodes, edges, idMap } | 複製 |
batch(fn) | 複数変更を 1 回の change と 1 つの Undo 項目にまとめる |
toJSON() / load(data) / clear() | 直列化(子は childs に入れ子)・置き換え・全消去 |
rules(プロパティ) | { maxInputs, maxOutputs, onFull } |
Graph 自身も on(type, handler) を持ち、change, node:add などに加えて Undo 用の op、load, clear を発火します。
テーマ(defaultTheme)
{
background: '#f6f7f9',
grid: { color: '#e0e3e8', size: 32, majorEvery: 4, majorColor: '#cfd4db' },
font: '13px system-ui, …', titleFont: 'bold 13px system-ui, …',
node: { fill, stroke, strokeWidth: 1, radius: 8, headerFill, headerHeight: 30, titleColor, textColor,
itemHeight: 26, itemSeparator, padding: 8, width: 200,
childIndent: 10, // 子ノードの左右インデント
childGap: 6, // 子ノードどうしの縦の間隔
selectedStroke, selectedStrokeWidth: 2, hoverStroke, shadow },
port: { radius: 5, fill, stroke, strokeWidth: 1.5, hoverFill, connectedFill, fullFill: '#f59e0b' },
edge: { stroke, strokeWidth: 2, selectedStroke, selectedStrokeWidth: 3, hoverStroke, pendingStroke,
type: 'bezier', // 'bezier' | 'straight' | 'step'
curvature: 0.5, // bezier の曲がり具合
stepOffset: 24, // step の水平突き出し量
deleteIcon: { radius: 9, fill, stroke, color, hoverFill, hoverColor } },
goto: { stroke: '#94a3b8', strokeWidth: 1.5, dash: [6, 4], arrow: 9, // ID 指定の遷移の点線
labelColor, labelFont, labelBg },
focus: { dimOpacity: 0.12, dimOpacityLod: 0.25, edgeStroke: null, edgeWidth: null },
selectionBox: { fill, stroke },
minimap: { background, border, node, selectedNode, viewportFill, viewportStroke },
lodZoom: 0.3 // この拡大率以下で文字・ポート・影を省いた簡易描画
}
setTheme(patch) で部分上書きします。deleteIcon.radius は画面ピクセル単位(ズームに影響されない)。node.shadow は拡大率 50% 未満では省略されます。
<canvas-flow-editor>(Lit 版)
コアを包む Web Component。el.editor で NodeEditor に触れます。ツールバー(検索・ズーム・Undo/Redo・複製・削除・コネクタ削除・整列・読み込み・書き出し)、インライン編集、ミニマップ、.json ドロップ、ResizeObserver による自動リサイズを内蔵します。
| 属性 | 型 / 既定 | 説明 |
|---|---|---|
data(プロパティ) | object | 設定すると load() |
theme(プロパティ) | object | テーマの部分上書き |
node-types | object / {} | ノード種別 |
minimap / minimap-width / minimap-height | true / 200 / 140 | ミニマップ |
toolbar | true | ツールバー表示 |
wheel-mode / drag-mode / read-only | zoom / pan / false | コアのオプションに対応 |
import-mode / export-filename | replace / canvas-flow.json | ツールバー・ドロップでの読み込み方法、書き出しファイル名 |
max-inputs / max-outputs / on-full | 無制限 / reject | 接続ルール |
edge-delete-icon | "false" で無効 | 削除アイコン |
move-snap | 0 | ノードの移動単位(px) |
edge-type | bezier | コネクタの描画方法(bezier / straight / step) |
focus-mode | off | 同じ流れの強調表示(off / connected / neighbors)。向きは setFocusMode(mode, {direction}) |
context-menu | 有効 | "false" で内蔵の右クリックメニューを無効(read-only では出ない)。項目は contextMenuItems で差し替え |
委譲メソッド: addNode, addNodeAt, addNodeAtPointer, addNodeAtCenter, getPointer, addEdge, removeNode, removeEdge, updateNode, updateItem, duplicateSelection, deleteSelection, deleteSelectedEdges, autoLayout, setPortVisible, setPortsVisible, setMoveSnap, snapNodes, addChild, detachChild, setParent, childrenOf, rootNodeOf, setEdgeType, setSelectedEdgeType, setFocusMode, selectFocused, selectConnected, setGoto, gotoLinks, gotoSources, closeContextMenu, openContextMenuAt, undo, redo, canUndo, canRedo, zoomIn, zoomOut, resetZoom, fitView, search, focusNode, exportData, exportJSON, downloadJSON, importJSON, insertJSON, insertJSONAtPointer, importFromFile, openImportDialog, toJSON, load。追加イベント: ready({ editor }), import-error({ errors })。スロット: 既定(canvas 上に重ねる要素)と toolbar(ツールバー末尾)。
操作・ショートカット
| 操作 | 動作 |
|---|---|
| ノードをクリック / ドラッグ | 選択(Shift・Ctrl・Cmd で追加・トグル)/ 選択中ノードをまとめて移動 |
| ポートをドラッグ | コネクタ作成(出力 → 入力。上限超過先には繋げない) |
| 右下グリップをドラッグ | 幅のリサイズ |
| コネクタをクリック / 中央の × をクリック | 選択 / 削除 |
| 空白を左ドラッグ | パン(dragMode='select' なら範囲選択。Shift で反転) |
| 中ボタン / Space + ドラッグ | パン |
| ホイール | ズーム。Shift + ホイールでパン(wheelMode='pan' なら逆)。慣性スクロール中は最初の動作を維持 |
| 2 本指(タッチ) | ピンチでズーム、同時にパン |
| ダブルクリック | ヘッダ / 項目: 編集イベント。空白: canvas:dblclick |
| Delete / Backspace | 選択を削除 |
| Shift + Delete / Backspace | 選択範囲内のコネクタだけを削除(ノードは残す) |
| Ctrl/Cmd + A / C / V / D | 全選択 / コピー / 貼り付け(カーソル位置)/ 複製 |
| Ctrl/Cmd + Shift + A | 強調表示されている要素をまとめて選択(selectFocused。ツールバー「強調を選択」も同じ) |
| 右クリック | 対象に応じたメニュー(↑ ↓ で移動、Enter で実行、Escape で閉じる) |
| Ctrl/Cmd + Z / Shift+Z / Y | Undo / Redo |
| Ctrl/Cmd + 0 / + / − | 縮尺リセット / 拡大 / 縮小 |
| 矢印キー(Shift で 10 倍) | 選択ノードを微移動(1px。moveSnap 設定時はその単位) |
| Escape | 操作キャンセル / 選択解除 |
キーボードは document で受け、keyboardScope 内を最後にポインタ操作していて入力欄にフォーカスが無いときだけ処理します。
canvas-flow v0.5.0 · この仕様書は docs/api.html として同梱されています。デモは docs/demo.html。