Zum Inhalt springen

Legende ​

UI.legend dockt eine Legende in einer Canvas-Ecke an: eine Zeile pro Kategorie, jeweils mit einem Farbfeld, einer Beschriftung und der Anzahl der Knoten dahinter. Wenn Sie auf eine Zeile zeigen, wird diese Kategorie auf der Arbeitsfläche beleuchtet. Wenn Sie auf einen klicken, wird er ausgeblendet, sodass die Legende gleichzeitig als schnellster Filter in der Benutzeroberfläche fungiert.

ts
const options = {
    UI: {
        legend: { key: 'type', title: 'Node type' },
    },
}

Möglicherweise benötigen Sie diesen Block überhaupt nicht: Ohne UI.legend erscheint eine Legende allein, wenn die Farben Ihres Diagramms durch ein deklariertes render.nodeTypeAccessor erklärt werden – siehe Standardmäßig aktiviert. UI.legend: false schaltet es aus.

Ein Graph, der mehrere Dinge gleichzeitig kodiert, benötigt mehrere Schlüssel: Übergeben Sie sections anstelle eines einzelnen Schlüssels – siehe Mehrere Schlüssel gleichzeitig.

Die Legende ist Teil des Chromes und erscheint daher nur in den Modi full und light – viewer und static haben keine.

Die Legende färbt nie etwas

Die Legende ist beschreibend. Es liest die Farbe, die der Renderer bereits für einen Knoten aufgelöst hat (renderer.getNodeStyle(node).color) und meldet sie. Die Farbgebung bleibt dort, wo sie war: render.defaultNodeStyle.color, render.nodeStyleMap oder ein knotenspezifischer Stil – normalerweise über ColorPaletteMapper. Ändern Sie die Palette und die Legende folgt von selbst.

Standardmäßig aktiviert ​

Eine Legende ist es nur wert, ungefragt angezeigt zu werden, wenn sie garantiert die Wahrheit sagt. Die automatische Legende beginnt also mit etwas, das Sie bereits angegeben haben, und nicht mit einer Vermutung über Ihre Daten: render.nodeTypeAccessor, die Dimension, auf die Sie nodeStyleMap zeigen.

Allerdings reicht es nicht aus, einen Kandidaten zu haben – eine Dimension kann Ihre Daten unterteilen, ohne etwas mit ihren Farben zu tun zu haben. Bevor also etwas gerendert wird, prüft die Legende, ob diese Dimension die Farben erklärt:

  • jede Kategorie wird in genau eine Farbe aufgelöst;
  • es gibt mindestens zwei unterschiedliche Farben (ansonsten unterscheiden die Farben die Kategorien nicht);
  • Es gibt höchstens 24 Kategorien – eine id-ähnliche Dimension ergibt einen Wert pro Knoten, jeder mit seiner eigenen Farbe, die sonst durch die obige Prüfung gehen würde.

Wenn irgendetwas davon fehlschlägt, erscheint keine Legende und es wird nichts protokolliert: Sie haben nicht danach gefragt. Die automatische Legende wird auch über 5000 Knoten hinaus übersprungen, wo es sich nicht lohnt, unaufgefordert die Farbe jedes Knotens zu testen – das wird gewarnt, also ist das Fehlen erklärbar. Das Deklarieren von UI.legend (oder true) hebt beides auf.

ts
// nothing declared → a legend appears iff `type` explains the colours
render: { nodeTypeAccessor: (node) => node.getData().type, nodeStyleMap: { … } }

UI: { legend: false }   // never
UI: { legend: true }    // derive from nodeTypeAccessor, check or no check
UI: { legend: { position: 'top-right' } }   // automatic entries, your placement

Ein automatischer Legendenschlüssel basiert auf einem Accessor und nicht auf einem Datenschlüssel, daher ist dies nicht möglichTeilen Sie einen Filtermit einer deklarierten Facette – geben Sie ihr eine explizitekeydafür.

Woher die Einträge kommen ​

Abgeleitet von einem Datenschlüssel ​

key ist die gesamte Konfiguration im allgemeinen Fall: Die unterschiedlichen Werte von node.getData()[key] werden in der zuerst angezeigten Reihenfolge zu den Zeilen, die mit dem Wert selbst beschriftet sind.

ts
UI: { legend: { key: 'type' } }

Ein Array-Wert-Schlüssel trägt eine Zeile pro Element bei, und unter jedem von ihnen zählt ein Knoten (tags: ['public', 'critical'] erscheint unter beiden).

