Desenvolvimento de diagramas de árvore dinâmicos usando SVG e Vue.js

O material, cuja tradução publicamos hoje, é dedicado ao processo de desenvolvimento de um sistema de visualização para diagramas de árvores dinâmicos. Para desenhar curvas cúbicas de Bezier, a tecnologia SVG (Scalable Vector Graphics, gráficos vetoriais escaláveis) é usada aqui. O trabalho reativo com dados é organizado por Vue.js.

Aqui está uma versão demo do sistema com o qual você pode experimentar.


Árvore interativa

A combinação dos poderosos recursos do SVG e da estrutura Vue.js. nos permitiu criar um sistema para criar diagramas baseados em dados, interativos e personalizáveis.

Um diagrama é uma coleção de curvas cúbicas de Bezier começando em um ponto. As curvas terminam em pontos diferentes eqüidistantes entre si. Sua posição final depende dos dados inseridos pelo usuário. Como resultado, o gráfico é capaz de reagir reativamente às alterações de dados.

Primeiro, falaremos sobre como as curvas cúbicas de Bezier são formadas, depois descobriremos como representá-las no sistema de coordenadas do elemento <svg> e falaremos sobre a criação de máscaras para imagens.

A autora do material diz que ela preparou muitas ilustrações para ele, tentando torná-lo compreensível e interessante. O objetivo do material é ajudar todos a adquirir o conhecimento e as habilidades necessárias para desenvolver seus próprios sistemas de diagramação.

Svg


▍ Como são formadas as curvas cúbicas de Bezier?


As curvas usadas neste projeto são chamadas de Curva Cúbica de Bezier. A figura a seguir mostra os principais elementos dessas curvas.


Elementos-chave de uma curva cúbica de Bezier

A curva é descrita por quatro pares de coordenadas. O primeiro par (x0, y0) é o ponto de ancoragem inicial da curva. O último par de coordenadas (x3, y3) é o ponto de referência final.

Entre esses pontos, você pode ver os chamados pontos de controle. Este é o ponto (x1, y1) e o ponto (x2, y2) .

A localização dos pontos de controle em relação aos pontos de controle determina o formato da curva. Se a curva fosse definida apenas pelos pontos inicial e final, pelas coordenadas (x0, y0) e (x3, y3) , essa curva pareceria um segmento reto localizado na diagonal.

Agora, usaremos as coordenadas dos quatro pontos descritos acima para plotar a curva usando o elemento SVG <path> . Aqui está a sintaxe usada no elemento <path> para construir curvas cúbicas de Bezier:

 <path D="M x0,y0 C x1,y1 x2,y2 x3,y3" /> 

A letra , que pode ser vista no código, é uma abreviação de Cubic Bezier Curve. Letra minúscula ( c ) significa o uso de valores relativos, maiúscula ( C ) significa o uso de valores absolutos. Eu uso valores absolutos para construir o diagrama, isso é indicado pela letra maiúscula usada no exemplo.

Criando um diagrama simétrico


A simetria é um aspecto essencial deste projeto. Para construir um diagrama simétrico, usei apenas uma variável, recebendo em sua base valores como altura, largura ou coordenadas do centro de um determinado objeto.

Vamos nomear esse size variável. Como o gráfico é orientado horizontalmente - a variável de size pode ser considerada como todo o espaço horizontal disponível para o gráfico.

Atribua a esta variável um valor realista. Usaremos esse valor para calcular as coordenadas dos elementos do gráfico.

 size = 1000 

Localizando as coordenadas dos elementos do gráfico


Antes de encontrarmos as coordenadas necessárias para construir o diagrama, precisamos lidar com o sistema de coordenadas SVG.

▍Coordenar sistema e viewBox


O atributo do elemento <svg> viewBox muito importante em nosso projeto. O fato é que ele descreve o sistema de coordenadas do usuário da imagem SVG. Simplificando, o viewBox determina a posição e o tamanho do espaço em que a imagem SVG visível na tela será criada.

O atributo viewBox consiste em quatro números que especificam os parâmetros do sistema de coordenadas e os seguintes nesta ordem: min-x , min-y , width , height . Os parâmetros min-x e min-y definem a origem do sistema de coordenadas do usuário, os parâmetros de width e height definem a largura e a altura da imagem exibida. Aqui está a viewBox atributo viewBox :

 <svg viewBox="min-x min-y width height">...</svg> 

A variável de size que descrevemos acima será usada para controlar os parâmetros de width e height deste sistema de coordenadas.

Posteriormente, na seção Vue.js, vincularemos o viewBox a uma propriedade computada para especificar os valores de width e height . Além disso, em nosso projeto, as propriedades min-x e min-y sempre serão definidas como 0.

