Aller au contenu

Légende ​

UI.legend ancre un legend dans un coin du canevas : une ligne par catégorie, chacune avec un échantillon de couleur, une étiquette et le nombre de nœuds derrière. Pointer une rangée éclaire cette catégorie sur la toile ; cliquer sur l'un d'entre eux le cache, de sorte que la légende constitue également le filtre le plus rapide de l'interface utilisateur.

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

Vous n'aurez peut-être pas du tout besoin de ce bloc : sans UI.legend, une légende apparaît elle-même lorsque les couleurs de votre graphique sont expliquées par un render.nodeTypeAccessor déclaré — voir Activé par défaut. UI.legend: false l'éteint.

Un graphique qui code plusieurs choses à la fois a besoin de plusieurs clés : passez sections au lieu d'une seule clé — voir Plusieurs clés à la fois.

La légende fait partie du chrome, elle apparaît donc uniquement dans les modes full et light — viewer et static n'en ont pas.

La légende ne colore jamais rien

La légende est descriptive. Il lit la couleur que le moteur de rendu a déjà résolue pour un nœud (renderer.getNodeStyle(node).color) et la signale. La coloration reste là où elle était : render.defaultNodeStyle.color, render.nodeStyleMap ou un style par nœud – généralement via ColorPaletteMapper. Changez de palette et la légende suit toute seule.

Activé par défaut ​

Une légende ne vaut la peine d'être montrée sans demande que si elle est garantie de dire la vérité, donc la légende automatique part de quelque chose que vous avez déjà déclaré plutôt que d'une supposition sur vos données : render.nodeTypeAccessor, la dimension sur laquelle vous pointez nodeStyleMap.

Mais avoir un candidat ne suffit pas : une dimension peut partitionner vos données sans rien avoir à voir avec ses couleurs. Donc avant de restituer quoi que ce soit, la légende vérifie que cette dimension explique les couleurs :

  • chaque catégorie se résout en exactement une couleur ;
  • il y a au moins deux couleurs distinctes (sinon les couleurs ne distinguent pas les catégories) ;
  • il y a au plus 24 catégories — une dimension de type identifiant donne une valeur par nœud, chacune avec sa propre couleur, qui autrement passerait par la vérification ci-dessus.

Si l'une de ces opérations échoue, aucune légende n'apparaît et rien n'est enregistré : vous n'en avez pas demandé. La légende automatique est également ignorée au-delà de 5000 nœuds, où l'échantillonnage de la couleur de chaque nœud ne vaut pas la peine de payer sans y être invité - cela prévient, donc l'absence est explicable. Déclarer UI.legend (ou true) lève les deux.

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

Une clé de légende automatique sur un accesseur plutôt qu'une clé de données, donc il ne peut pas partager un filtre avec une facette déclarée — donnez-lui un key explicite pour cela.

D'où viennent les entrées ​

Dérivé d'une clé de données ​

key est la configuration complète dans le cas courant : les valeurs distinctes de node.getData()[key] deviennent les lignes, dans l'ordre d'apparition, étiquetées avec la valeur elle-même.

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

Une clé array-valued contribue à une ligne par élément, et un nœud compte sous chacun d'eux (tags: ['public', 'critical'] apparaît sous les deux).

Deux choses sont à savoir sur les lignes dérivées :

  • Les nœuds sans valeur pour la clé (null, undefined, '') n'obtiennent aucune ligne et la légende ne peut jamais les masquer — elle n'agit que sur les catégories qu'elle répertorie. Un avertissement au moment du développement nomme la clé et le nombre de nœuds non représentés.
  • Une catégorie peinte en plus d'une couleur conserve la première couleur trouvée, avec un avertissement : une ligne ne peut afficher qu'un seul échantillon. Déclarez vous-même les entrées si le regroupement n'est pas vraiment d'une seule couleur.

Entrées déclarées ​