Zwei Dinge sind über abgeleitete Zeilen wissenswert:

  • Knoten ohne Wert für den Schlüssel (null, undefined, '') erhalten keine Zeile und die Legende kann sie niemals verbergen – sie wirkt sich nur auf die Kategorien aus, die sie auflistet. Eine Entwicklungszeitwarnung nennt den Schlüssel und wie viele Knoten nicht dargestellt sind.
  • Eine Kategorie, die in mehr als einer Farbe gemalt ist Behält die erste gefundene Farbe bei, mit einer Warnung: Eine Zeile kann nur ein Farbfeld anzeigen. Deklarieren Sie die Einträge selbst, wenn die Gruppierung nicht wirklich einfarbig ist.

Deklarierte Einträge ​

Übergeben Sie entries, wenn die Kategorien kein einfacher Datenschlüssel sind – berechnete Gruppierungen, übersetzte Beschriftungen, eine feste Farbskala. Jeder Eintrag trägt seine eigene predicate, nach der der Umschalter filtert.

ts
UI: {
    legend: {
        title: 'Tier',
        entries: [
            { id: 'edge', label: 'Edge', color: '#0072B2',
              predicate: (node) => node.getData().type === 'web' },
            { id: 'storage', label: 'Storage', color: '#009E73',
              predicate: (node) => ['database', 'cache'].includes(node.getData().type) },
        ],
    },
}

entries kann auch eine Funktion des Diagramms sein, die jedes Mal neu aufgelöst wird, wenn sich die Daten ändern – sodass die Liste den Daten folgt, ohne dass die Deklaration verändert wird:

ts
UI: {
    legend: {
        key: 'type',
        entries: (graph) => distinct(graph, 'type').map((type) => ({
            id: type,
            label: t(`type.${type}`),        // translated, used verbatim
            color: palette.getColor(type),
        })),
    },
}

Die Deklaration von sowohl entries als auch key ist der oben gezeigte nützliche Mittelweg: Die Einträge geben die Beschriftungen und Farben an, und key liefert das Standardprädikat (data[key] === id) für Einträge, die kein Prädikat tragen. Ein Eintrag ohne „weder“ warnt und stimmt mit nichts überein.

Der Umschaltstatus einer Zeile überdauert eine erneute Auflösung; Ein Eintrag, der aus den Daten verschwindet, verliert sie und bleibt nicht als verborgener Geist zurück.

Mehrere Tasten gleichzeitig ​

Ein Diagramm, das drei Dinge kodiert – Art in der Füllung, Herkunft in der Einfassung, Beziehung im Strich – benötigt drei Schlüssel, nicht einen. Übergeben Sie sections und jeder erhält seinen eigenen betitelten Block, der von oben nach unten in Deklarationsreihenfolge in einer angedockten Karte gestapelt wird:

ts
UI: {
    legend: {
        position: 'bottom-left',        // the card docks; sections don't
        sections: [
            { key: 'type',  title: 'Element' },
            { key: 'scope', title: 'Provenance' },
            { key: 'tlp',   title: 'Sharing', filterable: false },
        ],
    },
}

Ein Abschnitt nimmt alles, was eine einzelne Legende braucht, außer position, das zur Karte gehört. Abschnitte sind in jeder anderen Hinsicht unabhängig: ihre eigenen Einträge, ihre eigenen Zählungen, ihr eigener Faltstatus, ihr eigener Filter.

Filter und zusammen ​

Jeder filterbare Abschnitt steuert seinen eigenen Filter, und die Abfrage-Engine bearbeitet sie: Schalten Sie attribute im ersten Abschnitt aus und self im zweiten aus, und was auf der Arbeitsfläche bleibt, sind die Knoten, die weder sind. show all jedes Abschnitts löscht nur diesen Abschnitt.

Ein Abschnitt schreibt in __legend:<id>, wobei die ID die id des Abschnitts ist, andernfalls seine key, andernfalls seine Position im Stapel (section-0). Eine Einzelschlüssel--Legende – die Objektform – behält die einfache __legend bei, die sie immer verwendet hat. Wie bei einer einzelnen Legende steuert ein Abschnitt, dessen key eine deklarierte select / multiselect-Facette benennt, stattdessen diese Facette, und ihre Geschwister bleiben davon unberührt.

Kanten statt Knoten verschlüsseln ​

Ein Abschnitt, der scope: 'edge' deklariert, listet die Beziehungsarten des Diagramms mit einem Linien-Farbfeld auf – Strichfarbe, Strich und Markierung, wie sie vom Renderer aufgelöst wurden – und seine Schalter verbergen Kantenebenen statt Knoten. Es befindet sich neben Abschnitten mit Knotenbereich auf derselben Karte und lässt diese unberührt. Die gesamte Funktion finden Sie unter Randschichten.

