Zum Inhalt springen

Plugins ​

Ein Plugin ist ein eigenständiges Bündel von UI-Elementen, Tastenkombinationen und Lebenszyklus-Hooks, das sich selbst in einem Diagramm installiert, ohne dass der Kern wissen muss, dass es existiert. Registrieren Sie einen deklarativ oder imperativ:

ts
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' }))

Ein Plugin, das nach der Live-Schaltung des Diagramms installiert wird, wird in der aktuellen Lebenszyklusphase eingeholt, sodass es nicht „zu spät“ gibt – und alles, was es registriert, wird mit der Benutzeroberfläche abgebaut.

Die Form eines Plugins ​

ts
import type { PivotickPlugin } from 'pivotick'

const myPlugin: PivotickPlugin = {
    name: 'my-plugin',          // used for de-duplication and logging
    install(ctx) { /* … */ },   // called once
}

Wenn Sie dasselbe name zweimal installieren, wird das Duplikat gewarnt und übersprungen, sodass ein Plugin in plugins aufgeführt und bis graph.use erneut angewendet werden kann, ohne es zu verdoppeln.

Was install übergeben wird ​

ctx ist ein {@link PluginContext}:

MitgliedWofür es ist
graphdas Live Graph – Knoten, Kanten, der Datenereignisbus, queryEngine
uidie UIManager – Optionen, Benachrichtigungen, der Modusspeicher
layoutdas DOM-Gerüst, lesen Sie live (niemals ein Schnappschuss). layout.canvas ist der Ort, an dem an der Arbeitsfläche befestigtes Chrom eingesetzt wird
addElement(element, slot?)Fügen Sie eine UIComponent in den Lebenszyklus ein und montieren Sie sie in slot
addPanel(panel) / removePanel(id) / refreshPanel(id?)Seitenleisten – die gleiche Tür wie UI.extraPanels
addDockTab(tab) / removeDockTab(id)eine Scheibe im unteren Dock – dieselbe Tür, durch die auch der Einbautisch kommt
addRailMode(mode) / removeRailMode(id)ein Modus auf der linken Leiste neben Auswählen / Erstellen / Anzeigen / Physik
addTopBarAction(action) / removeTopBarAction(id)eine Pille in der oberen Leiste – die gleiche Tür wie UI.topBar.actions
addPivot(definition)eine ausführbare Anreicherung – siehe Pivots & Anreicherung
onPhase(phase, cb)Haken afterMount / graphReady / destroy; gibt eine Abmeldung zurück
addKeybinding(binding)eine Verknüpfung, die entfernt wird, wenn die Benutzeroberfläche heruntergefahren wird
keyManagerdie Tastenkombinationsregistrierung für alles Weitere

Das key einer Bindung ist ein Modifikator und dann der Schlüssel, zusammen mit +: 'Shift+K', 'Ctrl+F', 'ArrowUp'. Schreiben Sie Modifikatoren als Ctrl, Meta (Cmd auf macOS), Shift, Alt in dieser Reihenfolge und verwenden Sie Mod, um entweder Ctrl oder Meta zu finden – was eine Abkürzung zum Arbeiten bedeutete beide Plattformen wollen. Der Schlüssel selbst ist wörtlich KeyboardEvent.key, daher wird die Groß-/Kleinschreibung beachtet und durch Umschalten geändert: Die beiden Hälften eines Undo-Paares sind 'Mod+z' und 'Mod+Shift+Z'. Bindungen werden nie ausgelöst, während ein Textfeld den Fokus hat.

Welche layout Slots existieren, hängt vom Modus ab: canvas und notification immer, graphnavigation in jedem Modus außer static, mainheader / modal / slidePanel / moderail / toolpanel / flyout / legend in full und light und sidebar nur in full. Lesen Sie den Slot in dem Moment, in dem Sie ihn benötigen, anstatt ihn zwischenzuspeichern.

Beitrag eines UI-Elements ​

Erweitern Sie UIComponent und der Lebenszyklus wird für Sie gesteuert – einschließlich der Entfernung aller über track / listen / trackInteraction registrierten Elemente:

