插件
插件是一个独立的 UI 元素、键绑定和生命周期钩子包,可以将自身安装到图表中,而核心不需要知道它的存在。声明式或命令式注册一个:
import { Pivotick, minimap } from 'pivotick'
// declaratively
const graph = new Pivotick(container, data, { plugins: [minimap()] })
// …or at any point later
graph.use(minimap({ position: 'bottom-left' }))图表上线后安装的插件会赶上当前的生命周期阶段,因此不存在“太晚”的情况 - 并且它注册的所有内容都会随 UI 一起拆除。
插件的形状
import type { PivotickPlugin } from 'pivotick'
const myPlugin: PivotickPlugin = {
name: 'my-plugin', // used for de-duplication and logging
install(ctx) { /* … */ }, // called once
}安装相同的 name 两次会发出警告并跳过重复项,因此插件可以在 plugins 中列出,并通过 graph.use 重新应用,而无需加倍。
install 交到的是什么
ctx 是一个 {@link PluginContext}:
| 会员 | 这是做什么用的 |
|---|---|
graph | 实时 Graph — 节点、边、数据事件总线、queryEngine |
ui | UIManager — 选项、通知、模式存储 |
layout | DOM 支架,读取 live (不是快照)。 layout.canvas 是画布停靠的 chrome 所在的位置 |
addElement(element, slot?) | 将UIComponent放入生命周期,挂载到slot |
addPanel(panel) / removePanel(id) / refreshPanel(id?) | 侧边栏面板 — 与 UI.extraPanels 相同的门 |
addDockTab(tab) / removeDockTab(id) | 底部底座中的一个窗格 - 内置桌子通过同一扇门 |
addRailMode(mode) / removeRailMode(id) | 左栏上的模式,位于选择/创建/查看/物理旁边 |
addTopBarAction(action) / removeTopBarAction(id) | 顶部栏中的一颗药丸 - 与 UI.topBar.actions 同一扇门 |
addPivot(definition) | 可运行的丰富 - 请参阅拓展和丰富 |
onPhase(phase, cb) | 钩afterMount / graphReady / destroy;返回取消订阅 |
addKeybinding(binding) | 当 UI 被拆除时会被删除的快捷方式 |
keyManager | 键绑定注册表,用于更多涉及的内容 |
绑定的 key 是修饰符,然后是键,由 + 连接:'Shift+K'、'Ctrl+F'、'ArrowUp'。按顺序将修饰符编写为 Ctrl、Meta(在 macOS 上执行命令)、Shift、Alt,并使用 Mod 来匹配 Ctrl 或 Meta — 这就是快捷方式的作用两个平台都想要。键本身逐字为KeyboardEvent.key,因此它区分大小写,并且通过移位更改它:撤消对的两半是'Mod+z'和'Mod+Shift+Z'。当文本字段具有焦点时,绑定永远不会触发。
存在哪些 layout 插槽取决于模式:canvas 和 notification 始终,graphnavigation 除 static、mainheader / modal / slidePanel / moderail / toolpanel / flyout / 外仅full和light中的legend,以及full中的sidebar。在需要时读取槽而不是缓存它。
贡献 UI 元素
扩展 UIComponent 并为您驱动生命周期 — 包括拆除通过 track / listen / trackInteraction 注册的任何内容:
import { UIComponent } from 'pivotick'
class Watermark extends UIComponent {
onMount(slot) {
this.element = document.createElement('div')
this.element.textContent = 'draft'
slot?.appendChild(this.element)
}
onGraphReady() {
// `listen` and `trackInteraction` unsubscribe themselves on destroy
this.trackInteraction('canvasZoom', () => this.reposition())
}
onDestroy() {
this.element?.remove()
}
}
const watermark: PivotickPlugin = {
name: 'watermark',
install: (ctx) => ctx.addElement(new Watermark(ctx.ui), ctx.layout?.canvas),
}四个阶段是mount(slot)→afterMount()→graphReady()→destroy()。一旦模拟完成,graphReady就会触发,因此在安装到大图上之后,它可能需要秒——任何应该立即出现在屏幕上的东西都属于onMount/onAfterMount(如果需要graph.renderer,则延迟一帧,这是在UI之后构建的)。
请参阅使用插件Extend 图库卡以获取完整的实时示例。
贡献一个停靠面板
ctx.addDockTab 在底部停靠点中放置一个窗格,位于数据表旁边。码头拥有该区域 - 它的高度,它的分隔线,它的折叠和命名窗格的条带 - 并且您的窗格拥有其中的内容:
const auditLog: PivotickPlugin = {
name: 'auditLog',
install: (ctx) => ctx.addDockTab({
label: 'Audit',
render: () => buildPane(ctx.graph), // once, on first activation
toolbar: () => [clearButton], // on every activation
onActivate: () => resumePainting(),
onDeactivate: () => stopPainting(),
}),
}有四件事值得了解:
- 选项卡是一个窗格,而不是一个视图。如果您的窗格有自己的多个视图,它会保留单个停靠选项卡并在
toolbar中绘制自己的开关,调用handle.refresh()来更改正文 - 这会重新调用render。这正是数据表对Nodes/Edges所做的事情,也是为什么扩展坞的条带永远不会将一个窗格的视图展平到另一个窗格旁边。在 Dock 后面切换你自己的 DOM 是行不通的:它会保留元素render给它的元素,并且会在下次激活时重新附加一个过时的节点。将内部开关绘制为分段控件,而不是选项卡 - 外部级别已经看起来像选项卡。条上的pvt-dock-views和每个按钮上的pvt-dock-view(加上active)都是公共的,所以它看起来就像桌子的开关,并且遵循主题,而无需您重新声明。 - 第一个窗格构建区域。插件安装在构建UI之后,因此选项卡总是来得太晚,以至于坞站自己的模式门无法代表它说“是”。注册一个会导致扩展坞诞生,这意味着您的插件可以与
UI.table: false配合使用,除了full模式外不需要打开任何东西。 - 第一次打开窗格时,
render被懒惰地调用一次;;该元素被保留并随后重新附加,因此它保留自己的滚动位置。toolbar在每次激活时都会重建,因此它的控件可以读取窗格的当前状态。 onActivate/onDeactivate是您离开屏幕 的唯一信号,如何处理它们取决于您的窗格。作为图表当前状态函数的内容可以在隐藏时停止工作并在返回时重新派生 - 这就是表的作用。那些会错过某些东西的内容(观看实时巴士的窗格——事件一旦触发就会消失)必须继续工作,而只是停止绘画,在它回来时刷新其积压的内容。钩子也没有什么比这更好的了。
选项卡不是 UIComponent,因此没有任何东西可以驱动生命周期阶段进入其中。当您的窗格需要 graphReady 时,按住 UIComponent、addElement,然后从其 onMount 调用 addDockTab — 请参阅上面的贡献 UI 元素。
请参阅 添加停靠窗格图库卡以获取完整的实时示例 - 数据表旁边的窗格具有自己的两个视图。
贡献铁路模式
左栏的四种模式 - 选择、创建、查看、物理 - 都是内置的。其他一切都是你的:addRailMode将你自己的模式置于分隔线下方,在它们之后,按order在其同行中排序。
指针模式拥有上下文工具面板。声明它的 tools ,面板以选择和创建的方式绘制它们,并为您处理武装、启用/禁用状态和折叠:
const explore = {
name: 'explore-mode',
install(ctx) {
const selected = () => ctx.graph.renderer.getGraphInteraction().getSelectedNode()?.node
ctx.addRailMode({
id: 'explore',
label: 'Explore',
icon: compassSvg,
shortcut: 'E',
tools: [
{ id: 'expand', label: 'Expand neighbours', icon: plusSvg, kind: 'action',
enabled: () => !!selected(),
run: () => expandFrom(ctx.graph, selected()) },
{ id: 'walk', label: 'Path walk', icon: pathSvg, kind: 'toggle',
run: (armed) => armPathWalk(ctx.graph, armed) },
],
onExit: () => armPathWalk(ctx.graph, false),
})
},
}
new Pivotick(el, data, { plugins: [explore] })工具的 kind 决定它的行为方式。 'action' 运行一次并保持原来的模式。 'toggle' 配备一个模态工具:面板折叠,导轨按钮变形为该工具自己的图标和标签,就像 Select 的插槽变成Lasso一样。 'default' 是模式所依赖的工具;将其命名为 defaultTool ,这就是切换所恢复的内容。
tools也可能是一个功能,每次绘制面板时重新读取 - 当行取决于所选内容时使用它,而不仅仅是变灰。对于任何一行无法表达的事物,render()返回附加的元素以下行:
ctx.addRailMode({
id: 'explore', label: 'Explore', icon: compassSvg,
tools: () => toolsFor(ctx.graph.renderer.getGraphInteraction().getSelectedNode()),
render: () => depthSlider(),
})弹出模式会打开设置叠加层,例如“视图”和“物理”。子类 Flyout 和手 addRailMode 工厂 — 核心安装面板,在模式处于活动状态时打开它,然后将其与模式一起带走:
import { Pivotick, Flyout } from 'pivotick'
class EnrichFlyout extends Flyout {
mode = 'enrich' // must match the definition's id
template() { return this.headerRow(sparklesSvg, 'Enrich') + this.toggleRow('auto', gearSvg, 'Auto-enrich', 'Enrich on load') }
wire() { this.wireToggle('auto', () => toggleAuto(), () => isAuto()) }
}
ctx.addRailMode({
id: 'enrich', label: 'Enrich', icon: sparklesSvg,
kind: 'flyout',
flyout: (ui) => new EnrichFlyout(ui),
})模式是相互排斥的,因此您的模式免费排除“视图”和“物理”——一个商店,一种活动模式。
值得了解的三件事:
icon是一个原始的 SVG 字符串,作为 HTML 注入且从未经过清理。 它必须来自您信任的来源。 CSS 将其尺寸设置为在导轨上为 20px,在面板上为 18px。shortcut遵循正常的键绑定规则:声明其他已拥有的键会隐藏该键,并发出警告,直到您的模式被删除。onExit是您撤防的地方。 当离开该模式以及在活动状态下将其移除时,它会运行 - 在这种情况下,导轨会回落到“选择”状态。它在 UI 拆卸时运行而不是;您的插件已经跟踪的处理程序涵盖了这一点。
addRailMode 返回一个处理程序,并在每种模式下工作 - 按钮仅在有导轨的地方绘制,这意味着 full 和 light。
驱动视口
导航图形的任何内容(小地图、概述、“跳转到”控件)都使用两种渲染器方法:
// The extent of everything drawn, in graph coordinates (null when there's nothing).
const bounds = graph.renderer.getContentBounds()
// Put a graph-space point in the middle of the canvas.
graph.renderer.setViewport({ x: 120, y: -40 })
graph.renderer.setViewport({ x: 0, y: 0, scale: 1.5, animate: true })setViewport 除非您通过一个比例,否则将单独保留比例,因此它是平移的基元。要读取当前视图是的位置,请反转画布角而不是进行缩放变换 - 这是与渲染器无关的:
const rect = graph.UIManager.layout.canvas.getBoundingClientRect()
const topLeft = graph.renderer.screenToGraphCoordinates(rect.left, rect.top)
const bottomRight = graph.renderer.screenToGraphCoordinates(rect.right, rect.bottom)fitAndCenter()(适合所有情况)、zoomIn() / zoomOut() 和 focusElement(nodeOrEdge) 仍然是常见情况的快捷方式。
小地图
第一方插件:整个图表的缓存概述停靠在画布角上,并用一个矩形显示屏幕上的内容。 点击可重新显示视图; 拖动矩形进行平移。它所面对的角落里有一个非常小的开关,可以将其折叠到那个按钮上,然后再将其拉回来。
full 模式为您安装了一个 — 它是该模式镶边的一部分,就像标题、侧边栏和模式栏一样。所有其他模式都留给您:
// full mode: already there, nothing to install
new Pivotick(container, data, { UI: { mode: 'full' } })
// any other mode: ask for it, with `UI.minimap` …
new Pivotick(container, data, { UI: { mode: 'light', minimap: true } })
// … or as the plugin it is
import { Pivotick, minimap } from 'pivotick'
new Pivotick(container, data, { UI: { mode: 'light' }, plugins: [minimap()] })UI.minimap 采用与插件相同的 {@link MinimapOptions},因此 { minimap: { position: 'top-left' } } 配置了一种完整模式。 UI.minimap: false 抑制它。在 plugins 中传递您自己的 minimap() 也会获胜 - 完整模式会被搁置,而不是安装第二个 - 因此现有的 plugins: [minimap({ … })] 可以继续正常工作。
<!-- browser build: it hangs off the global, like Node and Edge -->
<script>new Pivotick(el, data, { plugins: [Pivotick.minimap()] })</script>选项
| 选项 | 类型 | 默认值 | 它的作用 |
|---|---|---|---|
position | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'bottom-right' | 它停靠在哪个角落。'bottom-right'是内置镀铬在full模式下唯一空闲的角落。 |
width | number | 200 | 宽度以 CSS 像素为单位,包括边框。 |
height | number | 导出的 | 高度(以 CSS 像素为单位)。省略后,它遵循画布的纵横比(限制为 70-400 像素),因此矩形保持真实视口的形状。 |
collapsed | boolean | 'auto' | false,或 'auto' 用于 full 模式安装 | 它在哪个状态下打开。切换开关始终在那里;这只是一切的开始。请参阅避开了解'auto'。 |
没有其他东西是可配置的,因为没有其他东西需要:细节级别和重绘节奏适应图形。
它画什么
节点是渲染器赋予它们的颜色的点,边缘是它们下面的细线,注释(唯一具有自己大小的内容)按比例绘制为以其自己的颜色的半透明块,在节点上就像它们在画布上一样。隐藏和过滤掉的内容将被排除。经过几千个节点后,图片变成密度图(每个节点一个印记,没有边缘),这比数万个重叠点的可读性更好;无论哪种方式都可以绘制注释。它全部被缓存,并且只有在图片真正发生变化时才重新栅格化 - 节点被删除,注释被移动,布局稳定,应用了过滤器 - 因此无论图形有多大,平移和缩放都保持自由。
HTML 卡(shape: 'none' 上的html)没有小地图可以读取的颜色,因此它的点采用作为形状的最小绘图的颜色:基本样式,或者tiers 中的第一个此类层。如果没有,或者使用 transparent 颜色,则用小地图自己的墨水 (--pvt-minimap-ink) 绘制点。点的大小取决于节点覆盖的大小,因此卡片或renderNode节点将获得卡片大小的点,而不是size大小的点。
闪开
您请求的小地图会保留在您放置的位置。没有要求代表您安装的一个full模式,因此它会打开collapsed: 'auto'并考虑画布:当画布至少为四个小地图宽和高时,它保持打开状态,并折叠到其下方的切换开关处。从那时起,它就一直跟随画布——当侧边栏在其上方打开或窗口变窄时折叠起来,当房间变窄时它又会回来。
一旦有人将其折叠起来或将其带回来(通过切换按钮或通过setCollapsed()),就会停止:明确的选择会被保留,并且以后的调整大小不会覆盖它。传递显式的 collapsed: true / false 以从一开始就选择退出 'auto'。
切换按钮的箭头指向小地图停靠的角落(它折叠起来的方向),并在折叠后翻转。折叠起来后,它什么也画不出来,甚至连矩形也画不出来,所以当它不碍事的时候,它不需要任何成本;打开它会重新栅格化,无论画布当时是什么样子。 Minimap实例上的setCollapsed(boolean)和isCollapsed()从代码中驱动相同的东西。
它吸引什么,花费什么
小地图将 ** 内容边界与当前视口 ** 映射在一起,因此矩形始终可见且尺寸真实 - 缩小得很远,图形会收缩到框架内,而不是矩形滑出框架。
它将图形保留在屏幕外位图中,并且仅在图片实际更改时重新光栅化:数据更改时、过滤器隐藏或恢复节点时、拖动后删除节点时、布局稳定时每 10 个模拟刻度时以及调整大小时。 平移和缩放会重绘一张图像和一个矩形,因此无论图形有 20 个节点还是 50,000 个节点,导航成本都是相同的。
故意随着尺寸的变化而降低细节:
| 图 | 绘制为 |
|---|---|
| ≤ 1500 个节点 | 渲染器解析的颜色中每个节点的一个点,加上细线边缘(超过 4000 个边缘) |
| > 1500 个节点 | 单个墨水中每个节点一个印记,alpha 累积 - 密集区域读取为密度图,并且根本没有解析每个节点的样式 |
它可以在 full、light 和 viewer 模式下工作,full 是无需询问即可安装的模式。在 static 中——它承诺没有交互——它没有安装,安装它会发出警告。
请参阅 小地图图库卡以获取实时地图。