ts
UI: {
    legend: {
        sections: [
            { key: 'type', title: 'Element' },
            { key: 'kind', title: 'Relationship', scope: 'edge' },
        ],
    },
}

Benennen Sie die Dimension, nach der Sie stylen ​

Ein Abschnitt ohne key oder entries ist die Dimension, die Sie bereits als render.nodeTypeAccessor deklariert haben – die einzige Schreibweise für eine Stildimension, die kein einfacher Datenschlüssel ist. Es überspringt die Farbprüfung: innerhalb einer sections Liste, nach der Sie gefragt haben. Dies darf nur ein Abschnitt tun; eine Sekunde wird mit einer Warnung weggelassen.

ts
UI: { legend: { sections: [{}, { key: 'scope', title: 'Provenance' }] } }
//                         ↑ whatever nodeTypeAccessor returns, headed "Type"

Ein Abschnitt, der die Farbdimension abtastet

Farbfelder werden aus den Farben abgetastet, die der Renderer aufgelöst hat, und das ist der einzige Kanal, den die Legende lesen kann. Ein Abschnitt, der auf einer Dimension basiert, die die Farben nicht kodieren – Herkunft, wenn Herkunft als Einfassung gezeichnet wird – erhält die Farbe des ersten Knotens in jeder Kategorie und warnt, dass die Kategorie mehr als einen darstellt. Deklarieren Sie entries mit Ihrem eigenen color in diesem Abschnitt, um ihm Farbfelder zu geben, die etwas bedeuten.

Raum ​

Abschnitte lassen sich einzeln falten; Alt-Klicken Sie auf ein beliebiges Chevron, um den gesamten Stapel auf einmal zu falten oder zu entfalten. Jeder Abschnitt scrollt seine eigene Liste immer noch über maxVisibleEntries hinaus, und die Karte als Ganzes wird auf die Arbeitsflächehöhe begrenzt und scrollt, anstatt darüber hinauszuwachsen – sechs Abschnitte bleiben also im Ansichtsfenster.

Ein Abschnitt, der zu keine Einträge aufgelöst wird (ein Schlüssel, den kein Knoten trägt, eine leere Deklaration), wird vollständig übersprungen und nicht als leeres Feld mit Titel angezeigt. Eine Karte, deren Abschnitte alle leer sind, ergibt überhaupt nichts.

Bewegen Sie den Mauszeiger über eine Kategorie ​

Zeigen Sie auf eine Zeile und die Arbeitsfläche antwortet: Die Elemente, für die die Kategorie steht, behalten ihr Aussehen, alles andere wird ausgeblendet, bis der Zeiger weggeht. Es wird nichts gefiltert und nichts bewegt sich, daher ist es die kostengünstige Möglichkeit, herauszufinden, wo eine Kategorie platziert ist, bevor man entscheidet, ob man sie ausschaltet.

Eine Reihe in einem Abschnitt scope: 'edge' beleuchtet ihre Linien, wobei die Knoten zusammen mit den anderen Schichten zurücktreten. Eine zu einem geschlossenen Cluster oder einer vereinfachen-Gruppe gefaltete Kante beleuchtet die an ihrer Stelle gezeichnete Linie.

Wenn Sie auf eine Kategorie zeigen, die bereits ausgeschaltet ist, wird nichts abgedunkelt – es gibt nichts mehr auf der Arbeitsfläche, das zum Leuchten gebracht werden könnte.

highlightOnHover: false schaltet es für einen Abschnitt aus. Eine filterable: false-Taste hebt auch hervor, wo sie das Einzige ist, was eine Zeile bewirkt.

Derselbe Satz kann vom Code aus beleuchtet werden und bleibt bestehen, bis er gelöscht wird:

js
graph.emphasiseElements(graph.getMutableNodes().filter((node) => node.getData()?.type === 'md5'))
graph.clearEmphasis()

Beide übernehmen die Elemente, die der Renderer gezeichnet hat, also lauten sie getMutableNodes() / getMutableEdges() und nicht die Klon-Getter.

Filtern ​

