Práctica 2: Reorganizando el código
En la anterior entrada, leímos una fuente de datos CSV desde d3.js y fuímos capaces de representar esos datos en una gráfica lineal. Para que el código javascript fuera lo más claro posible de leer, lo escribí lo más linealmente posible sin prestar atención a la forma más eficaz de hacerlo.
En esta nueva entrada, casi no vamos a introducir modificaciones. Aprovecharemos que ya tenemos asimilado el código anterior para reorganizarlo un poco.
Nuestro anterior código, tenía la siguiente organización:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
(function(d3) { jQuery(function() { // (a) definimos variables globales .... function redrawData() { // (b) creamos el elemento svg en nuestro documento ... // (c) definimos las escalas ... // (d) definimos los ejes ... // (e) definimos la gráfica ... // (f) añadimos los ejes al elemento svg ... // (g) añadimos la gráfica al elemento svg } // cuando termine de leer los datos, llama a redrawData() d3.csv('./data/YAHOO-IBEX.csv') .row(function(d) { return { date: new Date(d.Date), high: +d.High }; }) .get(function(error, rows) { data = rows; redrawData(); }); }) })(window.d3); |
Como he dicho antes, escribí este código para que fuera lo más secuencial posible. Además, la anchura width
del elemento svg
la calculaba en función del ancho disponible, para adaptarse a éste.
Ahora, vamos a reescribir este código para optimizarlo un poco:
- Podemos crear el elemento
svg
contenedor sin esperar a tener los datos. De este modo, si los datos tardan mucho en ser recuperados, evitamos que el documento esté vacío. - Las definiciones (escalas, ejes y gráfica) podemos también podemos calcularlas sin esperar a tener los datos (con la excepción que no sabemos cuáles son los valores extremos, ahora veremos cómo resolver este problema)
- Si el navegador cambia de tamaño, el gráfico ya no encajará. Vamos también a estar pendiente si el documento cambia de tamaño, para recalcular el tamaño del gráfico
La nueva organización quedaría así:
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 |
(function(d3) { jQuery(function() { // (a) definimos variables globales .... // (b) creamos el objeto svg .... // redrawSvg() se encarga de la definición preliminar de nuestra gráfica function redrawSvg() { // calculamos la altura y la anchura del elemento svg ... // (c.1) hacemos la definición previa de las escalas ... // (d) definimos los ejes ... // (e) definimos la gráfica ... } // redrawData() se encarga de ajustar la escala a los datos y dibujar la gráfica function redrawData() { // (c.2) terminamos de definir las escalas ... // (f) dibujamos los ejes ... // (g) dibujamos la gráfica ... } // realizo la definición preliminar redrawSvg(); // cuando termine de leer los datos, llama a redrawData() para dibujar la gráfica d3.csv('./data/YAHOO-IBEX.csv') .row(function(d) { return { date: new Date(d.Date), high: +d.High }; }) .get(function(error, rows) { data = rows; redrawData(); }); // cuando cambie el tamaño de la ventana, redefino el tamaño y redibujo la gráfica jQuery(window).resize(function() { redrawSvg(); redrawData(); }) })(window.d3); |
Espero que sea fácil de seguir cómo los bloques de código siguen siendo los mismos. La diferencia más importante es que los he reorganizado.
El código final es el siguiente:
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 |
'use strict'; (function(d3) { jQuery(function() { var data = []; var width, height; var margins = { top: 20, right: 50, bottom: 20, left: 50 }; var svg, xRange, yRange, xAxis, yAxis, valueFunc; svg = d3.selectAll('div#ibex35').append('svg'); var redrawSvg = function redrawSvg() { width = parseInt(jQuery('#ibex35').css('width'))-20; height = parseInt(jQuery('#wrapper').css('height')); svg .attr('width', width) .attr('height', height); xRange = d3.time.scale() .range([margins.left, width - margins.right]); yRange = d3.scale.linear() .range([height - margins.bottom, margins.top]); xAxis = d3.svg.axis() .scale(xRange) .tickSize(5) .tickSubdivide(true); yAxis = d3.svg.axis() .scale(yRange) .tickSize(5) .orient('left') .tickSubdivide(true); valueFunc = d3.svg.line() .x(function(d) { return xRange(d.date); }) .y(function(d) { return yRange(d.high); }) .interpolate('linear'); }; var redrawData = function redrawData() { xRange.domain([d3.min(data, function(d) { return d.date; }), d3.max(data, function(d) { return d.date; })]); yRange.domain([d3.min(data, function(d) { return d.high; }), d3.max(data, function(d) { return d.high; })]); svg.select('.x.axis').remove(); svg.select('.y.axis.left').remove(); svg.select('.daily-values').remove(); svg.append('svg:g') .attr('class', 'x axis') .attr('transform', 'translate(0,' + (height - margins.bottom) + ')') .call(xAxis); svg.append('svg:g') .attr('class', 'y axis left') .attr('transform', 'translate(' + (margins.left) + ',0)') .call(yAxis); svg.append('svg:path') .attr('class', 'daily-values') .attr('d', valueFunc(data)); }; redrawSvg(); d3.csv('./data/YAHOO-IBEX.csv') .row(function(d) { return { date: new Date(d.Date), high: +d.High }; }) .get(function(error, rows) { data = rows; redrawData(); }); jQuery(window).resize(function() { redrawSvg(); redrawData(); }); }); })(window.d3); |
El único detalle adicional que me falta por explicar es que he configurado esta nueva versión para que la altura de la gráfica sea variable, ocupando el 100% del área visible del navegador. Para conseguir este efecto, los pasos que he seguido son los siguientes:
- El
div
donde voy a insertar mi gráfica, está dentro de otrodiv
conid
wrapper
. - He definido una regla CSS para que este
div#wrapper
ocupe el 100% de la altura visible Midiv#ibex35
que es donde inserto la gráfica, lo he configuradoposition:absolute
para que su altura no afecte al contenedordiv#wrapper
. - Por último, recupero la altura de este
div#wrapper
para definir la altura de mi gráfica.
1 2 3 4 5 |
<div id="wrapper" class="container-fluid"> <div class="row"> <div id="ibex35"></div> </div> </div> |
1 2 3 |
html, body { height:100%; margin:0; } #wrapper { min-height: 100%; position:relative; } #ibex35 { width:95%; margin-left:auto; margin-right:auto; overflow:auto; position:absolute; } |
1 |
height = parseInt(jQuery('#wrapper').css('height')); |
El resultado final, podéis abrirlo en una nueva ventana en este enlace.