添加轨道模式
左栏容纳模式,而不是按钮。选择、创建、查看和物理都是内置的,每个工具都只显示自己的工具——这就是为什么无论图表能做什么,轨道仍保持四个槽高的原因。 addRailMode() 添加了第五个。
该卡添加了用于遍历服务图的 Explore 模式:选择一个节点,选择其邻居,将画布缩小到它们,然后退出。按 E,或单击分隔线下方的指南针。
您的模式落地的地方
注册模式位于分隔符下方的,在内置模式之后,按order排序。插件无法重新排序或删除内置的四个,因此每个Pivotick图中线上方的所有内容都是相同的,而线下方的所有内容都是您的。
声明工具,而不是标记
模式的 tools 成为上下文面板中的行,就像选择和创建一样绘制。你提供动词;每行的 kind 表示它的行为方式:
kind | 行为 |
|---|---|
'action' | 运行一次并保留该模式 - 所有三个工具都在这里 |
'toggle' | 装备一个工具:面板折叠,导轨按钮变成该工具,就像Select的插槽变成Lasso一样 |
'default' | 模式所依赖的工具,以及切换返回的内容 |
每当选择发生变化时,都会重新检查enabled() - 这就是为什么选择邻居和隔离保持灰色,直到您选择一个节点。
这里没有什么特权
上面的每个工具都调用公共 API,您可以调用自己:getEdges() 遍历关系,selectNodes() 选择它们,setVisibleNodes() 缩小画布。这种模式只是给了他们一个居住的地方——一个铁路槽口、一条捷径、一个武装状态和一个面板。
这就是要点:您可以构建一个了解“邻居”对您的数据意味着什么的探索模式,而我们却不能。
模式相互排斥
打开“探索”会关闭“视图”和“物理”,反之亦然 - 一个商店,一种活动模式。撤消您在 onExit 中设置的任何模式;如果在模式处于活动状态时删除该模式,它也会运行,并且导轨会回退到“选择”。
当浮出控件更适合时
配置而不是操作的模式需要宽覆盖,而不是窄工具条。设置 kind: 'flyout',对导出的 Flyout 进行子类化,并传递 addRailMode 工厂 — 请参阅贡献铁路模式。
js
// 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()],
}js
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' },
],
}