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

时间:2017-09-11 01:56:23

标签: javascript d3.js

我已经看到一些带有这种模式的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) 确实有效!元素被附加。

那么null的含义是什么?我在这里缺少什么?

注意:这是一个自我回答的问题trying to provide a "canonical" Q&A on a subject that has been touched on by many previous questions,API没有解释。以下大部分答案来自我在灭绝StackOverflow Documentation中写的一个例子。

1 个答案:

答案 0 :(得分:19)

TL;博士

使用selectAll(null)的目的是确保“输入”选择始终具有数据数组中存在的所有元素。

“输入”选择

要回答你的问题,我们必须简要解释一下D3.js中的“输入”选择。您可能知道,D3的一个主要特征是binding data对DOM元素的能力。

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

  1. 元素数量和数据点数相同;
  2. 元素多于数据点;
  3. 数据点多于元素;
  4. 在情况#3中,没有相应DOM元素的所有数据点都属于“输入”选择。

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

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

    enter image description here

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

    让我们打破您提议的附加圈子。

    此...

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

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

    然后,这......

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

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

    当然,当选择中没有元素(或给定的类)时,在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)
    <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>

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

    第一个数据元素“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)
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <p>Look Ma, I'm a paragraph!</p>

    这就是选择null的目的:我们保证所选元素与数据数组之间存在无匹配

    选择null和performance

    由于我们没有选择任何内容,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", ...
    

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

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