Aller au contenu

Couches de bord ​

Un graphe dont les relations se ressemblent toutes cesse d'être lisible dès qu'il comporte plus d'un type de relation. Couches de bord sont les trois éléments dont vous avez besoin pour résoudre ce problème, et ils sont destinés à être utilisés ensemble :

  1. Nommez le genre — render.edgeTypeAccessor.
  2. Démarquez-le — render.edgeStyleMap.
  3. Éteignez-le — UI.filter.edgeFacets, apparu sous forme de bascules en direct dans le panneau de filtre et, éventuellement, sous la forme d'une section de légende.
ts
const options = {
    render: {
        edgeTypeAccessor: (edge) => edge.getData()?.kind,
        edgeStyleMap: {
            reference:   { strokeColor: '#428bca' },
            correlation: { strokeColor: '#888888', dashed: true, animateDash: false },
            sighting:    { strokeColor: '#f39a1f', dashed: true, markerEnd: 'circle' },
        },
    },
    UI: {
        filter: { edgeFacets: [{ key: 'kind', label: 'Relationship' }] },
    },
}

La désactivation d'un calque ne déplace jamais le graphique

C'est la propriété autour de laquelle toute la fonctionnalité est construite — voir Le basculement est un objectif. La disposition, la sélection et la caméra reviennent bit pour bit identiques, à moins que vous n'optiez pour masquer les nœuds non connectés.

Nommer et styliser un genre ​

edgeTypeAccessor et edgeStyleMap reflètent exactement nodeTypeAccessor / nodeStyleMap : l'accesseur renvoie une chaîne (ou undefined), et la carte transforme cette chaîne en un Partial<EdgeStyle> fusionné sur le style de bord par défaut.

ts
render: {
    edgeTypeAccessor: (edge) => edge.getData()?.kind,
    edgeStyleMap: {
        reference: { strokeColor: '#428bca', strokeWidth: 2 },
        contains:  { strokeColor: '#5cb85c', markerEnd: 'diamond' },
    },
}

Chaque propriété EdgeStyle est un jeu équitable — strokeColor, strokeWidth, opacity, curveStyle, dashed, animateDash, markerEnd, markerStart, rotateLabel. Un bord dashed anime son tiret par défaut ; définissez animateDash: false pour un type qui doit se lire comme un contexte d'arrière-plan calme.

Un bord sans type, ou un type que la carte ne mentionne pas, conserve le style de bord par défaut. Rien ne prévient : un style sans style est un choix légitime.

Priorité ​

Le même ordre que les nœuds, le plus étroit gagne :

PrioritéOù
1 (gagne)le propre du bord style.styleCb — voir l'avertissement ci-dessous
2les propres propriétés statiques style du bord
3render.edgeStyleMap[kind]
4render.defaultEdgeStyle.styleCb
5render.defaultEdgeStyle littéraux

Les deux rôles styleCb ne sont pas la même chose, et cela vaut la peine de les garder droits. Sur le style own d'un bord, c'est une substitution qui l'emporte carrément ; sur render.defaultEdgeStyle, c'est la forme calculée de l'emplacement par défaut, remplissant uniquement ce qui n'est pas un ensemble plus étroit - y compris ce qu'un styleCb par bord a laissé de côté. Ordre de spécificité, pas de rappel-beats-static.

Le styleCb d'un Edge remplace tout son niveau

Lorsque le propre style d'un bord déclare un styleCb, sa valeur de retour est la source seule à ce niveau : les propres propriétés statiques du bord * et * edgeStyleMap sont toutes deux ignorées, et seul defaultEdgeStyle comble les lacunes. Ainsi, un styleCb renvoyant { strokeWidth: 4 } sur un bord qui définit également strokeColor perd cette couleur, ainsi que la couleur du type. Si vous souhaitez un rappel et le style du genre, effectuez la recherche à l'intérieur du rappel, ou placez le rappel sur render.defaultEdgeStyle, où il cède la place à la carte.

Le moteur de rendu Canvas résout moins

renderers/canvas honore edgeStyleMap mais ne lit que quatre des neuf propriétés EdgeStyle. Le moteur de rendu SVG – celui par défaut – les résout tous.

Faire une sorte de calque ​

Les styles mis à part ne représentent que la moitié du problème. UI.filter.edgeFacets déclare quelles dimensions de bord sont commutables :

ts
UI: {
    filter: {
        edgeFacets: [{ key: 'kind', label: 'Relationship' }],
    },
}

{ key: 'kind' } est une déclaration complète. Une facette d'arête a des valeurs par défaut en forme d'arête : un calque est un multiselect, et ses options sont dérivées des valeurs distinctes que portent réellement vos arêtes réelles, elle ne peut donc jamais offrir un choix qui reviendrait vide.

