Zum Inhalt springen

Diagrammlayout ​

Pivotick unterstützt mehrere Layoutstrategien zum Positionieren von Knoten. Sie können das Layout über die Option layout oder über simulation.layout konfigurieren; wenn beide gesetzt sind, gewinnt layout.

OptionTypStandardwertBeschreibung
type'force' | 'tree' | 'structured''force'Der zu verwendende Layout-Algorithmus. 'force' wendet ein physikbasiertes Kraftlayout an. 'tree' ordnet Knoten in einer Baumstruktur an. 'structured' zeichnet ein kleines Diagramm so, wie es eine Person tun würde, siehe Strukturiertes Layout.

Optionen für das Baumlayout ​

Wenn type: 'tree' ausgewählt ist, stehen die folgenden zusätzlichen Optionen zur Verfügung:

OptionTypStandardwertBeschreibung
rootIdstringundefinedGeben Sie die ID des Knotens an, der als Root verwendet werden soll. Wenn nicht angegeben, wählt Pivotick automatisch einen Stamm basierend auf rootIdAlgorithmFinder aus.
rootIdAlgorithmFinder-MaxReachabilityAlgorithmus zum automatischen Finden des Wurzelknotens.
parentKeystringundefinedName des node.data-Schlüssels, der die ID des übergeordneten Knotens enthält. Ungeklärt, Elternschaft kommt von den Rändern. Siehe Deklarieren der Hierarchie.
depthKeystringundefinedName der node.data-Taste, die die Zeile enthält, in der sich der Knoten befindet, gezählt von 0. Wenn der Knoten nicht gesetzt ist, liegt er eine Zeile unter seinem übergeordneten Knoten.
strengthnumber0.1Die Kraftstärke zur Aufrechterhaltung der Baumstruktur.
horizontalbooleanfalseOrdnen Sie Knoten horizontal statt vertikal an.
radialbooleanfalsePlatzieren Sie Knoten radial statt vertikal.
radialGapnumber750Lücke zwischen den Schichten im radialen Layout.
spacing'auto' | 'manual''auto'Ob sich die beiden folgenden Multiplikatoren auf die Größe der Knoten und die Form des Baums abstimmen. Das explizite Setzen eines davon impliziert 'manual'.
levelSpacingnumber1Multipliziert den Abstand zwischen aufeinanderfolgenden Ebenen (zwischen Ringen, wenn radial). 1 Passt den Baum wie zuvor an die Arbeitsfläche an.
siblingSpacingnumber1Multipliziert den Abstand zwischen Knoten innerhalb einer Ebene. Wird ignoriert, wenn radial, wobei eine Ebene immer den gesamten Kreis umfasst.

Zyklen und unzusammenhängende Diagramme ​

Die Hierarchie wird aus einem in der Breite ersten spannenden Baum mit Wurzel bei rootId aufgebaut, nicht aus den rohen Kanten, daher verhindert keines davon ein Baumlayout:

  • Zyklen. Die erste Kante, die einen Knoten erreicht, macht ihn zum übergeordneten Knoten dieses Knotens; Eine Kante, die an einem bereits platzierten Knoten ankommt, ist einfach nicht Teil der Hierarchie und wird wie jede andere Kante ebenenübergreifend gezeichnet. Ein Knoten mit zwei Eltern wird ebenfalls von einem von ihnen beansprucht.
  • Mehrere Komponenten. Jede Komponente, die die Wurzel nicht erreichen kann, erhält ihre eigene Wurzel – bevorzugt wird ein Knoten ohne eingehende Kanten, sodass eine Komponente, die ist eine Hierarchie ist, als eine gezeichnet wird – und die Komponenten werden nebeneinander angeordnet.
  • Knoten ohne Kanten überhaupt. Diese haben keinen Platz in einer Hierarchie, daher werden sie geparkt und nicht mit einem versehen: Sie füllen den toten Raum neben den flachen Ebenen an der Hinterkante des Layouts, den ein Baum leer lässt, weil er beim Abstieg breiter wird. Bei der radialen Anordnung bilden sie außerhalb des Leisten einen eigenen Ring. Nur Knoten, die nichts auf * und * zeigen und auf nichts zeigen, werden geparkt, sodass keine Kante übrigbleibt, die sich vom Baum bis zum Parkbereich erstreckt.

Beachten Sie, dass rootIdAlgorithmFinder: 'MinMaxDistance' und 'MinHeight' dieselbe Suche sind – der Knoten, dessen längster Pfad nach unten der kürzeste ist – und beide auf topologischer Sortierung basieren, sodass sie bei einem Diagramm mit einem Zyklus eine Warnung ausgeben und auf den ersten Knoten zurückgreifen. Die Standardeinstellung 'MaxReachability' weist keine solche Einschränkung auf.

