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

mWa*_*ang 14 d3.js

这可能是一个愚蠢的问题,但我只是无法弄清楚何时在 D3 中使用 .join 以及何时使用 .append 。
就像在这个块中一样,我不知道为什么在这里使用 join 而不是追加。

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']));
Run Code Online (Sandbox Code Playgroud)

有人能帮我理解吗?

And*_*eid 39

长话短说

\n

Selection.append 本身只是将一个子元素附加到调用它的选择中的每个元素(继承其父元素的数据)。Selection.join() 与数据相关:它执行输入/更新/退出循环,以便 DOM 中匹配元素的数量与数据数组项的数量相匹配。

\n

您拥有的代码表明您想要使用 .enter().append("circle") 而不是仅仅使用 .append("circle"):这完成了输入/更新/退出循环的 Enter() 部分,即也是通过使用.join()完成的。

\n

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

\n
\n

此方法是显式常规更新\n模式的便捷替代方案,替换了selection.enter、selection.exit、selection.append、\nselection.remove 和selection.order。(文档

\n
\n

输入/更新/退出

\n

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

\n

但是,如果所选元素的数量与 items\xc2\xb9 的数量不匹配,则 .data() 创建进入选择或退出选择。如果我们有多余的数据项,那么我们需要输入一个输入选择,其中每个需要添加的项都有一个元素,以便拥有相同数量的 DOM 元素和数据数组项。相反,如果我们有多余的 DOM 元素,那么我们就有一个退出选择。

\n

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

\n

相反,在更新选择上调用 .exit() 返回退出选择,通常可以使用 .exit().remove() 简单地删除它;

\n

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

\n
 let circle = svg.selectAll("circle")\n    .data([1,2,3,4])\n\n circle.exit().remove();    \n\n circle.enter()\n    .append("circle") \n    .attr...\n\n  circle.attr(...\n
Run Code Online (Sandbox Code Playgroud)\n

首先我们选择所有的圆圈,假设有 2 个圆圈可供选择。

\n

其次,我们使用 Selection.exit() 删除多余的元素:但是,由于我们有四个数据项并且只有两个匹配的 DOM 元素,因此没有任何内容可删除,因此选择为空,并且没有任何内容被删除。

\n

第三,我们根据需要添加元素,以确保匹配的 DOM 元素的数量与数据数组项的数量相同。由于我们有四个数据项和只有两个匹配的 DOM 元素,因此输入选择包含两个占位符(指向父级的指针)。我们将圆圈附加到它们上并根据需要设置它们的样式。

\n

最后,我们使用包含两个预先存在的圆圈的更新选择,并根据新数据根据需要设置它们的样式。

\n

通常我们希望新元素和现有元素的样式相同,因此我们可以使用 merge 方法来组合输入和更新选择:

\n
 let circle = svg.selectAll("circle")\n    .data([1,2,3,4])\n\n circle.exit().remove();    \n\n circle.enter()\n    .append("circle") \n    .merge(circle)\n    .attr(...\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

加入

\n

那么 .join() 从哪里来呢?是为了方便。最简单的形式: .join("elementTagName") .join 将上面的代码替换为:

\n
 let circle = svg.selectAll("circle")\n    .data([1,2,3,4])\n    .join("circle")\n    .attr(...\n
Run Code Online (Sandbox Code Playgroud)\n

这里 join 方法删除了退出选择并返回合并的更新选择和输入选择(包含新圆圈),我们现在可以根据需要设置样式。它本质上是一种速记方法,允许您编写更简洁的代码,但在功能上与第二个代码块相同\xc2\xb2。

\n

你的代码

\n

在您的代码中,您可以选择一个或多个元素(一个/某些父元素)。绑定数据包含一个子数组,您希望使用它来创建子元素。为此,您需要向 .data() 提供该子数据数组:

\n
parentElements.selectAll("circle")\n  .data(d=>d.values)\n
Run Code Online (Sandbox Code Playgroud)\n

您可以跟进.join():这将为每个父元素执行输入/更新/退出循环,以便它们每个都有适当数量的圆圈并返回所有这些圆圈的选择。

\n

您不能只使用 .append(),因为这会将一个圆圈附加到每个父元素,并返回这些圆圈的选择。这不太可能是期望的结果。

\n

相反,正如本答案顶部所述,您可以使用 .enter().append("circle") 以便正确使用该模式。

\n

如果您创建元素一次并且从不更新数据,则只需要输入选择,否则,您将需要使用输入/更新/退出方法来处理多余的元素、多余的数据项和更新的元素。

\n

最终,连接和输入/更新/退出之间的区别是代码偏好、风格、简洁性的问题,但除此之外,没有什么是你不能用其中一个做而不能用另一个做的。

\n
\n

\xc2\xb9 假设只向 .data() 提供一个参数 - 第二个可选参数是一个键控函数,它根据键匹配 DOM 元素和数据项。没有匹配数据的 DOM 元素放置在退出选择中,没有匹配 DOM 元素的数据数组项放置在输入选择中,其余的放置在更新选择中。

\n

\xc2\xb2 假设 .join() 没有提供第二个或第三个参数,这允许更精细地控制进入/退出/更新周期。

\n