Rien ici n'est jamais dérivé automatiquement. Contrairement aux facettes de nœud, qui reviennent à l'analyse de vos données, un graphique qui déclare non edgeFacets se comporte exactement comme celui qui n'a jamais entendu parler de couches. La visibilité des bords est trop importante pour être estimée.

Le panneau de filtre développe une section Relationships : une ligne par type, chacune avec un échantillon de ligne et un nombre, appliquant à la fois — comme les champs d'attributs au-dessus et la légende à côté. Rien dans ce panneau n'attend derrière un bouton Appliquer.

Facettes qui ne sont pas des calques ​

type accepte le vocabulaire complet des facettes — text, regex, select, multiselect, numberRange, boolean — plus accessor et predicate pour les dimensions qui ne sont pas une clé de données simple :

ts
edgeFacets: [
    { key: 'kind', label: 'Relationship' },                    // a layer
    { key: 'weight', label: 'Confidence', type: 'numberRange' }, // a batch control
    {
        key: 'recent',
        label: 'Seen this week',
        type: 'boolean',
        accessor: (edge) => Date.now() - edge.getData().seenAt < 7 * 864e5,
    },
]

Seul le type à sélection multiple est une bascule en direct. Tout le reste est un contrôle par lots, il s'affiche donc sous la forme d'attribut du panneau à côté des facettes du nœud, où un bouton Appliquer est la bonne option.

Options ​

OptionsTypeValeur par défautCe que ça fait
keystring—Filtrer l'identité. setEdgeFilter(key, …) prend ça.
labelstringjoli keyUtilisé textuellement, il peut donc provenir d'une couche de traduction.
typeFilterFacetType'multiselect'Un calque est un ensemble de types, chacun activé ou désactivé.
optionsarray | (graph) => arraydérivéRésolu par rapport au graphique en direct à chaque reconstruction.
matchModeFilterMatchMode'exact'
accessor(edge) => unknownedge.getData()[key]Ce qui rend les facettes calculées possibles.
predicate(edge, value) => boolean—Contrôle total ; l'emporte sur accessor / matchMode. S'exécute par bord et par application, alors restez bon marché.
ordernumberordonnance de déclarationOrdre d'affichage dans le panneau.

Le basculement est un objectif, pas un changement de mise en page ​

La désactivation d'un calque modifie quelles lignes sont dessinées et rien d'autre. Les positions des nœuds, la sélection et la caméra restent inchangées bit par bit et aucun redémarrage de la simulation.

C'est délibéré, et c'est ce que font les outils professionnels : vis-network comporte un hidden * et * par bord séparé physics, le edgeReducer de Sigma n'atteint jamais la mise en page, Cytoscape et KeyLines re-layout uniquement lorsque cela est demandé. Une relation cachée est une décision d'affichage. Si le masquage d'une couche de corrélation redistribuait le graphique, vous ne pouviez pas utiliser la bascule pour lire le graphique, qui représente le point entier.

La seule chose qui le plie est l'opt-in, et du côté des nœuds : UI.filter.hideDisconnected masque les nœuds d'une couche de brins. Ces nœuds quittent la simulation, donc le graphique se réinstalle – c'est pourquoi il est désactivé par défaut et pourquoi il s'agit d'un filtre de nœud plutôt que de quelque chose que le contrôle de couche fait lui-même.

Mécaniquement, Edge a fait pousser un deuxième drapeau :

PropriétéSignification
layerVisibleIndique si le calque de ce bord est activé. Un veto sur visible.
visibleIgnoringLayerQue serait visible si chaque couche était activée : le point de terminaison et les raisons de masquage manuel uniquement.
setLayerVisible(on)Changez de calque, redérivant visible. Renvoie si quelque chose a changé.
representedEdgesPour un remplaçant : les véritables atouts dont il parle. Indéfini sur un vrai bord.

Le lien force les portes sur visibleIgnoringLayer, donc un bord dont la couche est désactivée continue de rassembler ses extrémités. visible est également écrit par filtrage de point de terminaison et par hideNode / showNode, c'est pourquoi l'état de la couche est séparé de celui-ci.

Un nœud laissé sans bords visibles reste visible ​

Le cacher serait une décision de filtre node, et une facette de bord n'en prend jamais. Utilisez le panneau de filtrage ou queryEngine.setFilter si c'est ce que vous voulez.

Un choix vide signifie que chaque couche est supprimée ​

Pour un nœud à sélection multiple, une liste vide correspond à la façon dont le panneau épelle unset — aucune contrainte. Un contrôle de couche périphérique écrit exactement ce qui reste activé, donc une liste de couches vidée signifie que chaque couche est désactivée. Cela ne peut signifier rien d'autre, et c'est pourquoi une section de bord n'a pas de limite « impossible de désactiver la dernière ».