ts
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),
}

Die vier Phasen sind mount(slot) → afterMount() → graphReady() → destroy(). graphReady wird ausgelöst, sobald sich die Simulation beruhigt hat, also kann es Sekunden nach der Montage in einem großen Diagramm dauern – alles, was sofort auf dem Bildschirm angezeigt werden sollte, gehört in onMount / onAfterMount (ein Frame wird verschoben, wenn graph.renderer benötigt wird, das nach der Benutzeroberfläche erstellt wird).

Ein vollständiges Live-Beispiel finden Sie in der Galeriekarte Mit einem Plugin erweitern.

Einen Dockbereich beisteuern ​

ctx.addDockTab fügt einen Bereich im unteren Dock neben der Datentabelle ein. Das Dock besitzt die Region – ihre Höhe, ihre Trennlinie, ihre Falte und den Streifen, der die Bereiche benennt – und Ihr Bereich besitzt den Inhalt:

ts
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(),
    }),
}

Vier Dinge sind wissenswert:

  • Eine Registerkarte ist ein Bereich, keine Ansicht davon. Wenn Ihr Bereich mehrere eigene Ansichten hat, bleibt er ein einzelner Dock-Tab und zeichnet seinen eigenen Schalter in toolbar, indem er handle.refresh() aufruft, um den Körper zu ändern – wodurch render erneut aufgerufen wird. Das ist genau das, was die Datentabelle für Nodes / Edges tut und warum der Streifen des Docks niemals die Ansichten eines Bereichs neben einem anderen Bereich abflacht. Das Wechseln Ihres eigenen DOM hinter der Rückseite des Docks funktioniert nicht: Es behält das Element render, das ihm gegeben wurde, und würde bei der nächsten Aktivierung einen veralteten Knoten wieder anhängen. Zeichnen Sie einen inneren Schalter als segmentiertes Steuerelement und nicht als Registerkarten – die äußere Ebene sieht bereits wie Registerkarten aus. pvt-dock-views auf dem Streifen und pvt-dock-view auf jeder Taste (plus active) sind öffentlich, sodass es wie der Schalter des Tisches aussieht und dem Thema folgt, ohne dass Sie es noch einmal sagen müssen.
  • Der erste Bereich erstellt die Region. Plugins werden nach der Erstellung der Benutzeroberfläche installiert, sodass ein Tab immer zu spät ankommt, als dass das eigene Modus-Gate des Docks in seinem Namen „Ja“ gesagt hätte. Durch die Registrierung wird das Dock ins Leben gerufen, was bedeutet, dass Ihr Plugin mit UI.table: false funktioniert und nur der full-Modus aktiviert werden muss.
  • render wird einmal aufgerufen, langsam, wenn das Fenster zum ersten Mal geöffnet wird; Das Element wird beibehalten und anschließend wieder angehängt, sodass es seine eigene Bildlaufposition beibehält. toolbar wird bei jeder Aktivierung neu erstellt, sodass seine Steuerelemente den aktuellen Status Ihres Bereichs lesen können.
  • onActivate / onDeactivate sind das einzige Signal dafür, dass Sie sich außerhalb des Bildschirms befinden, und was mit ihnen zu tun ist, hängt von Ihrem Fenster ab. Inhalte, die eine Funktion des aktuellen Zustands des Diagramms sind, können im ausgeblendeten Zustand nicht mehr funktionieren und bei der Rückkehr erneut abgeleitet werden – genau das macht die Tabelle. Inhalte, die etwas übersehen (ein Fenster, das einen Live-Bus ansieht – ein Ereignis ist weg, sobald es ausgelöst wurde), müssen weiterarbeiten und lediglich aufhören zu malen und den Rückstand löschen, wenn er zurückkommt. An den Haken gefällt mir auch nichts.

Eine Registerkarte ist keine UIComponent, daher steuert nichts Lebenszyklusphasen in sie hinein. Wenn Ihr Bereich graphReady benötigt, halten Sie UIComponent gedrückt, addElement und rufen Sie addDockTab von onMount auf – siehe Beitragen eines UI-Elements oben.