Baumwurzelfinder ​

In einem baumbasierten Layout muss ein Knoten als Wurzel festgelegt werden. Sie können den Wurzelknoten mit der Option rootId angeben.

Wenn Sie nicht sicher sind, welcher Knoten der Root sein soll, machen Sie sich keine Sorgen! Pivotick wählt automatisch einen aus, indem er den in rootIdAlgorithmFinder definierten Algorithmus verwendet.

Ein benannter rootId wird durchlaufen ohne Berücksichtigung der Kantenrichtung, sodass jeder Knoten einen ganzen Baum verwurzeln kann – wählen Sie ein Blatt aus und der Graph hängt wieder darunter, wobei sein früherer übergeordneter Knoten eine Ebene tiefer liegt. (Wenn man den Pfeilen von einem Blatt aus folgen würde, würde man nichts erreichen und der Rest des Diagramms würde als zweite Komponente daneben angeordnet werden.) Die Kanten selbst werden immer noch so gezeichnet, wie sie sind, sodass ein andersherum verwendeter Link als Pfeil dargestellt wird, der eine Ebene nach oben zeigt. Eine Wurzel, die rootIdAlgorithmFinder gewählt hat, läuft weiter entlang der Pfeile: Sie wurde von ihnen zuerst abgelesen.

Es sei denn, die Pfeile stellen keine Hierarchie dar. Bei Daten, die konvergieren – jedes Blatt eine Quelle, die alle auf einige Knotenpunkte zeigen, was die Form der meisten Herkunfts- und „gesehen mit“-Daten ist – erreicht kein Knoten den Graphen entlang der Pfeile, unabhängig davon, welcher Finder gefragt wird. Bei einem gezielten Spaziergang bleibt dann fast jeder Knoten eine eigene Wurzel, und der Baum entsteht als Kamm aus Stummeln, wobei die meisten Kanten über das Layout fliegen. Wenn also überhaupt kein Knoten die Hälfte seiner eigenen Komponente abdecken kann, indem er den Pfeilen folgt, hört das Layout auf, die Richtung zu lesen: Es geht jede Kante in beide Richtungen ab und verwurzelt sich erneut in der Mitte des Diagramms. Die Kanten werden immer noch genau so gezeichnet, wie sie sind, was bedeutet, dass ihre Pfeile den Baum hinauf zur Wurzel zeigen – die ehrliche Darstellung von Daten, die in diese Richtung weisen.

Dies ist ein Fallback, nicht die Regel, und beim Test geht es um den Graphen und nicht um die ausgewählte Wurzel:

  • Wo die Pfeile eine Hierarchie bilden, ist es am besten, sie anzulegen, und jeder Finder behält seine eigene Antwort – einschließlich 'MinHeight', dessen Antwort auf einem Baum immer ein Blatt ist und absichtlich nichts erreicht. Eine Wurzel, die nur einen kleinen Teil des Diagramms abdeckt, wird nur überstimmt, wenn nichts anderes mehr hätte abdecken können.
  • Ein Diagramm mit mehreren separaten Hierarchien ergibt immer noch eine Gesamtstruktur: Die Abdeckung wird anhand der eigenen Komponente der Wurzel gemessen, sodass eine vollkommen gute Mehrkomponentenhierarchie nicht mit einem Fehler verwechselt werden kann.
  • Eine angeheftete rootId überspringt den Test vollständig – er ist bereits gegangen, ohne die Richtung zu beachten.

Eine rootId, die einen Knoten benennt, der sich nicht im Diagramm befindet – herausgefiltert, gelöscht, innerhalb eines reduzierten Clusters – wird ignoriert, solange dies wahr ist, und der Finder wählt stattdessen die Wurzel aus. Die Option wird beibehalten, sodass der zurückkommende Knoten den Baum neu verwurzelt.

Der Stamm kann auch zur Laufzeit geändert werden – das Flyout „Physik“ bietet ihn als Menü in der Zeile Root an, wenn ein Baumlayout aktiv ist. Das Menü benennt jeden Finder und sagt, was er tut; Sein erster Eintrag ist „der Knoten, den ich ausgewählt habe“, der ausgegraut ist, bis genau ein Knoten ausgewählt ist. Sobald ein Knoten angeheftet ist, wird in der Zeile die Bezeichnung dieses Knotens und nicht der Name eines Finders angezeigt:

ts
graph.simulation.setTreeRoot({ rootId: 'node-42' })          // pin the tree to a node
graph.simulation.setTreeRoot({ algorithm: 'MinHeight' })     // drop the pin, let the finder choose
graph.simulation.getTreeRoot() // { rootId: undefined, algorithm: 'MinHeight' }

Deklaration der Hierarchie ​

Normalerweise berechnet Pivotick die Hierarchie für sich selbst: Elternschaft von einem Spannbaum über die Kanten und jeder Knoten eine Zeile unter seinem Elternknoten. Zwei Optionen lassen stattdessen die Daten sagen.

  • parentKey benennt den node.data-Schlüssel, der die übergeordnete ID eines Knotens enthält.
  • depthKey benennt die node.data-Taste, die die Zeile enthält, in der sie liegt, gezählt von 0 an der flachsten Wurzel.

Beide sind standardmäßig deaktiviert und es wird überhaupt nichts gelesen, bis Sie einen Schlüssel benennen. Ein Diagramm, dessen Daten zufällig ein depth- oder parent-Feld enthalten, bleibt also unberührt, bis Sie danach fragen.

Bei jedem einzelnen Knoten kann einer der beiden Schlüssel fehlen. Ein Knoten ohne deklarierten übergeordneten Knoten erhält wie üblich einen von den Kanten. Ein Knoten ohne deklarierte Zeile befindet sich einen Knoten unter seinem übergeordneten Knoten. Ein Diagramm, das keines von beiden deklariert, ist genau so aufgebaut, wie es immer war.

Einen Baum weiter unten beginnen ​

Dafür ist depthKey hauptsächlich da. Ein Diagramm aus mehreren einzelnen Bäumen ist ein Wald, und seine Wurzeln teilen sich alle die flachste Reihe – bisher gab es keine Möglichkeit zu sagen, dass einer von ihnen weiter unten gehört. Geben Sie dieser Wurzel eine Zeile:

ts
const data = {
    nodes: [
        { id: 'a' }, { id: 'a1' }, { id: 'a2' },
        { id: 'b', data: { level: 2 } }, { id: 'b1' }, { id: 'b2' },
    ],
    edges: [
        { from: 'a', to: 'a1' }, { from: 'a', to: 'a2' },
        { from: 'b', to: 'b1' }, { from: 'b', to: 'b2' },
    ],
}

const graph = new Pivotick(container, data, {
    layout: { type: 'tree', depthKey: 'level' },
})
row 0     a
row 1   a1  a2
row 2                b          <- b asked for row 2
row 3              b1  b2

Der gesamte Baum bewegt sich mit seiner Wurzel: Die Kinder von b folgen ihm nach unten. Im radial-Layout ist eine Zeile ein Ring, daher beginnt die gleiche Deklaration damit, dass Baum zwei Ringe hervorbringt.

Was die Zeilen bedeuten ​

Eine Zeile kann einen Knoten immer nur um weiter nach unten verschieben. Ein aufgeräumter Baum platziert ein untergeordnetes Element genau eine Zeile unter seinem übergeordneten Element, sodass eine Zeile, die nicht unter der des übergeordneten Elements liegt, nicht gezeichnet werden kann – es wird auf parent + 1 geklemmt und eine Warnung gibt an, wie viele es waren. Die Alternative, die darin besteht, die Zeile zu respektieren, indem der Knoten von seinem übergeordneten Knoten getrennt wird, würde dazu führen, dass eine falsche Zahl einen Baum in zusätzliche Teile zerlegt.

Zeilen, die von den Daten übersprungen werden, bleiben leer und nehmen echten Platz ein – das ist es, was einer deklarierten Zeile ihre Bedeutung verleiht. Ein Baum wird so skaliert, dass er in die Arbeitsfläche passt. Wenn Sie also Zeile 50 in einem ansonsten flachen Diagramm deklarieren, wird er 51 Zeilen hoch und jede echte Zeile wird zunichte gemacht. levelSpacing und Auto-Abstand sind der Ausweg.

Was ein erklärter Elternteil tut ​

Ein deklariertes übergeordnetes Element wird berücksichtigt, unabhängig davon, ob eine Kante dem Paar beitritt oder nicht. Dadurch ist es möglich, eine Hierarchie anzugeben, die die Kanten nicht enthalten. Es bedeutet aber auch, dass ein solcher Zweig ohne Linie gezeichnet wird, da dort keine Kante zum Zeichnen vorhanden ist. Die Deklaration ist der Ursprung der Hierarchie; Die Kanten werden immer nur so gezeichnet, wie sie sind.

