Lógica do desenho de eixos utilizando a biblioteca D3.js

dougsource

doug-source

Posted on June 15, 2023

Lógica do desenho de eixos utilizando a biblioteca D3.js

Hoje é dia de registrar a maneira que eu projetei mentalmente, durante meus estudos, a identificação da disposição da área durante o desenho de eixos (x e y) em gráficos svg utilizando a biblioteca JavaScript D3.js.

Geralmente, em alguns tutoriais, se define alguns passos básicos, não obrigatórios, ao se desenhar o início de um gráfico. Dentre eles:

  1. Definir constantes dimensionais relativas ao gráfico
  2. Append uma group tag à svg tag, para conter os dados plotados no gráfico
  3. Append outra group tag à svg tag, para conter os dois eixos do gráfico
    • à essa group tag append uma group tag para plotar separadamente apenas eixo x
    • à essa group tag append outra group tag para plotar separadamente apenas eixo y
  4. Criação dos scales do gráfico
  5. Mais alguns passos adicionais
  6. Plotar o restante do gráfico

Identificação visual

Durante a declaração dos tutoriais, notei que normalmente encontro dificuldades em correlacionar as medidas declaradas no código com dimensionamento no desenho do gráfico. Por isso que decidi criar uma definição visual de como entender o relacionamento do gráfico com esses números.

Vamos lá

Inicialmente, vamos realizar o primeiro passo, que é definir as contantes dimensionais do gráfico:

const height = 600
const width = 800
const margins = {
    top: 10,
    bottom: 40,
    left: 40,
    right: 150
};
Enter fullscreen mode Exit fullscreen mode

Tendo isso declarado, imagine que dentro de seu código HTML exista um elemento HTML (div, talvez) que seja encarregado de possuir a svg tag com o qual desenharemos o gráfico. Essa svg tag teria as medidas width e height declaradas acima e seria imaginada como a seguinte imagem:

Image1

Esse retângulo de cor roxa acima seria relacionado à nossa svg tag. A partir de agora precisamos definir a àrea na qual serão plotados os dados dentro do gráfico.

Nesse ponto podem ser executados o 2º e o 3º passo, mencionados acima, com a criação dos groups relacionados aos eixos do gráfico, além do 4º com os scales.

Note que as medidas de margin acima definem as coordenadas top, left, right e bottom. Elas serão utilizadas tanto pelos groups quanto pelos scales durante suas definições.

Para que nossos eixos possam ser visualizados de uma maneira visualmente mais completa, é necessário que exista um espaçamento mínimo sobrando para eles.

Image2

Conforme a imagem acima e se baseando nos valores constantes de margin declarados, visualmente teriamos esse espaçamento à left da borda da svg tag, reservada especialmente para o eixo y (coordenada vertical).

Image3

Conforme a imagem acima, o mesmo segue para o espaçamento abaixo da borda superior da svg tag e acima do eixo y (coordenada vertical). Vamos então colocar agora algo que simbolize a group tag reservada para o eixo y:

Image4

Isso fica evidencialmente definido quando se declara o seguinte código:

let axisGroup = svg.append('g').attr('class', 'axisGroup')
let yAxisGroup = axisGroup.append('g').attr('class', 'yAxisGroup').attr(
    'transform',
    `translate(${margins.left}, ${margins.top})`
);
Enter fullscreen mode Exit fullscreen mode

O eixo x (coordenada horizontal) deve ser colocado na parte inferior da svg tag. Logo deve também haver uma área com um espaçamento "sobrando" e reservado para o desenho do eixo x:

Image5

Vamos também colocar aqui algo que simbolize a group tag reservada para o eixo x:

Image6

A representação visual acima pode ser encontrada no seguinte código, durante a criação da group tag relacionada ao eixo x:

let xAxisGroup = axisGroup.append('g').attr('class', 'xAxisGroup').attr(
    'transform',
    `translate(${margins.left}, ${height - margins.bottom})`
);
Enter fullscreen mode Exit fullscreen mode

Definição dos scales

Para que os dados possam ser plotados no gráfico, os scales são criados pela biblioteca D3 e eles utilizam tanto o range quanto o domain.

Bem resumidamente, domain seria a definição dos limites dos dados reais em seu valor bruto e range seria a demarcação dos limites dos dados inseridos e já convertidos dentro da proporção das medidas do desenho gráfico. Em outras palavras seria como desenhar a "Torre Eiffel", de tamanho gigantesco, em uma folha de papel, de tamanho A4.

Conversões de tamanho devem ser feitas nesse caso, tranformando um metro de altura da torre em centímetros do papel (em nosso caso, em píxeis).

Durante a declaração dos scales usando D3.js, definimos a limitação da área no qual o desenho será plotado utilizando os scales.

Por exemplo, utilizando o template das imagens anteriores, digamos que a área no qual desejamos plotar nos dados seja definida conforme o próximo desenho (área em azul fraco):

Image7

Se você notou, as margins left e top dessa área (uma group tag também) já começam a partir do mesmo posicionamento definido para os groups dos eixos.

Por exemplo:

yGroupAxis.call(yAxis).call((group) => {
    const { width } = group.node().getBoundingClientRect();
    // a variável "spacing" é o espaço ocupado pelo eixo y,
    // que cresce conforme o tamanho da maior label
    // presente no eixo
    const spacing = width - margin.left;
    groupAxis.attr('transform', `translate(${spacing}, 0)`);

    // group tag utilizada para plotar os dados no gráfico
    const groupData = svg
        .append('g')
        .attr(
            'transform',
            `translate(${spacing}, ${margin.top})`
        );

    // o restante do código
    // ...
});
Enter fullscreen mode Exit fullscreen mode

Aí você se pergunta: "Mas e as margins bottom e right? Como que elas são definidas?"

Ninguém define elas. Elas são deduzidas através do range estabelecido por você.

Ao declarar o range em um scale, você define os limites mínimos e máximos que podem ser plotados dentro de uma group tag.

const xScale = d3.scalePoint().range([0, width]);
Enter fullscreen mode Exit fullscreen mode

O código acima diz que o starting point é zero e o finish point tem o valor de width, gerando a seguinte visualização:

Image8

A barra laranja, na imagem acima, refere-se ao espaço imaginário no qual a plotagem ocorrerá quando utilizar a scale function do sentido horizontal (do eixo x).

Assim o plotagem pode ficar fora do gráfico em alguns casos. Precisamos remover a quantidade de área reservada que fica à left do eixo y:

const xScale = d3.scalePoint().range([
    0,
    width - margin.left
]);
Enter fullscreen mode Exit fullscreen mode

Os retângulos verdes presentes nas visualizações, a partir de agora, simbolizarão a quantidade de margin que foi removida da área horizontal disponível para plotar o gráfico. Confira a seguinte visualização:

Image9

Bom... Ainda está grande demais. Falta remover a margin right também.

const xScale = d3.scalePoint().range([
    0,
    width - margin.left - margin.right
]);
Enter fullscreen mode Exit fullscreen mode

Ficando da seguinte forma:

Image10

Horizontalmente está OK. Vejamos agora a porção vertical da área do gráfico:

Image11

A barra laranja, na imagem acima, refere-se ao espaço imaginário no qual a plotagem ocorrerá quando utilizar a scale function do sentido vertical (do eixo y).

Os retângulos verdes simbolizam novamente a quantidade de margin que foi removida da área vertical disponível e as flexas vermelhas simboliam as margins top e left adicionadas à group tag reservada para plotar os dados no gráfico.

Abaixo segue o código referente à criação da scale function referente ao sentido vertical:

const yScale = d3
    .scaleLinear()
    .range([height - margin.bottom - margin.top, margin.top]);
Enter fullscreen mode Exit fullscreen mode

Nota: A biblioteca D3.js define seu gráfico considerando sua origin (no retângulo azul da imagem) abaixo da origin da própria svg tag (no retângulo verde da imagem). Isso faz com que, além da conversão entre os dados de domain e os dados de range, tenha que ocorrer também uma conversão entre o posicionamento entre essas origins existentes.

Image11.1

Quando se declara pontos no gráfico que devam descer no eixo y, na verdade deve-se declarar ele com valores cada vez maiores. Isso não é um problema, pois D3.js cuida disso para nós, desde que os valores de range, na scale function do eixo y, sejam declarados de maneira invertida, ou seja, primeiro o valor considerando a localização inferior e depois o valor considerando a localização superior. O restante segue sendo declarado normalmente.

Abaixo segue a imagem com o resumo de tudo o que já foi mencionado.

Image12

Espero que dessa forma tenha ficado visualmente um pouco mais claro como que a biblioteca D3.js plota gráficos que utilizem dos eixos x e y.

Imagino que essa forma de pensar visualmente seja a mais próxima da realidade (e foi como eu entendi 😅 kkk). Porém pode ser que este enunciado possua algo incoerente que tenha passado despercebido ou que eu tenha esquecido de mencionar algo.

Fico à espera de sugestões 👍...

Valeu!

💖 💪 🙅 🚩
dougsource
doug-source

Posted on June 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related