Pular para o conteúdo

Lenda ​

UI.legend encaixa uma legenda em um canto da tela: uma linha por categoria, cada uma com uma amostra de cor, um rótulo e o número de nós atrás dela. Apontar para uma linha ilumina essa categoria na tela; clicar em um o oculta, de modo que a legenda também funciona como o filtro mais rápido na interface do usuário.

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

Você pode nem precisar desse bloco: sem UI.legend, uma legenda aparece por conta própria quando as cores do seu gráfico são explicadas por um render.nodeTypeAccessor declarado - consulte Ativado por padrão. UI.legend: false desliga.

Um gráfico que codifica várias coisas ao mesmo tempo precisa de várias chaves: passe sections em vez de uma única chave — veja Várias chaves ao mesmo tempo.

A legenda faz parte do cromo, portanto aparece apenas nos modos full e light — viewer e static não têm nenhuma.

A lenda nunca colore nada

A legenda é descritiva. Ele lê a cor que o renderizador já resolveu para um nó (renderer.getNodeStyle(node).color) e a reporta. A coloração permanece onde estava: render.defaultNodeStyle.color, render.nodeStyleMap ou um estilo por nó - normalmente via ColorPaletteMapper. Mude a paleta e a legenda seguirá sozinha.

Ativado por padrão ​

Uma lenda só vale a pena ser mostrada sem ser perguntada se for garantido que ela diz a verdade, então a automática começa com algo que você já declarou, e não com uma suposição sobre seus dados: render.nodeTypeAccessor, a dimensão para a qual você aponta nodeStyleMap.

Porém, ter um candidato não é suficiente — uma dimensão pode particionar seus dados sem ter nada a ver com suas cores. Portanto, antes de renderizar qualquer coisa, a legenda verifica se esta dimensão explica as cores:

  • cada categoria resolve exatamente one cor;
  • existem pelo menos duas cores distintas (caso contrário, as cores não diferenciam as categorias);
  • existem no máximo 24 categorias — uma dimensão semelhante a id produz um valor por nó, cada um com sua própria cor, que de outra forma passaria pela verificação acima.

Se alguma dessas coisas falhar, nenhuma legenda aparecerá e nada será registrado: você não pediu uma. A legenda automática também é ignorada além de 5000 nós, onde não vale a pena pagar pela amostragem da cor de cada nó sem ser convidado - isso avisa, então a ausência é explicável. Declarar UI.legend (ou true) eleva ambos.

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

Uma legenda automática é chaveada em um acessador em vez de uma chave de dados, portanto, não pode compartilhar um filtro com uma faceta declarada - forneça um key explícito para isso.

De onde vêm as entradas ​

Derivado de uma chave de dados ​

key é toda a configuração no caso comum: os valores distintos de node.getData()[key] tornam-se as linhas, na ordem vista pela primeira vez, rotuladas com o próprio valor.

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

Uma chave com valor de array contribui com uma linha por elemento e um nó conta em cada um deles (tags: ['public', 'critical'] aparece em ambos).

Vale a pena saber duas coisas sobre linhas derivadas:

  • Nós sem valor para a chave (null, undefined, '') não recebem linha e a legenda nunca pode ocultá-los - ela atua apenas nas categorias que lista. Um aviso em tempo de desenvolvimento nomeia a chave e quantos nós não estão representados.
  • Uma categoria pintada em mais de uma cor mantém a primeira cor encontrada, com um aviso: uma linha só pode mostrar uma amostra. Declare você mesmo as entradas se o agrupamento não for realmente de uma cor.

Entradas declaradas ​

Passe entries quando as categorias não são uma chave de dados simples — agrupamentos computados, rótulos traduzidos, uma escala de cores fixa. Cada entrada carrega seu próprio predicate, que é o que o botão de alternância filtra.

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 também pode ser uma função do gráfico, resolvida novamente sempre que os dados mudam - então a lista segue os dados sem a agitação da declaração:

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

Declarar entries e key é o meio-termo útil mostrado acima: as entradas fornecem os rótulos e cores, e key fornece o predicado padrão (data[key] === id) para entradas que não possuem um. Uma entrada sem aviso e sem correspondência com nada.

O estado de alternância de uma linha sobrevive a uma nova resolução; uma entrada que desaparece dos dados os perde, em vez de permanecer como um fantasma oculto.

Várias chaves ao mesmo tempo ​

Um gráfico que codifica três coisas – tipo no preenchimento, procedência no recinto, relacionamento no traço – precisa de três chaves, não de uma. Passe sections e cada um recebe seu próprio bloco intitulado, empilhado de cima para baixo em ordem de declaração dentro de um cartão encaixado:

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

Uma seção leva tudo o que uma única legenda leva, exceto position, que pertence ao cartão. As seções são independentes em todos os outros aspectos: suas próprias entradas, suas próprias contagens, seu próprio estado de dobra, seu próprio filtro.

Filtros e juntos ​

