D3.js 中的 .append 和 .join 有什么区别
What is the difference between .append and .join in D3.js
这可能是一个愚蠢的问题,但我就是想不通在 D3 中何时使用 .join 以及何时使用 .append。
就像在这个块中,我不知道为什么要使用 join 而不是 append 在这里。
elements.selectAll('circle')
.data(d=>d.values)
//.append('circle')
.join('circle')
.attr("class","dots")
.attr('r',2)
.attr('fill',d=>colorScale(d['track']))
.attr("cx", d=>dateScale(d['edate']))
.attr("cy", d=>valueScale(d['record_time']));
谁能帮我理解一下?
TL;DR
selection.append 本身只是将一个子元素附加到调用它的 selection 中的每个元素(继承其父元素的数据)。 selection.join() 依赖于数据:它执行一个 enter/update/exit 循环,以便 DOM 中匹配元素的数量与数据数组项的数量相匹配。
您的代码表明您要使用 .enter().append("circle") 而不仅仅是 .append("circle"):这完成了 [=84 的 enter() 部分=] 也使用 .join() 完成的循环。
您可以使用 join 或 individual enter/exit/update selections 来达到相同的结果,join 只是一种方便,如文档中所述:
This method is a convenient alternative to the explicit general update
pattern, replacing selection.enter, selection.exit, selection.append,
selection.remove, and selection.order. (docs)
Enter/Update/Exit
当您看到 selectAll()
后跟 .data()
时,我们正在 selecting 所有匹配的元素,对于每个存在的元素,我们将数据数组中的一个项目绑定到它。使用 .data() returns 所谓的更新 selection:它包含现有元素(如果有的话)以及绑定到这些现有项目的新提供的数据。
但是,如果 selected 元素的数量与项目数量不匹配¹,则 .data() 会创建一个进入 selection 或退出 selection。如果我们有多余的数据项,那么我们有一个 enter selection,我们需要添加的每个项目都有一个元素,以便拥有相等数量的 DOM 元素和数据数组项。相反,如果我们有多余的 DOM 个元素,那么我们就有一个出口 selection.
在更新 selection returns 上调用 .enter() 输入 selection。这个 selection 包含占位符(“从概念上讲,输入 selection 的占位符是指向父元素 docs” 的指针),我们可以使用 .append("tagname") 来添加我们需要的元素。
相反,在更新 selection returns 时调用 .exit() 退出 selection,通常使用 .exit().remove() 简单地删除它;
这种模式通常看起来像这样:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
circle.exit().remove();
circle.enter()
.append("circle")
.attr...
circle.attr(...
首先我们 select 所有的圈子,假设有 2 个圈子到 select。
其次,我们使用 selection.exit() 删除多余的元素:但是,由于我们有四个数据项并且只有两个匹配的 DOM 元素,所以没有什么要删除的,所以 select离子是空的,没有任何东西被移除。
第三,我们按要求添加元素,保证匹配的DOM元素个数与数据数组项个数相同。由于我们有四个数据项和只有两个匹配的 DOM 元素,因此输入 selection 包含两个占位符(指向父项的指针)。我们向它们附加圆圈并根据需要设置样式。
最后,我们使用更新 selection 包含两个预先存在的圆圈,并根据新数据根据需要设置样式。
通常我们希望新元素和现有元素的样式相同,所以我们可以使用 merge 方法来组合输入和更新 selections:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
circle.exit().remove();
circle.enter()
.append("circle")
.merge(circle)
.attr(...
这稍微简化了我们的代码,因为我们不需要分别为输入和更新复制样式。
加入
那么 .join() 是从哪里来的呢?是为了方便。最简单的形式:.join("elementTagName") .join 将上面的代码替换为:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
.join("circle")
.attr(...
这里的join方法去掉exit selection和returns合并更新selection并进入selection(包含新的圆圈),我们可以现在根据需要设置样式。它本质上是一种速记方法,可让您编写更简洁的代码,但在功能上与第二个代码块相同²。
您的代码
在您的代码中,您有一个 select 一个或多个元素的离子(a/some 父元素 element/s)。绑定数据包含一个子数组,您希望使用它来创建子元素。为此,您向 .data() 提供子数据数组:
parentElements.selectAll("circle")
.data(d=>d.values)
您可以使用 .join()
进行跟进:这将为每个父元素执行 enter/update/exit 循环,以便它们每个都有适当数量的圆圈和 returns a select所有这些圈子的离子。
您不能只使用 .append(),因为那样会向每个父元素附加一个圆圈,返回这些圆圈的 selection。这不太可能是期望的结果。
相反,如本答案顶部所述,您可以使用 .enter().append("circle") 以便正确使用模式。
只需要输入selection,如果你只创建一次元素,永远不会更新数据,否则,你需要使用enter/update/exit方法来处理多余的元素,多余的数据项, 和更新的元素。
最终,join 和 enter/update/exit 之间的区别是代码偏好、风格、简洁性的问题,但除此之外,没有什么是你做不到的其他.
¹ 假设仅向 .data() 提供一个参数 - 第二个可选参数是键控函数,它根据键匹配 DOM 元素和数据项。 DOM 没有匹配数据的元素被放置在出口 selection 中,没有匹配 DOM 元素的数据数组项被放置在输入 selection 中,其余被放置在更新 selection.
² 假设 .join() 没有提供它的第二个或第三个参数,这允许更精细地控制 enter/exit/update 循环。
这可能是一个愚蠢的问题,但我就是想不通在 D3 中何时使用 .join 以及何时使用 .append。
就像在这个块中,我不知道为什么要使用 join 而不是 append 在这里。
elements.selectAll('circle')
.data(d=>d.values)
//.append('circle')
.join('circle')
.attr("class","dots")
.attr('r',2)
.attr('fill',d=>colorScale(d['track']))
.attr("cx", d=>dateScale(d['edate']))
.attr("cy", d=>valueScale(d['record_time']));
谁能帮我理解一下?
TL;DR
selection.append 本身只是将一个子元素附加到调用它的 selection 中的每个元素(继承其父元素的数据)。 selection.join() 依赖于数据:它执行一个 enter/update/exit 循环,以便 DOM 中匹配元素的数量与数据数组项的数量相匹配。
您的代码表明您要使用 .enter().append("circle") 而不仅仅是 .append("circle"):这完成了 [=84 的 enter() 部分=] 也使用 .join() 完成的循环。
您可以使用 join 或 individual enter/exit/update selections 来达到相同的结果,join 只是一种方便,如文档中所述:
This method is a convenient alternative to the explicit general update pattern, replacing selection.enter, selection.exit, selection.append, selection.remove, and selection.order. (docs)
Enter/Update/Exit
当您看到 selectAll()
后跟 .data()
时,我们正在 selecting 所有匹配的元素,对于每个存在的元素,我们将数据数组中的一个项目绑定到它。使用 .data() returns 所谓的更新 selection:它包含现有元素(如果有的话)以及绑定到这些现有项目的新提供的数据。
但是,如果 selected 元素的数量与项目数量不匹配¹,则 .data() 会创建一个进入 selection 或退出 selection。如果我们有多余的数据项,那么我们有一个 enter selection,我们需要添加的每个项目都有一个元素,以便拥有相等数量的 DOM 元素和数据数组项。相反,如果我们有多余的 DOM 个元素,那么我们就有一个出口 selection.
在更新 selection returns 上调用 .enter() 输入 selection。这个 selection 包含占位符(“从概念上讲,输入 selection 的占位符是指向父元素 docs” 的指针),我们可以使用 .append("tagname") 来添加我们需要的元素。
相反,在更新 selection returns 时调用 .exit() 退出 selection,通常使用 .exit().remove() 简单地删除它;
这种模式通常看起来像这样:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
circle.exit().remove();
circle.enter()
.append("circle")
.attr...
circle.attr(...
首先我们 select 所有的圈子,假设有 2 个圈子到 select。
其次,我们使用 selection.exit() 删除多余的元素:但是,由于我们有四个数据项并且只有两个匹配的 DOM 元素,所以没有什么要删除的,所以 select离子是空的,没有任何东西被移除。
第三,我们按要求添加元素,保证匹配的DOM元素个数与数据数组项个数相同。由于我们有四个数据项和只有两个匹配的 DOM 元素,因此输入 selection 包含两个占位符(指向父项的指针)。我们向它们附加圆圈并根据需要设置样式。
最后,我们使用更新 selection 包含两个预先存在的圆圈,并根据新数据根据需要设置样式。
通常我们希望新元素和现有元素的样式相同,所以我们可以使用 merge 方法来组合输入和更新 selections:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
circle.exit().remove();
circle.enter()
.append("circle")
.merge(circle)
.attr(...
这稍微简化了我们的代码,因为我们不需要分别为输入和更新复制样式。
加入
那么 .join() 是从哪里来的呢?是为了方便。最简单的形式:.join("elementTagName") .join 将上面的代码替换为:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
.join("circle")
.attr(...
这里的join方法去掉exit selection和returns合并更新selection并进入selection(包含新的圆圈),我们可以现在根据需要设置样式。它本质上是一种速记方法,可让您编写更简洁的代码,但在功能上与第二个代码块相同²。
您的代码
在您的代码中,您有一个 select 一个或多个元素的离子(a/some 父元素 element/s)。绑定数据包含一个子数组,您希望使用它来创建子元素。为此,您向 .data() 提供子数据数组:
parentElements.selectAll("circle")
.data(d=>d.values)
您可以使用 .join()
进行跟进:这将为每个父元素执行 enter/update/exit 循环,以便它们每个都有适当数量的圆圈和 returns a select所有这些圈子的离子。
您不能只使用 .append(),因为那样会向每个父元素附加一个圆圈,返回这些圆圈的 selection。这不太可能是期望的结果。
相反,如本答案顶部所述,您可以使用 .enter().append("circle") 以便正确使用模式。
只需要输入selection,如果你只创建一次元素,永远不会更新数据,否则,你需要使用enter/update/exit方法来处理多余的元素,多余的数据项, 和更新的元素。
最终,join 和 enter/update/exit 之间的区别是代码偏好、风格、简洁性的问题,但除此之外,没有什么是你做不到的其他.
¹ 假设仅向 .data() 提供一个参数 - 第二个可选参数是键控函数,它根据键匹配 DOM 元素和数据项。 DOM 没有匹配数据的元素被放置在出口 selection 中,没有匹配 DOM 元素的数据数组项被放置在输入 selection 中,其余被放置在更新 selection.
² 假设 .join() 没有提供它的第二个或第三个参数,这允许更精细地控制 enter/exit/update 循环。