javascript d3.js

javascript - Seleccionar nulo: ¿cuál es la razón detrás de ''selectAll(null)'' en D3.js?



(1)

tl; dr

El objetivo de usar selectAll(null) es garantizar que la selección "enter" siempre corresponda a los elementos en la matriz de datos, conteniendo un elemento para cada elemento en los datos.

La selección "enter"

Para responder a su pregunta, tenemos que explicar brevemente qué es una selección "enter" en D3.js. Como probablemente sepa, una de las características principales de D3 es la capacidad de vincular datos a elementos DOM.

En D3.js, cuando uno vincula datos a elementos DOM, son posibles tres situaciones:

  1. El número de elementos y el número de puntos de datos son iguales;
  2. Hay más elementos que puntos de datos;
  3. Hay más puntos de datos que elementos;

En la situación # 3, todos los puntos de datos sin un elemento DOM correspondiente pertenecen a la selección "enter".

Por lo tanto, en D3.js, las selecciones "enter" son selecciones que, después de unir elementos a los datos, contienen todos los datos que no coinciden con ningún elemento DOM. Si usamos una función anexar en una selección "enter", D3 creará nuevos elementos, vinculando esos datos para nosotros.

Este es un diagrama de Venn que explica las posibles situaciones con respecto al número de puntos de datos / número de elementos DOM:

Enlace de datos a elementos DOM ya existentes

Rompamos su fragmento propuesto para agregar círculos.

Esta...

var circles = svg.selectAll("circle") .data(data)

... vincula los datos a una selección que contiene todos los círculos. En la jerga D3, esa es la selección de "actualización".

Luego esto...

.enter() .append("circle");

... representa la selección "enter", creando un círculo para cada punto de datos que no coincide con un elemento seleccionado.

Claro, cuando no hay ningún elemento (o una clase dada) en la selección, usar ese elemento (o esa clase) en el método selectAll funcionará según lo previsto. Entonces, en su fragmento, si no hay un elemento <circle> en la selección de svg , selectAll("circle") se puede usar para agregar un círculo para cada punto de datos en la matriz de datos.

Aquí hay un ejemplo simple. No hay <p> en el <body> , y nuestra selección "enter" contendrá todos los elementos en la matriz de datos:

var body = d3.select("body"); var data = ["red", "blue", "green"]; var p = body.selectAll("p") .data(data) .enter() .append("p") .text(d=> "I am a " + d + " paragraph!") .style("color", String)

<script src="https://d3js.org/d3.v4.min.js"></script>

Pero, ¿qué sucede si ya tenemos un párrafo en esa página? Echemos un vistazo:

var body = d3.select("body"); var data = ["red", "blue", "green"]; var p = body.selectAll("p") .data(data) .enter() .append("p") .text(d=> "I am a " + d + " paragraph!") .style("color", String)

<script src="https://d3js.org/d3.v4.min.js"></script> <p>Look Ma, I''m a paragraph!</p>

El resultado es claro: ¡el párrafo rojo desapareció! ¿Dónde está?

El primer elemento de datos, "rojo", estaba vinculado al párrafo ya existente. Luego, solo se crearon dos párrafos (nuestra selección "enter"), el azul y el verde.

Eso sucedió porque, cuando usamos selectAll("p") , ¡seleccionamos, bueno, elementos <p> ! Y ya había un elemento <p> en esa página.

Seleccionar nulo

Sin embargo, si usamos selectAll(null) , ¡ no se seleccionará nada ! No importa que ya haya un párrafo en esa página, nuestra selección "enter" siempre tendrá todos los elementos en la matriz de datos.

Vamos a verlo funcionando:

var body = d3.select("body"); var data = ["red", "blue", "green"]; var p = body.selectAll(null) .data(data) .enter() .append("p") .text(d=> "I am a " + d + " paragraph!") .style("color", String)

<script src="https://d3js.org/d3.v4.min.js"></script> <p>Look Ma, I''m a paragraph!</p>

Y ese es el propósito de seleccionar nulo: garantizamos que no hay coincidencia entre los elementos seleccionados y la matriz de datos.

Seleccionar nulo y rendimiento

Como no estamos seleccionando nada, selectAll(null) es, con mucho, la forma más rápida de agregar nuevos elementos: no tenemos que atravesar el DOM en busca de nada.

Aquí hay una comparación, usando jsPerf :

https://jsperf.com/d3-selecting-null/1

En este escenario muy simple, selectAll(null) fue sustancialmente más rápido. En una página real, llena de elementos DOM, la diferencia puede ser aún mayor.

Cuándo NO usar selectAll (nulo)

Como acabamos de explicar, selectAll(null) no coincidirá con ningún elemento DOM existente. Es un buen patrón para un código rápido que siempre agrega todos los elementos en la matriz de datos.

Sin embargo, si planea actualizar sus elementos, es decir, si planea tener una selección de "actualización" (y una "salida"), no use selectAll(null) . En ese caso, seleccione el elemento (o la clase) que planea actualizar.

Entonces, si desea actualizar los círculos de acuerdo con una matriz de datos cambiante, haría algo como esto:

//this is the "update" selection var circles = svg.selectAll("circle") .data(data); //this is the "enter" selection circles.enter() .append("circle") .attr("foo", ... //this is the "exit" selection circles.exit().remove(); //updating the elements circles.attr("foo", ...

En ese caso, si usa selectAll(null) , los círculos se agregarán constantemente a la selección, se acumularán y no se eliminará ni actualizará ningún círculo.

PD: Solo como curiosidad histórica, la creación del selectAll(null) se remonta a estos comentarios de Mike Bostock y otros: https://github.com/d3/d3-selection/issues/79

He visto algunos códigos D3 con un patrón como este para agregar elementos:

var circles = svg.selectAll(null) .data(data) .enter() .append("circle");

Realmente no entiendo este fragmento. ¿Por qué seleccionar null ?

La forma en que entiendo D3, si uno agrega círculos, debería ser:

var circles = svg.selectAll("circle") .data(data) .enter() .append("circle");

De la misma manera, si uno agrega párrafos HTML, debería ser:

var circles = svg.selectAll("p") .data(data) .enter() .append("p");

Lo mismo ocurre con las clases: si uno agrega elementos con una clase foo , debería ser selectAll(".foo") .

Sin embargo, selectAll(null) funciona! Los elementos se agregan.

Entonces, ¿cuál es el significado de ese null ? ¿Que me estoy perdiendo aqui?

Nota: esta es una pregunta con respuesta propia, que intenta proporcionar preguntas y respuestas "canónicas" sobre un tema que ha sido abordado por muchas preguntas anteriores y no explicado por la API. La mayor parte de la respuesta a continuación es de un ejemplo que escribí en la extinta documentación de StackOverflow .