Auf der Galeriekarte Einen Dockbereich hinzufügen finden Sie ein vollständiges Live-Beispiel – einen Bereich mit zwei eigenen Ansichten neben der Datentabelle.

Beitrag zum Schienenmodus ​

Die vier Modi der linken Leiste – Auswählen, Erstellen, Anzeigen, Physik – sind integriert. Alles andere gehört Ihnen: addRailMode fügt einen eigenen Modus unter einen Teiler ein, danach, sortiert nach order unter seinen Kollegen.

Ein Zeigermodus besitzt das kontextbezogene Werkzeugfeld. Deklarieren Sie seine tools und das Panel zeichnet sie auf die Art und Weise, wie „Auswählen“ und „Erstellen“ gezeichnet werden, wobei die Scharfschaltung, die aktivierten/deaktivierten Zustände und das Reduzieren für Sie erledigt werden:

js
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] })

Die kind eines Werkzeugs entscheidet darüber, wie es sich verhält. Ein 'action' läuft einmal und verlässt den Modus wie er war. Ein 'toggle' aktiviert ein modales Werkzeug: Das Bedienfeld wird ausgeblendet und die Schienenschaltfläche verwandelt sich in das eigene Symbol und die Beschriftung dieses Werkzeugs, genau wie der Slot von Select zu Lasso wird. Ein 'default' ist das Werkzeug, auf dem der Modus basiert; Benennen Sie es in defaultTool und es ist das, worauf ein Umschalter zurückgreift.

tools kann auch eine Funktion sein, jedes Mal neu lesen, wenn das Panel gezeichnet wird – verwenden Sie das, wenn die Zeilen von der Auswahl abhängen, anstatt nur ausgegraut zu werden. Für alles, was eine Zeile nicht ausdrücken kann, gibt render() ein angehängtes Element unterhalb der Zeilen zurück:

js
ctx.addRailMode({
    id: 'explore', label: 'Explore', icon: compassSvg,
    tools: () => toolsFor(ctx.graph.renderer.getGraphInteraction().getSelectedNode()),
    render: () => depthSlider(),
})

Ein Flyout-Modus öffnet stattdessen ein Einstellungs-Overlay, wie „Ansicht“ und „Physik“. Unterklasse Flyout und Hand addRailMode eine Fabrik – der Kern montiert das Panel, öffnet es genau dann, wenn Ihr Modus aktiv ist, und nimmt es mit dem Modus weg:

js
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),
})

Die Modi schließen sich gegenseitig aus, sodass Ihr Modus „Ansicht“ und „Physik kostenlos“ ausschließt – ein Store, ein aktiver Modus.

Drei Dinge, die es zu wissen gilt:

  • icon ist eine rohe SVG-Zeichenfolge, die als HTML injiziert und niemals bereinigt wurde. Sie muss aus einer Quelle stammen, der Sie vertrauen. CSS passt die Größe auf 20 Pixel auf der Schiene und 18 Pixel im Panel an.
  • shortcut folgt den normalen Tastenkombinationsregeln: Wenn Sie einen Schlüssel beanspruchen, den jemand anderes bereits besitzt, wird dieser mit einer Warnung verdeckt, bis Ihr Modus entfernt wird.
  • onExit ist die Stelle, an der Sie die Funktion deaktivieren. Sie wird ausgeführt, wenn der Modus verlassen wird, und wenn sie entfernt wird, während sie aktiv ist – in diesem Fall fällt die Schiene auf „Auswählen“ zurück. Es wird nicht beim UI-Teardown ausgeführt; Die Entsorger, die Ihr Plugin bereits verfolgt, decken dies ab.

addRailMode gibt einen Entsorger zurück und funktioniert in jedem Modus – die Schaltfläche wird nur dort gezeichnet, wo eine Schiene vorhanden ist, was full und light bedeutet.

Fahren des Ansichtsfensters ​

Alles, was im Diagramm navigiert – eine Minikarte, eine Übersicht, ein „Springen zu“-Steuerelement – verwendet zwei Renderer-Methoden:

