Pular para o conteúdo

Camadas de borda ​

Um gráfico cujas relações são todas iguais deixa de ser legível no momento em que carrega mais de um tipo de relação. Camadas de borda são as três coisas que você precisa para corrigir isso e devem ser usadas juntas:

  1. Nomeie o tipo — render.edgeTypeAccessor.
  2. Estilize-o à parte — render.edgeStyleMap.
  3. Desligar — UI.filter.edgeFacets, apareceu como alternância ao vivo no painel de filtro e, opcionalmente, como uma seção de legenda.
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' }] },
    },
}

Desativar uma camada nunca move o gráfico

Esta é a propriedade em torno da qual todo o recurso é construído - consulte Alternar é uma lente. Layout, seleção e câmera voltam bit a bit idênticos, a menos que você opte por ocultar nós não conectados.

Nomeando e estilizando um tipo ​

edgeTypeAccessor e edgeStyleMap espelham nodeTypeAccessor / nodeStyleMap exatamente: o acessador retorna uma string (ou undefined), e o mapa transforma essa string em um Partial<EdgeStyle> mesclado sobre o estilo de borda padrão.

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

Cada propriedade EdgeStyle é um jogo justo - strokeColor, strokeWidth, opacity, curveStyle, dashed, animateDash, markerEnd, markerStart, rotateLabel. Uma aresta dashed anima seu traço por padrão; defina animateDash: false para um tipo que deve ser lido como um contexto de fundo calmo.

Uma aresta sem tipo, ou um tipo que o mapa não menciona, mantém o estilo de aresta padrão. Nada avisa: um tipo sem estilo é uma escolha legítima.

Precedência ​

Na mesma ordem dos nós, as vitórias mais estreitas:

PrioridadeOnde
1 (vitórias)o próprio style.styleCb da borda — veja o aviso abaixo
2as próprias propriedades estáticas style da borda
3render.edgeStyleMap[kind]
4render.defaultEdgeStyle.styleCb
5render.defaultEdgeStyle literais

As duas funções styleCb não são a mesma coisa e vale a pena mantê-las corretas. No estilo own de um edge é um override que vence imediatamente; em render.defaultEdgeStyle é a forma computada * do slot padrão*, preenchendo apenas o que nada mais restrito definiu - incluindo o que um styleCb por aresta deixou de fora. Ordenação de especificidade, não callback-beats-static.

O próprio styleCb de uma vantagem substitui todo o seu nível

Quando o próprio estilo de uma borda declara um styleCb, seu valor de retorno é a fonte only naquela camada: as próprias propriedades estáticas da borda * e* edgeStyleMap são ignoradas e apenas defaultEdgeStyle preenche as lacunas. Portanto, um styleCb retornando { strokeWidth: 4 } em uma aresta que também define strokeColor perde aquela cor e perde a cor do tipo também. Se você quiser um retorno de chamada * e * o estilo do tipo, faça a pesquisa dentro do retorno de chamada ou coloque o retorno de chamada em render.defaultEdgeStyle, onde ele cede ao mapa.

O renderizador de tela resolve menos

renderers/canvas honra edgeStyleMap mas lê apenas quatro das nove propriedades EdgeStyle. O renderizador SVG — o padrão — resolve todos eles.

Fazendo uma espécie de camada ​

Estilizar os tipos separados é apenas metade disso. UI.filter.edgeFacets declara quais dimensões de borda são switchable:

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

{ key: 'kind' } é uma declaração completa. Uma faceta de aresta tem padrões de formato de aresta: uma camada é multiselect e suas opções são derivadas dos valores distintos que suas arestas reais realmente carregam, portanto, ela nunca pode oferecer uma escolha que voltaria vazia.

Nada aqui é derivado automaticamente. Ao contrário de facetas de nó, que recorrem à varredura de seus dados, um gráfico que declara não edgeFacets se comporta exatamente como aquele que nunca ouviu falar de camadas. A visibilidade da borda é muito importante para ser adivinhada.

