Graficando Relaciones

Algunos conjuntos de datos representan relaciones: la estructura de los datos está compuesta de nodos que representan entidades y links que representan relaciones entre las entidades. Por ejemplo,

// Entidades var nodes = [ {name: 'A', color: 'red'}, // 0 {name: 'B', color: 'blue'}, // 1 {name: 'C', color: 'green'}, // 2 {name: 'D', color: 'yellow'} // 3 ]; // Relaciones entre las entidades var links = [ {source: 0, target: 1}, {source: 0, target: 2}, {source: 1, target: 2}, {source: 1, target: 3}, {source: 1, target: 3} ];

Aquí, la entidad A está relacionada con las entidades B y C, pero no con D. D3 tiene layouts para transformar esta estructura en una visualización de la red.

Force Layout

El force layout está especialmente diseñado para datos que representan relaciones. Simula una fuerza repulsiva entre los nodos. Con los parámetros adecuados, permite dibujar elemenos sin que se traslapen demasiado.

var width = 300, height = 300; var force = d3.layout.force() .nodes(nodes) .links(links) .charge(-400) .gravity(0.2) .size([width, height]);

El force layout calcula posiciones óptimas para los nodos usando la simulación de fuerzas. Para que empiece el cálculo, hay que invocar el método start().

force.start();

El force layout extiende los nodos: ahora tienen index, posición, peso y momentum. Por ejemplo, examinando el nodo A:

var nodo = { color: "red", index: 0, name: "A", px: 308.69924879528753, py: 197.36366137277378, weight: 2, x: 308.715038678495, y: 197.35672484112365 };

Ahora tenemos un arreglo de elementos que podemos graficar como antes:

//Seleccionamos el div y creamos el SVG var div = d3.select('#ejemplo-a01'), svg = div.selectAll('svg').data([nodes]); svg.enter().append('svg'); svg.attr('width', width).attr('height', height); svg.exit().remove(); //Creamos los links var lines = svg.selectAll('line.link').data(links); lines.enter().append('line') .classed('link', true); lines .attr('x1', function(d) { return d.source.x; }) .attr('x2', function(d) { return d.target.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('y2', function(d) { return d.target.y; }); lines.exit().remove(); //Creamos los círculos var circles = svg.selectAll('circle.node').data(nodes); circles.enter().append('circle') .classed('node', true); circles .attr('r', 5) .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); circles.exit().remove();

Animando el Force Layout

El force layout permite crear gráficos que responden ante perturbaciones de alguno de los nodos: el force layout puede volver a calcular las posiciones en una serie de ticks. En cada tick, se evalúa la posición de cada nodo, se calcula la fuerza que resulta de la nueva configuración, se aplica la fuerza a los nodos y se configura la nueva posición y la nueva velocidad.

En la práctica, actualizamos el gráfico en cada tick para que se actualice la posición.

//Configuramos el layout var force = d3.layout.force() .nodes(nodes) .links(links) .charge(-500) .gravity(0.1) .size([width, height]); //Seleccionamos el div y creamos el SVG var div = d3.select('#ejemplo-a02'), svg = div.selectAll('svg').data([nodes]); svg.enter().append('svg'); svg.attr('width', width).attr('height', height); svg.exit().remove(); //Creamos los links var lines = svg.selectAll('line.link').data(links); lines.enter().append('line') .classed('link', true); lines.exit().remove(); //Creamos los círculos var circles = svg.selectAll('circle.node').data(nodes); circles.enter().append('circle') .attr('r', 5) .classed('node', true) .call(force.drag); circles.exit().remove(); //Específicamos el comportamiento en los ticks force.on('tick', function() { circles .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); lines .attr('x1', function(d) { return d.source.x; }) .attr('x2', function(d) { return d.target.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('y2', function(d) { return d.target.y; }); });
force.start();

Ejemplo: red social de delfines

Usaremos el force layout para graficar una red social de delfines. Cargamos los datos:

var graph; d3.json('/assets/data/dolphins.json', function(error, data) { if (error) { console.error(error); } graph = data; });

Definimos los parámetros del gráfico y creamos el layout:

var width = 600, height = 600; var force = d3.layout.force() .nodes(graph.nodes) .links(graph.links) .charge(-300) .size([width, height]);

Finalmente, creamos los elementos del gráfico:

var div = d3.select('#ejemplo-b01'), svg = div.selectAll('svg').data([graph]); svg.enter().append('svg'); svg.attr('width', width).attr('height', height); var lines = svg.selectAll('line.link').data(graph.links); lines.enter().append('line') .classed('link', true); lines.exit().remove(); var labels = svg.selectAll('text.label').data(graph.nodes); labels.enter().append('text') .classed('label', true); labels.exit().remove(); var circles = svg.selectAll('circle.node').data(graph.nodes); circles.enter().append('circle') .attr('r', 5) .classed('node', true) .call(force.drag); circles.exit().remove(); force.on('tick', function() { circles .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); labels .attr('x', function(d) { return d.x + 10; }) .attr('y', function(d) { return d.y; }) .text(function(d) { return d.label; }); lines .attr('x1', function(d) { return d.source.x; }) .attr('x2', function(d) { return d.target.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('y2', function(d) { return d.target.y; }); }); force.start();

El force layout se puede usar para representar redes bastante complejas. Sin embargo, la generación y actualización del gráfico se vuelve rápidamente muy costosa.