d3.js - D3.js 中的 .append 和 .join 有什么区别
问题描述
这可能是一个愚蠢的问题,但我只是不知道何时在 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;博士
selection.append 本身仅将单个子元素附加到调用它的选择中的每个元素(继承其父元素的数据)。selection.join() 依赖于数据:它执行进入/更新/退出循环,以便 DOM 中匹配元素的数量与数据数组项的数量相匹配。
您的代码建议您要使用 .enter().append("circle") 而不是仅使用 .append("circle"):这完成了 enter/update/exit 循环的 enter() 部分,即也通过使用 .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(...
首先我们选择所有的圆圈,假设有 2 个圆圈可供选择。
其次,我们使用 selection.exit() 删除多余的元素:但是,由于我们有四个数据项并且只有两个匹配的 DOM 元素,所以没有什么要删除的,所以选择是空的,没有任何内容被删除。
第三,我们根据需要添加元素,以确保匹配的 DOM 元素的数量与数据数组项的数量相同。由于我们有四个数据项和只有两个匹配的 DOM 元素,因此输入选择包含两个占位符(指向父级的指针)。我们将圆圈附加到它们并根据需要设置它们的样式。
最后,我们使用包含两个预先存在的圆圈的更新选择,并根据新数据为它们设置样式。
通常我们希望新元素和现有元素的样式相同,因此我们可以使用 merge 方法来组合输入和更新选择:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
circle.exit().remove();
circle.enter()
.append("circle")
.merge(circle)
.attr(...
这稍微简化了我们的代码,因为我们不需要分别为 enter 和 update 复制样式。
加入
那么 .join() 是从哪里来的呢?它是为了方便。最简单的形式: .join("elementTagName") .join 将上面的代码替换为:
let circle = svg.selectAll("circle")
.data([1,2,3,4])
.join("circle")
.attr(...
在这里,join 方法删除了退出选择并返回合并的更新选择和输入选择(包含新圆圈),我们现在可以根据需要对其进行样式设置。它本质上是一种速记方法,可让您修改更简洁的代码,但在功能上与第二个代码块相同²。
你的代码
在您的代码中,您可以选择一个或多个元素(一个/一些父元素)。绑定的数据包含一个子数组,您希望使用它来创建子元素。为此,您向 .data() 提供该子数据数组:
parentElements.selectAll("circle")
.data(d=>d.values)
您可以跟进.join()
:这将为每个父元素执行进入/更新/退出循环,以便它们每个都有适当数量的圆圈并返回所有这些圆圈的选择。
您不能只使用 .append() ,因为这会将一个圆圈附加到每个父元素,并返回这些圆圈的选择。这不太可能是预期的结果。
相反,如本答案顶部所述,您可以使用 .enter().append("circle") 以便正确使用该模式。
如果您只创建一次元素并且从不更新数据,则只需要 enter 选择,否则,您将需要使用 enter/update/exit 方法来处理多余的元素、多余的数据项和更新的元素。
归根结底,加入和进入/更新/退出之间的区别在于代码偏好、样式、简洁性,但除此之外,没有什么是你不能用另一个做的。
¹ 假设只为 .data() 提供一个参数 - 第二个可选参数是一个键控函数,它基于键匹配 DOM 元素和数据项。没有匹配数据的 DOM 元素被放置在退出选择中,没有匹配 DOM 元素的数据数组项被放置在输入选择中,其余的被放置在更新选择中。
² 假设 .join() 没有提供它的第二个或第三个参数,这样可以更精细地控制进入/退出/更新周期。
推荐阅读
- vue.js - 如何修复导致未定义错误的属性“状态”的 Axios 拦截器
- python - 在 Panda 中计算两种类型记录的数字表
- python - 返回不匹配的值
- unity3d - UnityWebRequest 数据为空
- apache-flink - 在 Java Flink 应用程序中使用 Python 处理器
- adobe-illustrator - 我正在寻找一个自动编号系统,用于在 Photoshop 中按编号工具包自定义绘画
- sorbet - Sorbet:检查包含目标模块的类
- google-analytics - 引入新的 utm 参数值时,GA4 中的 traffic_source.medium 是否会更新?
- python - 使用 from Scratch 构建 Sklearn 的 (vectorizer.idf_) 函数
- docker - 寻找一种方法来列出来自 DockerHub 的 arm 架构的所有社区镜像(非官方)