Durch das Umschalten einer Zeile wird ein Filter auf graph.queryEngine geschrieben – dieselbe Engine, die das Filterpanel antreibt – es handelt sich also nichts um einen zweiten, parallelen Mechanismus:

  • Knoten, die nach einem Umschalter in einer ausgeblendeten Kategorie hinzugefügt wurden, werden ausgeblendet angezeigt.
  • Eine ausgeblendete Kategorie bleibt im Untergraphen eines erweiterten Clusters verborgen.
  • resetFilters() oder der Reset des Panels schaltet jede Reihe neu ein.
  • Mit jeder angezeigten Zeile wird der Filter entfernt, sodass die Anzahl der aktiven Filter im Header keine Legende meldet, die nichts verbirgt.

Standardmäßig besitzt die Legende einen eigenen reservierten Filterschlüssel (__legend), der durch ein Prädikat abgeglichen wird – eines pro Abschnitt, mit dem Namensraum __legend:<id>, in der gestapelten Form.

Einen Filter mit dem Panel teilen ​

Wenn key eine deklarierte select / multiselect Facette benennt, steuert die Legende stattdessen den Schlüssel dieser Facette. Die Legende und das Bedienfeld werden dann zu zwei Ansichten eines Filters: Schalten Sie ein Farbfeld aus, und die Mehrfachauswahl des Bedienfelds senkt diesen Wert. Wählen Sie Werte im Bedienfeld aus und die Legende wird entsprechend neu beleuchtet.

ts
UI: {
    filter: { facets: [{ key: 'type', label: 'Type', type: 'multiselect', options: … }] },
    legend: { key: 'type' },   // ⇒ writes setFilter('type', …)
}

Zwei Konsequenzen, wenn man die Sprache des Gremiums spricht:

  • Eine leer-Mehrfachauswahl bedeutet keine Einschränkung für das Panel, sodass die Legende nicht „Alles ausblenden“ ausdrücken kann: Die zuletzt angezeigte Zeile wird deaktiviert, anstatt einen Wert zu schreiben, der das gesamte Diagramm zurückbringen würde. Für eine Legende mit eigenem Schlüssel gibt es keine solche Beschränkung.
  • Knoten mit keinem Wert für den Schlüssel werden von jeder Auswahl ausgeblendet, genau wie beim Filtern aus dem Bedienfeld.

Wenn key eine Facette eines anderen Typs benennt (text, regex, numberRange, boolean), behält die Legende ihren eigenen Schlüssel und warnt – eine Werteliste hat dort keine Bedeutung.

Lesen der Schalter ​

Jeder Wechsel wird auf dem Datenereignisbus angekündigt:

ts
graph.on('legendToggle', ({ section, hidden, visible }) => {
    localStorage.setItem(`legend.hidden.${section}`, JSON.stringify(hidden))
})

section ist die ID des Abschnitts, der umgeschaltet wurde, und hidden / visible sind die Zeilen dieses Abschnitts. Eine Single-Key-Legende meldet ihre eigene abgeleitete ID (ihr key, andernfalls section-0).

Wiederherstellen ist derselbe Aufruf, den die Legende macht:

ts
graph.queryEngine.setFilter('__legend', { value: visible, matchMode: 'exact' })

Ändern der Legende zur Laufzeit ​

graph.setLegend(config) ersetzt UI.legend live. Für ein Diagramm, das ohne Legende begann, wird an Ort und Stelle eine erstellt, und false entfernt sie – und löscht damit seinen Filter, sodass nichts hinter einer Legende verborgen bleibt, die nicht mehr vorhanden ist.

ts
graph.setLegend({ key: 'zone', title: 'Region' })
graph.setLegend({ sections: [{ key: 'zone' }, { key: 'tier' }] })
graph.setLegend(false)        // remove it
graph.setLegend(true)         // back to the derived one

Durch den Wechsel zwischen den beiden Formularen werden die Filterschlüssel umbenannt, sodass die Filter des Formulars, das Sie verlassen haben, gelöscht werden: Nichts bleibt hinter einem weggelassenen Abschnitt verborgen.

Optionen ​

UI.legend akzeptiert auch einen booleschen Wert: false unterdrückt die Legende, true leitet einen von render.nodeTypeAccessor ab, ohne vorher die Farben zu überprüfen. Alles darunter ist die Objektform.

Die gestapelte-Form ist { enabled?, position?, sections }: sections ist die Liste unten minus position und enabled / position beschreiben die gesamte Karte.

