Skip to content
Pivotick
Main Navigation HomeGetting StartedConfigurationGalleryTry it out!Generated API docs

Appearance

Sidebar Navigation

Getting Started

Configuration

Callbacks

Layout

Render

Simulation

Customizing UI

Sidebar

Tooltip

Context Menu

Styling UI

Pivotick API

Security

API docs

Gallery

Basics

Node styling

Edge styling

Layouts

Events & callbacks

UI customization

Editing & authoring

Notes & annotations

Filtering, search & hierarchy

Programmatic control

Theming & performance

Showpieces

Extend with a plugin ​

A plugin is a self-contained bundle of UI elements, keybindings and lifecycle hooks that installs itself into a graph — the core never needs to know it exists. Register plugins declaratively through the plugins option, or imperatively at any time with graph.use(plugin).

On install, a plugin is handed a PluginContext with everything it needs: addElement() to drop a UI element into a layout slot (its lifecycle is then managed for you), onPhase() to hook afterMount / graphReady / destroy, addKeybinding() for shortcuts that are cleaned up automatically, plus direct access to graph, ui and layout.

The example below installs a one-element plugin: a stats overlay pinned to the canvas that reads live node/edge counts once the graph is ready. It extends UIComponent, so its mount → afterMount → graphReady → destroy lifecycle (and any listeners it tracks) is driven by the same machinery as the built-in sidebar, toolbar and tooltip — adding a new interface is just one addElement.

Click the graph, then press B to toggle the overlay.

js
// 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 = {
    UI: { mode: 'full', sidebar: { collapsed: true } },
    plugins: [statsPlugin]
}
js
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' } }
    ]
}

Last updated:

Pager
Next pageGetting Started