Dividir para Conquistar
Vamos a usar una versión aumentada del set de datos del ejemplo anterior para crear un tipo de gráfico diferente conocido como scatter plot .
var data = [
{nombre: 'Manzana', color: '#ff0000', calorias: 52, grasa: 0.2, proteinas: 0.3, azucar: 14},
{nombre: 'Hamburguesa', color: '#993300', calorias: 295, grasa: 14, proteinas: 17, azucar: 24},
{nombre: 'Pizza', color: '#cc9900', calorias: 266, grasa: 10, proteinas: 11, azucar: 33},
{nombre: 'Palta', color: '#004400', calorias: 160, grasa: 15, proteinas: 2, azucar: 9},
{nombre:'Platano', color:'#ffcc00', calorias: 89, grasa: 0.3, proteinas: 1.1, azucar: 23},
{nombre:'Nuez', color:'#cc6600', calorias: 576, grasa: 49, proteinas: 21, azucar: 22},
{nombre:'Almendra', color:'#660033', calorias: 576, grasa: 49, proteinas: 21, azucar: 22},
{nombre:'Pollo', color:'#cc9900', calorias: 219, grasa: 12, proteinas: 27, azucar: 0},
{nombre:'Pavo', color:'#ffcc33', calorias: 111, grasa: 0.7, proteinas: 25, azucar: 0.1},
{nombre:'Baggel', color:'#cc9966', calorias: 250, grasa: 1.5, proteinas: 10, azucar: 49},
{nombre:'Brocoli', color:'#00bb00', calorias: 34, grasa: 0.4, proteinas: 2.8, azucar: 7},
{nombre:'Pan', color:'#cc6633', calorias: 289, grasa: 1.8, proteinas: 12, azucar: 56},
{nombre:'Papas fritas', color:'#ffcc00', calorias: 536, grasa: 35, proteinas: 7, azucar: 53},
{nombre:'Vino', color:'#660066', calorias: 83, grasa: 0, proteinas: 0.1, azucar: 2.7},
{nombre:'Uva', color:'#66cc66', calorias: 67, grasa: 0.4, proteinas: 0.6, azucar: 17},
{nombre:'Granola', color:'#ff9900', calorias: 471, grasa: 20, proteinas: 10, azucar: 64},
{nombre:'Zanahoria', color:'#ff3300', calorias: 41, grasa: 0.2, proteinas: 0.9, azucar: 10},
{nombre:'Tomate', color:'#ff0000', calorias: 18, grasa: 3.9, proteinas: 0.9, azucar: 3.9},
{nombre:'Naranja', color:'#ff6600', calorias: 47, grasa: 0.1, proteinas: 0.9, azucar: 12},
{nombre:'Pera', color:'#00ff00', calorias: 57, grasa: 0.1, proteinas: 0.4, azucar: 15},
{nombre:'Nutella', color:'#550000', calorias: 500, grasa: 27, proteinas: 5, azucar: 50},
{nombre:'Arroz', color:'#ffffcc', calorias: 111, grasa: 0.9, proteinas: 2.6, azucar: 23},
{nombre:'Chocolate', color:'#330000', calorias: 546, grasa: 31, proteinas: 4.9, azucar: 61},
{nombre:'Rabano', color:'#cc0033', calorias: 16, grasa: 0.1, proteinas: 0.7, azucar: 3.4},
{nombre:'Soya', color:'#004400', calorias: 446, grasa: 20, proteinas: 36, azucar: 30},
{nombre:'Aceite', color:'#007700', calorias: 884, grasa: 100, proteinas: 0, azucar: 0},
{nombre:'Leche', color:'#eeeeee', calorias: 42, grasa: 1, proteinas: 3.4, azucar: 5},
{nombre:'Queso', color:'#ffff00', calorias: 371, grasa: 32, proteinas: 18, azucar: 3.7},
{nombre:'Pan Pita', color:'#660033', calorias: 275, grasa: 1.2, proteinas: 9, azucar: 56},
{nombre:'Vacuno', color:'#660000', calorias: 250, grasa: 15, proteinas: 26, azucar: 0},
{nombre:'Zapayo', color:'#ff6600', calorias: 26, grasa: 0.1, proteinas: 1, azucar: 6},
{nombre:'Piña', color:'#ffff99', calorias: 50, grasa: 0.1, proteinas: 0.5, azucar: 13},
{nombre:'Coco', color:'#ffffcc', calorias: 354, grasa: 33, proteinas: 3.3, azucar: 15}
];
El Scatter Plot o gráfico de burbujas permite graficar hasta cuatro dimensiones en el plano. Para un ejemplo básico, referimos al siguiente ejemplo de Mike Bostock.
El código del scatter plot es muy parecido al del gráfico de barras. Básicamente, los rectángulos se cambian por círculos, habrán dos ejes y una escala especial para el radio. Vamos a dividir el código en bloques de acuerdo a la función que desempeña cada bloque de código.
Configuración del gráfico
Esta vez, agregaremos más parámetros de configuración para el gráfico. Además de las dimensiones, agregaremos un margen y funciones de acceso para la variable asociada al radio, la variable de la coordenada del eje x y la coordenada del eje y.
var width = 800,
height = 300,
margin = {top: 30, right: 20, bottom: 20, left: 40},
maxRadius = 20,
duration = 1e3;
var x = function(d) { return d.proteinas; },
y = function(d) { return d.calorias; },
r = function(d) { return d.grasa; };
var div = d3.select('#ejemplo-a01').data([data]);
Enter
Creamos el elementos SVG y los grupos que contendrán el gráfico y los ejes. Podemos asignar más de una clase separando las clases con un espacio en blanco.
var svg = div.selectAll('svg').data([data]);
var svgEnter = svg.enter().append('svg');
// Setup SVG
svgEnter
.attr('id', 'svg-ejemplo-a01')
.attr('width', width)
.attr('height', height);
svgEnter.append('g').attr('class', 'chart');
svgEnter.append('g').attr('class', 'axis xaxis');
svgEnter.append('g').attr('class', 'axis yaxis');
Actualizando los Grupos
Seleccionamos los grupos y los trasladamos a sus posiciones respectivas. Recuerde que los grupos no tienen posición!
// Update groups
var gchart = svg.selectAll('g.chart').data([data]),
gxaxis = svg.selectAll('g.xaxis').data([data]),
gyaxis = svg.selectAll('g.yaxis').data([data]);
gchart.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
gyaxis.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
gxaxis.attr('transform', 'translate(' + margin.left + ',' + (height - margin.bottom) + ')');
Escalas
Referimos nuevamente a la documentación de D3 para mayor información sobre escalas, en particular sobre las escalas lineales y de raíz cuadrada.
Acto seguido, definimos las escalas. Queremos que el área de los círculos sea proporcional a la cantidad que representan. Para eso, el radio del círculo tiene que ser proporcional a la raíz cuadrada de la cantidad. Esta escala se logra usando d3.scale.sqrt()
.
// Escalas
var xScale = d3.scale.linear()
.domain([0, d3.max(data, x)])
.range([0, width - margin.left - margin.right]);
var yScale = d3.scale.linear()
.domain([0, d3.max(data, y)])
.range([height - margin.top - margin.bottom, 0]);
var rScale = d3.scale.sqrt()
.domain([0, d3.max(data, r)])
.range([5, maxRadius]);
Ejes
Dibujamos el eje horizontal y el eje vertical. Notar la orientación left
para el eje y.
// Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom');
gxaxis.call(xAxis);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left');
gyaxis.call(yAxis);
Círculos
Finalmente, dibujamos los círculos usando el mismo patrón que en la sección anterior. Creamos una selección y vinculamos los datos, creamos los círculos en la selección enter, actualizamos los círculos y removemos los círculos en la selección exit.
// Circles
var circles = gchart.selectAll('circle.bubble').data(data);
circles.enter().append('circle')
.attr('class', 'bubble')
.attr('cx', function(d) { return xScale(x(d)); })
.attr('cy', function(d) { return yScale(y(d)); })
.attr('fill', function(d) {return d.color})
.attr('opacity', 0.7)
.attr('stroke', 'black')
.attr('stroke-width','1');
circles.transition().duration(duration)
.attr('r', function(d) { return rScale(r(d)); })
.attr('cx', function(d) { return xScale(x(d)); })
.attr('cy', function(d) { return yScale(y(d)); });
circles.exit().transition().duration(duration)
.attr('r', 0);
En la próxima sección vamos a usar estos bloques de código para crear progresivamente un gráfico reutilizable.