Zwei nützliche Konsequenzen: Ein Knoten mit mehreren eingehenden Kanten kann sagen, welche davon sein eigentlicher übergeordneter Knoten ist (der Spanning Tree würde andernfalls denjenigen nehmen, der ihn zuerst erreicht hat), und ein Knoten ohne Kanten, der einen übergeordneten Knoten deklariert, erhält einen richtigen Platz im Baum anstelle des geparkten Bereichs.

Deklarationen, die nicht verwendet werden können, werden für diesen Knoten verworfen, der dann auf die Kanten zurückfällt, und eine Warnung fasst sie alle zusammen:

  • ein übergeordneter Knoten, der einen Knoten benennt, der nicht angeordnet ist – herausgefiltert, gelöscht oder in einem reduzierten Cluster –, greift auf die gleiche Weise zurück. rootId greift zurück, wenn der Knoten, den er benennt, fehlt;
  • ein übergeordnetes Element, das einen Zyklus schließen würde (der Link, der ihn schließt, ist derjenige, der gelöscht wird);
  • eine Zeile, die keine Zahl 0 oder größer ist.
Vorrang ​

rootId übertrifft parentKey. Das Anheften einer Wurzel – in den Optionen oder durch Auswählen einer Wurzel in der Leiste „Physik“ – ist eine Aufforderung, den Baum an diesem Knoten neu aufzuhängen, sodass die Elternschaft von den Kanten neu abgeleitet wird und deklarierte Eltern für diesen Durchgang beiseite gelegt werden. Deklarierte Zeilen sind davon nicht betroffen: Sie geben an, wie tief ein Knoten sitzt, nicht woran er hängt.

egoTree ignoriert beide Optionen; Es bildet einen Stern aus den eigenen Nachbarn der Wurzel.

Abstand ​

Ein Baum wird darauf ausgelegtfitDer natürliche Abstand richtet sich also bereits nach der Arbeitsflächegröße und nach der Tiefe und Breite des Baums.levelSpacingUndsiblingSpacingMaßstab, der zur Geometrie passte:2bedeutet „doppelt so weit auseinander wie das angepasste Layout“ auf der Tiefen- bzw. Breitenachse. Beide sind standardmäßig auf1, das ist das angepasste Layout selbst, und beide akzeptieren0.5Zu10.

Sie können auch zur Laufzeit geändert werden – das Flyout „Physik“ bietet sie als Schieberegler an, wann immer ein Baumlayout aktiv ist (anstelle der Simulationsknöpfe, die ein Baumlayout ignoriert):

ts
graph.simulation.setTreeSpacing({ levelSpacing: 2 })
graph.simulation.getTreeSpacing() // { levelSpacing: 2, siblingSpacing: 1 }

Automatischer Abstand ​

Standardmäßig berechnet der Baum seinen eigenen Abstand. Die Größe eines Baumlayouts wird anhand der Arbeitsfläche ermittelt und es wird nie darauf geachtet, wie groß die Knoten sind. Daher werden ein Baum aus kleinen Punkten und ein Baum aus großen beschrifteten Knoten identisch angeordnet – und der zweite Knoten überlappt sich. spacing: 'auto' misst das engste Nachbarpaar auf jeder Achse, berechnet, was sie für ihre Radien benötigen (plus Platz für eine Pfeilspitze zwischen den Ebenen) und skaliert die Multiplikatoren entsprechend. Es leitet sie jedes Mal neu ab, wenn sich das Diagramm ändert.

Zwei Dinge wird es bewusst nicht tun:

  • Es packt einen Baum nie enger als das angepasste Layout. „Auto“ erhöht immer nur einen Multiplikator über 1, sodass ein Diagramm, das nie überfüllt war, genau so angeordnet wird, wie es immer war.
  • Es setzt niemals eine von Ihnen getroffene Auswahl außer Kraft. Ein Baum, der levelSpacing oder siblingSpacing einstellt, wird als entschieden angesehen, und wenn Sie einen der Schieberegler von Hand bewegen, bleibt automatisch automatisch. Geben Sie die Multiplikatoren mit simulation.enableAutoTreeSpacing() zurück.

Im radial-Layout, bei dem eine Ebene immer den gesamten Kreis abdeckt, kann das Gedränge innerhalb eines Rings nur dadurch verringert werden, dass die Ringe weiter nach außen geschoben werden – beide Messungen führen also zu levelSpacing.

Strukturiertes Layout ​

