Saltar al contenido

La filosofía modular de D3

Qué es D3 exactamente y qué no es: una colección de módulos independientes donde solo una minoría toca el DOM, el mapa de las familias, y por qué la curva de aprendizaje es tan pronunciada.

Qué es y qué no es

D3 son las siglas de Data-Driven Documents, y viene de un artículo de Michael Bostock, Vadim Ogievetsky y Jeffrey Heer presentado en InfoVis en 2011. Su antecesor directo, Protovis, era un lenguaje declarativo de marcas gráficas; D3 abandonó ese enfoque a propósito y lo sustituyó por algo más bajo nivel: manipular el DOM directamente en función de los datos.

La consecuencia de esa decisión es que D3 no tiene tipos de gráfico. No existe d3.barChart(). Lo que existe son las piezas: escalas, generadores de forma, algoritmos de disposición, formateadores, interpoladores. Un gráfico de barras se escribe combinándolas, y por eso hay tanto código de D3 en internet que hace lo mismo de veinte formas distintas.

Esto es a la vez su mayor fortaleza y el origen de toda su mala fama. La fortaleza: puedes construir cualquier representación, incluida una que nadie haya construido antes, porque no estás limitado por el catálogo de nadie. La mala fama: para hacer un gráfico de barras estándar, que es lo que necesita el noventa por ciento de la gente, escribes cien líneas donde una biblioteca de gráficos te pide cinco.

El criterio honesto para decidir si D3 es tu herramienta:

  • Si necesitas tres gráficos de tipos estándar, una biblioteca de gráficos es la respuesta correcta y D3 es una pérdida de tiempo.
  • Si necesitas una representación que no está en ningún catálogo, o control total sobre cada píxel, D3 es la respuesta.
  • Si necesitas un sparkline, escríbelo tú: es lo que llevas haciendo desde generar rutas a partir de datos y son treinta líneas.

El mapa de los módulos

Los paquetes se publican por separado con el prefijo d3-, y el paquete d3 no es más que un índice que los reexporta todos. La división que de verdad importa no es por temas, es por si tocan el DOM o no.

Familia Módulos ¿Toca el DOM?
Datos d3-array, d3-dsv, d3-fetch, d3-random No
Escalas y formato d3-scale, d3-scale-chromatic, d3-format, d3-time, d3-time-format, d3-interpolate, d3-color No
Geometría d3-shape, d3-path, d3-polygon, d3-contour, d3-delaunay, d3-quadtree, d3-geo No
Disposiciones d3-hierarchy, d3-force, d3-chord No
Tiempo de ejecución d3-timer, d3-ease, d3-dispatch No
DOM d3-selection, d3-transition, d3-axis, d3-drag, d3-zoom, d3-brush Sí

Seis módulos de treinta tocan el DOM. Los otros veinticuatro son funciones puras sobre números, arrays y objetos: se pueden ejecutar en un worker, en el servidor, en una prueba unitaria sin navegador, y alimentar un lienzo 2D exactamente igual que un SVG.

Esa tabla es la lección entera del nivel. Cuando alguien dice «D3 es incompatible con React», está hablando de esos seis módulos. Los otros veinticuatro no tienen ninguna opinión sobre quién dibuja.

De la familia de datos, cuatro funciones de d3-array justifican el paquete por sí solas y casi nadie las conoce:

JS
import { extent, bin, rollup, bisector, fsum } from 'd3-array';extent(datos, d => d.valor);              // [min, max] en una pasadabin().thresholds(20)(valores);            // histograma: bins con x0, x1rollup(datos, v => v.length, d => d.pais); // agrupar y agregar, devuelve un Mapbisector(d => d.fecha).left;              // busqueda binaria sobre array ordenadofsum(valores);                            // suma sin error acumulado de coma flotante

extent es la que sustituye al par Math.min/Math.max con un solo recorrido y sin el desbordamiento de pila que produce Math.max(...array) con cien mil elementos. fsum usa el algoritmo de suma compensada de Neumaier para que sumar un millón de decimales no derive. bisector es la búsqueda binaria que necesita el hit testing de qué es una escala, ya escrita y probada.

Y una que cierra el círculo del nivel anterior: d3.ticks y d3.tickIncrement, que viven en d3-array, son exactamente el algoritmo de las marcas bonitas. Si ya lo has implementado, no necesitas el paquete; si no, ahí está.

Por qué la curva es tan pronunciada

D3 es difícil, y conviene ser exacto sobre por qué, porque casi todas las explicaciones que circulan son incorrectas.

No es difícil por las matemáticas. scaleLinear es la interpolación lineal que ya sabes escribir. d3.line() genera una cadena de d que ya sabes leer desde la anatomía del atributo d.

Es difícil porque son dos bibliotecas en una. d3-selection es una API de manipulación del DOM completa, con su propio modelo de selección, su propio sistema de eventos y su propia semántica de encadenado. Aprender D3 por el camino clásico significa aprender esa segunda API del DOM antes de llegar a la parte que te interesaba. Y esa API compite con la que ya usas: si tu aplicación es React, Svelte o Vue, ya tienes un dueño del DOM.

Es difícil porque el encadenamiento oculta el tipo. En svg.selectAll('rect').data(datos).join('rect').attr('x', d => x(d.mes)), cada eslabón devuelve un objeto distinto con métodos distintos, y no hay forma de saber cuál sin conocer la biblioteca. Un error de orden produce un objeto vacío en silencio, sin excepción y sin nada dibujado.

Es difícil porque la documentación está repartida por paquetes. Cada módulo tiene su propia referencia, y una tarea sencilla («eje temporal con etiquetas en español») cruza cuatro: d3-scale, d3-time, d3-time-format y d3-axis.

Nada de eso es un defecto de diseño: es el precio de la modularidad. Pero explica por qué el camino corto es aprender d3-scale, d3-shape y d3-array, que se leen en una tarde, y dejar d3-selection para cuando de verdad haga falta.