Usar D3 solo para la matemática
El enfoque que usa la mayoría en 2026: importar los módulos puros, calcular la geometría y dejar que tu framework o tu lienzo escriban el resultado, sin que dos dueños se peleen por el DOM.
La frontera
La regla cabe en una frase: D3 calcula, tú dibujas.
import { scaleLinear, scaleBand } from 'd3-scale';import { line, curveMonotoneX } from 'd3-shape';import { extent, max } from 'd3-array';import { format } from 'd3-format';export function calcular(datos, { ancho, alto }) { const x = scaleLinear() .domain(extent(datos, d => d.fecha)) .range([0, ancho]); const y = scaleLinear() .domain([0, max(datos, d => d.valor)]) .nice() .range([alto, 0]); const fmt = format(',.0f'); return { d: line().x(d => x(d.fecha)).y(d => y(d.valor)).curve(curveMonotoneX)(datos), marcasY: y.ticks(5).map(v => ({ y: y(v), texto: fmt(v) })), marcasX: x.ticks(6).map(v => ({ x: x(v), texto: String(v) })), ancho, alto, };}Esa función es exactamente la capa de especificación de el patrón de componente de gráfico: pura, testable sin navegador, sin una sola referencia al DOM. Lo único que ha cambiado es que las escalas y el generador vienen de D3 en lugar de estar escritos a mano.
Y el render, en el framework que sea, es un mapeo directo de esa estructura a marcado. En una plantilla declarativa se escribe como se escribe cualquier lista. En imperativo, con el ayudante de creación de elementos del nivel 26. En un lienzo, cambiando el generador a la variante con contexto.
Las tres ventajas de la frontera, en orden de importancia:
Un solo dueño del DOM. Es la razón principal y se desarrolla en el callout.
La especificación se puede testar. Comprobar que la primera marca del eje vale 0 y la última 250 es una prueba unitaria de dos líneas que corre en milisegundos. Comprobar lo mismo a través de d3-selection requiere un DOM simulado y una consulta por selector.
El render se puede cambiar sin tocar la geometría. La misma calcular alimenta un SVG en el servidor y un lienzo en el cliente. Es la puerta al enfoque híbrido.
Qué merece la pena de la parte que sí toca el DOM
Descartar d3-selection no implica descartar los seis módulos que tocan el DOM. Tres de ellos resuelven problemas que reimplementar cuesta semanas.
d3-zoom. Encapsula el desplazamiento y el zoom con rueda, con pellizco táctil, con doble clic y con arrastre, normalizando las diferencias entre navegadores en el evento de rueda, que son considerables. Mantiene una transformación con k, x e y, y expone métodos para reescalar una escala existente. Se usa así, sin que D3 dibuje nada:
import { zoom, zoomIdentity } from 'd3-zoom';import { select } from 'd3-selection';let transformacion = zoomIdentity;select(lienzo).call( zoom() .scaleExtent([1, 40]) .on('zoom', (evento) => { transformacion = evento.transform; dibujar(); // tu funcion, tu lienzo }));function dibujar() { const xz = transformacion.rescaleX(x); // escala reescalada por el zoom // ...dibuja con xz en lugar de x}d3-selection aparece ahí solo como adaptador para enganchar el comportamiento a un elemento. Nada más.
d3-drag hace lo mismo con el arrastre, unificando ratón y táctil, y resolviendo los casos raros (el puntero que sale de la ventana, el botón secundario, el arrastre que empieza sobre un hijo).
d3-brush implementa la selección rectangular con sus asas redimensionables. Es mucho más trabajo del que parece.
d3-axis, en cambio, no compensa: genera marcado SVG con sus propias convenciones de clases y hay que pelearse con él para estilarlo. Escribir el eje a mano, como en construir un eje a mano, da más control por menos código.
Lo que llega al paquete final
Los módulos de D3 se publican como módulos ES, así que un empaquetador moderno puede eliminar lo que no uses. Puede, no siempre lo hace.
// Lo que quieres escribirimport { scaleLinear } from 'd3-scale';import { line } from 'd3-shape';// Aceptable con un empaquetador que sacuda bien el arbolimport { scaleLinear, line } from 'd3';// Lo que destruye cualquier posibilidad de eliminacionimport * as d3 from 'd3';const escala = d3[`scale${tipo}`](); // acceso dinamicoLa última línea es la que hay que evitar a toda costa. Un acceso por propiedad calculada obliga al empaquetador a conservar el objeto completo, porque no puede saber qué propiedades se van a leer. Y aparece con más frecuencia de lo que parece: en cualquier función que elija el tipo de escala a partir de una configuración.
La forma correcta de resolver ese caso es un mapa explícito:
import { scaleLinear, scaleLog, scaleTime, scaleBand } from 'd3-scale';const ESCALAS = { lineal: scaleLinear, log: scaleLog, tiempo: scaleTime, banda: scaleBand };const escala = (ESCALAS[tipo] ?? scaleLinear)();Ahora las cuatro importaciones son estáticas, el empaquetador ve exactamente qué se usa, y el resto de d3-scale no viaja.
Dos avisos concretos sobre el peso: d3-geo y d3-scale-chromatic son grandes en relación con lo que suele usarse de ellos. De d3-scale-chromatic se importa casi siempre una sola paleta, así que importar la constante concreta en lugar del módulo entero ahorra bastante. Y en cualquier caso, la única forma de saberlo es medirlo con el analizador de tu empaquetador, no estimarlo.
Elegir la capa
Hay cuatro niveles de herramienta y elegir el correcto es más importante que dominar cualquiera de ellos.
Nada. Un sparkline, una barra de progreso, un gráfico de dos series con un eje. Las escalas del nivel 27 y una plantilla de cadena. Cero dependencias, treinta líneas, control total. Es la respuesta correcta muchas más veces de lo que la gente cree.
D3 para la matemática. Cuando aparecen escalas temporales, formateo con locale, curvas, apilados o jerarquías. Es donde la relación entre lo que ahorras y lo que pesa es mejor. Y es lo que hace hoy la mayoría de la gente que dice «uso D3».
Una capa declarativa sobre D3. Existen bibliotecas de gráficos con una gramática de alto nivel, construidas sobre los mismos módulos, donde un gráfico exploratorio se escribe en cinco líneas. Para explorar datos y para gráficos estándar son un multiplicador enorme, y el precio es que salirse de lo que la gramática expresa cuesta más que no haberla usado.
Una biblioteca de gráficos completa. Cuando necesitas quince tipos de gráfico con leyendas, tooltips, exportación y temas, y ninguno de ellos es especial. Escribir eso con D3 es meses de trabajo que ya está hecho.
El error caro no es elegir mal el primer día: es no volver a plantearse la elección cuando el proyecto cambia. Un panel que empezó con dos gráficos y ahora tiene veinte se ha convertido en una biblioteca de gráficos mal escrita, y ese es el momento de mirar hacia abajo en la lista, no de seguir añadiendo.