Saisir les calques dans la légende ​

Une section legend déclarant scope: 'edge' répertorie les types avec un échantillon line — couleur de trait, tiret et marqueur tels que le moteur de rendu les a résolus — assis à côté des sections de portée de nœud dans la même carte ancrée :

ts
UI: {
    legend: {
        sections: [
            { key: 'type', title: 'Element' },                     // nodes
            { key: 'kind', title: 'Relationship', scope: 'edge' },  // edges
        ],
    },
}
  • Étant donné une déclaration edgeFacets correspondante, la section ** pilote cette facette ** — le panneau et la légende deviennent deux vues d'un filtre, chacune se succédant.
  • Sans cela, elle réserve une facette qui lui est propre et continue de filtrer, de sorte qu'une légende n'est jamais simplement décorative.
  • Une section sans key ni entries utilise render.edgeTypeAccessor, exactement comme une section de nœud revient à nodeTypeAccessor.
  • Les décomptes sont des arêtes réel : les remplaçants d'un cluster réduit sont exclus, donc un décompte ne signale jamais en double une relation.

Les couleurs des échantillons sont échantillonnées à partir de ce que le moteur de rendu a peint, donc un type rendu dans plus d'une couleur conserve la première et avertit. Déclarez entries avec votre propre color lorsque les types ne correspondent pas à ceux encodés par les traits.

Clusters réduits ​

Lorsqu'un cluster est fermé, une arête avec une extrémité à l'intérieur est dessinée vers le cluster, à n'importe quelle profondeur. Les arêtes dont les extrémités atterrissent sur les deux mêmes nœuds à l'écran partagent une ligne, un stand-in, donc une ligne peut parler de plusieurs relations réelles de plusieurs types. Les remplaçants dessinent uniquement : getEdges() et l'onglet Bords du tableau répertorie les bords réels.

Chaque remplaçant porte donc representedEdges, et il survit pendant que n'importe lequel d'entre eux passe le filtre. Deux conséquences à connaître :

  • Masquer un calque masque un remplaçant uniquement une fois que toutes les relations derrière lui sont masquées. Une ligne qui représente à la fois un reference et un correlation reste pendant que l'un ou l'autre est activé.
  • Chaque type de facette atteint l'intérieur d'un cluster réduit - un numberRange ou un predicate, pas seulement une liste de types - car la facette est comparée aux bords réels plutôt qu'aux propres données (vides) du remplaçant.

Un remplaçant pour un seul bord ressemble à ce bord, en pointillés, et cliquer dessus sélectionne le bord. Un remplaçant pour plusieurs est simple, et sa sélection montre le nombre d'arêtes qu'il représente.

Le conduire à partir du code ​

Les facettes de bord sont enregistrées dans le constructeur, donc le filtrage programmatique ** fonctionne dans tous les modes ** — seul le panneau est en mode chrome full.

ts
graph.queryEngine.setEdgeFilter('kind', { value: ['reference'], matchMode: 'exact' })
graph.queryEngine.getHiddenEdgeCount()      // 12
graph.queryEngine.removeEdgeFilter('kind')  // every layer back on
MéthodeCe que ça fait
setEdgeFilter(key, config)Écrivez un filtre de bord.
getEdgeFilters()Les filtres de bord actifs, sans préfixe.
removeEdgeFilter(key)Déposez-en un : cet ensemble de calques réapparaît.
getHiddenEdgeCount()Combien d’arêtes sont masquées par un calque.
getEdgeFacetValues(key)Les valeurs distinctes, chacune avec un nombre et un bord d'échantillon.
setEdgeFacets(facets)Remplacez l'ensemble déclaré.
registerEdgeFacet(facet)Ajoutez-en un de manière additive : ce qu'une section de légende utilise pour réserver la sienne.
replaceFilters(ownedKeys, filters)Remplacez uniquement les clés que vous possédez, laissant les autres tranquilles.

Les filtres de nœud et de bord partagent un enregistrement GraphFilters, avec des clés de bord portées sous un préfixe edge: (EDGE_FILTER_PREFIX, exporté depuis GraphQueryEngine), de sorte qu'un nom de clé peut être à la fois une facette de nœud et une facette de bord. Chaque méthode ci-dessus masque le préfixe ; vous ne le rencontrez que si vous lisez le dossier brut.

renderer.getEdgeStyle(edge) est public, renvoyant le style réellement résolu par le moteur de rendu - le même appel sur lequel les échantillons de légende sont construits et la manière d'affirmer dans un test que ce que vous avez déclaré est ce qui est peint.

Voir aussi ​