Gráfico de barras com D3 e React

Na era dos exabytes de informação é imprescindível uma forma de visualizar os dados de maneira que seja possível comunicar ideias e conceitos importantes. Algo que possa ir além de simples tabelas. É nessa hora que entram em ação os gráficos de barras, pizza, linhas, etc.

Hoje o que faremos é criar um gráfico de barras como um componente usando as bibliotecas D3 e React.

Do que vamos precisar?

Alguns conceitos antes

Vamos dar uma pincelada em alguns conceitos que vão ser úteis para o nosso projeto:

React ou ReactJS: é uma biblioteca JavaScript desenvolvida pelo Facebook para criação de interfaces de usuário.

ES6, ECMAScript 6 ou ES2015: a ECMA é a associação internacional que desenvolve padrões e relatórios técnicos para hardware e softwere.  ECMAScript é a especificação da linguagem de script que o JavaScript implementa. Por fim ECMAScript 6 (definida em 2015) é a sexta edição dessa especificação.

D3: é uma biblioteca JavaScript para visualização de dados no navegador de forma dinâmica e interativa.

SVG: é uma linguagem XML para descrever de forma vetorial desenhos e gráficos bidimensionais.

Node.js e NPM: ver aqui.

Muito bem, acho que já temos o bastante para começarmos.

Criando um projeto React

Com o Node.js já instalado abra o terminal e criaremos o boilerplate, isto é, o esqueleto da nossa aplicação através do Create React App.

Digite o seguinte para instalar: npm install –g create-react-app

Agora, ainda pelo terminal, acesse o diretório onde você deseja criar o projeto digite: create-react-app barchart

Agora com npm start podermos ver a página que foi criada acessando http://localhost:3000

No Visual Studio Code adicione, como workspace, o diretório barchart.

Na estrutura que foi criada temos alguns arquivos.

  • public/index.html: template da página;
  • src/index.js: ponto de entrada da nossa aplicação;
  • serviceWorker.js: um recurso para interceptar e gerenciar solicitações de rede (não estaremos utilizando);
  • …test.js: arquivo de teste para os nosso componentes;
  • package-lock.json e package.json: lista as dependências do projeto;
  • .gitignore: evita que alguns arquivos sejam enviados para o  repositório git.

Organizando o projeto

Vamos organizar nosso projeto antes de começarmos a programar.

  1. Crie um diretório chamado components em src e mova App.js e App.test.js para ele;
  2. Remova os arquivos App.css e serviceWorker.js. E remova seus imports em index.js e App.js;
  3. Em App.js deixe somente as tags <div className=”App”></div> na função render.

Ao final teremos algo assim:

O que é JSX?

