Escalas lineales en d3 e interacción con los datos mapeados con d3.invert()

En gráficos de tipo time-series como en contratos menores se exponen datos en dos ejes. El eje X, suele representar el tiempo en alguna medida (día,mes, año, etc) y en el eje Y se presenta una cantidad abstracta. En la mayoría de los casos se tiene que hacer un ´mapping´ de estos datos para convertir la cantidad abstracta (aqui dinero en miles de €) en píxeles segun las dimensiones que queremos que tenga nuestro grafico. Existen muchas manera de hacer el mapping con la lineal siento la mas común cuanto se expone datos de dinero.

D3js tiene unas funciones que permiten mapear una serie de numeros a una otra de manera linear:

var x = d3.scaleLinear().domain([10, 130]).range([0, 960]);

En el ejemplo se ha definido una función x. Unos datos que están entre 10 y 130 se mapean en una escala de 0 a 960.

Esa función la podemos utilizar de la siguiente manera.

x(10);

<- 0

x(130);

<- 960

x(70);

<- 480

x(2);

<- -64

Se puede observar que dando un numero fuera de rango del .domain() se devuelve un valor negativo. Finalmente la funcion X representa una manera de proyectar un valor de una escala lineal a una otra.

La función x se puede también invertir con x.invert() que seria en realidad como hacer :

var y = d3.scaleLinear().domain([0, 960]).range([10, 130]);
o hacer:
x.invert()

Pero utilizando .invert() tiene la ventaja, aparte la economía de código, podemos tener un mapeo que no sea fijo, si por ejemplo tenemos un gráfico que cambia sus dimensiones según el tamaño de la pantalla. .invert() nos asegura que la inversión sera correcta.

Resulta muy cómodo a la hora de interactuar con los datos. Se pueden obtener la cantidad inicial para imprimir la en la pantalla p.ej. En seguida veremos como hemos aprovechado de esa funcionalidad para calcular el importe en dinero de todas las barras activas en el grafico de contratos menores.

Una vez aplicados unos filtros de empresa, centro de gasto o categoria de gastos, en nuestro grafico aparecen los que cumplen estos filtros.

barrasActivas = d3.selectAll(‘svg .bar’+ filters);

Cada barra tiene un attributo height que ha sido calculado con el scaleLinear() como en el ejemplo arriba. haciendo la inversion con :

altura = barrasActivas.attr(‘height’); // Leer la altura en pixeles

dinero = yScale.invert(altura) // Revesar la operación que mapeo el importe del contrato en una altura en píxeles

Haciendo una repetitiva se puede sumar los importes de todas la barras activas y imprimir la en la pantalla.

 

Filtrar selecciones en d3.js

La primera tarea que he realizado en el proyecto de contratos menores ha sido una mejora en la interactividad del gráfico donde se exponen de manera cuantitativa los contratos del ayuntamiento de Valencia.

Existen tres maneras de seleccionar los contratos que el usuario quiere visualizar. Los contratos relacionados con una empresa, con un centro de gasto o con una palabra descriptiva que se podía encontrar en los datos publicados por el ayuntamiento.

De momento se puede seleccionar solo un botón para visualizar los contratos relacionados con ello.

Una posible mejora que he estado trabajando, es poder sumar filtros de la siguiente manera:

empresa + centro : Ver los contratos de una empresa que se han hecho por un solo centro del ayuntamiento.

empresa + centro + descripción : Ver los contratos de una empresa que se han hecho por un solo centro del ayuntamiento y además tienen una palabra descriptiva concreta.

centro + descripción : Ver todos los contratos que realizo el centro y tengan que ver con la descripción elegida.

etc…


Para conseguir eso tenia que crear una variable global donde se van guardando las opciones que el usuario ha elegido mediante los botones.

En seguida, cambiar los selectores que se ocupan de filtrar los datos para mostrar solo los que corresponden a la selección.

svg.selectAll('svg .bar.'+ dni).style("opacity",activeopacity).style("visibility","visible");
svg.selectAll('svg .bar'+ filtersText).style("opacity",activeopacity).style("visibility","visible");

Se puede ver, en el código arriba que en vez de utilizar el dni de la empresa para seleccionar los contratos se utiliza una variable que puede contener o no dos cosas mas, la descripción y/o el centro.

Lo que merece la pena mencionar desde un punto de vista de programación es que tuve que tener cuidado con el foco (scope) de las variables. El array que lleva los filtros ha sido declarado en el principio del código javascript, para que no pierda el foco y guarde en la memoria las anteriores selecciones:

var filters = [];

mientras la variable filtersText que lleva los filtros en forma de cadena (string) listos para utilizar en la selectALL es una variable local que cada ves que el usuario elige un botón se vuelve a declarar para crear el texto, borrando a la ves su anterior contenido.


¿Porque?

Los filtros pueden resultar útiles para hacer visualizaciones mas detalladas. Por ejemplo, una vez seleccionada la empresa EL CORTE INGLÉS S.L y navegando por los distintos centros de gasto, se podrá verificar que tiene contratos que llegan desde 5 centros de gastos distintos.


¿Luego que?

Si el usuario dedica un poco de tiempo eligiendo entre empresas y centros vera que muchas combinaciones (quizás la mayoría) están vacías.

Los gráficos interactivos tienen la ventaja de ofrecer nuevas informaciones a medida que interactuamos con ellos. Una idea que puede resultar interesante, sería ofrecer al usuario la información de los posibles filtros que si tienen al menos un contrato.

En el ejemplo de CORTE INGLES eso significa que después de clicar a la empresa, los botones de los centros que si contienen un contrato cambien a un color distinto, avisando así al usuario que si los selecciona no vera un gráfico vacío sino algún/os contratos.

d3.js selectAll

En d3.js el método mas utilizado es tal vez el d3.selectAll()

Su función es sencilla:

Seleccionar elementos de un HTML DOM. Se pueden seleccionar todos los elementos como se haría en una hoja de estilos CSS utilizando selectores.

d3.selectAll('div')
d3.selectAll('#myId')
d3.selectAll('.myClass')
d3.selectAll('div.myClass')
d3.selectAll('div, p, span.mySpan')

All the above would return an array of elements that corresponds to the selector in the arguments of the method.

The selected nodes can be saved into a variable for further processing

a = d3.selectAll('div');
a.style({stroke:"black", "stroke-width:" "2px"});

OR append the next method directly to the selection:

d3.selectAll('div').style({stroke:"black", "stroke-width:" "2px"});