Passez entries lorsque les catégories ne sont pas une clé de données simple : regroupements calculés, étiquettes traduites, échelle de couleurs fixe. Chaque entrée porte son propre predicate, sur lequel est activé le filtre à bascule.

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 peut également être une fonction du graphique, re-résolue chaque fois que les données changent — de sorte que la liste suit les données sans que la déclaration ne soit modifiée :

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

Déclarer à la fois entries et key est le juste milieu utile indiqué ci-dessus : les entrées donnent les étiquettes et les couleurs, et key fournit le prédicat par défaut (data[key] === id) pour les entrées qui n'en portent pas. Une entrée sans aucun avertissement et ne correspond à rien.

L'état de bascule d'une ligne survit à une nouvelle résolution ; une entrée qui disparaît des données la perd plutôt que de persister comme un fantôme caché.

Plusieurs clés à la fois ​

Un graphique qui code trois éléments – le type dans le remplissage, la provenance dans l’enceinte, la relation dans le trait – a besoin de trois clés, pas une. Passez sections et chacun obtient son propre bloc intitulé, empilé de haut en bas dans ordre de déclaration à l'intérieur d'une carte ancrée :

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 },
        ],
    },
}

Une section prend tout ce qu'une seule légende prend sauf position, qui appartient à la carte. Les sections sont indépendantes à tous égards : leurs propres entrées, leurs propres décomptes, leur propre état de repli, leur propre filtre.

Filtres et ensemble ​

Chaque section filtrable pilote son propre filtre, et le moteur de requête les suit : désactivez attribute dans la première section et self dans la seconde, et ce qui reste sur le canevas, ce sont les nœuds qui ne sont ni. Le show all de chaque section efface uniquement cette section.

Une section écrit dans __legend:<id>, où l'identifiant est le id de la section, sinon son key, sinon sa place dans la pile (section-0). Une légende à clé unique — la forme de l'objet — conserve le simple __legend qu'elle a toujours utilisé. Comme pour une légende solitaire, une section dont key nomme une facette select / multiselect déclarée pilote cette facette à la place, et ses frères et sœurs ne sont pas affectés.

Saisir des arêtes au lieu de nœuds ​

Une section déclarant scope: 'edge' répertorie les types de relations du graphique avec un échantillon line — la couleur du trait, le tiret et le marqueur tels que le moteur de rendu les a résolus — et ses bascules masquent les couches de bord plutôt que les nœuds. Il se trouve à côté des sections de portée de nœud dans la même carte et les laisse intactes. Voir Couches de bord pour l'ensemble de la fonctionnalité.

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

Nommer la dimension avec laquelle vous stylisez ​

Une section sans key ni entries est la dimension que vous avez déjà déclarée comme render.nodeTypeAccessor — la seule orthographe pour une dimension de style qui n'est pas une clé de données simple. Il ignore le vérification de couleur : à l'intérieur d'une liste sections vous l'avez demandé. Une seule section peut le faire ; une seconde est supprimée avec un avertissement.

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

Une section délimitée par la dimension couleur

Les échantillons sont **échantillonnés à partir des couleurs résolues par le moteur de rendu **, et c'est le seul canal que la légende peut lire. Une section saisie sur une dimension que les couleurs ne codent pas (la provenance, lorsque la provenance est dessinée comme une enceinte) obtient la couleur du premier nœud de chaque catégorie et avertit que la catégorie en restitue plusieurs. Déclarez entries avec votre propre color sur cette section pour lui donner des échantillons qui signifient quelque chose.

Espace ​

Les sections se plient individuellement ; alt-cliquez sur n'importe quel chevron pour plier ou déplier toute la pile en même temps. Chaque section fait toujours défiler sa propre liste au-delà de maxVisibleEntries, et la carte dans son ensemble est plafonnée par rapport à la hauteur du canevas et défile plutôt que de la dépasser - donc six sections restent à l'intérieur de la fenêtre.

Une section qui se résout en aucune entrée (une clé qu'aucun nœud ne porte, une déclaration vide) est entièrement ignorée plutôt que affichée sous la forme d'une boîte intitulée vide ; une carte dont les sections sont toutes vides ne rend rien du tout.

Survol d'une catégorie ​

Pointez une ligne et le canevas répond : les éléments que représente cette catégorie conservent l'apparence qu'ils ont, tout le reste s'assombrit jusqu'à ce que le pointeur s'en aille. Rien n'est filtré et rien ne bouge, c'est donc un moyen peu coûteux de savoir où se situe une catégorie avant de décider de la désactiver.

Une rangée sur une section scope: 'edge' éclaire ses lignes, les nœuds s'éloignant avec les autres couches. Une arête repliée en cluster fermé ou un groupe simplify éclaire la ligne tracée à sa place.

Pointer sur une catégorie déjà désactivée n’obscurcit rien : il ne reste plus rien sur la toile à éclairer.

highlightOnHover: false le désactive pour une section. Une touche filterable: false met également en évidence, là où c'est la seule chose que fait une ligne.

Le même ensemble peut être allumé à partir du code et reste conservé jusqu'à ce qu'il soit effacé :

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

Les deux prennent les éléments dessinés par le moteur de rendu, ils lisent donc getMutableNodes() / getMutableEdges() plutôt que les getters de clonage.

Filtrage ​

Basculer une ligne écrit un filtre sur graph.queryEngine — le même moteur que le panneau de filtrage — donc rien à ce sujet n'est un deuxième mécanisme parallèle :

  • Les nœuds ajoutés après une bascule, dans une catégorie masquée, arrivent masqués.
  • Une catégorie masquée reste cachée dans le sous-graphe d'un cluster développé.
  • resetFilters(), ou le Reset du panneau, rallume chaque ligne.
  • À chaque ligne affichée, le filtre est supprimé, donc le nombre de filtres actifs de l'en-tête ne rapporte pas de légende qui ne cache rien.

Par défaut, la légende possède sa propre clé de filtre réservée (__legend) correspondant via un prédicat — une par section, avec un espace de noms __legend:<id>, sous la forme empilée.

Partager un filtre avec le panel ​

Lorsque key nomme une select / multiselect facette déclarée, la légende entraîne la clé de cette facette à la place. La légende et le panneau deviennent alors deux vues d'un même filtre : désactivez un échantillon et la sélection multiple du panneau supprime cette valeur ; choisissez des valeurs dans le panneau et la légende se rallume en conséquence.

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

Deux conséquences de parler la langue du panel :

  • Une sélection multiple vide signifie aucune contrainte pour le panneau, donc la légende ne peut pas exprimer "tout cacher" : la dernière ligne affichée est désactivée plutôt que d'écrire une valeur qui ramènerait tout le graphique. Une légende sur sa propre clé n'a pas une telle limite.
  • Les nœuds avec aucune valeur pour la clé sont masqués par toute sélection, exactement comme ils le sont lorsque vous filtrez à partir du panneau.

Si key nomme une facette d'un autre type (text, regex, numberRange, boolean) la légende garde sa propre clé et prévient — une liste de valeurs n'a aucun sens ici.

Lire les bascules ​

Chaque bascule est annoncée sur le bus d'événements de données :

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

section est l'identifiant de la section qui a été basculée, et hidden / visible sont les lignes de cette section. Une légende à clé unique rapporte son propre identifiant dérivé (son key, sinon section-0).

La restauration est le même appel que la légende fait :

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

Changer la légende au moment de l'exécution ​

graph.setLegend(config) remplace UI.legend en direct. Un graphique qui a commencé sans légende en obtient une construite sur place, et false le supprime — effaçant son filtre avec, afin que rien ne reste caché derrière une légende qui a disparu.

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

L'échange entre les deux formulaires renomme les clés de filtre, donc les filtres du formulaire que vous avez quitté sont supprimés : rien ne reste caché derrière une section qui a disparu.

Options ​

UI.legend prend également un booléen : false supprime la légende, true en dérive une de render.nodeTypeAccessor sans vérifier au préalable les couleurs. Tout ce qui suit est le formulaire objet.

La forme stacked est { enabled?, position?, sections } : sections est la liste ci-dessous moins position, et enabled / position décrit la carte entière.

OptionsTypeValeur par défautCe que ça fait
enabledbooleantruefalse conserve la déclaration mais n'affiche rien (identique à legend: false).
idstringkey, sinon section-<index>Identité de la section : sa clé de filtre (__legend:<id>) et le champ legendToggle section.
titlestringjoli key, sinon 'Legend'Texte d'en-tête, utilisé textuellement (afin qu'il puisse être traduit).
keystring—Clé de données dont les lignes sont dérivées et prédicat par défaut pour les entrées déclarées. Omettez ceci et entries pour la légende automatic.
entriesLegendEntry[] | (graph) => LegendEntry[]—Lignes déclarées ; une fonction est re-résolue en cas de changement de données.
position'bottom-left' | 'bottom-right' | 'top-left' | 'top-right''bottom-right' en mode full, sinon 'bottom-left'Dans quel coin de toile il s'ancre. Appartient à la carte, c'est donc pas une option de section. 'top-left' partage son ancre avec le panneau d'outils contextuel, qui couvre la légende lorsqu'il est ouvert.
collapsiblebooleantrueMontrez le chevron qui le plie à son titre.
collapsedbooleanfalseCommencez plié.
showCountsbooleantrueAffichez le nombre de nœuds par catégorie (sur l'ensemble du graphique, afin qu'il ne scintille pas lorsque vous basculez).
filterablebooleantruefalse restitue une clé simple et non interactive.
highlightOnHoverbooleantrueLe survol d’une ligne éclaire cette catégorie sur la toile et atténue le reste.
maxVisibleEntriesnumber12Lignes affichées avant que la liste défile à l'intérieur de la légende.

Un LegendEntry est { id, label?, color, badge?, predicate?, order? }. id est l'identité de la ligne, la valeur écrite dans le filtre et le repli de l'étiquette.

Lorsque les nœuds sont distingués par un badge plutôt que par leur remplissage, indiquez l'entrée badge et la ligne affiche la marque au lieu d'un point. Il est dessiné de la même façon que le canevas le dessine, et un badge portant le numéro color prend celui de l'entrée. Le filtrage est inchangé.

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

maxVisibleEntries est un plafond à la hauteur de la légende ; la toile est l'autre.

En mode full, la légende s'ancre en bas à droite par défaut, s'empilant au-dessus de la mini-carte que le mode monte également : la colonne de gauche appartient au rail de mode et à ses panneaux, et un mode rail dont le panneau est un espace de travail plutôt qu'une feuille de paramètres s'étend suffisamment loin pour laisser la légende sur une seule ligne. Lorsqu'une mini-carte partage le coin choisi, la légende se trouve au-dessus, la suivant lorsqu'elle se réduit, se redimensionne ou se démonte.

Une légende bottom-left partage sa colonne avec le rail de mode, donc dans une courte fenêtre d'affichage, elle se réduit à la pièce qui les sépare et défile sur la ligne sur laquelle elle atterrit, plutôt que de grandir sous le rail. Il ne bouge jamais tout seul.

Interactions ​

GesteEffet
Survolez une ligneÉclairez cette catégorie sur la toile, atténuez tout le reste
Cliquez sur une ligneMasquer/afficher cette catégorie
Alt-cliquez sur une ligneAfficher uniquement cette catégorie
En-tête afficher toutRallume chaque ligne (efface le filtre de la légende)
En-tête inverserÉchanger les catégories affichées
En-tête chevronPliez cette section à son titre
Alt-cliquez sur le chevronPliez ou dépliez chaque section en même temps

Les lignes sont de vrais boutons : accessibles par tabulation, bascule Enter / Space et aria-pressed porte l'état. Une ligne masquée est dessinée avec un échantillon creux ** et ** du texte grisé, de sorte que la couleur n'est jamais le seul signal.

Voir la carte de galerie Légende filtrable pour un exemple en direct, et LegendOptions / LegendGroupOptions pour les types complets.