选择null:D3.js中'selectAll(null)'背后的原因是什么?

Ger*_*ado 8 javascript d3.js

我已经看到一些带有这样的模式的D3代码用于追加元素:

var circles = svg.selectAll(null)
    .data(data)
    .enter()
    .append("circle");
Run Code Online (Sandbox Code Playgroud)

我真的没有得到这个片段.为何选择null

我理解D3的方式,如果有人附加圈子,它应该是:

var circles = svg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle");
Run Code Online (Sandbox Code Playgroud)

同样,如果要添加HTML段落,它应该是:

var circles = svg.selectAll("p")
    .data(data)
    .enter()
    .append("p");
Run Code Online (Sandbox Code Playgroud)

对于类来说也是如此:如果一个是用类附加元素foo,那么它应该是selectAll(".foo").

但是,selectAll(null) 确实有效!元素得到追加.

那么,那是什么意思null呢?我在这里错过了什么?


注意:这是一个自我回答的问题,试图提供一个主题的"规范"问答,这个问题已被许多先前的问题所触及,而不是由API解释.以下大部分答案来自我在已灭绝的StackOverflow文档中编写的示例.

Ger*_*ado 22

TL;博士

使用的目的selectAll(null)是保证"输入"选择始终具有数据数组中存在的所有元素.


"输入"选择

要回答你的问题,我们必须简要解释一下D3.js中的"输入"选择.您可能知道,D3的主要功能之一是将数据绑定到DOM元素的能力.

在D3.js中,当一个数据绑定到DOM元素时,有三种情况是可能的:

  1. 元素的数量和数据点的数量是相同的;
  2. 元素多于数据点;
  3. 数据点多于元素;

在情况#3中,没有相应DOM元素的所有数据点都属于"输入"选择.

因此,在D3.js中,"输入"选择是在将元素连接到数据之后包含与任何DOM元素不匹配的所有数据的选择.如果我们在"输入"选择中使用追加函数,D3将创建新元素,为我们绑定数据.

这是一个维恩图,解释了有关数据点数/ DOM元素数量的可能情况:

在此输入图像描述

将数据绑定到已存在的DOM元素

让我们打破你提出的附加圈子的片段.

这个...

var circles = svg.selectAll("circle")
    .data(data)
Run Code Online (Sandbox Code Playgroud)

...将数据绑定到包含所有圆圈的选择.在D3 lingo中,这是"更新"选择.

然后,这......

.enter()
.append("circle");
Run Code Online (Sandbox Code Playgroud)

...表示"输入"选择,为每个与所选元素不匹配的数据点创建一个圆.

当然,当选择中没有元素(或给定的类)时,在方法中使用该元素(或该类)selectAll将按预期工作.因此,在您的代码段中,如果选择中没有<circle>元素svg,selectAll("circle")则可以使用为数据数组中的每个数据点附加一个圆.

这是一个简单的例子.有没有<p><body>和我们的"进入"选择将包含的数据阵列中的所有元素:

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)
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

但是,如果我们在该页面中已经有一个段落会发生什么?我们来看一下:

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)
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.min.js"></script>
<p>Look Ma, I'm a paragraph!</p>
Run Code Online (Sandbox Code Playgroud)

结果很清楚:红色段落消失了!它在哪里?

第一个数据元素"red"与已存在的段落绑定.然后,只创建了两个段落(我们的"输入"选择),蓝色段落和绿色段落.

发生这种情况是因为,当我们使用时selectAll("p"),我们选择了<p>元素!<p>该页面中已有一个元素.

选择null

但是,如果我们使用selectAll(null),将不会选择任何东西!在该页面中已经存在段落并不重要,我们的"输入"选择将始终具有数据数组中的所有元素.

让我们看看它的工作原理:

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)
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.min.js"></script>
<p>Look Ma, I'm a paragraph!</p>
Run Code Online (Sandbox Code Playgroud)

这就是选择null的目的:我们保证所选元素和数据数组之间不匹配.

选择null和性能

因为我们没有选择任何东西,所以selectAll(null)是迄今为止添加新元素的最快方法:我们不必遍历DOM搜索任何东西.

这是一个比较,使用jsPerf:

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

在这个非常简单的场景中,selectAll(null)速度要快得多.在一个充满DOM元素的真实页面中,差异可能更大.

何时不使用selectAll(null)

正如我们刚才解释的那样,selectAll(null)不会匹配任何现有的DOM元素.对于快速代码来说,这是一个很好的模式,它总是附加数据数组中的所有元素.

但是,如果您计划更新元素,即,如果您计划选择"更新"(和"退出"),请不要使用selectAll(null).在这种情况下,请选择您计划更新的元素(或类).

因此,如果您想根据不断变化的数据数组更新圈子,您可以执行以下操作:

//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", ...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果您使用selectAll(null),圆圈将不断附加到选区,堆积,并且不会删除或更新任何圆圈.


PS:正如历史的好奇心一样,selectAll(null)模式的创建可以追溯到Mike Bostock和其他人的评论:https://github.com/d3/d3-selection/issues/79