Observe que não usamos os atributos height e width do próprio elemento <svg> . Vamos configurá-los para width: 100% e height: 100% usando CSS. Isso nos permitirá criar uma imagem SVG que se ajusta de maneira flexível ao tamanho da página.

Agora que o sistema de coordenadas do usuário está pronto para desenhar um gráfico, vamos falar sobre o uso da variável size para calcular as coordenadas dos elementos do gráfico.

▍ Coordenadas invariáveis ​​e dinâmicas



Conceito gráfico

O círculo em que o desenho é exibido faz parte do diagrama. É por isso que é importante incluí-lo nos cálculos desde o início. Vamos, com base na ilustração acima, descobrir as coordenadas do círculo e de uma curva experimental.

A altura do gráfico é dividida em duas partes. Estes são topHeight (20% do size ) e bottomHeight (os 80% restantes do size ). A largura total do diagrama é dividida em 2 partes - o comprimento de cada uma delas é de 50% do size .

Isso faz com que a conclusão dos parâmetros do círculo não exija explicações especiais (aqui os indicadores topHeight e topHeight são usados). O parâmetro radius está definido como metade do valor de topHeight . Graças a isso, o círculo se encaixa perfeitamente no espaço disponível.

Agora vamos dar uma olhada nas coordenadas das curvas.

  • As coordenadas (x0, y0) definem o ponto de referência inicial da curva. Essas coordenadas permanecem constantes o tempo todo. A coordenada x0 é o centro do gráfico (metade do size ) e y0 é a coordenada na qual a parte inferior do círculo termina. Portanto, na fórmula para calcular essa coordenada, o raio do círculo é usado. Como resultado, as coordenadas deste ponto podem ser encontradas pela seguinte fórmula : (50% size, 20% size + radius) .
  • As coordenadas (x1, y1) são o primeiro ponto de controle da curva. Também permanece inalterado para todas as curvas. Se não esquecermos que as curvas devem ser simétricas, verifica-se que os valores de x1 e y1 sempre são iguais à metade do valor do size . Daí a fórmula para o seu cálculo: (50% size, 50% size) .
  • As coordenadas (x2, y2) representam o segundo ponto de controle da curva de Bezier. Aqui x2 indica qual deve ser a forma da curva. Este indicador é calculado dinamicamente para cada curva. E o indicador y2 , como antes, terá metade do size . Daí a seguinte fórmula para calcular essas coordenadas: (x2, 50% size) .
  • As coordenadas (x3, y3) são o ponto de referência final da curva. Essa coordenada indica onde você deseja concluir o desenho da linha. Aqui, o valor de x3 , como x2 , é calculado dinamicamente. E y3 assume um valor igual a 80% do size . Como resultado, obtemos a seguinte fórmula: (x3, 80% size) .

Reescrevemos, em termos gerais, o código do elemento <path> , levando em consideração as fórmulas que acabamos de derivar. As porcentagens usadas acima são apresentadas aqui dividindo-as por 100.

 <path d="M size*0.5, (size*0.2) + radius          C size*0.5, size*0.5           x2,    size*0.5           x3,    size*0.8" > 

Observe que, à primeira vista, o uso de porcentagens em nossas fórmulas pode parecer opcional, com base apenas em minha própria opinião. No entanto, esses valores não são aplicados por capricho, mas porque seu uso ajuda a obter simetria e as proporções corretas do diagrama. Depois de sentir seu papel no gráfico, você pode tentar seus próprios valores percentuais e examinar os resultados obtidos aplicando-os.

Agora vamos falar sobre como procuraremos as coordenadas x2 e x3 . Eles permitem criar dinamicamente muitas curvas com base no index elementos na matriz correspondente.

A divisão do espaço horizontal disponível do gráfico em partes iguais é baseada no número de elementos na matriz. Como resultado, cada parte recebe o mesmo espaço ao longo do eixo x.

A fórmula que derivamos deve funcionar posteriormente com qualquer número de elementos. Mas aqui vamos experimentar uma matriz contendo 5 elementos: [0,1,2,3,4] . A visualização de uma matriz desse tipo significa que é necessário desenhar 5 curvas.

IndingEncontrando coordenadas dinâmicas (x2 e x3)


Primeiro, eu dividi o size pelo número de elementos, ou seja, pelo comprimento da matriz. Eu chamei essa distance variável. Representa a distância entre dois elementos.

 distance = size/arrayLength // distance = 1000/5 = 200 

Então eu andei pela matriz e multipliquei o índice de cada um de seus elementos ( index ) pela distance . Por uma questão de simplicidade, chamo simplesmente x parâmetro x2 e x3 .

 //  x2  x3 x = index * distance 

Se você aplicar os valores obtidos ao criar o diagrama, ou seja, usar o valor x calculado acima para x2 e x3 , isso parecerá um pouco estranho.


O diagrama é assimétrico

