Filtrar selecciones en d3.js II

En el anterior articulo vimos como construir el selector para poder ver los contratos que cumplan mas de un filtro.

En ese post vamos a ver como utilizando el metodo d3.filter() de d3.js y como podemos saber los centros de gasto que tienen al menos un contrato con una empresa, una vez que ella haya sido seleccionada.

Si hemos realizado una seleccion con d3.selectAll() que la llamemos select:

var select = d3.selectAll(‘selector’);

Luego podemos filtrar todavia mas :

select.filter(‘nuevo_selector’);

Esa funcionalidad me ha ayudado repasar con un .each() todos los centros de gasto del ayuntamiento cuando se elige una empresa. En cada iteración, se verifica si el .filter(‘centro_de_gasto’) da un array vacio o no. Si no esta vacio entonces con .style(‘background-color’,’yellow’) se cambia el fondo del boton avisando asi que tiene contenido.

Tambien el fondo se pone gris cuando se de-selecciona la empresa.

Javascript Performance

La pagina de w3schools, es una referencia para todo tipo de desarrollo web. Da unos consejos para mejorar el rendimiento de código Javascript. Leyendo esa pagina he podido detectar unas “malas practicas” en contratos menores que podría dar un respiro a la pagina y reducir el tiempo de carga.

El consejo que mas me llamo la atencion es el siguiente:

Reduce DOM Access

Accessing the HTML DOM is very slow, compared to other JavaScript statements. If you expect to access a DOM element several times, access it once, and use it as a local variable:

Example:
var obj;
obj = document.getElementById("demo");
obj.innerHTML = "Hello";

En contratosmenores es una practica común seleccionar muchas veces todas las barras asi

svg.selectAll('svg .bar')

para hacerlas invisibles y luego volver a seleccionar las que corresponden a la acción de usuario para hacer visibles.

La primera selección es siempre la misma con lo cual podría hacer una sola vez al cargar la pagina y guardar en un objeto de javascript como aparece en el ejemplo de w3schools.

La manera de proceder para saber si ese cambio de verdad mejora el rendimiento, seria utilizar las herramientas de desarrolladores de Firefox o Chrome para hacer un benchmark para verificar cual es la mas performante.

EDIT 2017/06/15:

Antes de proceder con ese experimento, he encontrado mas información sobre la manera que d3.js hace las selecciones. En el blog de Mike Bostock https://bost.ocks.org/mike/selection/

commenta:

A Subclass of Array

You were probably told that selections are arrays of DOM elements. False. For one, selections are a subclass of array; this subclass provides methods to manipulate selected elements, such as setting attributes and styles. Selections inherit native array methods as well, such as array.forEach and array.map. However, you won’t often use native methods as D3 provides convenient alternatives, such as selection.each. (A few native methods are overridden to adapt their behavior to selections, namely selection.filter and selection.sort.)

Resumiendo, los métodos de d3.js extienden la clase Array, y según Mike no son arrays de DOM elements como los que resultarian de una selección nativa de javascript como la .getByClassName(). Son simples arrays que se convertirán en elementos de DOM a la hora de renderizar la pagina.

El benchmark.

Es difícil hacer un benchmark en un proyecto ya crecido como ese, sobre todo porque es difícil aislar lo que quieres evaluar. Finalmente he decidido aprovechar de la consola de javascript y sus métodos .time() y .timeEnd() para evaluar solo las dos maneras de seleccionar barras.

var barras = svg.selectAll('svg .bar');

console.time('benchmark'); svg.selectAll('svg .bar').style('visibility','visible');console.timeEnd('benchmark');

console.time('benchmark');barras.style('visibility','visible');console.timeEnd('benchmark');

He repetido diez veces el benchmark para sacar unas medias para las dos opciones que se pueden ver en la tabla:

 

Method/Selector selectAll(‘svg .bar’) var barras
.style(‘visibility’,’visible’) 8.42ms 3.37ms
.attr(‘class’,’foo’) 0.29ms 4.54ms

 

Evaluación

Lo que resulta interesante es que la dos maneras de seleccionar son buenas cada una para distintos métodos.

selectAll() resulta ultra rápido para asignar atributos a un elemento, pero tarda casi el doble en cambiar el estilo de un elemento comparando con la variable que tiene guardada la selecciones. Eso supongo que tiene que ver con como .selectAll() hereda los métodos nativos de Array.

Mi conclusión es que no mejoraría significativamente el redimiendo de la pagina optando por una u otra opción, y desde luego el principal problema es la primera carga de la pagina y no la parte donde se interactúa con el contenido.

 

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"});

Visualización de datos

Empiezo hoy a colaborar en un proyecto colaborativo promovido por montera34.com. Contratos menores es un proyecto que surge tras la publicación de datos en unos ayuntamientos españoles sobre contratos que el ayuntamiento asigna a terceros sin concurso.

Mi estancia tiene como objetivo inicial realizar unas practicas no laborales para finalizar la formación profesional que he hecho este año.

Aparte de la formalidad de obtener el titulo de CP, he fijado unos objetivos personales, que espero cumplir en esa estancia de dos meses.

  • Aprender la librería d3.js
  • Tener una fuerte introducción en el mundo de visualización de datos e gráficos interactivos.
  • Trabajar de forma colaborativa.
  • Conocer de primera mano un proyecto de co-working como en el que estare realizando mis practicas.