ts
// 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 lässt die Skala in Ruhe, es sei denn, Sie passieren eine, daher ist sie das Grundelement für das Schwenken. Um zu lesen, wo sich die Ansicht derzeit befindet, kehren Sie die Arbeitsflächeecken um, anstatt nach einer Zoomtransformation zu greifen – dies ist rendererunabhängig:

ts
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() (passt auf alles), zoomIn() / zoomOut() und focusElement(nodeOrEdge) bleiben die Abkürzungen für die häufigsten Fälle.

Die Minikarte ​

Ein Plugin eines Erstanbieters: eine zwischengespeicherte Übersicht über das gesamte Diagramm, angedockt in einer Canvas-Ecke, mit einem Rechteck, das anzeigt, was auf dem Bildschirm angezeigt wird. Klicken Sie darauf, um die Ansicht neu zu zentrieren; ziehen das Rechteck zum Schwenken. Ein sehr kleiner Schalter in der Ecke, zu der es zeigt, klappt es auf genau diesen Knopf und bringt es zurück.

full Modus stellt eines für Sie bereit – es ist Teil des Chroms dieses Modus, wie die Kopfzeile, die Seitenleiste und die Modusschiene. Jeder andere Modus überlässt es Ihnen:

ts
// 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 nimmt das gleiche {@link MinimapOptions} wie das Plugin, also konfiguriert { minimap: { position: 'top-left' } } den einen Vollmodus, den es mit sich bringt. UI.minimap: false unterdrückt es. Das Übergeben Ihrer eigenen minimap() in plugins gewinnt ebenfalls – der Vollmodus bleibt beiseite, anstatt einen zweiten zu installieren –, sodass eine vorhandene plugins: [minimap({ … })] weiterhin genau so funktioniert, wie sie es getan hat.

html
<!-- browser build: it hangs off the global, like Node and Edge -->
<script>new Pivotick(el, data, { plugins: [Pivotick.minimap()] })</script>

Optionen ​

OptionTypStandardwertWas es bewirkt
position'bottom-right' | 'bottom-left' | 'top-right' | 'top-left''bottom-right'In welcher Ecke es andockt. 'bottom-right' ist die einzige Ecke, die das eingebaute Chrom im full-Modus frei lässt.
widthnumber200Breite in CSS Pixel, inklusive Rand.
heightnumberabgeleitetHöhe in CSS Pixel. Wenn es weggelassen wird, folgt es dem Seitenverhältnis der Arbeitsfläche (begrenzt auf 70–400 Pixel), sodass das Rechteck die Form des echten Ansichtsfensters beibehält.
collapsedboolean | 'auto'false oder 'auto' für den einen full-ModusIn welchem Zustand es geöffnet wird. Der Schalter ist immer da; Das ist erst der Anfang. Siehe Aus dem Weg gehen für 'auto'.

Nichts anderes ist konfigurierbar, weil nichts anderes sein muss: Der Detaillierungsgrad und die Neuzeichnungsfrequenz passen sich dem Diagramm an.

Was es zeichnet ​

Knoten sind Punkte in der Farbe, die ihnen der Renderer gegeben hat, Kanten sind Haarlinien darunter und Notizen – der einzige Inhalt mit einer eigenen Größe – werden maßstabsgetreu als durchscheinende Blöcke in ihrer eigenen Farbe über den Knoten gezeichnet, so wie sie sich auf der Arbeitsfläche befinden. Versteckte und herausgefilterte Inhalte bleiben außen vor. Nach ein paar tausend Knoten wird das Bild stattdessen zu einer Dichtekarte (ein Stempel pro Knoten, keine Kanten), die sich besser lesen lässt als Zehntausende überlappender Punkte; Notizen werden in beide Richtungen gezeichnet. Es wird alles zwischengespeichert und nur dann neu gerastert, wenn sich das Bild wirklich geändert hat – ein Knoten wurde gelöscht, eine Notiz verschoben, das Layout stabilisiert, ein Filter angewendet – sodass das Schwenken und Zoomen unabhängig von der Größe des Diagramms frei bleibt.