Como você pode ver, os elementos estão localizados na área em que deveriam estar, mas o diagrama acabou sendo assimétrico. Parece que na parte esquerda há mais elementos do que na direita.

Agora, preciso fazer com que o valor x3 esteja no centro dos segmentos correspondentes, cujo comprimento é definido usando a variável distance .

Para trazer o diagrama para o formulário necessário, simplesmente adicionei x metade do valor da distance .

 x = index * distance + (distance * 0.5) 

Como resultado, encontrei o centro do segmento de distance e coloquei a coordenada x3 nele. Além disso, trouxe para o formulário que precisamos da coordenada x2 para a curva n ° 2.


Diagrama simétrico

A adição de metade do valor da distance às coordenadas x2 e x3 tornou a fórmula de cálculo adequada para a visualização de matrizes contendo um número par e ímpar de elementos.

▍ Máscara de imagem


Precisamos que uma determinada imagem seja exibida na parte superior do diagrama, dentro do círculo. Para resolver esse problema, criei uma máscara de recorte contendo um círculo.

 <defs>  <mask id="svg-mask">     <circle :r="radius"             :cx="halfSize"             :cy="topHeight"             fill="white"/>  </mask> </defs> 

Em seguida, usando a tag <image> do elemento <image> <svg> <image> para exibir a imagem, vinculei a imagem ao elemento <mask> criado acima, usando o atributo mask do elemento <image> .

 <image mask="url(#svg-mask)"      :x="(halfSize-radius)"      :y="(topHeight-radius)" ... > </image> 

Como estamos tentando ajustar uma imagem quadrada em uma “janela” redonda, ajustei a posição do elemento subtraindo o parâmetro radius parâmetros correspondentes. Como resultado, a imagem é visível através de uma máscara feita na forma de um círculo.

Vamos coletar tudo o que falamos em um desenho. Isso nos ajudará a ver o quadro geral do progresso do trabalho.


Dados usados ​​no cálculo dos parâmetros do gráfico

Criando uma imagem SVG dinâmica usando o Vue.js


Nesse ponto, descobrimos as curvas cúbicas de Bezier e realizamos os cálculos necessários para formar o diagrama. Como resultado, agora podemos criar diagramas estáticos de SVG. Se combinarmos os recursos do SVG e do Vue.js, podemos criar diagramas controlados por dados. Os gráficos estáticos se tornarão dinâmicos.

Nesta seção, revisamos o diagrama SVG, apresentando-o como um conjunto de componentes do Vue. Também anexaremos atributos SVG às propriedades computadas e faremos com que o gráfico responda às alterações de dados.

Além disso, no final do projeto, criaremos um componente que é um painel de configuração. Este componente será usado para inserir dados que serão transmitidos ao gráfico.

Data Vinculando dados aos parâmetros do viewBox


Vamos começar ajustando o sistema de coordenadas. Sem fazer isso, não conseguiremos desenhar imagens SVG. A propriedade viewbox computada retornará o que precisamos usando a variável size . Haverá quatro valores separados por espaços. Tudo isso se tornará o valor do atributo viewBox do elemento <svg> .

 viewbox() {   return "0 0 " + this.size + " " + this.size; } 

No SVG, o nome do atributo viewBox já foi gravado usando o estilo camel.

 <svg viewBox="0 0 1000 1000"> </svg> 

Portanto, para vincular corretamente esse atributo à propriedade calculada, anotei o nome do atributo no estilo kebab e coloquei o modificador .camel depois dele. Com essa abordagem, é possível "enganar" o HTML e implementar corretamente a ligação de atributo.

 <svg :view-box.camel="viewbox">   ... </svg> 

Agora, ao alterar o size gráfico é reconfigurado independentemente. Não precisamos alterar manualmente o layout.

▍Cálculo dos parâmetros da curva


Como a maioria dos valores necessários para construir as curvas é calculada com base em uma única variável ( size ), eu costumava encontrar todas as coordenadas fixas pelas propriedades calculadas. O que chamamos aqui de "coordenadas fixas" é calculado com base no size , e depois disso não muda e não depende de quantas curvas o diagrama incluirá.

Se você alterar o size - "coordenadas fixas" serão recontadas. Depois disso, eles não serão alterados até a próxima alteração de size . Dado o exposto, aqui estão cinco valores que precisamos desenhar curvas de Bezier:

  • topHeight — size * 0.2
  • bottomHeight — size * 0.8
  • width — size
  • halfSize — size * 0.5
  • distance — size/arrayLength

Agora, temos apenas dois valores desconhecidos - x2 e x3 . A fórmula para calculá-los já derivamos:

 x = index * distance + (distance * 0.5) 

Para encontrar valores específicos, precisamos substituir os índices dos elementos da matriz nesta fórmula.

Agora vamos nos perguntar se a propriedade computada é correta para encontrarmos x . Responda brevemente a essa pergunta, então - não, não será.