OptionTypStandardwertWas es bewirkt
enabledbooleantruefalse behält die Deklaration bei, zeigt aber nichts an (wie legend: false).
idstringkey, sonst section-<index>Abschnittsidentität: sein Filterschlüssel (__legend:<id>) und das Feld legendToggle section.
titlestringgeschönt key, sonst 'Legend'Kopfzeilentext, wörtlich verwendet (damit er übersetzt werden kann).
keystring—Datenschlüssel, von dem die Zeilen abgeleitet sind, und das Standardprädikat für deklarierte Einträge. Lassen Sie sowohl dies als auch entries für die Legende automatisch weg.
entriesLegendEntry[] | (graph) => LegendEntry[]—Deklarierte Zeilen; Eine Funktion wird bei Datenänderung neu aufgelöst.
position'bottom-left' | 'bottom-right' | 'top-left' | 'top-right''bottom-right' im full-Modus, sonst 'bottom-left'In welcher Canvas-Ecke es angedockt wird. Gehört zur Karte, daher ist es keine eine Abschnittsoption. 'top-left' teilt seinen Anker mit der kontextuellen Werkzeugleiste, die die Legende im geöffneten Zustand abdeckt.
collapsiblebooleantrueZeigen Sie den Chevron, der den Titel bildet.
collapsedbooleanfalseBeginnen Sie gefaltet.
showCountsbooleantrueZeigt die Anzahl der Knoten pro Kategorie an (im gesamten Diagramm, damit es beim Umschalten nicht flackert).
filterablebooleantruefalse gibt einen einfachen, nicht interaktiven Schlüssel wieder.
highlightOnHoverbooleantrueWenn Sie den Mauszeiger über eine Zeile bewegen, wird diese Kategorie auf der Arbeitsfläche beleuchtet und der Rest abgedunkelt.
maxVisibleEntriesnumber12Zeilen, die vor der Liste angezeigt werden, scrollen innerhalb der Legende.

Ein LegendEntry ist { id, label?, color, badge?, predicate?, order? }. id ist die Identität der Zeile, der in den Filter geschriebene Wert und der Fallback der Bezeichnung.

Wenn Knoten durch ein Abzeichen und nicht durch ihre Füllung unterschieden werden, geben Sie den Eintrag badge ein und die Zeile zeigt die Markierung anstelle eines Punktes. Es wird so gezeichnet, wie die Arbeitsfläche es zeichnet, und ein Abzeichen mit der Bezeichnung Nr. color übernimmt den Eintrag. Die Filterung bleibt unverändert.

js
{ id: 'enriched', label: 'From enrichment', color: '#8b5cf6',
  badge: { svgIcon: sparkle }, predicate: (node) => node.getData().enriched }

maxVisibleEntries ist eine Obergrenze auf der Höhe der Legende; die Arbeitsfläche ist das andere.

Im full-Modus wird die Legende standardmäßig unten rechts angedockt und über der Minikarte gestapelt, die dieser Modus ebenfalls einhängt: Die linke Spalte gehört zur Modusschiene und ihren Panels, und ein Rail-Modus, dessen Panel ein Arbeitsbereich und kein Einstellungsblatt ist, reicht weit genug nach unten, um die Legende in einer einzigen Zeile zu belassen. Wenn eine Minikarte die ausgewählte Ecke teilt, befindet sich die Legende darüber und folgt ihr, wenn sie zusammengeklappt, in der Größe geändert oder ausgehängt wird.

Eine bottom-left-Legende teilt ihre Spalte mit der Modusschiene, sodass sie in einem kurzen Ansichtsfenster auf den Raum dazwischen schrumpft und an der Zeile scrollt, auf der sie landet, anstatt unter der Schiene aufzuwachsen. Es bewegt sich nie von alleine um die Ecke.

Interaktion ​

GesteWirkung
Bewegen Sie den Mauszeiger über eine ZeileBeleuchten Sie diese Kategorie auf der Arbeitsfläche und dimmen Sie alles andere
Klicken Sie auf eine ZeileDiese Kategorie ausblenden/anzeigen
Alt-klicken Sie auf eine ZeileNur diese Kategorie anzeigen
Kopfzeile alle anzeigenJede Zeile neu beleuchten (löscht den Filter der Legende)
Kopfzeile invertierenTauschen Sie die angezeigten Kategorien aus
Kopfzeile chevronFalten Sie diesen Abschnitt bis zum Titel
Alt-klicken Sie auf den ChevronFalten oder entfalten Sie jeden Abschnitt auf einmal

Zeilen sind echte Schaltflächen: per Tab erreichbar, Enter / Space umschalten und aria-pressed trägt den Status. Eine ausgeblendete Zeile wird mit einem leeren Farbfeld ** und ** abgeblendetem Text gezeichnet, sodass Farbe nie das einzige Signal ist.

Ein Live-Beispiel finden Sie in der Galeriekarte Filterbare Legende und in LegendOptions / LegendGroupOptions für die vollständigen Typen.