Cada seção filtrável aciona seu próprio filtro, e o mecanismo de consulta os desliga: desligue attribute na primeira seção e self na segunda, e o que permanece na tela são os nós que são nem. O show all de cada seção limpa apenas aquela seção.

Uma seção grava em __legend:<id>, onde o id é o id da seção, caso contrário é key, caso contrário, seu lugar na pilha (section-0). Uma legenda single-key — a forma do objeto — mantém o __legend simples que sempre usou. Tal como acontece com uma lenda solitária, uma seção cujo key nomeia uma faceta select / multiselect declarada direciona essa faceta, e seus irmãos não são afetados.

Codificando arestas em vez de nós ​

Uma seção declarando scope: 'edge' lista os tipos de relação do gráfico com uma amostra line - cor do traço, traço e marcador conforme o renderizador os resolveu - e seus botões de alternância ocultam camadas de borda em vez de nós. Ele fica ao lado das seções com escopo de nó no mesmo cartão e as deixa intocadas. Consulte Camadas de borda para ver o recurso completo.

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

Nomeando a dimensão que você estiliza ​

Uma seção sem key nem entries é a dimensão que você já declarou como render.nodeTypeAccessor — aquela que significa uma dimensão de estilo que não é uma chave de dados simples. Ele pula a verificação de cores: dentro de uma lista sections que você solicitou. Apenas uma seção pode fazer isso; um segundo é descartado com um aviso.

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

Uma seção desvinculada da dimensão da cor

As amostras são amostradas a partir das cores que o renderizador resolveu, e esse é o único canal que a legenda pode ler. Uma seção digitada em uma dimensão que as cores não codificam — proveniência, quando a proveniência é desenhada como um recinto — obtém a cor do primeiro nó em cada categoria e avisa que a categoria renderiza mais de um. Declare entries com seu próprio color nessa seção para fornecer amostras que signifiquem algo.

Espaço ​

As seções são dobradas individualmente; alt-clique em qualquer divisa para dobrar ou desdobrar a pilha inteira de uma vez. Cada seção ainda rola sua própria lista além de maxVisibleEntries, e o cartão como um todo é limitado à altura da tela e rola em vez de crescer além dela - portanto, seis seções permanecem dentro da janela de visualização.

Uma seção que resolve nenhuma entrada (uma chave que nenhum nó carrega, uma declaração vazia) é totalmente ignorada em vez de ser mostrada como uma caixa com título vazia; um cartão cujas seções estão todas vazias não renderiza absolutamente nada.

Passando o mouse sobre uma categoria ​

Aponte para uma linha e a tela responde: os elementos que essa categoria representa mantêm a aparência que têm, todo o resto escurece até que o ponteiro saia. Nada é filtrado e nada se move, por isso é a maneira barata de descobrir onde está uma categoria antes de decidir se deve desligá-la.

Uma linha em uma seção scope: 'edge' ilumina suas linhas, com os nós recuando junto com as outras camadas. Uma aresta dobrada em um cluster fechado ou um grupo simplify ilumina a linha desenhada em seu lugar.

Apontar para uma categoria que já está desligada não escurece nada — não resta nada na tela para acender.

highlightOnHover: false desativa para uma seção. Uma tecla filterable: false também destaca onde é a única coisa que uma linha faz.

O mesmo conjunto pode ser aceso a partir do código e permanece até ser apagado:

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

Ambos pegam os elementos que o renderizador desenhou, então eles leem getMutableNodes() / getMutableEdges() em vez dos getters de clonagem.

Filtragem ​

Alternar uma linha grava um filtro em graph.queryEngine - o mesmo mecanismo que o filter panel aciona - então nada sobre isso é um segundo mecanismo paralelo:

  • Os nós adicionados após uma alternância, em uma categoria oculta, chegam ocultos.
  • Uma categoria oculta permanece oculta dentro do subgráfico de um cluster expandido.
  • resetFilters(), ou Reset do painel, acende novamente cada linha.
  • Com cada linha mostrada, o filtro é removido, então a contagem de filtros ativos do cabeçalho não relata uma legenda que não esconde nada.

Por padrão, a legenda possui uma chave de filtro reservada própria (__legend) correspondida por meio de um predicado - uma por seção, com espaço de nome __legend:<id>, na forma empilhada.

Compartilhando um filtro com o painel ​

Quando key nomeia uma select / multiselect faceta declarada, a legenda direciona a chave dessa faceta. A legenda e o painel tornam-se então duas visualizações de um filtro: desative uma amostra e a seleção múltipla do painel descarta esse valor; escolha valores no painel e a legenda acenderá novamente de acordo.

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

Duas consequências de falar a língua do painel:

  • Uma seleção múltipla empty significa nenhuma restrição para o painel, então a legenda não pode expressar "ocultar tudo": a última linha mostrada é desativada em vez de escrever um valor que traria todo o gráfico de volta. Uma legenda por si só não tem esse limite.
  • Nós com nenhum valor para a chave são ocultados por qualquer seleção, exatamente como ficam quando você filtra no painel.