A propriedade calculada não pode receber parâmetros. O fato é que isso é uma propriedade, não uma função. Além disso, a necessidade de usar um parâmetro para calcular algo significa que não há vantagem tangível de usar propriedades calculadas em termos de armazenamento em cache.

Observe que há uma exceção em relação ao princípio acima. É sobre Vuex. Se você usar getters Vuex que retornam funções, poderá passar parâmetros para eles.

Nesse caso, não usamos o Vuex. Mas mesmo nesta situação, temos algumas maneiras de resolver esse problema.

▍ Opção número 1


Você pode declarar uma função para a qual o index passado como argumento e retorna o resultado que precisamos. Essa abordagem parece mais clara se vamos usar o valor retornado por uma função semelhante em vários locais do modelo.

 <g v-for="(item, i) in itemArray">  <path :d="'M' + halfSize + ','     + (topHeight+r) +' '+            'C' + halfSize + ','     + halfSize +' '+                     calculateXPos(i) + ',' + halfSize +' '+                  calculateXPos(i) + ',' + bottomHeight"  /> </g> 

O método calculateXPos() realizará cálculos sempre que for chamado. Este método toma como argumento o índice do elemento - i .

 <script>  methods: {    calculateXPos (i)    {      return distance * i + (distance * 0.5)    }  } </script> 

Aqui está um exemplo no CodePen que usa esta solução.


Tela para a primeira variante de aplicativo

▍ Opção número 2


Esta opção é melhor que a primeira. Podemos extrair a pequena marcação SVG necessária para criar a curva em um componente filho pequeno separado e passar o index para ela como uma das propriedades.

Com essa abordagem, você pode usar a propriedade computada para encontrar x2 e x3 .

 <g v-for="(item, i) in items">    <cubic-bezier :index="i"                   :half-size="halfSize"                   :top-height="topHeight"                   :bottom-height="bottomHeight"                   :r="radius"                   :d="distance"     >     </cubic-bezier> </g> 

Esta opção nos dá a oportunidade de organizar melhor o código. Por exemplo, podemos criar outro componente filho para a máscara:

 <clip-mask :title="title"           :half-size="halfSize"           :top-height="topHeight"                               :r="radius"> </clip-mask> 

Panel Painel de configuração



Painel de configuração

Você provavelmente já viu o painel de configuração, chamado pelo botão localizado no canto superior esquerdo da tela do exemplo acima. Esse painel facilita a adição de elementos à matriz e a remoção deles. Seguindo as idéias discutidas na seção "Opção nº 2", criei um componente filho para o painel de configuração. Graças a isso, o componente de nível superior é limpo e bem legível. Como resultado, nossa pequena e agradável árvore de componentes Vue se parece com a abaixo.


Árvore de componentes do projeto

Deseja dar uma olhada no código que implementa esta versão do projeto? Se assim for, dê uma olhada aqui .


Tela para a segunda variante de aplicativo

Repositório do projeto


Aqui está o repositório GitHub do projeto ("Opção 2" é implementada aqui). Eu acredito que será útil que você olhe antes de passar para a próxima seção.

Lição de casa


Tente criar o mesmo diagrama que descrevemos aqui, mas faça-o orientado verticalmente. Aproveite as idéias descritas neste artigo.

Se você acha que essa é uma tarefa fácil, que para construir um diagrama como esse é suficiente para trocar as coordenadas y , você está certo. Considerando que o projeto considerado aqui não foi criado como universal, depois de alterar as coordenadas onde você precisar, também será necessário editar o código renomeando algumas variáveis ​​e métodos.

Graças ao Vue.js, nosso gráfico simples pode ser equipado com recursos adicionais. Por exemplo, o seguinte:

  • Você pode criar um mecanismo que permita alternar entre os modos de gráfico horizontal e vertical.
  • As curvas podem tentar animar. Por exemplo, usando GSAP.
  • Você pode ajustar as propriedades das curvas (digamos - cor e largura da linha) no painel de configuração.
  • Você pode usar uma biblioteca externa para organizar o salvamento de diagramas em qualquer formato gráfico ou como um arquivo PDF. É possível fazer o download desses materiais para aqueles que trabalham com o gráfico.

Tente esta lição de casa. E se você tiver algum problema - abaixo será dado um link para sua solução.

Sumário


O elemento <path> é um dos recursos avançados do SVG. Este elemento permite criar várias imagens com alta precisão. Aqui nós descobrimos como as curvas de Bezier são estruturadas e como colocá-las em prática para criar seus próprios diagramas.

, , JavaScript-. Vue.js . , , , , DOM. , — .

, , , , , Vue.js SVG. — , Vue.js. — .

, - , , , — .

Caros leitores! ?

Source: https://habr.com/ru/post/pt463329/


All Articles