Eine HTML-Karte (html auf shape: 'none') hat keine Farbe, die die Minikarte lesen kann, daher nimmt ihr Punkt die Farbe der kleinsten Zeichnung an, die eine Form ist: den Basisstil, oder andernfalls die erste solche Stufe in tiers. Wenn keine oder eine transparent-Farbe vorhanden ist, wird der Punkt mit der eigenen Tinte der Minikarte gezeichnet (--pvt-minimap-ink). Die Größe der Punkte entspricht der Größe, die der Knoten abdeckt, sodass eine Karte oder ein renderNode-Knoten einen kartengroßen Punkt erhält und nicht einen mit der Größe size.

Aus dem Weg gehen ​

Eine von Ihnen angeforderte Minikarte bleibt dort, wo Sie sie abgelegt haben. Der eine full-Modus, der in Ihrem Namen aktiviert wird, wurde nicht angefordert, daher öffnet er collapsed: 'auto' und berücksichtigt die Arbeitsfläche: Er bleibt geöffnet, solange die Arbeitsfläche mindestens vier Minikarten breit und hoch ist, und klappt sich zum darunter liegenden Schalter zusammen. Von da an folgt es der Arbeitsfläche – es klappt weg, wenn sich die Seitenleiste darüber öffnet oder das Fenster schmaler wird, und kehrt zurück, wenn der Raum dies tut.

In dem Moment, in dem jemand es wegklappt oder zurückbringt – durch den Schalter oder durch setCollapsed() – stoppt das: Eine explizite Auswahl bleibt bestehen und wird von keiner späteren Größenänderung überschrieben. Übergeben Sie eine explizite collapsed: true / false, um 'auto' von Anfang an abzulehnen.

Der Pfeil des Schalters zeigt auf die Ecke, in der die Minikarte andockt – in die Richtung, in der sie weggeklappt wird – und dreht sich um, sobald sie zusammengeklappt ist. Zusammengeklappt zeichnet es überhaupt nichts, nicht einmal das Rechteck, also kostet es nichts, solange es nicht im Weg ist; Beim Öffnen wird die Arbeitsfläche neu gerastert, so wie sie bis dahin aussieht. setCollapsed(boolean) und isCollapsed() auf der Minimap-Instanz steuern dasselbe aus dem Code.

Was es kostet und was es kostet ​

Die Minikarte bildet die Inhaltsgrenzen zusammen mit dem aktuellen Ansichtsfenster ab, sodass das Rechteck immer sichtbar ist und eine korrekte Größe hat – wenn Sie weit herauszoomen, wird das Diagramm innerhalb des Rahmens verkleinert, anstatt dass das Rechteck davon abrutscht.

Es behält das Diagramm in einer Bitmap außerhalb des Bildschirms bei und rastern es nur dann neu, wenn sich das Bild tatsächlich geändert hat: bei einer Datenänderung, wenn ein Filter Knoten verbirgt oder wiederherstellt, wenn ein Knoten nach dem Ziehen abgelegt wird, bei jedem 10. Simulationstick, während sich ein Layout beruhigt, und bei einer Größenänderung. Durch Schwenken und Zoomen werden ein Bild und ein Rechteck neu gezeichnet, sodass die Navigation gleich viel kostet, unabhängig davon, ob das Diagramm 20 oder 50.000 Knoten hat.

Die Details nehmen mit der Größe ab, absichtlich:

GrafikGezeichnet als
≤ 1500 Knotenein Punkt pro Knoten in der Farbe, die der Renderer aufgelöst hat, plus Haarlinienkanten (ab 4000 Kanten entfernt)
> 1500 Knotenein Stempel pro Knoten in einer einzigen Tinte, Alpha-Akkumulation – dichte Regionen werden als Dichtekarte gelesen, und es wird überhaupt kein Stil pro Knoten aufgelöst

Es funktioniert in den Modi full, light und viewer, und full ist derjenige, der es ohne Aufforderung bereitstellt. In static – was keine Interaktionen verspricht – ist es nicht gemountet und die Installation dort gibt eine Warnung aus.

Eine Liveansicht finden Sie auf der Galeriekarte Minimap.