レールモードを追加する
左側のレールにはボタンではなく、モードが保持されます。選択、作成、表示、物理演算機能が組み込まれており、 それぞれは独自のツールのみを表示します。ただし、レールの高さが 4 スロットのままであるのはこのためです。 グラフでできることはたくさんあります。 addRailMode() は 5 番目を追加します。
このカードは、サービス グラフをウォークスルーするための Explore モードを追加します。ノードを選択し、そのノードを選択します。 隣人たち、キャンバスを自分たちに狭め、そして一歩退いてください。 E を押すか、コンパスをクリックします ディバイダーの下。
モードが着地する場所
登録されたモードは、組み込みの後に、分割線 ** の下の ** に配置され、モード間で順序付けされます。 order 。プラグインは、組み込みの 4 つの要素を並べ替えたり削除したりすることはできないため、その線より上にあるものはすべて削除されます はすべての Pivotick グラフで同じであり、その下にあるものはすべてあなたのものになります。
マークアップではなくツールを宣言する
モードの tools はコンテキスト パネル内の行になり、選択と同様に描画されます。 作成します。動詞を指定するのはあなたです。各行の kind は、その動作を示します。
kind | 行動 |
|---|---|
'action' | 一度実行すると、モードはそのままになります - 3 つのツールはすべてここにあります |
'toggle' | ツールを装備します: パネルが折りたたまれ、レール ボタンがそのツールになり、Select のスロットが Lasso になります。 |
'default' | モードが置かれているツール、およびトグルで戻るもの |
enabled() は、選択が変更されるたびに再チェックされます。これが、隣接するものを選択 する理由です。 ノードを選択するまで、Isolate はグレー表示のままです。
ここには特権的なものは何もない
上記のすべてのツールは、関係を構築するための getEdges() と呼ぶこともできるパブリック API を呼び出します。 selectNodes() で選択し、setVisibleNodes() でキャンバスを狭めます。モードはただ 彼らにレールスロット、近道、武装国家、パネルなど、住む場所を与えます。
重要なのは、データにとって「近隣」が何を意味するかを知る探索モードです。 あなたには構築できますが、私たちには構築できないものです。
モードは相互に排除します
Explore を開くと View と Physics が閉じられ、その逆も同様です (1 つのストア、1 つのアクティブ モード)。元に戻す onExit でどのモードを装備していても;実行中にモードが削除された場合にも実行されます。 アクティブになり、レールは選択に戻ります。
フライアウトがより適切にフィットする場合
動作するのではなく構成するモードでは、狭いツール ストリップではなく、広いオーバーレイが必要です。 kind: 'flyout' を設定し、エクスポートされた Flyout をサブクラス化し、addRailMode をファクトリーに渡します — レールモードへの貢献を参照してください。
// The rail's four modes — Select, Create, View, Physics — are built in. `addRailMode`
// is how you add a fifth. It renders below a divider, after them, and drives the same
// contextual tool panel the built-ins do: declare `tools`, and the arming, the
// enabled/disabled states and the collapse are handled for you.
//
// This card adds an **Explore** mode for walking the graph: reach the neighbours of what
// is selected, isolate a subgraph, and step back out. Nothing here is privileged — every
// tool is public graph API a consumer could call themselves.
const compass = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20"><path fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" d="M12 21a9 9 0 1 0 0-18a9 9 0 0 0 0 18Z"/><path fill="currentColor" d="M15.5 8.5l-2 5l-5 2l2-5z"/></svg>'
const expandIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M11 13v3a1 1 0 0 0 2 0v-3h3a1 1 0 0 0 0-2h-3V8a1 1 0 0 0-2 0v3H8a1 1 0 0 0 0 2zm1 9a10 10 0 1 1 0-20a10 10 0 0 1 0 20m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16"/></svg>'
const isolateIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="18" height="18" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M16 6H8a2 2 0 0 0-2 2v8m10 26H8a2 2 0 0 1-2-2v-8m26 10h8a2 2 0 0 0 2-2v-8M32 6h8a2 2 0 0 1 2 2v8"/><circle cx="24" cy="24" r="6"/></svg>'
const resetIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 5V2L8 6l4 4V7a5 5 0 1 1-5 5H5a7 7 0 1 0 7-7"/></svg>'
function exploreMode() {
return {
name: 'explore-mode',
install(ctx) {
const interaction = () => ctx.graph.renderer.getGraphInteraction()
const selected = () => interaction().getSelectedNode()?.node
/** Every node one hop from `id`, itself included. */
const withNeighbours = (id) => {
const keep = new Set([id])
for (const edge of ctx.graph.getEdges()) {
if (edge.from.id === id) keep.add(edge.to.id)
if (edge.to.id === id) keep.add(edge.from.id)
}
return keep
}
ctx.addRailMode({
id: 'explore',
label: 'Explore',
icon: compass,
// Pressing E switches to the mode, or toggles its panel when it is
// already active — exactly what V and C do for Select and Create.
shortcut: 'E',
tools: [
{
// A one-shot action: it runs and leaves the mode as it was.
id: 'expand',
label: 'Select neighbours',
icon: expandIcon,
kind: 'action',
// Re-checked on every selection change, so the row greys out
// when it would do nothing.
enabled: () => !!selected(),
run: () => {
const node = selected()
if (!node) return
const keep = withNeighbours(node.id)
interaction().selectNodes(
ctx.graph.getMutableNodes()
.filter(n => keep.has(n.id))
.map(n => ({ node: n, element: n.getGraphElement() }))
)
},
},
{
id: 'isolate',
label: 'Isolate neighbourhood',
icon: isolateIcon,
kind: 'action',
enabled: () => !!selected(),
run: () => {
const node = selected()
if (!node) return
const keep = withNeighbours(node.id)
ctx.graph.setVisibleNodes(
ctx.graph.getMutableNodes().filter(n => keep.has(n.id))
)
},
},
{
id: 'reset',
label: 'Show everything',
icon: resetIcon,
kind: 'action',
run: () => ctx.graph.setVisibleNodes(ctx.graph.getMutableNodes()),
},
],
})
},
}
}
const options = {
UI: {
mode: 'full',
// Neither is what this card is about, and both crowd a small canvas.
minimap: false,
table: false,
},
plugins: [exploreMode()],
}const data = {
nodes: [
{ id: 'api', data: { label: 'api-gateway', kind: 'service' } },
{ id: 'auth', data: { label: 'auth-service', kind: 'service' } },
{ id: 'billing', data: { label: 'billing-service', kind: 'service' } },
{ id: 'search', data: { label: 'search-service', kind: 'service' } },
{ id: 'users-db', data: { label: 'users-db', kind: 'datastore' } },
{ id: 'orders-db', data: { label: 'orders-db', kind: 'datastore' } },
{ id: 'index', data: { label: 'search-index', kind: 'datastore' } },
{ id: 'cache', data: { label: 'edge-cache', kind: 'datastore' } },
{ id: 'mailer', data: { label: 'mailer', kind: 'worker' } },
{ id: 'reindexer', data: { label: 'reindexer', kind: 'worker' } },
],
edges: [
{ from: 'api', to: 'auth' },
{ from: 'api', to: 'search' },
{ from: 'api', to: 'billing' },
{ from: 'api', to: 'cache' },
{ from: 'auth', to: 'users-db' },
{ from: 'billing', to: 'orders-db' },
{ from: 'billing', to: 'mailer' },
{ from: 'search', to: 'index' },
{ from: 'reindexer', to: 'index' },
{ from: 'reindexer', to: 'orders-db' },
],
}