Layout do Gráfico
Pivotick suporta múltiplas estratégias de layout para posicionar nós. Você pode configurar o layout através da opção layout, ou através de simulation.layout; quando ambos estão definidos, layout vence.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
type | 'force' | 'tree' | 'structured' | 'force' | O algoritmo de layout a ser usado. 'force' aplica um layout de força baseado na física. 'tree' organiza os nós em uma estrutura de árvore. 'structured' desenha um pequeno gráfico como uma pessoa faria, veja Layout Estruturado. |
Opções de layout de árvore
Quando type: 'tree' é selecionado, as seguintes opções adicionais estão disponíveis:
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
rootId | string | undefined | Especifique o ID do nó a ser usado como raiz. Se não for fornecido, Pivotick selecionará automaticamente uma raiz baseada em rootIdAlgorithmFinder. |
rootIdAlgorithmFinder | - | MaxReachability | Algoritmo usado para encontrar automaticamente o nó raiz. |
parentKey | string | undefined | Nome da chave node.data que contém o id do pai do nó. Indefinida, a paternidade vem das bordas. Consulte Declarando a hierarquia. |
depthKey | string | undefined | Nome da chave node.data que contém a linha em que o nó está, contando a partir de 0. Não definido, um nó fica uma linha abaixo de seu pai. |
strength | number | 0.1 | A força da força para manter a estrutura da árvore. |
horizontal | boolean | false | Organize os nós horizontalmente em vez de verticalmente. |
radial | boolean | false | Coloque os nós em um layout radial em vez de vertical. |
radialGap | number | 750 | Espaço entre camadas no layout radial. |
spacing | 'auto' | 'manual' | 'auto' | Se os dois multiplicadores abaixo se ajustam ao tamanho dos nós e ao formato da árvore. Definir qualquer um deles implica explicitamente 'manual'. |
levelSpacing | number | 1 | Multiplica a distância entre níveis consecutivos (entre anéis, quando radial). 1 ajusta a árvore à tela, como antes. |
siblingSpacing | number | 1 | Multiplica a distância entre os nós dentro de um nível. Ignorado quando radial, onde um nível sempre abrange o círculo completo. |
Ciclos e gráficos desconectados
A hierarquia é construída a partir de uma árvore abrangente em largura com raiz em rootId, não a partir das bordas brutas, portanto, nenhum deles interrompe um layout de árvore:
- Ciclos. A primeira aresta a alcançar um nó torna-o pai desse nó; uma aresta que chega a um nó já colocado simplesmente não faz parte da hierarquia e é desenhada cruzando níveis como qualquer outra aresta. Um nó com dois pais também é reivindicado por um deles.
- Vários componentes. Cada componente que a raiz não pode alcançar obtém sua própria raiz — preferindo um nó sem arestas de entrada, então um componente que é uma hierarquia é desenhado como um — e os componentes são dispostos lado a lado.
- Nós sem arestas. Eles não têm lugar em uma hierarquia, então são estacionados em vez de receberem um: eles preenchem o espaço morto ao lado dos níveis rasos, na borda de fuga do layout, que uma árvore deixa vazia porque se alarga à medida que desce. No layout radial eles levam um anel próprio fora do último. Apenas os nós que apontam para nada em * e * que apontam para nada estão estacionados, portanto, nenhuma borda é deixada estendendo-se da árvore até a área de estacionamento.
Observe que rootIdAlgorithmFinder: 'MinMaxDistance' e 'MinHeight' são a mesma pesquisa - o nó cujo caminho mais longo é o mais curto - e ambos são baseados em classificação topológica, portanto, em um gráfico com um ciclo, eles avisam e retornam para o primeiro nó. O padrão 'MaxReachability' não tem tal limitação.
Localizador de raiz de árvore
Em um layout baseado em árvore, um nó deve ser designado como raiz. Você pode especificar o nó raiz usando a opção rootId.
Se você não tiver certeza de qual nó deve ser a raiz, não se preocupe! Pivotick selecionará automaticamente um usando o algoritmo definido em rootIdAlgorithmFinder.
Um nome rootId é percorrido ignorando a direção da borda, então qualquer nó pode enraizar uma árvore inteira - escolha uma folha e o gráfico fica novamente pendurado abaixo dela, com seu antigo pai um nível abaixo. (Seguir as setas de uma folha não levaria a nada, e o resto do gráfico seria disposto ao lado dela como um segundo componente.) As bordas em si ainda são desenhadas como estão, então um link usado ao contrário é renderizado como uma seta apontando para um nível acima. Uma raiz escolhida por rootIdAlgorithmFinder continua caminhando ao longo das setas: ela foi lida nelas em primeiro lugar.
A menos que as setas não sejam uma hierarquia. Em dados que convergem - cada folha é uma fonte, todas apontando para alguns hubs, que é a forma da maioria dos dados de proveniência e "vistos com" - nenhum nó atinge o gráfico ao longo das setas, qualquer que seja o localizador solicitado. Um passeio direcionado deixa quase todos os nós como uma raiz própria, e a árvore surge como um pente de tocos com a maioria das arestas voando pelo layout. Portanto, quando nenhum nó pode cobrir metade de seu próprio componente seguindo as setas, o layout para de ler a direção: ele percorre todas as arestas em ambos os sentidos e enraíza-se novamente no meio do gráfico. As arestas ainda estão desenhadas exatamente como estão, o que significa que suas setas apontam para cima na árvore em direção à raiz — a representação honesta dos dados que apontam nessa direção.
Isto é uma alternativa, não a regra, e o teste é sobre o gráfico e não sobre a raiz que foi escolhida:
- Onde as setas formam uma hierarquia, elas são a melhor opção para definir, e cada descobridor mantém sua própria resposta - incluindo
'MinHeight', cuja resposta em uma árvore é sempre uma folha, não alcançando nada propositalmente. Uma raiz que cobre pouco do gráfico só é anulada quando nada mais poderia cobrir mais. - Um gráfico de várias hierarquias separadas ainda aparece como uma floresta: a cobertura é medida em relação ao próprio componente raiz, portanto, uma hierarquia multicomponente perfeitamente boa não é confundida com uma falha.
- Um
rootIdfixado ignora completamente o teste - ele já foi percorrido ignorando a direção.
Um rootId nomeando um nó que não está no gráfico - filtrado, excluído, dentro de um cluster recolhido - é ignorado enquanto isso for verdade, e o localizador escolhe a raiz. A opção é mantida, então o nó que retorna enraíza novamente a árvore.
A raiz também pode ser alterada em tempo de execução — o submenu Física a oferece como um menu na linha Root sempre que um layout de árvore estiver ativo. O menu nomeia cada localizador e diz o que ele faz; sua primeira entrada é "o nó que selecionei", que fica esmaecido até que exatamente um nó seja selecionado. Depois que um nó é fixado, a linha mostra o rótulo desse nó em vez do nome do localizador:
graph.simulation.setTreeRoot({ rootId: 'node-42' }) // pin the tree to a node
graph.simulation.setTreeRoot({ algorithm: 'MinHeight' }) // drop the pin, let the finder choose
graph.simulation.getTreeRoot() // { rootId: undefined, algorithm: 'MinHeight' }Declarando a hierarquia
Normalmente Pivotick resolve a hierarquia por si mesmo: paternidade de uma árvore geradora nas bordas e cada nó uma linha abaixo de seu pai. Duas opções permitem que os dados digam isso.
parentKeynomeia a chavenode.dataque contém o ID pai de um nó.depthKeynomeia a teclanode.dataque contém a linha em que ela está, contando a partir de0na raiz mais rasa.
Ambos não são definidos por padrão e nada é lido até que você nomeie uma chave - portanto, um gráfico cujos dados carregam um campo depth ou parent não é afetado até que você o solicite.
Qualquer uma das chaves pode estar faltando em qualquer nó individual. Um nó sem pai declarado obtém um das bordas como de costume; um nó sem linha declarada fica um abaixo de seu pai. Um gráfico que não declara nenhum dos dois é apresentado exatamente como sempre foi.
Começando uma árvore mais abaixo
É para isso que depthKey serve principalmente. Um gráfico de várias árvores separadas é uma floresta, e todas as suas raízes compartilham a linha mais rasa — anteriormente não havia como dizer que uma delas pertencia mais abaixo. Dê uma linha a essa raiz:
const data = {
nodes: [
{ id: 'a' }, { id: 'a1' }, { id: 'a2' },
{ id: 'b', data: { level: 2 } }, { id: 'b1' }, { id: 'b2' },
],
edges: [
{ from: 'a', to: 'a1' }, { from: 'a', to: 'a2' },
{ from: 'b', to: 'b1' }, { from: 'b', to: 'b2' },
],
}
const graph = new Pivotick(container, data, {
layout: { type: 'tree', depthKey: 'level' },
})row 0 a
row 1 a1 a2
row 2 b <- b asked for row 2
row 3 b1 b2A árvore inteira se move com sua raiz: os filhos de b a seguem. No layout radial, uma linha é um ring, então a mesma declaração inicia aquela árvore com dois anéis.
O que as linhas significam
Uma linha só pode empurrar um nó mais para baixo. Uma árvore organizada coloca um filho exatamente uma linha abaixo de seu pai, portanto, uma linha que não esteja abaixo do pai não pode ser desenhada - ela é fixada em parent + 1 e um aviso indica quantas eram. A alternativa, honrar a linha separando o nó de seu pai, permitiria que um número errado quebrasse a árvore em pedaços extras.
As linhas que os dados ignoram ficam vazias e ocupam espaço real - é isso que faz uma linha declarada significar alguma coisa. Uma árvore é dimensionada para caber na tela, portanto, declarar a linha 50 em um gráfico raso aumenta a altura de cinquenta e uma linhas e reduz cada linha real a nada. levelSpacing e espaçamento automático são o caminho de volta.
O que um pai declarado faz
Um pai declarado é honrado independentemente de uma aresta se juntar ao par ou não. Isso o torna capaz de estabelecer uma hierarquia que as arestas não contêm - mas também significa que tal ramo é desenhado sem nenhuma linha ao longo dele, porque não há nenhuma aresta para desenhar. A declaração é de onde vem a hierarquia; as bordas só são desenhadas como estão.
Duas consequências úteis: um nó com várias arestas de entrada pode dizer qual delas é seu pai real (caso contrário, a árvore geradora pegaria o que a alcançasse primeiro) e um nó sem nenhuma aresta que declara que um pai obtém um lugar adequado na árvore em vez da área estacionada.
As declarações que não podem ser usadas são descartadas para esse nó, que então volta para as bordas, e um aviso registra todas elas:
- um pai nomeando um nó que não está sendo disposto - filtrado, excluído ou dentro de um cluster recolhido - da mesma forma que
rootIdretorna quando o nó que ele nomeia está faltando; - um pai que fecharia um ciclo (o elo que o fecha é o que foi descartado);
- uma linha que não é um número
0ou superior.
Precedência
rootId supera parentKey. Fixar uma raiz - nas opções ou escolher uma no trilho de Física - é uma solicitação para recolocar a árvore naquele nó, de modo que a paternidade seja derivada novamente das bordas e os pais declarados sejam reservados para essa passagem. As linhas declaradas não são afetadas: elas dizem a profundidade de um nó, não em que ponto ele está pendurado.
egoTree ignora ambas as opções; constrói uma estrela a partir dos vizinhos da própria raiz.
Espaçamento
Uma árvore é disposta para encaixar na tela, então seu espaçamento natural já segue o tamanho da tela e a profundidade e largura da árvore. levelSpacing e siblingSpacing escala que ajusta a geometria: 2 significa "duas vezes mais distante que o layout ajustado" nos eixos de profundidade e largura, respectivamente. Ambos têm como padrão 1, que é o próprio layout ajustado, e ambos aceitam 0.5 a 10.
Eles também podem ser alterados em tempo de execução — o submenu Física os oferece como controles deslizantes sempre que um layout de árvore está ativo (no lugar dos botões de simulação, que um layout de árvore ignora):
graph.simulation.setTreeSpacing({ levelSpacing: 2 })
graph.simulation.getTreeSpacing() // { levelSpacing: 2, siblingSpacing: 1 }Espaçamento automático
Por padrão, a árvore trabalha com seu próprio espaçamento. Um layout de árvore é dimensionado a partir da tela e nunca analisa o tamanho dos nós; portanto, uma árvore de pequenos pontos e uma árvore de grandes nós rotulados são dispostas de forma idêntica - e a segunda se sobrepõe. spacing: 'auto' mede o par de vizinhos mais próximo em cada eixo, calcula o que eles precisam para seus raios (além de espaço para uma ponta de seta entre os níveis) e dimensiona os multiplicadores para se adequar. Ele os deriva sempre que o gráfico muda.
Duas coisas que deliberadamente não fará:
- Ele nunca compacta uma árvore mais apertada do que o layout ajustado. Auto só aumenta um multiplicador acima de
1, então um gráfico que nunca foi lotado é apresentado exatamente como sempre foi. - Nunca substitui uma escolha que você fez. Uma árvore que define
levelSpacingousiblingSpacingé considerada como tendo se decidido, e mover qualquer um dos controles deslizantes manualmente deixa o modo automático para sempre. Devolva os multiplicadores comsimulation.enableAutoTreeSpacing().
No layout radial, onde um nível sempre abrange o círculo completo, o amontoamento dentro de um anel só pode ser aliviado empurrando os anéis ainda mais para fora - então ambas as medições levam levelSpacing até lá.
Layout Estruturado
type: 'structured' apresenta um pequeno gráfico do jeito que uma pessoa o desenharia, para um visualizador incorporado que deve ser legível à primeira vista. É calculado em vez de simulado: cada nó é colocado e fixado, de modo que o mesmo gráfico sempre fornece a mesma imagem, e arrastar um nó move apenas esse nó.
- Componentes lado a lado. Cada componente conectado é disposto individualmente e os componentes são compactados em linhas. A largura da linha é aquela que ajusta todo o layout à tela com o maior zoom.
- Os leques se alinham. Três ou mais folhas do mesmo tipo penduradas no mesmo um ou dois nós (uma folha tem no máximo dois vizinhos) ficam em uma linha ou coluna uniformemente espaçadas, com os nós nos quais eles ficam pendurados em um lado dela. O agrupamento é aquele que a regra mesmos vizinhos do Simplify usa, digitado por
UI.simplify.typeOf, caso contrário,render.nodeTypeAccessor. Nada está dobrado. - As distâncias seguem as arestas. Todo o resto é colocado pela majoração de tensão: nós com uma aresta de distância ficam separados por um comprimento de aresta, duas arestas separadas por duas vezes isso e assim por diante. Os vizinhos de um hub contornam-no e uma corrente sai reta, sem nenhum caso especial para nenhum dos dois.
- Uma bandeja para nós soltos. Nós sem borda são colocados em grade abaixo do resto, ou à sua direita.
- Rótulos no lado livre. Um rótulo flutuante (
textVerticalShiftoutextHorizontalShiftde 1 ou mais) é desenhado no lado de seu nó que suas bordas deixam livres, tão longe quanto o estilo o define. Todos os rótulos de um ventilador ficam voltados para longe dos nós nos quais ele está pendurado.
O layout é refeito sempre que o gráfico visível muda, e novamente após o primeiro desenho, quando os tamanhos reais dos nós são conhecidos. Os nós são espaçados por getLayoutRadius(), portanto, um estilo de nó que declara layoutSize é disposto nesse tamanho, independentemente do que for desenhado. Destina-se a gráficos de até algumas centenas de nós.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
groupMin | number | 3 | O menor número sai compartilhando um tipo e seus vizinhos fazem fila como torcedores. Menos são colocados um por um. |
direction | 'auto' | 'row' | 'column' | 'auto' | Como um fã se alinha. 'auto' fica do outro lado da linha até os nós nos quais ele está pendurado. |
gap | number | 30 | Limpe o espaço entre dois nós. Uma aresta é desenhada com o dobro do comprimento; os componentes e a bandeja ficam três vezes mais separados. |
trayPosition | 'bottom' | 'right' | 'bottom' | Para onde vão os nós sem arestas. |
labelSides | boolean | true | Mova cada etiqueta flutuante para o lado que suas bordas deixam livres. false deixa rótulos onde o estilo os coloca. |
new Pivotick(container, data, {
layout: { type: 'structured', trayPosition: 'right' },
render: {
nodeTypeAccessor: (node) => node.getData().type,
defaultNodeStyle: { text: (node) => node.getData().label, textVerticalShift: -1.4 },
},
})graph.simulation.getLabelSide(id) informa o lado escolhido para um nó: 'top', 'bottom', 'left' ou 'right', ou undefined em qualquer outro layout.
A visualização é ajustada aos nós, não aos seus rótulos, portanto, um rótulo longo ao lado de um nó na borda do layout pode passar pela tela.
perigo
Adicione exemplo com os dois layouts!
Árvore vertical
const options = {
layout: {
type: 'tree'
},
UI: {
mode: 'static'
}
}Árvore vertical
const options = {
layout: {
type: 'tree',
radial: true,
radialGap: 300,
},
UI: {
mode: 'static'
}
}