Nesse tutorial, vamos ver como carregar dados de um arquivo externo e criar um gráfico de população x área para as 10 cidades mais populosas do mundo.
Introdução
Originalmente criada como uma ferramenta pra ensinar programação a artistas visuais, a linguagem Processing hoje tem aplicação em áreas bem diversas, entre elas a visualização de dados. Sua sintaxe simplificada é uma boa alternativa para iniciantes no mundo do código, e sua plataforma de desenvolvimento pode ser baixada gratuitamente no site.
Nesse tutorial, vamos ver como carregar dados de um arquivo externo e criar um gráfico de população x área para as 10 cidades mais populosas do mundo.
O ambiente Processing
Ao abrir o programa, você vai encontrar algo como um editor de texto. É lá que vamos digitar os comandos para fazer a visualização — não, não existe uma “barra de ferramentas” aqui, já que o objetivo original do Processing é ensinar fundamentos de programação.
Pra executar o script que vamos escrever, é só apertar o botão de play no topo esquerdo da janela. Uma nova janela vai se abrir, por enquanto vazia, já que não criamos nenhum comando ainda. Feche ela (ou pressione o botão stop) e salve seu arquivo — por default o nome dele é a data de criação, mas você pode escolher qualquer outro nome— antes de começarmos a programar.
Comandos básicos
Para começar a entender como funciona a sintaxe do Processing, digite o código abaixo e execute o programa:
size(1200, 200);
ellipse(600, 100, 100, 100);
Você vai ver uma janela como essa:
O que isso significa?
Esses comandos são chamados de funções. Usamos duas delas: size e ellipse. A primeira define o tamanho (em pixels) do documento em que vamos trabalhar. A segunda desenha uma elipse. Ao lado de cada uma delas, dentro dos parênteses, temos os parâmetros: informações de que o programa precisa pra saber como vai executar cada uma das funções. No nosso caso:
size(largura do documento, altura do documento);
ellipse(coordenada horizontal da elipse, coordenada vertical da elipse, largura da elipse, altura da elipse);
Ou seja, dá pra traduzir a segunda linha como “desenhe uma elipse a 400 pixels da margem esquerda e 100 pixels do topo, com 100 pixels de altura e 100 pixels de largura”. Experimente mudar esses números para entender melhor como eles funcionam.
Por fim, alguns detalhes aos quais temos que ficar atentos:
- os parâmetros têm que ser sempre separados por vírgula (os espaços em branco não fazem diferença);
- sempre precisamos de um ; ao fim de cada comando.
- não importa se você der espaço ou parágrafo entre esses símbolos, mas eles têm que estar lá!
Se você estiver curioso para experimentar outras formas antes de seguir adiante, dê uma olhada nos comandos abaixo:
line()
point()
rect()
triangle()
Você vai encontrar instruções para cada um deles na página de referência do Processing.
Visualizando: precisão vs síntese
Para enxergar a densidade demográfica de cada cidade aqui, vamos usar uma técnica bem direta: representar o número total de habitantes como pontos dentro da área da cidade. Esse recurso acaba tendo uma leitura menos precisa que um gráfico de barras, por exemplo — você não consegue dizer a área ou a população das cidades apenas olhando as figuras abaixo:
Nossa intenção, porém, é simplesmente apresentar uma comparação visual direta e intuitiva do tema: mais pontos, mais gente; quadrado maior, maior área. Ao escolher uma forma ou outra de visualização, pense sempre no balanço entre essas duas variáveis e analise como elas podem funcionar melhor de acordo com o objetivo do seu gráfico.
Formatando os dados
Vamos utilizar os dados de 2013 do Demographia World Urban Areas para desenhar um gráfico com o ranking das 10 cidades mais populosas do mundo (tabela 1).
Um formato de dados bem simples e prático que pode ser lido no Processing é o tsv (tab-separated values). Basicamente, é um formato de texto que funciona como uma tabela, com os valores separados por tabs ao invés de células.
Transportar dados de um pdf para uma tabela nem sempre é uma tarefa fácil, uma vez que as quebras dificilmente seguem o formato de células de que precisamos.
Na imagem acima, só vamos precisar das colunas Urban Area, Population Estimate e Land Area km2. Você pode baixar o arquivo já formatado aqui ou tentar criar o seu:
- no Windows: use o Bloco de Notas e salve o arquivo como “cidades.tsv”
- no Mac: use o Text Edit. Pressione command + shift + T para transformar o arquivo em texto não formatado. E, quando salvar, selecione Unicode (UTF-8) na opção encoding.
Carregando os dados
Arraste o arquivo para a janela do Processing e você verá a mensagem One file added to the sketch. Isso apenas criou uma cópia de cidades.tsv na pasta que o Processing criou para seu arquivo. Para você ler ele dentro do script, adicione o código abaixo e execute:
size(1200, 200);
Table tabela = loadTable("cidades.tsv", "header");
for(int i = 0;
i < tabela.getRowCount();
i = i + 1){
TableRow linha = tabela.getRow(i);
rect(120*i, 50,
linha.getInt("área"), linha.getInt("área"));
}
Visualizando a área
Ok, o que esse código está fazendo? Vamos entender linha a linha:
// Definindo o tamanho do documento
size(1200, 200);
// Carregando o arquivo dentro de uma tabela. "header" significa que a primeira linha do arquivo deve ser entendida como um cabeçalho.
Table tabela = loadTable("cidades.tsv", "header");
// Criando uma ação que vai ser repetida várias vezes,...
for(int i = 0; // …a partir de um contador que começa em zero,…
i < tabela.getRowCount(); // …termina no tamanho da tabela…
i = i + 1){ // …e cresce de um em um.
// Em resumo, é como se o programa começasse a contar “0, 1, 2, 3…” até “10”, que é o número de linhas da nossa tabela. Para cada vez que ele conta, executa a ação abaixo:
// Seleciona a linha correspondente à contagem (i)
TableRow linha = tabela.getRow(i);
// Desenha um retângulo na posição y 50 e x segundo a fórmula “120 vezes contagem”.
// Quer dizer que, na sequência, a coordenada x vai ser: 0 , 120, 240, 360… até 1080.
// Como nossa contagem de 10 elementos começa em 0, ela vai até 9!
rect(120*i, 50,
Define como largura e altura de cada retângulo
o valor que for lido na coluna “área”
linha.getInt(“área”), linha.getInt(“área”));
}
Na imagem resultante, não vemos os retângulos inteiros, porque os valores de área que temos para cada cidade são muito grandes:
Uma boa solução pra resolver isso é trocar o comando do retângulo para:
rect(120*i, 50,
sqrt(linha.getInt("área")), sqrt(linha.getInt("área")));
A função sqrt calcula a raiz quadrada de um valor. Pra nós, é um jeito rápido e útil de diminuir os tamanhos:
Além disso, essa correção faz com que as áreas dos retângulos, não sua altura e largura, sejam proporcionais aos valores que estamos representando. Isso é indispensável para evitarmos distorções sempre que usarmos uma figura como um círculo ou um retângulo. Se só tivéssemos divido o valor da área por 100, por exemplo, teríamos uma imagem desproporcional:
rect(120*i, 50,
linha.getInt("área")/100, linha.getInt("área")/100);
A imagem acima faz as cidades parecerem muito mais diferentes em área do que realmente são, uma distorção causada pela confusão entre dimensão linear (altura e largura) e dimensão quadrada (área).
Simplicando o código
Antes de partir para o segundo dado da nossa visualização, a população de cada cidade, vamos tentar simplificar algumas coisas para deixar nosso algoritmo mais inteligível:
size(1200, 200);
Table tabela = loadTable("cidades.tsv", "header");
for(int i = 0;
i < tabela.getRowCount();
i = i + 1){
TableRow linha = tabela.getRow(i);
int posX = 120*i;
int posY = 50;
float lado = sqrt(linha.getInt("área"));
rect(posX, posY, lado, lado);
}
Se você executar o código acima, vai ver que o resultado visual não se altera em nada. O que fizemos foi apenas escrever de uma forma diferente os mesmos comandos: ao invés de calcular tudo dentro da função rect(), definimos 3 variáveis com os valores e depois usamos elas no desenho do retângulo:
int // Especifica a criação de uma variável de valor inteiro
posX // Nome da variável, inventado, a fim de que eu lembre ao que ela se refere
= 120*i; // atribui o valor "120 vezes i" à minha variável
No caso do lado do retângulo, float quer dizer que essa variável pode armazenar um número fracionário, já que não sabemos se o resultado da fórmula “raiz da área” vai ser um número inteiro.
Visualizando a população
Como falamos anteriormente, a ideia aqui vai ser representar o número total de habitantes de cada cidade como pontos dentro das áreas que já desenhamos. Abaixo da linha com o comando rect(), adicione o seguinte código:
for(int j = 0;
j < linha.getInt("população");
j = j + 1){
point(posX,posY);
}
Fique atento pra escrever esse comando antes da última chave } que você já tinha no documento. Estando dentro dessa chave, o Processing vai entender que esse comando deve ser repetido a cada vez que ele estiver lendo uma linha da tabela — é o que queremos, já que vamos desenhar a população para cada cidade. A estrutura aqui é parecida com a anterior:
// A partir de um contador "j"...
for(int j = 0; //...que começa em zero...
j < linha.getInt("população"); //..vai até o número lido
// na coluna "população", de cada linha...
j = j + 1){ //...e cresce de um em um...
//…desenhe um ponto.
point(posX,posY);
}
Se você tentar executar esse código, é provável que seu computador demore bastante — se não travar. Isso porque o número de repetições que estamos usando é muito grande: 37.239.000 pontos só para a cidade de Tóquio! Para resolver isso, troque a segunda linha desse trecho para:
j < linha.getInt("população")/5000;
Com isso, vamos desenhar 1 ponto para cada 5000 habitantes (não mais 1 para 1).
Ainda falta corrigir um detalhe: se você estudar com atenção o comando point(), vai perceber que todos os pontos de cada cidade estão desenhados no mesmo local, uns sobre os outros: na coordenada x e y que corresponde ao canto superior esquerdo dos quadrados. Troque essa linha para:
point( random(posX, posX + lado),
random(posY, posY + lado));
O comando random() resulta em um número aleatório, sorteado entre um valor mínimo e outro máximo. No nosso caso, o resultado é:
point( número aleatório entre (a coordenada x do retângulo e a mesma coordenada + o lado,
número aleatório entre (a coordenada y do retângulo e a mesma coordenada + o lado);
Execute o programa e teremos nosso resultado final:
No fim, as comparações ficam evidentes: Tóquio e Nova York são as cidades com maior área, mas densidade demográfica é bem menor que a de cidades como Manila e Karachi.
Se você quiser salvar o arquivo gerado como uma imagem para usar em outro programa, simplesmente acrescente uma última linha:
save("cidades.jpg");
Para salvar em outro formato, como tif ou png, simplesmente altere a extensão do arquivo no código acima.
Conclusão
Programar pela primeira vez pode parecer uma tarefa pouco amigável. Porém, à medida que você vai entendendo a lógica por trás dos comandos, entender várias linguagens é apenas uma questão de aprender diferentes sintaxes, umas mais parecidas, outras menos. Algo como falar vários idiomas.
Mesmo que o trabalho seja árduo no começo, você vai ver que as vantagens compensam: no caso do arquivo que usamos, por exemplo, basta alterar os dados do .tsv para gerar uma versão diferente da visualização, sem ter que fazer ajustes manuais na imagem. Além disso, programar deve ser entendido mais como uma linguagem que uma ferramenta: ao invés de recursos pré-definidos, você pode contruir o gráfico que quiser e julgar mais adequado pros seus dados. As possibilidades são infinitas.
Dê uma olhada no arquivo desse exemplo e tente alterar aos poucos os parâmetros para personalizá-lo: cor de fundo, tamanho, espaço entre as áreas etc. A partir disso, explore a página de referência do Processing e tente refazer esse exemplo usando outro tipo de visualização, como barras ou linhas.