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

2024-01-05

我见过一些 D3 代码具有这样的用于附加元素的模式:

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

我真的不明白这个片段。为什么选择null?

按照我对D3的理解,如果是附加圆圈,应该是:

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

同样,如果要附加 HTML 段落,则应该是:

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

类也是如此:如果用类附加元素foo, 它应该是selectAll(".foo").

然而,selectAll(null) does工作!元素被附加。

那么,这有什么意义null?我在这里缺少什么?


Note: this is a self-answered question, and not explained by the API. Most of the answer below is from an example I wrote in the extinct StackOverflow Documentation https://stackoverflow.com/documentation.


tl;dr

使用目的selectAll(null)是为了保证“输入”选择always对应于数据数组中的元素,数据中的每个元素包含一个元素。


“输入”选择

为了回答您的问题,我们必须简要解释一下 D3.js 中的“输入”选择是什么。您可能知道,D3 的主要功能之一是能够绑定数据 http://alignedleft.com/tutorials/d3/binding-data到 DOM 元素。

在 D3.js 中,当将数据绑定到 DOM 元素时,可能会出现三种情况:

  1. 元素个数和数据点个数相同;
  2. 元素多于数据点;
  3. 数据点多于元素;

在情况 #3 中,所有没有对应 DOM 元素的数据点都属于“输入”选择。

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

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

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

让我们打破您提议的添加圆圈的代码片段。

This...

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

...将数据绑定到包含所有圆圈的选择。用 D3 行话来说,这就是“更新”选择。

那么,这个...

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

...代表“输入”选择,为每个与所选元素不匹配的数据点创建一个圆圈。

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

这是一个简单的例子。没有<p> in the <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)
<script src="https://d3js.org/d3.v4.min.js"></script>

但如果我们已经有一段了在那个页面?我们来看一下:

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>

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

第一个数据元素“红色”绑定到已经存在的段落。然后,只创建了两个段落(我们的“输入”选择),蓝色段落和绿色段落。

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

选择空

但是,如果我们使用selectAll(null), nothing将会被选中!该页面中已经有一个段落并不重要,我们的“输入”选择将always拥有数据数组中的所有元素。

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

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>

这就是选择 null 的目的:我们保证有no match所选元素和数据数组之间。

选择 null 和性能

由于我们没有选择任何东西,selectAll(null)是迄今为止追加新元素最快的方法:我们不必遍历 DOM 来搜索任何内容。

这是一个比较,使用jsPerf https://jsperf.com/:

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

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

何时不使用 selectAll(null)

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

但是,如果您打算update您的元素,也就是说,如果您计划进行“更新”(和“退出”)选择,do not use 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", ...

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


PS:作为一种历史好奇心,selectAll(null)模式可以追溯到 Mike Bostock 等人的评论:https://github.com/d3/d3-selection/issues/79 https://github.com/d3/d3-selection/issues/79

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

选择 null:D3 中 selectAll(null) 背后的原因是什么? 的相关文章

随机推荐