JSX é uma extensão da sintaxe JavaScript semelhante ao XML que nos permite misturar HTML com código JavaScript. Para que o navegador seja capaz de interpretar essa sintaxe, o código passa por um processo chamado “transpilação” onde tudo se torna JavaScript com chamadas de funções do React. O responsável por essa mágica é o Babel (leia mais sobre ele em: https://babeljs.io/docs/en). Veremos ele em uso a seguir.

Criando um componente

Adicione um novo arquivo no diretório components chamado BarChart.js. Nele insira o código abaixo.

import React, { Component } from 'react';

class BarChart extends Component {

    render() {
        return <div id="chart-container">
            <svg>
                <rect width={this.props.width} height={this.props.height} fill={this.props.fillColor}></rect>
            </svg>
        </div>
    }
}

export default BarChart;

E em App.js altere o HTML para

<div className="App">
     <BarChart width="200" height="200" fillColor="green"></BarChart>
 </div>

Se você executar a aplicação verá um quadrado verde no canto da página. O que fizemos foi extender a classe Component presente no pacote do React para criar nosso componente que agora pode ser utilizado na página através da tag BarChart, e passamos os atributos largura, altura e cor de preenchimento para ele.

Um componente é uma classe ou função JavaScript que aceita entradas opcionais, ou seja, propriedades (props) e retorna um elemento React que descreve como uma seção da interface do usuário deve aparecer.

Incluindo a biblioteca D3

Agora que já sabemos criar um componente vamos incluir a biblioteca D3 no nosso projeto. Abra o terminal, acesse o diretório do projeto e digite npm install d3. Volte em BarChart.js e altere o código para:

import React, { Component } from 'react';
import * as d3 from "d3";

class BarChart extends Component {

    componentDidMount() {
        this.createChart();
    }

    createChart(){
        const svg = d3.select("#chart-container")
        .append("svg");

        svg.append("rect")
        .attr("width", this.props.width)
        .attr("height", this.props.height)
        .attr("fill", this.props.fillColor);
    }

    render() {
        return <div id="chart-container"></div>
    }

}

export default BarChart;

Ainda temos nosso quadrado verde, porém agora ele foi gerado usando D3. Primeiro fizemos o import da biblioteca D3, depois incluímos duas novas funções componentDidMount e createChart, a primeira faz parte do ciclo de vida de qualquer componente React e é invocada assim que o componente é inserido no DOM, já a segunda é responsável por incluir na div chart-container as tag’s svg e rect. Repare que a sintaxe D3 é semelhante a sintaxe do JQuery.

Criando os eixos

Vamos tomar como exemplo um gráfico que vai mostrar o total de vendas de um produto X de janeiro a julho.

A primeira coisa que precisamos fazer é modificar a tag BarChart passando mais algumas informações, como os dados que vão aparecer no gráfico e a definição dos intervalo nos eixos x e y:

const data = [
      {
        xField: "Janeiro",
        yField: 100
      },
      {
        xField: "Fevereiro",
        yField: 80
      },
      {
        xField: "Março",
        yField: 76
      },
      {
        xField: "Abril",
        yField: 90
      },
      {
        xField: "Maio",
        yField: 57
      },
      {
        xField: "Junho",
        yField: 62
      },
      {
        xField: "Julho",
        yField: 57
      }
    ];

    return (
      <div className="App">
        <BarChart
          width="700"
          height="420"
          data={data}
          xRange={[0, 700]}
          xDomain={data.map((s) => s.xField)}
          yRange={[390, 0]}
          yDomain={[0, 100]}>
        </BarChart>
      </div>
    );

O domain é um valor que varia de 0 a 100 em y e é mapeado para um intervalo de 390 a 0, esse intervalo determina a altura do nosso gráfico, como ele é criado de baixo para cima e os eixos em svg começam com a origem no canto superior esquerdo (imagem abaixo), começamos pelo valor maior.

Em x o domínio é representado pelos meses e o intervalo vai de 0 a 700.

Em seguida vamos alterar a função createChart para receber os dados e desenhar os eixos:

const svg = d3.select("#chart-container")
            .append("svg")
            .attr("height", this.props.height)
            .attr("width", this.props.width);

        const chart = svg.append('g').attr('transform', `translate(30, 5)`);

        const xAxis = d3.scaleBand()
            .range(this.props.xRange)
            .domain(this.props.xDomain)
            .padding(0.4)

        const yAxis = d3.scaleLinear()
            .range(this.props.yRange)
            .domain(this.props.yDomain);

        chart.append('g')
            .call(d3.axisLeft(yAxis));

        chart.append('g')
            .attr('transform', `translate(0, ${this.props.yRange[0]})`)
            .call(d3.axisBottom(xAxis));

Continuamos passando as propriedades da forma que fizemos anteriormente e agora chamamos as funções scaleLinear e scaleBand para definir os eixos. Outro detalhe é o elemento g, ele serve para agrupar outros elementos.

Por último, pelo amor a estética, vamos colocar um pouco de CSS nisso todo:

body {
  margin: 0;
  background-color: #282c35;
  font-family: 'Open Sans', sans-serif;
}

#chart-container {
  width: 700px;
  height: 400px;
  border: 1px solid #444a58;
  margin: 50px auto;
  padding: 50px;
}

.value {
  fill: #fff;
}

text {      
  font-size: 12px;
  fill: #8e98a2;
}

path {
  stroke: #444a58;
}

line {
  stroke: #444a58;
}

rect {
  cursor: pointer;
  fill: #4fc1f7;
  border-radius: 10px;
}

Ao final executando a aplicação com npm start você verá essa belezinha.

Barras

Para as barras vamos usar o elemento rect novamente. Inclua o trecho de código ao final da função createChart:

chart.selectAll()
            .data(this.props.data)
            .enter()
            .append('rect')
            .attr('x', (s) => xAxis(s.xField))
            .attr('y', (s) => yAxis(s.yField))
            .attr('height', (s) => this.props.height - yAxis(s.yField) - 30)
            .attr('width', xAxis.bandwidth());

Detalhando o que acontece aí:

  1. selectAll seleciona todos os elementos DOM de algum tipo (poderia ser um “li” ou um seletor css aqui como parâmetro por exemplo). Como não estamos passando nada, essa função retorna um conjunto vazio;
  2. Com data(), atribuímos um conjunto de dados a uma coleção de elementos HTML, como nenhum foi selecionado com selectAll a função enter() fará o trabalho de identificar quais elementos precisam ser adicionados, em outras palavras, quantos rect’s precisam ser anexados ao gráfico por meio do append() que vem em seguida;
  3. Para cada rect atribuímos as coordenadas x e y com base nos valores dos eixos;
  4. Depois definimos a altura das barras pelo cálculo altura_do_gráfico – valor_no_eixo_y – valor_de_ajuste (mantém as barras acima do eixo x) e a largura;

Valores

Antes de ver como ficou vamos colocar os valores nas barras seguindo o mesmo raciocínio:

     chart.selectAll()
            .data(this.props.data)
            .enter()
            .append('text')
            .attr('class', 'value')
            .attr('x', (a) => xAxis(a.xField) + xAxis.bandwidth() / 2)
            .attr('y', (a) => yAxis(a.yField) + 30)
            .attr('text-anchor', 'middle')
            .text((a) => a.yField);

Agora sim, execute a aplicação e veremos isso:

Enfim, ainda podemos melhorar nosso gráfico colocando uma série de outras coisas que iriam estender muito mais este post, como título, texto na lateral, grid, eventos, etc.

Para ver como isso foi feito você pode fazer download do código completo a partir do repositório.

Qualquer dúvida não deixe de comentar, até a próxima!

Compartilhe!