【问题标题】:d3.js - What is the purpose of selectAll that returns non-existing element [duplicate]d3.js - selectAll 返回不存在元素的目的是什么[重复]
【发布时间】:2020-12-12 11:02:59
【问题描述】:

使用 d3.js,我有以下简单的代码,可以根据数据集添加矩形。

我没有 svg 也没有 rect,所以我通过 .append() 附加它。

            let dataset = [ 1, 2, 3, 4, 5 ]
            let svg = d3.select("body")
                        .append("svg")
                        .attr /* width, height */

            svg.selectAll("rect")
               .data(dataset)
               .enter()
               .append("rect")
               .attr /* x, y, width, height, etc */

这很好用。

我的问题是selectAll("rect")

为什么需要这个? 如果我像下面这样删除它,rect 元素将超出 body 元素。

但是 svg 的 append 不是应该附加到引用元素的末尾吗? (在这种情况下是 svg?)

            svg.data(dataset)
               .enter()
               .append("rect")
               .attr /* x, y, width, height, etc */

我知道 selectAll 返回引用。

如果元素不存在,是否返回特殊引用?

【问题讨论】:

标签: javascript d3.js


【解决方案1】:

D3 使用称为data join 的更新范例。它针对数据的增量更新进行了优化,适合图表使用。

要使用数据连接,您需要对“选择”进行操作。您不是通过选择来表示您想要做什么,而是表示您希望数据如何转换。

let dataset = [ {name: "a", value: 1}, {name: "b", value: 2}, {name: "c", value: 3} ]
let svg = d3.select("body")
  .append("svg")

const selection = svg.selectAll("rect")
  .data(dataset, d => d.name) // data join, each "rect" will correspond to an data
// second argument is a key function to uniquely identify each data

selection
  .enter()
  .append("rect")
  /* attr: x, y, height, width */

selection 代表数据连接。在这里,您告诉 D3 您正在进行选择。不过,目前 DOM 中不存在任何内容,因此我们得到一个空选择。

定义选择时:

  • DOM 没有“矩形”节点
  • 但是有数据

换个角度看:

         data
 data  &nodes  nodes
       +-----+
+--------------------+
|      |     |       |
|  a:1 |     |       |
|  b:2 |     |       |
|  c:3 |     |       |
|      |     |       |
+--------------------+
       +-----+

enter 选择将包含没有节点的数据。这就是您在使用 enter 时正在处理的数据。

假设我们现在更改数据集。我们如何更新 DOM 节点?

dataset =  [ {name: "a", value: 9}, {name: "c", value: 3}, {name: "d", value: 4} ]
// note how we need to select rect to get the existing data.
// this is the "update" selection
const selection = svg.selectAll("rect")
  .data(dataset, d => d.name)
  .attr("x", d => d.value) 

// the "enter" selection
selection.enter()
  .append("rect")

// the "exit" selection
selection.exit().remove()

我们现在有三个选择。如果我们再次绘制图表,这就是我们现在的数据所在的位置:

         data
 data  &nodes  nodes
       +-----+
+--------------------+
|      |     |       |
| d:4  | a:9 |  b:2  |
|      |     |       |
|      |     |       |
|      |     |       |
+--------------------+
       +-----+

在我们进行数据连接时会发生什么?数据发生了变化。现在svg.selectAll("rect") 采用已经存在的选择:D3 保存在 DOM 中。 这就是您需要将 CSS 选择器传递给 selectAll 的原因。 D3 使用该选择器来查找现有选择,通过每个 DOM 节点上的 .__data__ 属性保存在 DOM 中。

现在我们正在观察新数据,并将其与 DOM 中的数据进行比较:

  • a 仍然有一个数据,并且它有一个来自先前数据连接的节点。我们知道 a 仍然存在,但它的值已经改变,因为我们的数据是具有 {name, value} 的对象。所以它在“数据和节点”中。
  • b在dom中有节点,但是对应的新数据中不存在。它在“节点”中。
  • d 是一个新项目:它是没有节点的数据。所以它进入“数据”。

由于 c 没有更新,它不在任何选择中

让我们重命名图中的内容:

 enter  update  exit
       +-----+
+--------------------+
|      |     |       |
| d:4  | a:9 |  b:2  |
|      |     |       |
|      |     |       |
|      |     |       |
+--------------------+
       +-----+

当您直接使用选择(更新选择)、调用enter() 或调用exit() 时,您始终在处理这三个选择中的任何一个。只在需要时管理更新,或者不每次都重新创建节点 - 只在需要时销毁,效率要高得多,尤其是对于大数据。

即使您只使用enter() 转换,D3 也会通过确保只创建新项目来提高效率。

这个想法是,您可以将管理所有三种状态的 sn-p 放在类似 render() 函数的东西中,并一直调用它:当您第一次生成图形时和更新数据时。 D3 为您管理所有数据簿记。

【讨论】:

  • 非常感谢您提供的详细信息。我现在可以理解 selectAll 的重要性及其对数据进行分类以进入/更新/退出的功能。我仍然不清楚的一部分是:当您删除 selectAll (如我的第二个示例)时,为什么要在 body 元素之后创建所有 rect
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-17
  • 2020-07-31
  • 1970-01-01
  • 1970-01-01
  • 2020-03-01
  • 1970-01-01
  • 2016-09-11
相关资源
最近更新 更多