O painel de filtro cria uma seção Relationships: uma linha por tipo, cada uma com uma amostra de linha e uma contagem, aplicando de uma só vez — como os campos de atributos acima e a legenda ao lado. Nada nesse painel fica atrás de um botão de aplicação.

Facetas que não são camadas ​

type aceita o vocabulário de facetas completo — text, regex, select, multiselect, numberRange, boolean — mais accessor e predicate para dimensões que não são uma chave de dados simples:

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

Somente o tipo de seleção múltipla é uma alternância ativa. Todo o resto é um controle em lote, portanto ele é renderizado no formato de atributo do painel junto com as facetas do nó, onde um botão aplicar é a capacidade correta.

Opções ​

OpçãoTipoPadrãoO que isso faz
keystring—Filtrar identidade. setEdgeFilter(key, …) pega isso.
labelstringembelezado keyUsado literalmente, portanto pode vir de uma camada de tradução.
typeFilterFacetType'multiselect'Uma camada é um conjunto de tipos, cada um ativado ou desativado.
optionsarray | (graph) => arrayderivadoResolvido em relação ao gráfico ao vivo em cada reconstrução.
matchModeFilterMatchMode'exact'
accessor(edge) => unknownedge.getData()[key]O que torna possíveis as facetas computadas.
predicate(edge, value) => boolean—Controle total; vence accessor / matchMode. Executa por borda por aplicativo, portanto, mantenha-o barato.
ordernumberordem de declaraçãoOrdem de exibição no painel.

Alternar é uma lente, não uma mudança de layout ​

Desativar uma camada altera quais linhas são desenhadas e nada mais. As posições dos nós, a seleção e a câmera permanecem inalteradas bit a bit e nenhuma simulação é reiniciada.

Isso é deliberado e é o que as ferramentas profissionais fazem: vis-network carrega um hidden * e * por borda separado physics, o edgeReducer da Sigma nunca atinge o layout, o Cytoscape e o KeyLines re-layout apenas quando solicitado. Uma relação oculta é uma decisão de exibição. Se ocultar uma camada de correlação refluísse no gráfico, você não poderia usar a alternância para ler o gráfico, que é o ponto inteiro.

A única coisa que o dobra é o opt-in, e no lado do nó: UI.filter.hideDisconnected esconde os nós em uma camada de fios. Esses nós saem da simulação, então o gráfico é reinstalado – e é por isso que ele está desativado por padrão e é por isso que é um filtro de nó em vez de algo que o controle da camada faz por conta própria.

Mecanicamente, Edge cresceu uma segunda bandeira:

PropriedadeSignificado
layerVisibleSe a camada desta borda está ativada. Um veto sobre visible.
visibleIgnoringLayerO que seria visible se todas as camadas estivessem ativadas: apenas o ponto final e os motivos de ocultação manual.
setLayerVisible(on)Troque a camada, derivando novamente visible. Retorna se algo mudou.
representedEdgesPara um substituto: as verdadeiras vantagens que ele representa. Indefinido em uma borda real.

O link força as portas em visibleIgnoringLayer, então uma aresta cuja camada está desativada continua unindo suas extremidades. visible também é escrito pela filtragem de endpoint e por hideNode / showNode, e é por isso que o estado da camada vive separado dele.

Um nó deixado sem arestas visíveis permanece visível ​

Ocultá-lo seria uma decisão de filtro node, e uma faceta de borda nunca toma uma. Use o painel de filtro ou queryEngine.setFilter se for isso que você deseja.

Uma escolha vazia significa todas as camadas removidas ​

Para uma seleção múltipla de nó, uma lista vazia é como o painel soletra unset - sem restrição. Um controle de camada de borda grava exatamente o que permanece ativado, portanto, uma lista de camadas vazia significa que todas as camadas estão desativadas. Não há mais nada que isso possa significar, e é por isso que uma seção de borda não tem limite "não é possível desligar a última".

Digitando as camadas na legenda ​

Uma seção legend declarando scope: 'edge' lista os tipos com uma amostra line - cor do traço, traço e marcador conforme o renderizador os resolveu - ao lado das seções com escopo de nó no mesmo cartão encaixado:

