تمديد مع البرنامج المساعد
plugin عبارة عن حزمة قائمة بذاتها من عناصر واجهة المستخدم وارتباطات المفاتيح و خطافات دورة الحياة التي تثبت نفسها في رسم بياني - لا تحتاج النواة إلى معرفتها أبدًا إنه موجود. قم بتسجيل المكونات الإضافية بشكل تصريحي من خلال الخيار plugins، أو حتمًا في أي وقت مع graph.use(plugin).
عند التثبيت، يتم منح المكون الإضافي PluginContext بكل ما يحتاج إليه: addElement() لإسقاط عنصر واجهة المستخدم في فتحة التخطيط (تكون دورة حياته بعد ذلك تمكنت لك)، onPhase() لربط afterMount / graphReady / destroy، addKeybinding() للاختصارات التي يتم تنظيفها تلقائيًا بالإضافة إلى المباشرة الوصول إلى graph و ui و layout.
يقوم المثال أدناه بتثبيت مكون إضافي مكون من عنصر واحد: stats overlay مثبت على يتم احتساب اللوحة القماشية التي تقرأ العقدة/الحافة المباشرة بمجرد أن يصبح الرسم البياني جاهزًا. يمتد UIComponent، لذا فإن دورة حياتها mount → afterMount → graphReady → destroy (و أي مستمع يتتبعه) يتم تشغيله بواسطة نفس الآلة المدمجة الشريط الجانبي وشريط الأدوات وتلميح الأدوات - تعد إضافة واجهة جديدة مجرد خطوة واحدة addElement.
انقر على الرسم البياني، ثم اضغط على B لتبديل التراكب.
// import { UIComponent } from 'pivotick'
// A plugin bundles UI elements, keybindings and lifecycle hooks and installs
// itself through the PluginContext it is handed — the core never needs to know
// it exists.
//
// Our plugin contributes one custom UI element: a small stats overlay pinned to
// the canvas. Because it extends UIComponent, its lifecycle (mount → afterMount
// → graphReady → destroy) and any tracked listeners are driven for free — the
// same machinery the built-in sidebar/toolbar/tooltip use.
class StatsOverlay extends UIComponent {
// Build the DOM and drop it into the slot we were mounted into.
onMount(slot) {
this.el = document.createElement('div')
// Bottom-right corner: the top-left / top / left edges are taken by the
// B3 chrome (top bar, mode rail), so pin the overlay clear of them.
this.el.style.cssText = [
'position:absolute', 'bottom:12px', 'right:12px', 'z-index:5',
'padding:8px 12px', 'border-radius:8px', 'font:12px/1.6 sans-serif',
'background:rgba(20,24,32,0.85)', 'color:#fff',
'box-shadow:0 2px 8px rgba(0,0,0,0.25)', 'pointer-events:none'
].join(';')
slot?.appendChild(this.el)
}
// Runs once the graph data is ready — read live counts off the graph.
onGraphReady() {
this.render()
const graph = this.uiManager.graph
const cb = () => this.render()
graph.on('dataBatchChanged', cb)
// graph.on lives on the Graph, which outlives the UI — so track the
// unsubscribe and destroy() tears it down with everything else.
this.track(() => graph.off('dataBatchChanged', cb))
}
render() {
const graph = this.uiManager.graph
this.el.innerHTML =
'<strong>Graph stats</strong><br>' +
`${graph.getNodes().length} nodes · ${graph.getEdges().length} edges`
}
toggle() {
this.el.style.display = this.el.style.display === 'none' ? '' : 'none'
}
// Only DOM cleanup needed here; children and everything registered via
// track() (including the dataBatchChanged unsubscribe above) are torn down
// by UIComponent.
onDestroy() {
this.el?.remove()
}
}
const statsPlugin = {
name: 'stats-overlay',
install(ctx) {
const overlay = new StatsOverlay(ctx.ui)
// Mount the element into the canvas slot; its lifecycle is then managed.
ctx.addElement(overlay, ctx.layout?.canvas)
// Bonus: click the graph, then press "B" to toggle the overlay.
ctx.addKeybinding({ key: 'b', callback: () => overlay.toggle() })
}
}
// Register plugins declaratively with `plugins`, or imperatively at any time
// with `graph.use(statsPlugin)` (late installs are caught up to the current
// lifecycle phase automatically).
const options = {
// Off: `full` mode brings a minimap and the data dock, and this card is about
// neither (see the Minimap and Data table cards for those).
UI: { mode: 'full', minimap: false, table: false, sidebar: { collapsed: true } },
plugins: [statsPlugin]
}const data = {
nodes: [
{ id: 'web', data: { label: 'Web' } },
{ id: 'api', data: { label: 'API' } },
{ id: 'db', data: { label: 'Database' } },
{ id: 'cache', data: { label: 'Cache' } },
{ id: 'worker', data: { label: 'Worker' } }
],
edges: [
{ from: 'web', to: 'api', data: { label: 'calls' } },
{ from: 'api', to: 'db', data: { label: 'reads' } },
{ from: 'api', to: 'cache', data: { label: 'reads' } },
{ from: 'worker', to: 'db', data: { label: 'writes' } }
]
}