Se key nomeia uma faceta de outro tipo (text, regex, numberRange, boolean) a legenda mantém sua própria chave e avisa — uma lista de valores não tem significado ali.

Lendo os alternadores ​

Cada alternância é anunciada no barramento de eventos data:

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

section é o id da seção que foi alternada e hidden / visible são as linhas dessa seção. Uma legenda de chave única relata seu próprio ID derivado (é key, caso contrário, section-0).

Restaurar é a mesma chamada que a lenda faz:

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

Alterando a legenda em tempo de execução ​

graph.setLegend(config) substitui UI.legend ao vivo. Um gráfico que começou sem legenda é construído imediatamente e false o remove - limpando seu filtro com ele, para que nada fique escondido atrás de uma legenda que desapareceu.

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

Alternar entre os dois formulários renomeia as chaves de filtro, de modo que os filtros do formulário que você deixou são descartados: nada fica escondido atrás de uma seção que desapareceu.

Opções ​

UI.legend também recebe um booleano: false suprime a legenda, true deriva uma de render.nodeTypeAccessor sem verificar as cores primeiro. Tudo abaixo é a forma do objeto.

O formato stacked é { enabled?, position?, sections }: sections é a lista abaixo menos position e enabled / position descreve o cartão inteiro.

OpçãoTipoPadrãoO que isso faz
enabledbooleantruefalse mantém a declaração, mas não mostra nada (o mesmo que legend: false).
idstringkey, senão section-<index>Identidade da seção: sua chave de filtro (__legend:<id>) e o campo legendToggle section.
titlestringembelezado key, senão 'Legend'Texto do cabeçalho, usado literalmente (para que possa ser traduzido).
keystring—Chave de dados da qual as linhas são derivadas e o predicado padrão para entradas declaradas. Omita isso e entries para a legenda automatic.
entriesLegendEntry[] | (graph) => LegendEntry[]—Linhas declaradas; uma função é resolvida novamente na alteração de dados.
position'bottom-left' | 'bottom-right' | 'top-left' | 'top-right''bottom-right' no modo full, caso contrário 'bottom-left'Em qual canto da tela ele se encaixa. Pertence ao cartão, portanto é não uma opção de seção. 'top-left' compartilha sua âncora com o painel de ferramentas contextuais, que cobre a legenda enquanto está aberto.
collapsiblebooleantrueMostre a divisa que o dobra ao título.
collapsedbooleanfalseComece dobrado.
showCountsbooleantrueMostre a contagem de nós por categoria (em todo o gráfico, para que não pisque quando você alterna).
filterablebooleantruefalse renderiza uma chave simples e não interativa.
highlightOnHoverbooleantruePassar o mouse sobre uma linha ilumina essa categoria na tela e escurece o restante.
maxVisibleEntriesnumber12Linhas mostradas antes da lista rolar dentro da legenda.

Um LegendEntry é { id, label?, color, badge?, predicate?, order? }. id é a identidade da linha, o valor gravado no filtro e o substituto do rótulo.

Quando os nós são diferenciados por um badge em vez de por seu preenchimento, forneça a entrada badge e a linha mostrará a marca em vez de um ponto. Ele é desenhado da maneira que a tela o desenha, e um crachá com o nome nº color ocupa a entrada. A filtragem permanece inalterada.

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

maxVisibleEntries é um teto na altura da legenda; a tela é a outra.

No modo full, a legenda é encaixada no canto inferior direito por padrão, empilhando-se acima do minimapa que o modo também monta: a coluna esquerda pertence ao trilho de modo e seus painéis, e um modo de trilho cujo painel é um espaço de trabalho em vez de uma folha de configurações chega longe o suficiente para deixar a legenda em uma única linha. Onde um minimapa compartilha o canto escolhido, a legenda fica em cima dele, acompanhando-o conforme ele é recolhido, redimensionado ou desmontado.

Uma legenda bottom-left compartilha sua coluna com o trilho de modo, portanto, em uma janela de visualização curta, ela encolhe para o espaço entre eles e rola em qualquer linha que aparecer, em vez de crescer sob o trilho. Ele nunca se move sozinho.

Interação ​

GestoEfeito
Passe o mouse sobre uma linhaIlumine essa categoria na tela, escureça todo o resto
Clique em uma linhaOcultar/mostrar essa categoria
Alt-clique em uma linhaMostrar apenas essa categoria
Cabeçalho mostrar tudoIlumine novamente cada linha (limpa o filtro da legenda)
Cabeçalho inverterTrocar quais categorias são mostradas
Cabeçalho chevronDobre essa seção até o título
Alt-clique na divisaDobre ou desdobre cada seção de uma vez

As linhas são botões reais: acessíveis por tabulação, alternância Enter / Space e aria-pressed carrega o estado. Uma linha oculta é desenhada com uma amostra vazia ** e ** texto esmaecido, então a cor nunca é o único sinal.

Veja o cartão da galeria Legenda filtrável para um exemplo ao vivo, e LegendOptions / LegendGroupOptions para os tipos completos.