O senhor já deve ter ouvido falar sobre o d3.js, a deslumbrante biblioteca JavaScript que permite que o senhor crie belos gráficos e tabelas com apenas algumas linhas de código. O senhor já deve ter visto alguns dos exemplos fantásticos do D3 em ação, ou o senhor pode ter ouvido falar que o New York Times o utiliza para criar seu histórias visuais interativas.
Se o senhor já tentou mergulhar no mundo do D3, então já deve estar familiarizado com o sua famosa curva de aprendizado acentuada.
O senhor simplesmente não consegue criar coisas logo de cara com o D3.
Com suas cadeias de métodos confusas, sintaxe estranha e funções de caixa preta que parecem funcionar por mágica, o D3 pode rapidamente parecer mais incômodo do que vale a pena. Mas não se preocupe, pois o D3 fica muito mais fácil se o senhor entender apenas alguns conceitos-chave.
Quero conduzi-lo por um tutorial simples, explicando cinco das áreas mais comuns de confusão que os iniciantes enfrentam quando começam a usar o D3.
Vamos criar um gráfico de dispersão dinâmico, que é atualizado a cada segundo entre dois conjuntos diferentes de dados:
Reserve um momento para apreciar os pequenos detalhes aqui. Veja como esses pontos deslizam suavemente pela tela. Veja como eles entram e saem da tela suavemente. Observe a oscilação calma de nossos eixos entre seus diferentes valores.
Esses são, na verdade, alguns dos recursos mais fáceis de implementar no D3. Quando o senhor consegue superar a dificuldade inicial de descobrir os blocos de construção básicos da biblioteca, adicionar esse tipo de coisa é muito fácil.
Antes de nos anteciparmos, vamos falar sobre o que realmente é o D3.
D3 significa Documentos orientados por dados.
O dados podem ser absolutamente qualquer coisa, o que é parte do que torna o D3 tão poderoso. Na maioria das vezes, no D3, o senhor vai querer ler esses dados de um arquivo, mas, neste exemplo, usaremos apenas duas matrizes armazenadas como variáveis:
var data0 = [ { gpa: 3.42, height: 138 }, { gpa: 3.54, height: 153 }, { gpa: 3.14, height: 148 }, { gpa: 2.76, height: 164 }, { gpa: 2.95, height: 162 }, { gpa: 3.36, height: 143 } ] var data1 = [ { gpa: 3.15, height: 157 }, { gpa: 3.12, height: 175 }, { gpa: 3.67, height: 167 }, { gpa: 3.85, height: 149 }, { gpa: 2.32, height: 165 }, { gpa: 3.01, height: 171 }, { gpa: 3.54, height: 168 }, { gpa: 2.89, height: 180 }, { gpa: 3.75, height: 153 } ]
O documentos parte em D3 refere-se ao Document Object Model (DOM). O D3 trata da movimentação de elementos na página, com base no que os dados estão dizendo. Especificamente, estamos trabalhando com elementos de forma especiais chamados SVGs.
Conceito crucial nº 1 – Trabalhando com SVGs
Chegamos, então, ao primeiro conceito desafiador com o qual todo novato em D3 tem de lidar. O senhor precisa dominar imediatamente um tipo especial de marcação que talvez nunca tenha visto antes.
Veja como pode ser a marcação SVG:
<svg width="400" height="60"> <rect x="0" y="0" width="50" height="50" fill="green"></rect> <circle cx="90" cy="25" r="25" fill="red"></circle> <ellipse cx="145" cy="25" rx="15" ry="25" fill="grey"></ellipse> <line x1="185" y1="5" x2="230" y2="40" stroke="blue" stroke-width="5"></line> <text x="260" y="25" font-size="20px" fill="orange">Hello World</text> </svg>
Se colocarmos esse snippet em um documento HTML, nosso navegador o interpretará da seguinte forma:
Basicamente, cada um desses SVGs tem um conjunto de atributos que o navegador usa para colocar essas formas na tela. Algumas coisas que o senhor deve saber sobre SVGs:
- Há uma distinção entre a tela SVG (desenhada com as tags <canvas>) e as formas SVGs propriamente ditas.
- Há um sistema de coordenadas pouco intuitivo que o senhor precisará entender, pois o ponto (0, 0) de uma grade SVG está no canto superior esquerdo, e não no canto inferior esquerdo.
- O senhor pode se deparar com um comportamento bastante estranho se não entender o que está acontecendo nos bastidores.
Pode ser tentador ignorar esse assunto e, em vez disso, optar por mergulhar de cabeça na excitante tarefa de criar algum código D3 imediatamente, mas as coisas parecerão muito mais claras mais tarde se o senhor souber como essas formas estão funcionando.
Recursos para entender os SVGs…
Como primeira etapa para criar nosso gráfico de dispersão, queremos adicionar um SVG de círculo pequeno para cada item de dados que desejamos exibir. Adicionamos SVGs no D3 da seguinte forma:
d3.select("#canvas") .append("circle") .attr("cx", 50) .attr("cy", 50) .attr("r", 5) .attr("fill", "grey");
Redação d3.select(“#canvas”) aqui é análogo a escrever $(“#canvas”) no jQuery, pois ele pega o elemento com o ID de “canvas”. d3.select vai um passo além, adicionando alguns métodos especiais a essa seleção que usaremos mais tarde.
Estamos usando o d3.append para adicionar um círculo SVG a esse elemento, e estamos definindo cada um dos atributos do círculo com o método d3.attr method.
Como queremos adicionar um círculo para cada item em nossa matriz, o senhor pode pensar que queremos usar um loop for:
for(var i = 0; i < data0.length; i++) { d3.select("#canvas") .append("circle") .attr("cx", data0[i].gpa) .attr("cy", data0[i].height) .attr("r", 5) .attr("fill", "grey"); }
No entanto, como este é o D3, faremos algo um pouco mais complicado e um pouco mais poderoso…
Conceito crucial nº 2 – vinculação de dados
O próximo obstáculo que todo novo desenvolvedor de D3 precisa superar é a junção de dados do D3. O D3 tem sua própria maneira especial de vincular dados aos nossos SVGs.
Veja como adicionamos um círculo para cada item em nossa matriz com o D3:
var circles = d3.select("#canvas").selectAll("circle") .data(data0); circles.enter().append("circle") .attr("cx", function(d, i){ return 25 + (50 * i); }) .attr("cy", function(d, i){ return 25 + (50 * i); }) .attr("r", 5) .attr("fill", "grey");
Para um desenvolvedor que está apenas começando com o D3, isso pode parecer confuso. Na verdade, para muitos desenvolvedores experientes com anos de experiência em D3, isso ainda pode parecer confuso…
O senhor poderia pensar que chamar selectAll(“circle”) em uma página desprovida de círculos retornaria uma seleção de nada. Em seguida, estamos chamando o método data() nessa seleção de nada, passando nosso array. Temos uma chamada misteriosa para o método enter() e, em seguida, temos uma configuração semelhante à anterior.
Esse bloco de código adiciona um círculo para cada item em nossa matriz, o que nos permite definir nossos atributos com funções anônimas. O primeiro argumento dessas funções nos dá acesso ao item em nossos dados que estamos analisando, e o segundo argumento nos dá o índice do item em nossa matriz.
Criar uma “junção de dados” como essa marca a primeira etapa para fazer algo útil com nossos dados, portanto, é importante entender essa etapa. Essa sintaxe estranha pode ser assustadora quando o senhor a encontra pela primeira vez, mas é uma ferramenta útil para saber como usá-la.
Recursos para entender a vinculação de dados no D3:
Quando executamos o código que escrevemos até agora, obtemos algo parecido com o seguinte:
Colocamos o número certo de círculos na tela e os espaçamos um pouco, mas o que temos até agora não é particularmente útil. Para um gráfico de dispersão, as coordenadas desses círculos devem corresponder a dois valores diferentes.
Os valores de GPA e altura que temos em nossas matrizes não são muito úteis para nós no momento. Nossos valores de GPA variam de 2,32 a 3,85 e nossos valores de altura variam de 138 a 180. Ao posicionar nossos círculos, queremos trabalhar com valores x entre 0 e 800 (a largura do nosso SVG) e valores y entre 0 e 500 (a altura do nosso SVG).
Precisaremos aplicar algum tipo de transformação aos nossos dados brutos para converter esses valores em um formato que possamos usar.
No D3, fazemos isso usando escalas.
Conceito crucial nº 3 – Escalas
Aqui está o nosso próximo grande desafio para a adoção do D3.
É confuso falar sobre balanças quando o senhor está começando. Elas precisam ser definidas com um domínio e um gamaque pode ser muito fácil de confundir. O domínio representa o intervalo que nosso valores de entrada será executado entre, e o intervalo representa o intervalo que nosso valores de saída será executado entre.
Uma escala é uma função no D3 que recebe um valor como entrada e gera um valor diferente como saída. Neste exemplo, precisaremos de uma escala x que converta um GPA em um valor de pixel e uma escala y que converta a altura de uma pessoa em um valor de pixel, para que possamos usar nossos dados para definir os atributos de nossos círculos.
Aqui está um diagrama para mostrar aos senhores o que nossa escala x deve fazer:
Precisamos inicializar nosso domínio e intervalo com alguns valores mínimos e máximos. Estamos dizendo que um valor de 3,54 deve ser traduzido para um valor de pixel de 800, e um GPA de 2,76 deve ser traduzido para um valor de pixel de 0. Portanto, se passarmos um valor de 3,15 para nossa escala, o resultado será 400, pois 3,15 está na metade do caminho entre o mínimo e o máximo do nosso domínio.
Neste exemplo, estamos usando uma escala linear, o que significa que os valores devem ser dimensionados proporcionalmente entre os dois extremos que estamos observando. No entanto, há alguns tipos diferentes de escalas que o senhor deve conhecer.
- Se o senhor estiver trabalhando com dados que aumentam exponencialmente ao longo do tempo, talvez queira usar um escala logarítmica.
- Se estiver trabalhando com valores de data, o senhor usará um escala de tempo.
- Se quiser atribuir cores entre diferentes categorias, o senhor pode usar um escala ordinal.
- Se estiver espaçando retângulos em um gráfico de barras, o senhor usará um escala de banda.
Para cada uma dessas escalas, a sintaxe é ligeiramente diferente, mas ainda seguirá o mesmo formato geral da nossa escala linear.
Recursos para entender as escalas no D3…
Portanto, agora podemos adicionar duas escalas lineares para usar em nossos eixos x e y.
var x = d3.scaleLinear() .domain([d3.min(data0, function(d){ return d.gpa; }) / 1.05, d3.max(data0, function(d){ return d.gpa; }) * 1.05]) .range([0, 800]); var y = d3.scaleLinear() .domain([d3.min(data0, function(d){ return d.height; }) / 1.05, d3.max(data0, function(d){ return d.height; }) * 1.05]) .range([500, 0]);
Cada uma de nossas escalas receberá um valor em algum lugar entre o mínimo e o máximo de cada variável em nossos dados e emitirá um valor de pixel que poderemos usar em nossos SVGs. Estou usando as funções d3.min() e d3.max() aqui para que o D3 se ajuste automaticamente se o conjunto de dados mudar. Também estou dando aos nossos domínios um buffer de 5% em ambos os sentidos, para que todos os nossos pontos caibam na tela.
Também estamos invertendo os valores de intervalo da nossa escala y, já que uma entrada de 0 deve gerar uma saída de 500px (a parte inferior de uma grade cartesiana no sistema de coordenadas SVG).
Em seguida, podemos fazer algumas edições em nosso código anterior, de modo que os valores dos círculos venham de nossas escalas.
var circles = d3.select("#canvas").selectAll("circle") .data(data0); circles.enter() .append("circle") .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", function(d){ return y(d.height) }) .attr("r", 5) .attr("fill", "grey");
Neste ponto, temos algo que se parece com uma visualização real!
A próxima etapa é adicionar alguns eixos, para que possamos dizer o que esses pontos devem representar. Podemos fazer isso usando as funções de geração de eixos do D3, mas logo encontraremos alguns problemas…
Conceito crucial nº 4 – Margens e eixos
Os geradores de eixos do D3 funcionam anexando um eixo ao elemento em que são chamados. O problema é que, se tentarmos anexar eixos diretamente à nossa tela SVG, acabaremos com algo assim:
Nosso primeiro problema é que os eixos estão sempre posicionados no canto superior esquerdo da grade. Isso é bom para o eixo y neste caso, mas não é bom para o eixo x, que queremos colocar na parte inferior.
Outro problema aqui é que, como os eixos estão saindo da borda da tela SVG, as marcas de escala dos eixos não aparecem no eixo y.
Podemos corrigir isso usando alguns grupos SVG, elementos invisíveis para adicionar estrutura às nossas páginas.
No D3, precisamos nos acostumar com a “convenção de margem” que todos os nossos projetos devem seguir:
A ideia é que queremos nos dar um buffer em torno da borda da nossa área de visualização, dando-nos algum espaço para nossos eixos viverem. Precisamos definir algumas variáveis de largura, altura e margem na parte superior do nosso arquivo, o que nos permitirá simular esse efeito:
ar svg = d3.select("#canvas"); var margin = {top: 10, right: 10, bottom: 50, left: 50}; var width = +svg.attr("width") - margin.left - margin.right; var height = +svg.attr("height") - margin.top - margin.bottom; var g = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Agora precisamos usar essas variáveis de largura e altura para definir o intervalo de nossas escalas, e anexaremos nossos círculos a essa variável g, que representa nossa área de visualização principal.
Se também anexarmos nossos eixos a grupos SVG, poderemos deslocá-los para a posição correta usando o atributo transform que acompanha o elemento group. Este é o código que usaremos para adicionar os eixos ao gráfico:
// Axes var xAxisCall = d3.axisBottom(x) var xAxis = g.append("g") .attr("class", "x-axis") .attr("transform", "translate(" + 0 + "," + height + ")") .call(xAxisCall); var yAxisCall = d3.axisLeft(y) var yAxis = g.append("g") .attr("class", "y-axis") .call(yAxisCall) // Labels xAxis.append("text") .attr("class", "axis-title") .attr("transform", "translate(" + width + ", 0)") .attr("y", -6) .text("Grade Point Average") yAxis.append("text") .attr("class", "axis-title") .attr("transform", "rotate(-90)") .attr("y", 16) .text("Height / Centimeters");
Também estou adicionando alguns SVGs de texto como rótulos, que nos informarão o que cada um dos eixos está mostrando.
A convenção de margem pode parecer um pouco aleatória para os recém-chegados ao D3, e há uma grande variedade de métodos que podemos usar para editar a aparência de nossas marcas de escala.
Recursos para entender as margens e os eixos no D3…
Agora que podemos ver o que nosso gráfico está mostrando, quero levá-lo ao próximo nível adicionando uma atualização aos nossos dados. Para fazer isso, usaremos o método de intervalo do D3 para executar um código continuamente:
var flag = true; // Run this code every second... d3.interval(function(){ // Flick between our two data arrays data = flag ? data0 : data1; // Update our chart with new data update(data); // Update our flag variable flag = !flag; }, 1000)
A cada 1000 ms, essa função executará uma função de atualização, alterando os dados que estamos usando entre nossas duas matrizes diferentes.
Precisamos fazer algumas edições em nosso código para que tudo seja atualizado como queremos:
// Scales var x = d3.scaleLinear() .range([0, width]); var y = d3.scaleLinear() .range([height, 0]); // Axes var xAxisCall = d3.axisBottom(x) var xAxis = g.append("g") .attr("class", "x-axis") .attr("transform", "translate(" + 0 + "," + height + ")"); var yAxisCall = d3.axisLeft(y) var yAxis = g.append("g") .attr("class", "y-axis"); // Labels xAxis.append("text") .attr("class", "axis-title") .attr("transform", "translate(" + width + ", 0)") .attr("y", -6) .text("Grade Point Average") yAxis.append("text") .attr("class", "axis-title") .attr("transform", "rotate(-90)") .attr("y", 16) .text("Height / Centimeters"); var flag = true; // Run this code every second... d3.interval(function(){ // Flick between our two data arrays data = flag ? data0 : data1; // Update our chart with new data update(data); // Update our flag variable flag = !flag; }, 1000) // Run for the first time update(data0); function update(data){ // Update our scales x.domain([d3.min(data, function(d){ return d.gpa; }) / 1.05, d3.max(data, function(d){ return d.gpa; }) * 1.05]) y.domain([d3.min(data, function(d){ return d.height; }) / 1.05, d3.max(data, function(d){ return d.height; }) * 1.05]) // Update our axes xAxis.call(xAxisCall); yAxis.call(yAxisCall); // Update our circles var circles = g.selectAll("circle") .data(data); circles.exit().remove() circles .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", function(d){ return y(d.height) }) circles.enter() .append("circle") .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", function(d){ return y(d.height) }) .attr("r", 5) .attr("fill", "grey"); }
Estamos definindo nossos domínios de escala dentro da função de atualização, de modo que eles se ajustem aos dados com os quais estamos trabalhando. Também estamos chamando nossos geradores de eixo aqui, o que os atualizará de acordo. Em seguida, temos um bloco de código confuso que lida com a forma como queremos que nossos círculos sejam atualizados.
Conceito crucial nº 5 – O padrão geral de atualização
O padrão de atualização geral é usado em praticamente todas as visualizações que o senhor deseja criar com o D3. Ele define o comportamento dos elementos em nossos dados que devem entrar, atualizar ou sair da tela. Para um iniciante, todo esse código pode parecer um pouco complicado.
Vamos dar uma olhada mais de perto no que cada uma dessas linhas está fazendo.
Primeiro, estamos vinculando nossa nova matriz de dados à nossa seleção D3:
// JOIN new data with old elements. var circles = g.selectAll("circle") .data(data);
Em seguida, esse bloco de código removerá todos os pontos que não existem mais em nossa nova matriz de dados:
// EXIT old elements not present in new data. circles.exit().remove()
Aqui, estamos atualizando a posição de todos os pontos na tela que ainda existem em nossa nova matriz de dados.
// UPDATE old elements present in new data. circles .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", function(d){ return y(d.height) })
Por fim, estamos adicionando um ponto para cada item em nossa nova matriz de dados que não tem um círculo correspondente na tela.
// ENTER new elements present in new data. circles.enter().append("circle") .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", function(d){ return y(d.height) }) .attr("r", 5) .attr("fill", "grey");
O mais difícil de entender o padrão geral de atualização é descobrir exatamente o que selectAll(), enter() e exit() estão fazendo. O D3 funciona por meio de um conjunto de “seletores virtuais”, que podemos usar para controlar quais elementos precisam ser atualizados.
Embora seja possível ter apenas uma compreensão superficial do padrão de atualização com muitos gráficos que você deseja criar, toda a biblioteca se torna muito mais clara quando você consegue descobrir o que cada um desses seletores está fazendo.
Recursos para entender o padrão geral de atualização no D3…
Depois de adicionarmos nossas atualizações, este é o aspecto do nosso gráfico:
Nossa visualização agora está alternando entre as duas matrizes de dados que queremos exibir. Vou acrescentar mais um toque final para deixar nosso gráfico um pouco mais organizado.
Podemos adicionar algumas transições bonitas usando o excelente conjunto de transições do D3. Primeiro, estamos definindo uma variável de transição na parte superior da nossa função de atualização, que está distribuindo cada uma das nossas transições em uma duração de 750 ms.
// Standard transition for our visualization var t = d3.transition().duration(750);
Quaisquer atributos que definirmos antes de chamar o método de transição em uma seleção D3 serão definidos imediatamente, e quaisquer atributos que definirmos após esse método de transição serão aplicados gradualmente.
Podemos adicionar transições aos nossos eixos da seguinte forma:
// Update our axes xAxis.transition yAxis.transition
E podemos adicionar transições aos nossos círculos desta forma:
// Update our circles var circles = g.selectAll("circle") .data(data); circles.exit().transition .attr("fill-opacity", 0.1) .attr("cy", y(0)) .remove() circles.transition .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", function(d){ return y(d.height) }) circles.enter().append("circle") .attr("cx", function(d){ return x(d.gpa) }) .attr("cy", y(0)) .attr("r", 5) .attr("fill", "grey") .attr("fill-opacity", 0.1) .transition .attr("fill-opacity", 1) .attr("cy", function(d){ return y(d.height) });
Estamos fazendo a transição entre uma opacidade de preenchimento de 0 e 1 para fazer com que nossos pontos apareçam e desapareçam suavemente, e estamos deslocando suavemente os círculos de atualização para suas novas posições.
Então, é isso. Agora temos um belo gráfico de dispersão que está sendo atualizado entre diferentes fontes de dados. O senhor pode encontrar o produto final de todo esse código na minha página do GitHub aqui.
Embora dominar os conceitos deste artigo possa parecer um grande passo a ser dado apenas para começar a usar o D3, o código fica cada vez mais fácil de entender com a prática.
Você logo descobrirá que os mesmos conceitos-chave sustentam todas as visualizações do D3 e que, depois de saber como uma visualização funciona no D3, poderá aprender rapidamente a criar quase tudo o que puder imaginar.
Confira os exemplos em bl.ocks.org e blockbuilder.org para ver algumas implementações prontas de muitos projetos interessantes. Como o próprio D3, todo esse código é de código aberto, o que significa que o senhor pode copiá-lo em seu computador local e usá-lo em seus próprios projetos.
Uma maneira fácil de começar a usar o D3…
Se o senhor está procurando a maneira mais rápida e fácil de aprender D3, então eu ensino um na Udemy que oferece uma introdução abrangente à biblioteca. O curso inclui:
- 7 horas de conteúdo de vídeo de qualidade.
- Uma introdução passo a passo aos conceitos fundamentais do D3, abrangendo todos os tópicos abordados neste artigo e muito mais.
- Quatro projetos de aula incríveis para praticar as habilidades que o senhor está aprendendo com dados do mundo real.
- Uma forte ênfase no design de visualização de dados, ajudando o senhor a criar visualizações personalizadas para seus próprios dados.
- Orientações sobre 12 das visualizações mais usadas, ensinando-o a entender e adaptar o código comunitário pré-escrito para seus próprios fins.
- Uma introdução a uma abordagem orientada a objetos para a criação de aplicativos da Web complexos, em que várias visualizações na página são atualizadas ao mesmo tempo.
O senhor pode obter o curso a um preço com desconto de apenas US$ 20,99, inscrevendo-se por meio deste link aqui.

Sobre Adam Janes
Adam se apaixonou pela primeira vez pelo D3.js quando era estudante de Economia e Ciência da Computação na Universidade de Harvard. Atualmente, ele trabalha como engenheiro de visualização de dados, ajudando empresas de todo o mundo a encontrar as melhores maneiras de exibir seus dados. Ele também ministra um curso on-line na Udemy, oferecendo aos alunos uma introdução abrangente ao D3 em 93 aulas em vídeo.