d3.each() 确实从索引 0 开始。鉴于您在代码中的内容,您在代码中看到的是预期的行为。
这里的问题很简单:当然,页面中有一个<body> 元素。您的数据数组有 5 个元素,其中一个被绑定到 <body>。
让我们展示一下。看看“enter”选择的大小:
data = [0, 1, 2, 3, 4]
var foo = d3.select("body")
.data(data)
.enter();
console.log("Size of enter selection: " + foo.size())
<script src="https://d3js.org/d3.v4.js"></script>
我们还可以证明数组中的第一个元素绑定到<body>:
data = [0, 1, 2, 3, 4]
var foo = d3.select("body")
.data(data)
.enter();
console.log("Data of body: " + d3.select("body").data())
<script src="https://d3js.org/d3.v4.js"></script>
另一种显示方式是使用第三个参数(从技术上讲,参数),即当前组:
data = [0, 1, 2, 3, 4]
d3.select("body")
.data(data)
.enter()
.each((d, i, p) =>
// ^---- this is the third argument
console.log(p)
)
这里我无法提供一个工作堆栈 sn-p,因为如果我们尝试记录 D3 选择,它会崩溃。但结果会是这样:
[undefined × 1, EnterNode, EnterNode, EnterNode, EnterNode]
undefined 是“更新”选择(主体),4 个 EnterNodes 是“输入”选择。这让我们解释了为什么 each() 在您的代码中表现得那样。
如果你看看源代码...
function(callback) {
for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
if (node = group[i]) callback.call(node, node.__data__, i, group);
}
}
return this;
}
您会看到它可以将节点与组进行比较,并且您的组同时包含“更新”选择和“输入”选择。更新选择对应索引0,回车选择对应索引1、2、3和4。
解决方案:
这是你想要的,关注selectAll和null:
data = [0, 1, 2, 3, 4]
d3.select("body")
.selectAll(null)
.data(data)
.enter()
.each((d, i) =>
console.log(i, d)
)
<script src="https://d3js.org/d3.v4.js"></script>
因此,如您所见,选择 null 可确保我们的“输入”选择始终包含数据数组中的所有元素。
奖励:select 和 selectAll 的行为不同。大多数人认为唯一的区别是前者只选择 1 个元素,而后者选择所有元素。但还有更细微的差别。看看这张表:
| Method |
select() |
selectAll() |
| Selection |
selects the first element that matches the selector string |
selects all elements that match the selector string |
| Grouping |
Does not affect grouping |
Affects grouping |
| Data propagation |
Propagates data |
Doesn't propagate data |