Hacer un Gráfico de Líneas en D3.js c. 5

Ha llegado el momento de intensificar nuestro juego y crear un gráfico de líneas desde cero. Y no cualquier gráfico de líneas: un gráfico de varias series que puede acomodar cualquier número de líneas. Además de manejar múltiples líneas, trabajaremos con escalas de tiempo y lineales, ejes y etiquetas, o mejor dicho, haremos que funcionen para nosotros. Hay mucho que hacer, así que sugiero que dispares tu servidor D3 y empecemos a trabajar.

¡Crearemos esta belleza! ¡Divertido!

Preparación de documentos

Como primer paso necesitamos preparar los datos y la estructura de archivos para la visualización. Crear line_chart.html, estilos.css y datos.csv en la carpeta de tu proyecto y rellénalos con los fragmentos que siguen. Esto nos ayudará a empezar.

Pegue esto en el diagrama de línea.archivo html. El código define el elemento svg para nosotros para que podamos empezar a dibujar de inmediato. También he creado una estructura básica por adelantado para que sea más fácil navegar por el documento a medida que trabajamos en sus secciones particulares.

Deje los estilos.documento css vacío por ahora. Pegue las siguientes filas en datos.csv. El gráfico de líneas contará con varias series: correspondientes a las columnas A, B y C.

date,A,B,C20-Jul-2019,10,20,1621-Jul-2019,11,22,1822-Jul-2019,13,19,2123-Jul-2019,11,17,2224-Jul-2019,15,16,2025-Jul-2019,16,19,1826-Jul-2019,19,21,1827-Jul-2019,22,25,1528-Jul-2019,18,24,1229-Jul-2019,14,20,1630-Jul-2019,14,18,1831-Jul-2019,16,18,2101-Aug-2019,15,20,2202-Aug-2019,14,21,19

Preparación de datos

El primer paso, y un paso crucial para toda la visualización, es leer correctamente los datos. He utilizado un ejemplo de varias series por una razón: si bien es bastante simple trazar un solo camino, manejar varias líneas (especialmente un número indefinido de ellas) requiere un poco más de delicadeza D3. Pegue lo siguiente en la sección de DATOS, vuelva a cargar el documento html y revise el inicio de sesión de la consola en su navegador:

const timeConv = d3.timeParse("%d-%b-%Y");const dataset = d3.csv("data.csv");dataset.then(function(data) { const slices = data.columns.slice(1).map(function(id) { return { id: id, values: data.map(function(d){ return { date: timeConv(d.date), measurement: +d }; }) }; });console.log("Column headers", data.columns);console.log("Column headers without date", data.columns.slice(1));// returns the sliced datasetconsole.log("Slices",slices); // returns the first sliceconsole.log("First slice",slices);// returns the array in the first sliceconsole.log("A array",slices.values); // returns the date of the first row in the first sliceconsole.log("Date element",slices.values.date); // returns the array's lengthconsole.log("Array length",(slices.values).length);

Repasemos las transformaciones llamadas a nuestro conjunto de datos una por una: – datos.columnas devuelve los encabezados csv-datos.columna.slice (1) devuelve los encabezados csv sin la columna de fecha (el slice comienza en la columna indexada en 1) – map () llama a una función en cada elemento de la matriz (que consiste en A, B y C) – llamemos a cada uno de esos elementos un ‘slice’– map () asigna el nombre de la columna como un elemento id a cada segmento– luego asigna una matriz de valores a cada segmento– observe cómo el elemento values evoca una función. Aquí mapeamos la información del conjunto de datos original: la matriz constará de 2 columnas, fecha y medición. La fecha se deriva de la primera columna (y se transforma a un formato de fecha), y la medición se toma de la columna correspondiente al segmento id.At al final de esas transformaciones obtenemos 3 matrices: A, B y C, con 2 columnas cada una: fecha y medida. Esta es una forma increíblemente flexible de dividir un conjunto de datos: ¡independientemente de cuántas columnas tenga! Todo está hecho en esas pocas filas. He impreso información en la consola para ayudarte a revisar el fragmento.

 Registro de consola

Revise el registro de consola para obtener más información

Preparación de escalas

Después de leer los datos, necesitamos configurar el mecanismo de escala. Esto se hace para imprimir el gráfico de acuerdo con los bienes raíces del svg. Las escalas transforman la entrada de datos (nuestras fechas y valores) en coordenadas en el plano svg. Pegue las siguientes líneas en la sección ESCALAS.

const xScale = d3.scaleTime().range();const yScale = d3.scaleLinear().rangeRound();xScale.domain(d3.extent(data, function(d){ return timeConv(d.date)}));yScale.domain();

Trazaremos las fechas en el eje x y los valores en el eje y. D3 proporciona un método scaleTime () para escalar fechas, y un método scaleLinear () para valores continuos. Primero decidimos el rango de la escala: a qué se deben traducir los valores de entrada. En este caso, estiraremos los valores de datos de 0 a la anchura del svg, y los valores numéricos de la altura del svg a 0. Como segundo paso, especificamos el dominio de datos de entrada. Un dominio consta de todos los valores entre un mínimo y un máximo especificados que un conjunto de datos puede tomar. En lugar de buscar manualmente esos valores, lo pasamos a través de funciones D3 integradas– – d3.extent () devuelve un valor mínimo y máximo de un array (en orden natural) – esto funcionará perfectamente en nuestro conjunto de fechas– d3.max() devuelve un valor máximo de la matriz. Observe cómo en este ejemplo primero extraemos un valor máximo de cada matriz para luego seleccionar un máximo de los tres. También añadí 4 al valor máximo por razones estéticas puramente subjetivas: quería tener un poco de espacio por encima del gráfico.

Las básculas ahora están configuradas. Si no tienes suficientes escalas y te gustaría ver más ejemplos, echa un vistazo a mi tutorial anterior.

Una báscula bien configurada nos permite comenzar a trazar valores en el svg. Cada vez que evocamos el conjunto de datos, solo necesitamos llamar a una escala adecuada en él.

Ejes

Basta de charla – ¡dibujemos algo ya! Los ejes son un buen punto de partida: si se trazan correctamente, nos asegurarán que los datos se han leído como se esperaba y que se escalan tan bien como nos hemos imaginado.

Pegue esto en los EJES debajo de la sección Preparación:

const yaxis = d3.axisLeft().scale(yScale); const xaxis = d3.axisBottom().scale(xScale);

Y esto a los EJES debajo de la sección de dibujo:

svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + height + ")") .call(xaxis);svg.append("g") .attr("class", "axis") .call(yaxis);

¡Y tan simple como eso, hemos trazado los ejes x e y!

Los ejes están aquí!

Es cierto que los ejes no son los más elegantes del mundo (hay algunos ejes bonitos por ahí), ¡pero están aquí! Hay algunos ajustes adicionales que podemos aplicar para hacerlos más amigables para el lector.

Veamos primero el eje x: hay algo divertido en las fechas. Leyendo desde la izquierda, obtenemos ‘Sáb 20′,’ Jul 21′,’ Lun 22′, y en algún momento llegamos a’Agosto’. Parece que los meses y los días han llegado en una mezcla insubordinada de variaciones. Tenemos que poner fin a este estilo libre, y con esto quiero decir que debemos decidir qué formato de fecha nos gustaría imprimir en la pantalla. El d3.el método axis () nos permite ajustar todo tipo de cosas para los ticks: su número, intervalo entre los puntos, formato de visualización, etc. Vamos a configurar algunos de ellos para ambos ejes.

Reemplace la definición de ejes en la sección Preparación con el siguiente fragmento y actualice la visualización:

const yaxis = d3.axisLeft() .ticks((slices.values).length) .scale(yScale);const xaxis = d3.axisBottom() .ticks(d3.timeDay.every(1)) .tickFormat(d3.timeFormat('%b %d')) .scale(xScale);

El código anterior especifica un número determinado de marcas para el eje y (14, o tantas como haya elementos de matriz / filas csv). En el caso del eje x, se mostrará una marca con una granularidad de un día, todos los días. Esto se consigue estableciendo la propiedad tick en d3.timeDay.cada (1). El formato de las fechas mostradas mostrará el día y el mes abreviado para cada marca. Después de esos cambios, terminamos con ejes algo mejorados:

Ordnung, por fin

¡Las fechas desobedientes ya no son un problema!

Para hacerlo aún mejor (¡es posible!!!) podemos añadir una etiqueta al eje y para mostrar lo que significan los valores. Si bien las fechas se explican por sí solas, los números por sí solos no contienen información. Agregue una etiqueta (llámela como quiera, fui con frecuencia) agregando lo siguiente al dibujo del eje y:

//this you hadsvg.append("g") .attr("class", "axis") .call(yaxis)//this you append .append("text") .attr("transform", "rotate(-90)") .attr("dy", ".75em") .attr("y", 6) .style("text-anchor", "end") .text("Frequency");

(No hay un conjunto de estilos para la etiqueta, por lo que no se mostrará en el gráfico, pero créanme y las herramientas para desarrolladores de Google Chrome, está ahí)

Etiqueta

La etiqueta del eje y es invisible

Finalmente, mejoremos el aspecto de los ejes. Por mejorar me refiero a: establecer los colores, anchos y renderizado de cada elemento y decidir la fuente a usar. Pegue lo siguiente en el archivo css y siéntase libre de tomar sus propias decisiones de estilo:

/* AXES *//* ticks */.axis line{stroke: #706f6f;stroke-width: 0.5;shape-rendering: crispEdges;}/* axis contour */.axis path {stroke: #706f6f;stroke-width: 0.7;shape-rendering: crispEdges;}/* axis text */.axis text {fill: #2b2929;font-family: Georgia;font-size: 120%;}

Las garrapatas son controladas por el .elemento de línea del eje, mientras que el eje real se establece con el .elemento path. Los ejes se ven nítidos (la humilde opinión del autor) y listos para recibir algunos datos:

Ejes embellecidos

Sin más preámbulos, vamos a trazar el gráfico!

Gráfico de líneas

Las líneas son esencialmente d3.rutas () que conectan un montón de coordenadas (x, y) en un plano 2D. Para construir una línea, debe indicarle dónde encontrar sus coordenadas x e y y luego agregarlas al svg. Pegue los siguientes fragmentos en los marcadores de posición creados anteriormente y revisemos el código juntos.

Esto debería llegar al bit LÍNEAS en la sección Preparación:

const line = d3.line() .x(function(d) { return xScale(d.date); }) .y(function(d) { return yScale(d.measurement); });

En este fragmento, llamamos a un constructor de líneas, d3.line() que utiliza dos accesores: x para valores en el plano horizontal, e y para el eje vertical. Aquí simplemente señalamos los valores más granulares de nuestra matriz, fecha y medición (este no es el momento de preocuparse por la estructura csv anidada). Una vez hecho esto, pegue lo siguiente en las LÍNEAS debajo de la sección Dibujo:

const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("d", function(d) { return line(d.values); });

Esto requiere alguna explicación. La variable lines selecciona un número no identificado de líneas del svg – e inmediatamente le dice a D3 que habrá 3 líneas apuntando al conjunto de secciones (líneas A, B y C). A continuación, añade un elemento g a cada uno de ellos: un elemento de agrupación que nos facilitará la vida a su debido tiempo. El elemento g recopilará todo lo que tenga que ver con una serie de gráficos en particular (también conocida como un segmento en el array): la línea (representada arriba como una ruta), sus puntos de datos sobre los que podremos pasar el cursor y las etiquetas de serie.

Lo primero que debe añadirse a las líneas (que de hecho son 3 contenedores g vacíos)son las líneas del gráfico en sí. Llamamos al d3.constructor line () en los datos para dibujar una ruta. Vea cómo primero necesitamos acceder a los valores debajo de cada sector. Esto se pasa al constructor que extrae las fechas y las mediciones según sea necesario.

Después de guardar los cambios, la visualización se actualiza a este:

En lugar de un gráfico de líneas, tenemos un gráfico de montaña

Bueno, esto no es perfecto, pero créeme, ¡estamos llegando! Apliquemos algunas correcciones estéticas en el gráfico y observemos cómo se forma. Añada lo siguiente a los estilos.css:

/* LINE CHART */path {fill: none;stroke: #ed3700;}

Necesitamos establecer el relleno en ninguno para que las formas reaparezcan como líneas. Actualizar el gráfico:

Las líneas han surgido

¿Qué separa un gráfico de líneas de un grupo de líneas pegadas en un gráfico? La capacidad de diferenciar entre las series. Por el momento solo tenemos lo primero.

Para empezar, necesitamos hacer una distinción entre las líneas del código. Agreguemos un id a cada clase de línea: agregue lo siguiente a la sección LÍNEAS en la parte de Preparación:

let id = 0;const ids = function () { return "line-"+id++;}

Este pequeño fragmento de código crea un contador que podemos aprovechar para asignar automáticamente un id de línea a cada línea agregada. Vamos a hacer referencia al contador en la propiedad class de las rutas. Ajuste el código en la sección LÍNEAS para agregar la propiedad clase:

const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("class", ids) .attr("d", function(d) { return line(d.values); });

¡Y mágicamente, cada camino tiene su propia clase!

 Clase de ruta

A las líneas se les da su propia identidad

Lo que nos queda por hacer es hacer referencia a estas clases en el css y dar a cada línea su propio carácter único. Cambiar la sección de Gráfico de líneas del css para decir:

/* LINE CHART */path.line-0 {fill: none;stroke: #ed3700;}path.line-1 {fill: none;stroke: #2b2929;stroke-dasharray: 2;}path.line-2 {fill: none;stroke: #9c9c9c;stroke-dasharray: 6;}

Ten en cuenta que no solo estoy modificando el color, sino también cambiando el trazo de cada línea. Recuerde que alrededor del 10% de todas las personas tienen algún grado de daltonismo y, para ser justos, diferenciar entre colores puede ser complicado para cualquiera de nosotros. Los colores simplemente se mezclarán si hay demasiadas series de datos y su tono se mostrará de manera diferente en cada monitor.

Después de aplicar los cambios, las líneas se separarán claramente en el gráfico que se muestra a continuación:

Las líneas son visualmente diferentes entre sí

Ahora las series están diferenciadas, pero aún es imposible decir cuál es cuál a menos que haya memorizado los datos subyacentes y tenga una imaginación visual bastante enferma, en cuyo caso me pregunto por qué necesitó un gráfico en primer lugar. Para ayudar a la mayoría de nosotros en el reconocimiento de series, propongo que agreguemos el nombre de la serie al lado derecho del gráfico. Agregue lo siguiente a la sección dibujo de LÍNEAS:

lines.append("text") .attr("class","serie_label") .datum(function(d) { return { id: d.id, value: d.values}; }) .attr("transform", function(d) { return "translate(" + (xScale(d.value.date) + 10) + "," + (yScale(d.value.measurement) + 5 ) + ")";}) .attr("x", 5) .text(function(d) { return ("Serie ") + d.id; });

El fragmento localiza el final de cada línea y le añade un elemento de texto. El texto se imprimirá como Serie A, Serie B o Serie C, dependiendo de la línea. Agregue lo siguiente al documento css para ajustar las etiquetas de serie:

.serie_label {fill: #2b2929;font-family: Georgia;font-size: 80%;}

Las etiquetas se adjuntan! Buenos tiempos.

Cada serie tiene su propia etiqueta

¡Todos estamos de acuerdo en que este es un hermoso gráfico de líneas! He pegado el código completo a continuación. Asegúrese de revisar la segunda parte del tutorial que presenta dos escenarios para agregar interactividad al gráfico.

Síganme en Twitter para más proyectos de ciencia de datos / visualización de datos!

Ejemplos de código

line_chart.html: estilos

.css:

/* AXES *//* ticks */.axis line{stroke: #706f6f;stroke-width: 0.5;shape-rendering: crispEdges;}/* axis contour */.axis path {stroke: #706f6f;stroke-width: 0.7;shape-rendering: crispEdges;}/* axis text */.axis text {fill: #2b2929;font-family: Georgia;font-size: 120%;}/* LINE CHART */path.line-0 { fill: none; stroke: #ed3700;}path.line-1 { fill: none; stroke: #2b2929; stroke-dasharray: 2;}path.line-2 { fill: none; stroke: #9c9c9c; stroke-dasharray: 6;}.serie_label { fill: #2b2929; font-family: Georgia; font-size: 80%;}

data.csv: