10. Visualización de datos

10.3. Tablas

Muchos de los datos a los que podemos tener acceso los podemos encontrar en forma de tablas con filas y columnas. En P5 disponemos de un método para leer los datos de tablas accediendo a sus celdas mediante coordenadas.

Figura 125. Coordenadas de las celdas de una tabla
Fuente: adaptación de L. McCarthy; A. P. C. Reas; B. Fry (2015). Getting Started with P5.Js: Making Interactive Graphics in JavaScript and Processing. Make Community, LLC.

Para cargar una tabla, normalmente lo haremos en un formato de texto plano conocido como .csv (comma-separated values), en el que los valores de cada fila están separados mediante comas. Desde cualquier editor de hojas de cálculo podemos exportar los datos de una tabla en formato .csv.

Tomemos como ejemplo los siguientes datos sobre los diez lagos más grandes del mundo:

Figura 126. Datos en formato tabla
Fuente: elaboración propia.

Vemos que los datos que contiene la tabla son el nombre del lago, su superficie, su latitud y su longitud. Su equivalente en formato .csv, que podemos abrir en cualquier editor de texto, es el siguiente:

Figura 127. Datos en formato .csv
Fuente: elaboración propia.

Leyendo una tabla

Este archivo .csv es el que podemos cargar en nuestro sketch de p5.js para acceder a sus datos y visualizarlos. Lo primero que debemos hacer, al igual que con cualquier archivo externo, es subirlo a la mediateca de CodeLab. Una vez hecho esto, usaremos el método loadTable() para cargar la tabla.

let tableData;

function preload() {
  tableData = loadTable("lakes.csv", "header");
}

Notemos que cargamos la tabla en preload(), para evitar que tratemos de acceder a sus datos sin haberla descargado aún. Como primer parámetro de loadTable() tenemos que pasarle el nombre del archivo que contiene la tabla y como segundo parámetro vamos a pasarle “header” para indicarle que, en nuestro caso, la primera fila no son datos en sí sino que son los nombres de cada columna.

Ahora ya podemos acceder a los datos de la tabla haciendo uso de sus coordenadas. Lo haremos mediante el método get(), al que le pasaremos las coordenadas de la celda que queramos consultar. Si queremos por ejemplo acceder al dato de la celda situada en la primera fila (recordemos que no tenemos en cuenta la primera fila con los nombres) y en la primera columna, necesitaremos la coordenada (0, 0).

let lakesData;

function preload() {
  lakesData = loadTable("lakes.csv", "header");
}

function setup() {
  createCanvas(800, 300);
  // Primera fila, primera columna
  console.log(lakesData.get(0, 0));
  // Segunda fila, tercera columna
  console.log(lakesData.getNum(1, 2));
}

Cuando busquemos extraer datos numéricos podemos usar getNum() en lugar de get() para asegurarnos de que el dato se almacene como número y no como texto, lo que nos puede llevar a errores más adelante.

Para leer toda la tabla de una vez y almacenar sus datos, podemos utilizar bucles y arrays con el fin de automatizar el proceso y tener los datos ordenados de manera más conveniente:

let lakesData;
let areas = [];

function preload() {
  lakesData = loadTable("lakes.csv", "header");
}

function setup() {
  createCanvas(800, 300);
  for(let i = 0; i < lakesData.getRowCount(); i++) {
    areas[i] = lakesData.getNum(i, 2);
  }
}

En este caso, hemos almacenado en el array areas los datos de la superficie de cada lago, que están en la tercera columna. Podemos hacer lo mismo para el resto de los datos.

Visualizando la tabla

Ahora que ya hemos extraído los datos, podemos usarlos para dibujar en nuestro sketch. Podríamos por ejemplo crear una clase Lake() que se encargue de dibujarlos.

class Lake {
	constructor(name, lat, lon, area) {
		this.name = name;
		this.posX = map(lon, -180, 180, 0, width);
		this.posY = map(lat, -90, 90, height, 0);
		this.area = area;
		this.diameter = 2 * sqrt(area / PI);
		this.alpha = 50;
	}
	
	display(){
		fill(0, 0, 255, this.alpha);
		noStroke();
		circle(this.posX, this.posY, this.diameter);
	}
}

En el constructor le damos como parámetros los datos que podemos extraer de la tabla: nombre, latitud, longitud y superficie, y los asignamos a las propiedades que más tarde utilizaremos en el método display() para dibujarlos. Para calcular posX y posY hemos tenido en cuenta que la latitud puede ir de –180 a 180 grados y que la longitud puede ir de –90 a 90 grados.

Ahora podemos colocar el código de la clase en otra pestaña y escribir el código principal de nuestro sketch:

let lakesData;
let areas = [];
let names = [];
let lats = [];
let lons = [];

let lakes = [];

function preload() {
	lakesData = loadTable("lakes.csv", "header");	
}

function setup() {
	createCanvas(400, 250);
	for(let i = 0; i < lakesData.getRowCount(); i ++){
		areas[i] = lakesData.getNum(i, 2);
		names[i] = lakesData.get(i, 1);
		lats[i] = lakesData.get(i, 3);
		lons[i] = lakesData.get(i, 4);	
	}
	
	for(let i = 0; i < lakesData.getRowCount(); i++) {
		let newLake = new Lake(names[i], 
                                 lats[i], 
                                 lons[i],
                                 areas[i] * 0.025);
		lakes[i] = newLake;
	}
}

function draw() {
	background(250);
	for(let i = 0; i < lakes.length; i ++) {
		lakes[i].display();
	}	
}

Primero creamos una serie de arrays para almacenar los datos que extraeremos de la tabla, que rellenamos en el setup() mediante bucles. A continuación, creamos los objetos Lake pasándoles como parámetros los datos anteriormente extraídos y, por último, en el draw() iteramos por el array de objetos Lake para dibujarlos mediante su método display().

Como resultado obtendremos una visualización de la superficie de los lagos asociada a su posición geográfica:

Figura 128. Visualización de los lagos
Fuente: elaboración propia.

Con los datos que tenemos, podríamos hacer más interesante la visualización y hacerla interactiva con el fin de que al pasar por encima de los círculos nos mostrase el nombre del lago. Es tan sencillo como añadir un nuevo método a nuestra clase Lake:

checkMouse() {
  let d = dist(mouseX, mouseY, this.posX, this.posY);
  if(d < this.diameter * 0.5){
    this.alpha = 150;
    textAlign(CENTER);
    textSize(20);
    text(this.name, width * 0.5, height - 25);
  } else {
    this.alpha = 50;
  }
}

Y añadir la comprobación de la distancia para cada objeto Lake en el draw() del sketch principal:

function draw() {
	background(250);
	for(let i = 0; i < lakes.length; i ++) {
		lakes[i].display();
		lakes[i].checkMouse();
	}	
}
Figura 129. Visualización interactiva de los lagos
Fuente: elaboración propia.