D3.js中的.append和.join有什么区别?

14

这可能是个愚蠢的问题,但我就是搞不清楚在 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']));

有谁可以帮助我理解吗?

1个回答

38

TL;DR:

selection.append仅向选择的每个元素附加单个子元素(继承其父项的数据)。selection.join()是数据相关的:它执行进入/更新/退出循环,以使DOM中匹配的元素数与数据数组项数相同。

您的代码建议使用.enter().append("circle")而不仅仅是.append("circle"):这完成了enter()部分的进入/更新/退出循环,也可通过使用.join()来完成。

您可以使用join或单独的进入/退出/更新选择来实现相同的结果,join只是一种方便的方式,如文档所述:

该方法是显式通用更新模式的方便替代,取代了selection.enter、selection.exit、selection.append、selection.remove和selection.order。(文档

进入/更新/退出

当您看到selectAll()后跟.data(),我们正在选择所有匹配的元素,对于每个存在的元素,我们将一个数据数组项绑定到它上面。.data()的使用返回所谓的更新选择:它包含已有的元素(如果有)和新提供的数据绑定到这些现有条目。

但是,如果选择的元素数量不匹配项目数¹,则.data()将创建输入选择或退出选择。如果我们有多余的数据项,则有一个输入选择,每个需要添加的项目都有一个元素,以便具有相等数量的DOM元素和数据数组项。反之,如果我们有多余的DOM元素,则我们有一个退出选择。

在更新选择上调用.enter()返回输入选择。此选择包含占位符(“概念上,进入选择的占位符是指向父元素的指针。文档”),我们可以使用.append(“tagname”)向其添加所需的元素。

相反,在更新选择上调用.exit()会返回退出选择,通常只需使用.exit().remove()删除它们。

这个模式通常看起来像这样:

 let circle = svg.selectAll("circle")
    .data([1,2,3,4])

 circle.exit().remove();    

 circle.enter()
    .append("circle") 
    .attr...

  circle.attr(...

首先,我们选择所有圆形元素,假设有两个圆形需要选中。

其次,我们使用selection.exit()方法删除多余的元素。但是,由于我们有四个数据项,而只有两个匹配的DOM元素,因此没有元素被删除,所以选择结果为空,没有任何元素被删除。

第三步,我们添加必要的元素,以确保匹配的DOM元素数量与数据数组项数相同。由于我们有四个数据项,而只有两个匹配的DOM元素,因此enter selection包含两个占位符(对父元素的指针),我们向它们追加圆形元素并按照自己的需求进行样式设置。

最后,我们使用update selection来处理已经存在的两个圆形元素,并根据新数据对它们进行样式设置。

通常情况下,我们希望将新元素和现有元素设置为相同的样式,因此可以使用merge方法来合并enter和update selections:

 let circle = svg.selectAll("circle")
    .data([1,2,3,4])

 circle.exit().remove();    

 circle.enter()
    .append("circle") 
    .merge(circle)
    .attr(...

这简化了我们的代码,因为我们不需要分别为输入和更新复制样式。

Join(连接)

那么,.join() 在哪里呢?它是为了方便。以最简单的形式:.join("elementTagName") .join 用“elementTagName”替换了上面的代码:

 let circle = svg.selectAll("circle")
    .data([1,2,3,4])
    .join("circle")
    .attr(...

在这里,join方法会移除exit selection并返回一个合并后的update selection和enter selection(包含新圆圈),我们现在可以根据需要进行样式设置。它本质上是一种简写方法,使您能够编写更简洁的代码,但在功能上与第二个代码块相同²。

你的代码

在你的代码中,你有一个选择器选择了一个或多个元素(一个/一些父元素)。绑定的数据包含一个子数组,你希望用它来创建子元素。为了这样做,你提供给.data()那个子数据数组:

parentElements.selectAll("circle")
  .data(d=>d.values)
您可以使用.join()来跟进这一点:这将为每个父元素执行输入/更新/退出循环,以便它们每个都有适当数量的圆,并返回所有这些圆的选择集。
您不能仅使用.append(),因为这将向每个父元素附加一个圆,返回这些圆的选择集。这很可能不是所需的结果。
相反,如本答案顶部所述,您可以使用.enter().append("circle")来正确使用该模式。
如果您只创建元素一次并且从未更新数据,则仅需要输入选择,否则,您将需要使用输入/更新/退出方法来处理过多的元素、过多的数据项和更新的元素。
最终,join和enter/update/exit之间的区别在于代码偏好、风格、简洁性,但除此之外,您可以使用其中任何一个实现另一个无法实现的功能。
¹ 假设只向.data()提供了一个参数——第二个可选参数是一个键控函数,根据键将DOM元素和数据项配对。没有匹配数据的DOM元素被放置在退出选择中,没有匹配DOM元素的数据数组项被放置在输入选择中,其余部分被放置在更新选择中。
² 假设.join()没有提供其第二个或第三个参数,这使得更加细致地控制输入/退出/更新周期成为可能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接