ts
UI: {
    legend: {
        sections: [
            { key: 'type', title: 'Element' },                     // nodes
            { key: 'kind', title: 'Relationship', scope: 'edge' },  // edges
        ],
    },
}
  • Dada uma declaração edgeFacets correspondente, a seção direciona essa faceta — o painel e a legenda tornam-se duas visualizações de um filtro, uma seguindo a outra.
  • Sem ela, ela reserva uma faceta própria e ainda filtra, portanto uma legenda nunca é meramente decorativa.
  • Uma seção sem key nem entries usa render.edgeTypeAccessor, exatamente como uma seção de nó volta para nodeTypeAccessor.
  • As contagens são de arestas real: os substitutos de um cluster recolhido são excluídos, portanto, uma contagem nunca relata duas vezes uma relação.

As cores da amostra são amostradas a partir do que o renderizador pintou, portanto, um tipo renderizado em mais de uma cor mantém a primeira e avisa. Declare entries com seu próprio color quando os tipos não forem os codificados pelos traços.

Clusters recolhidos ​

Enquanto um cluster está fechado, uma aresta com uma extremidade dentro dela é desenhada para o cluster, em qualquer profundidade. Arestas cujas extremidades pousam nos mesmos dois nós na tela compartilham uma linha, um stand-in, então uma linha pode falar por várias relações reais de vários tipos. Os substitutos estão desenhando apenas: getEdges() e a guia Arestas da tabela lista as arestas reais.

Cada substituto, portanto, carrega representedEdges e sobrevive enquanto qualquer deles passa no filtro. Duas consequências que vale a pena conhecer:

  • Ocultar uma camada oculta um substituto apenas quando todas as relações por trás dela estiverem ocultas. Uma linha que representa reference e correlation permanece enquanto qualquer uma delas está ativada.
  • Cada tipo de faceta chega a um cluster recolhido - um numberRange ou um predicate, não apenas uma lista de tipos - porque a faceta é comparada com as arestas reais em vez dos próprios dados (vazios) do substituto.

Um substituto para uma única aresta se parece com aquela aresta, pontilhada, e clicar nele seleciona a aresta. Um substituto para vários é simples e selecioná-lo mostra quantas arestas ele representa.

Conduzindo a partir do código ​

As facetas da borda são registradas no construtor, portanto, a filtragem programática ** funciona em todos os modos** - apenas o painel é cromado no modo full.

ts
graph.queryEngine.setEdgeFilter('kind', { value: ['reference'], matchMode: 'exact' })
graph.queryEngine.getHiddenEdgeCount()      // 12
graph.queryEngine.removeEdgeFilter('kind')  // every layer back on
MétodoO que isso faz
setEdgeFilter(key, config)Escreva um filtro de borda.
getEdgeFilters()Os filtros de borda ativos, sem prefixo.
removeEdgeFilter(key)Solte um - esse conjunto de camadas volta.
getHiddenEdgeCount()Quantas arestas estão ocultas por uma camada.
getEdgeFacetValues(key)Os valores distintos, cada um com uma contagem e uma borda de amostra.
setEdgeFacets(facets)Substitua o conjunto declarado.
registerEdgeFacet(facet)Adicione um aditivo – o que uma seção de legenda usa para reservar a sua própria.
replaceFilters(ownedKeys, filters)Substitua apenas as chaves que você possui, deixando as outras em paz.

Os filtros de nó e borda compartilham um registro GraphFilters, com chaves de borda transportadas sob um prefixo edge: (EDGE_FILTER_PREFIX, exportado de GraphQueryEngine) para que um nome de chave possa ser uma faceta de nó e uma faceta de borda ao mesmo tempo. Cada método acima oculta o prefixo; você só o encontrará se ler o registro bruto.

renderer.getEdgeStyle(edge) é público, retornando o estilo que o renderizador realmente resolveu — a mesma chamada na qual as amostras da legenda são construídas e a maneira de afirmar em um teste que o que você declarou é o que está pintado.

Veja também ​