En la entrada anterior usamos d3.js para definir una gráfica lineal que evolucionaba temporalmente en tiempo real, siguiendo la evolución de una fuente de datos. El código era bastante lineal:
- Simulamos una fuente de datos, creando un objeto de la clase
DataSource. - En nuestra página, definimos un
divcon idgraph1. - Usando d3.js, creamos una gráfica que insertamos en el
divanterior y la refrescábamos cada vez que la fuente de datos tenía variaciones.
Pero, si en lugar de una sola fuente de datos (y una sola gráfica) ¿qué ocurre si tenemos seis fuentes de datos, cada una con su propia gráfica? ¿Cómo podemos modularizar el código anterior, para reutilizarlo?
En esta entrada, vamos a encapsular el código anterior, creando un plugin para jQuery.
El resultado final
El resultado final, es el siguiente:
Podéis abrir este mismo resultado en una ventana independiente aquí.
El código html
Veamos el nuevo código html:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<div class="container-fluid"> <div class="row"> <div id="graph1" class="col-lg-3 col-sm-4 graph" data-max-value="20" data-time-interval="1000"> <p class="title">Graph 1</p> </div> <div id="graph2" class="col-lg-3 col-sm-4 graph" data-max-value="30" data-time-interval="2000"> <p class="title">Graph 2</p> </div> <div id="graph3" class="col-lg-3 col-sm-4 graph" data-max-value="40" data-time-interval="3000"> <p class="title">Graph 3</p> </div> <div id="graph4" class="col-lg-3 col-sm-4 graph" data-max-value="50" data-time-interval="1000"> <p class="title">Graph 4</p> </div> <div id="graph5" class="col-lg-3 col-sm-4 graph" data-max-value="60" data-time-interval="2000"> <p class="title">Graph 5</p> </div> <div id="graph6" class="col-lg-3 col-sm-4 graph" data-max-value="70" data-time-interval="3000"> <p class="title">Graph 6</p> </div> </div> </div> |
- Defino seis
divcontenedores, uno para cada gráfica. - Todos los contenedores, son de la calse
graph. - Cada
divtiene dos atributosdata-max-valueydata-time-interval. - Cada
divtambién tiene unidúnico.
El código javascript
Veamos primero el código completo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
'use strict'; (function(jQuery, d3) { jQuery.fn.lineGraph = function(options) { var opts = jQuery.extend({ data : [], maxValue : 100 }, options); var margins = { top: 10, right: 15, bottom: 20, left: 20 }; var svg = d3.selectAll(this).append('svg'); var width = parseInt(jQuery(this).css('width')) * 0.9; var height = Math.min(width, 600); svg.attr('width', width).attr('height', height); var xAxisLine = svg.append('svg:g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + (height - margins.bottom) + ')'); var yAxisLine = svg.append('svg:g') .attr('class', 'y axis left') .attr('transform', 'translate(' + (margins.left) + ',0)'); var graphLine = svg.append('svg:path') .attr('class', 'graph-line'); var xRange = d3.time.scale() .range([margins.left, width - margins.right]); var yRange = d3.scale.linear() .range([height - margins.bottom, margins.top]) .domain([0,opts.maxValue]); var xAxis = d3.svg.axis() .scale(xRange) .tickFormat(d3.time.format('%H:%M')) .tickSize(5) .tickSubdivide(true); var yAxis = d3.svg.axis() .scale(yRange) .tickSize(5) .orient('left') .tickSubdivide(true); var valueFunc = d3.svg.line() .x(function(d) { return xRange(d.date); }) .y(function(d) { return yRange(d.value); }) .interpolate('linear'); var redrawData = function redrawData() { xRange.domain([d3.min(opts.data, function(d) { return d.date; }), d3.max(opts.data, function(d) { return d.date; })]); xAxisLine.transition().call(xAxis); yAxisLine.transition().call(yAxis); graphLine.attr('d', valueFunc(opts.data)); }; redrawData(); return { redraw : function() { redrawData(); } }; }; })(window.jQuery, window.d3); (function(jQuery, DataSource) { jQuery(function() { var generateCallback = function(lineGraph, dataSource) { dataSource.onNewValue(function() { lineGraph.redraw(); }); }; jQuery('div.graph').each(function(index, value) { var maxValue = parseInt(jQuery(this).attr('data-max-value')); var timeInterval = parseInt(jQuery(this).attr('data-time-interval')); var dataSource = new DataSource({ maxValue : maxValue, timeInterval : timeInterval }); var lineGraph = jQuery(this).lineGraph({ data: dataSource.getData(), maxValue: maxValue }); dataSource.onNewValue(function() { lineGraph.redraw(); }); }); }); })(window.jQuery, window.DataSource); |
Ahora, vamos a ir estudiándolo, poco a poco.
La activación del plugin
La primera parte del código, es la definición del nuevo plugin. Pero, antes de estudiarla, vamos a ver su activación que está al final del código (creo que es lo más didáctico):
|
1 2 3 4 5 6 7 8 9 10 11 |
(function(jQuery, DataSource) { jQuery(function() { jQuery('div.graph').each(function(index, value) { var maxValue = parseInt(jQuery(this).attr('data-max-value')); var timeInterval = parseInt(jQuery(this).attr('data-time-interval')); var dataSource = new DataSource({ maxValue : maxValue, timeInterval : timeInterval }); var lineGraph = jQuery(this).lineGraph({ data: dataSource.getData(), maxValue: maxValue }); dataSource.onNewValue(function() { lineGraph.redraw(); }); }); }); })(window.jQuery, window.DataSource); |
- Selecciono los
divque van a contener mis gráficas conjQuery('div.graph'). - Para cada
divencontrado: - Extraigo sus atributos
data-max-valueydata-time-interval. - Creo un objeto de la clase
DataSource. - Activo el nuevo plugin
lineGraphsobre eldiv. - Configuro el evento
onNewValuedel objetodataSourcepara que refresque la gráfica (llamando al métodoredrawdel plugin que acabo de crear).
Yo creo que el uso del plugin es bastante directo. No le he añadido muchas opciones, porque mi objetivo era conseguir la configuración básica. Pero, aún así, se entiende de que hemos conseguido el objetivo (reutilizar el código encapsulándolo dentro del plugin para crear nuevas gráficas de forma bastante sencilla).
La definición del plugin
Ahora que hemos visto cómo usar el plugin, vamos a ver cómo debemos definirlo para conseguir ese interfase:
|
1 2 3 4 5 |
jQuery.fn.lineGraph = function(options) { var opts = jQuery.extend({ data : [], maxValue : 100 }, options); |
- Defino el plugin dentro de jQuery.
- Defino las opciones por defecto y la extiendo con las opciones que se le pasen al plugin cuando es activado.
La creación de la gráfica
El código que viene a continuación, prácticamente es el mismo usado en la entrada anterior para crear la gráfica:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
var svg = d3.selectAll(this).append('svg'); var width = parseInt(jQuery(this).css('width')) * 0.9; var height = Math.min(width, 600); svg.attr('width', width).attr('height', height); var xAxisLine = svg.append('svg:g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + (height - margins.bottom) + ')'); var yAxisLine = svg.append('svg:g') .attr('class', 'y axis left') .attr('transform', 'translate(' + (margins.left) + ',0)'); var graphLine = svg.append('svg:path') .attr('class', 'graph-line'); var xRange = d3.time.scale() .range([margins.left, width - margins.right]); var yRange = d3.scale.linear() .range([height - margins.bottom, margins.top]) .domain([0,opts.maxValue]); var xAxis = d3.svg.axis() .scale(xRange) .tickFormat(d3.time.format('%H:%M')) .tickSize(5) .tickSubdivide(true); var yAxis = d3.svg.axis() .scale(yRange) .tickSize(5) .orient('left') .tickSubdivide(true); var valueFunc = d3.svg.line() .x(function(d) { return xRange(d.date); }) .y(function(d) { return yRange(d.value); }) .interpolate('linear'); var redrawData = function redrawData() { xRange.domain([d3.min(opts.data, function(d) { return d.date; }), d3.max(opts.data, function(d) { return d.date; })]); xAxisLine.transition().call(xAxis); yAxisLine.transition().call(yAxis); graphLine.attr('d', valueFunc(opts.data)); }; redrawData(); return { redraw : function() { redrawData(); } }; |
Sólo merece la pena comentar:
- La gráfica, es insertada en el objeto
thisque se le pasa al plugin (y que corresponde con el elementodivsobre el que se está activando la gráfica). - Usa las opciones
opt.maxValueyopt.datapara configurar la gráfica (el máximo valor en el eje vertical y los datos para dibujar la gráfica, respectivamente). - Devuelve un objeto con un sólo método
redrawque será el que invoque para pedir al plugin que redibuje la gráfica.
El código css
Una vez que hemos visto el código html que hemos necesitado, vamos a ver el código css:
|
1 2 3 4 5 6 7 8 9 10 11 |
p.title { text-align:center; } .axis path, .axis line { fill:none; stroke:#777; shape-rendering:crispEdges; } .axis text { font-family:'Arial'; font-size:9px; } .x.axis text { font-family:'Arial'; font-size:8px; } .tick { stroke-dasharray:1, 2; } .graph-line { stroke:black; stroke-width:1; fill:none; } #graph2 .graph-line { stroke:blue; } #graph3 .graph-line { stroke:red; } #graph4 .graph-line { stroke:green; } #graph5 .graph-line { stroke:purple; } #graph6 .graph-line { stroke:yellow; } |
El detalle más importante es que, como he identificado a cada div contenedor con su propio id puedo usar estos id para dar formatos específicos a cada gráfica individual (en este caso, he dado un color distinto a cada gráfica).
Conclusiones
El plugin que he creado es bastante básico. Pero ha sido suficiente para conseguir encapsular mi código d3.js y poder reutilizarlo de forma muy limpia.
En una próxima entrada, me gustaría volver a encapsular nuevamente este código … pero dentro de un componente angular.js, para estudiar las diferencias entre ambas versiones.