type: 'structured' stellt ein kleines Diagramm so dar, wie eine Person es zeichnen würde, für einen eingebetteten Viewer, der auf einen Blick lesbar sein muss. Es wird eher berechnet als simuliert: Jeder Knoten wird platziert und fixiert, sodass derselbe Graph immer das gleiche Bild liefert und das Ziehen eines Knotens nur diesen Knoten verschiebt.

  • Komponenten nebeneinander. Jede verbundene Komponente ist einzeln angeordnet und die Komponenten sind in Reihen gepackt. Die Zeilenbreite ist diejenige, die bei größter Vergrößerung das gesamte Layout an die Arbeitsfläche anpasst.
  • Fächer reihen sich in einer Reihe auf. Drei oder mehr Blätter desselben Typs, die an denselben ein oder zwei Knoten hängen (ein Blatt hat höchstens zwei Nachbarn), stehen in einer gleichmäßig beabstandeten Reihe oder Spalte, wobei die Knoten, an denen sie hängen, auf einer Seite davon hängen. Die Gruppierung ist diejenige, die die Simplify-Regel gleiche Nachbarn verwendet, eingegeben durch UI.simplify.typeOf, andernfalls render.nodeTypeAccessor. Nichts ist weggeklappt.
  • Abstände folgen den Kanten. Alles andere wird durch Spannungsmajorisierung platziert: Knoten, die eine Kante voneinander entfernt sind, sitzen eine Kantenlänge voneinander entfernt, zwei Kanten haben den doppelten Abstand und so weiter. Die Nachbarn eines Hubs kommen um ihn herum heraus und eine Kette kommt gerade heraus, ohne dass es für einen von beiden einen Sonderfall gibt.
  • Eine Ablage für lose Knoten. Knoten ohne Kante werden gerastert unter dem Rest oder rechts davon angeordnet.
  • Beschriftungen auf der freien Seite. Eine schwebende Beschriftung (textVerticalShift oder textHorizontalShift von 1 oder mehr) wird auf der Seite ihres Knotens gezeichnet, die ihre Kanten frei lassen, so weit draußen, wie es der Stil vorgibt. Die Etiketten eines Lüfters zeigen alle von den Knoten ab, an denen er hängt.

Das Layout wird jedes Mal neu erstellt, wenn sich das sichtbare Diagramm ändert, und noch einmal nach der ersten Zeichnung, wenn die tatsächlichen Größen der Knoten bekannt sind. Knoten haben einen Abstand von getLayoutRadius(), daher wird ein Knotenstil, der layoutSize deklariert, unabhängig von der Größe in dieser Größe angeordnet. Es ist für Diagramme mit bis zu einigen hundert Knoten gedacht.

OptionTypStandardwertBeschreibung
groupMinnumber3Die wenigsten Blätter teilen sich einen Typ und ihre Nachbarn stellen sich als Fächer auf. Weniger werden einzeln platziert.
direction'auto' | 'row' | 'column''auto'Wie sich ein Fan aufstellt. 'auto' stellt es über die Linie zu den Knoten, an denen es hängt.
gapnumber30Freiraum zwischen zwei Knoten. Eine Kante wird doppelt so lang gezeichnet; Komponenten und Tablett liegen dreimal so weit auseinander.
trayPosition'bottom' | 'right''bottom'Wohin die Knoten ohne Kante gehen.
labelSidesbooleantrueBewegen Sie jedes schwebende Etikett zur Seite, sodass die Kanten frei bleiben. false hinterlässt Beschriftungen dort, wo der Stil sie platziert.
ts
new Pivotick(container, data, {
    layout: { type: 'structured', trayPosition: 'right' },
    render: {
        nodeTypeAccessor: (node) => node.getData().type,
        defaultNodeStyle: { text: (node) => node.getData().label, textVerticalShift: -1.4 },
    },
})

graph.simulation.getLabelSide(id) gibt die für einen Knoten ausgewählte Seite an: 'top', 'bottom', 'left' oder 'right' oder undefined unter jedem anderen Layout.

Die Ansicht wird an die Knoten angepasst, nicht an deren Beschriftungen, sodass eine lange Beschriftung neben einem Knoten am Rand des Layouts über die Arbeitsfläche hinausgehen kann.

Gefahr

Beispiel mit den beiden Layouts hinzufügen!

Vertikaler Baum ​

js
const options = {
    layout: {
        type: 'tree'
    },
    UI: {
        mode: 'static'
    }
}

Vertikaler Baum ​

js
const options = {
    layout: {
        type: 'tree',
        radial: true,
        radialGap: 300,
    },
    UI: {
        mode: 'static'
    }
}