【译】Learn|【译】Learn D3 入门文档(Joins)

引子 继 Learn D3: Animation 第七篇,只是英文翻译,可修改代码的部分用静态图片替代了,想要实时交互请阅读原文。

  • 原文:Learn D3: Joins
  • 版本:Published Mar 24, 2020
  • Origin
  • My GitHub
正文 如果你熟悉此教程中的 D3 ,你可能会惊讶于 D3 选择器被提及的如此之少。
那是因为你可能不需要它们!
D3 选择器适用一个特殊的定位:快速,动态图表的增量更新。如果你关注的是静态图表,或者每帧都可以从头开始重新绘制的图表,那么你可能更喜欢不同的抽象。另一方面,如果你想要动画过渡或从现代浏览器中榨取最佳性能,选择器适合你。
(即使你决定不使用选择器,请记住 D3 还有大量其它有用的可视化工具。比例尺、形状、插值器、颜色、地图投影和许多其它功能可用于 Canvas、WebGL 或其它 DOM 抽象,如 Observable 的 HTML 标记模板文字、React 或 Svelte 。D3 还可以使用统计、分组和聚合、时间序列和解析的方法,协助数据清理和分析。)
本质上,D3 选择器指定的是转换而不是表示。你可以指定将当前状态转换为所需状态所做的更改(插入、更新和删除),而不是表示图表(DOM)的所需状态。这有时很乏味,但允许你设置过渡动画,并最小化对 DOM 的更改,从而提高性能。
让我们看看如何做。
假设我们想显示字母表中的字母。虽然不是很直观,但我们将保持简单,以专注于技术(有关实际的示例,请参见 D3 图库 的动画和交互部分。)
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

这个图表是静态的,因为它是在每次单元运行时从头开始创建的,这使得 D3 代码在本质上等同于 HTML 文本。
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

那么,何必要用选择器呢?对,对于静态图表,没有什么理由这样做。
但假设你想更新此图表以响应不断变化的数据。并且你不想从头开始重新绘制,希望应用最小的更新集来反映新数据。你希望重用现有元素,添加需要的元素,删除不需要的元素。只需将上述代码移动到一个在数据更改时调用的方法中,你就可以获得高性能的增量更新!
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

让我们把代码分析一下。
text 是一组文本元素,最初为空,其父元素是 SVG 元素。这个父项决定了输入的文本元素稍后将附加到何处。
通过调用 selection.data ,text 被绑定到一个新的数据数组 letters 。这将计算 text 选择集的三个子集:enter 选择集表示在新数据中,去除已存在元素与新数据的交集之后,剩下的数据;update 选择集表示新数据与已存在元素的交集;exit 选择集表示在已存在元素中,去除已存在元素与新数据的交集之后,剩下的元素。
作为图示如下:
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

(这些选择集隐藏在代码中:selection.data 返回 update 选择集,从中可以调用 selection.enter 或 selection.exit 访问其它选择集。)
我们可以手动处理这三种情况,但 selection.join 本就能方便的处理。enter 选择集添加进来;exit 选择集被移除;最后, updateenter 选择集被合并、排序并返回。然后,我们可以为这些加入或更新元素分配属性和文本。
我们观察到只要字母和文本元素之间的关联保持不变,就不需要在更新元素时重新分配某些属性和文本内容,这样就可以更高效。要保留此关联,selection.data 需要一个键函数;为了对 enterupdateexit 的精确操作,selection.join 需要相应的函数。如果 update 比 enter 和 exit 更常见,这将大大提高性能!
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

【【译】Learn|【译】Learn D3 入门文档(Joins)】与前面一样,selection.join 返回合并的 enter 和 update 选择集,因此我们可以共享应用于两者的代码,例如设置 x 属性。
传递给 selection.data 的键函数用于计算每个新数据和已选元素数据的(字符串)键,确定哪个数据绑定到哪个元素:如果元素和数据具有相同的键,则该数据绑定到元素,并且元素被放入 update 选择集。字母是很好的键,因此标识函数(d => d)在这里是合适的。
(如果未指定键函数,则数据由索引绑定:第一个数据绑定到第一个元素,依此类推。作为练习,尝试重写上面的代码以通过索引联接。你将会也想把设置 x 属性与设置文本内容交换!)
然而,选择器真正闪耀的地方是过渡!?
在下面,字母从顶部进入,更新时水平滑动,然后从底部退出。这比上面的瞬时转变更容易理解。
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

好的过渡不仅仅是为了吸引注意力而让图表“舞动”;它们帮助观看者通过变动了解数据是如何变化的
良好的过渡保持对象的恒定性:在过渡之前表示特定事物(如字母 C )的图表元素应在整个过渡过程中和过渡之后表示相同的事物,从而允许观看者连续跟踪。相反,如果给定元素的含义在过渡期间发生变化,那么变动将毫无意义。
举个更实际的例子怎么样?
下图显示了按特定年龄组人口百分比排列的前十个州(以及华盛顿特区,外地居住者)。这表明犹他州的年轻人口比例过高,反映了 LDS 教会对养育家庭的重视。相比之下,佛罗里达州退休人口众多,许多人都在 70 岁或以上。
当你更改选定的年龄组时,请观察图表如何重新排序以反映排名的变化。x 轴同时重新缩放以适应新数据。
【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

chart = { const svg = d3.create("svg") .attr("viewBox", [0, 0, width, height]); console.info(data)// For the initial render, reference the current age non-reactively. const agedata = https://www.it610.com/article/viewof agedata.value; const x = d3.scaleLinear() .domain([0, d3.max(agedata, d => d.value)]) .rangeRound([margin.left, width - margin.right]); const y = d3.scaleBand() .domain(agedata.map(d => d.name)) .rangeRound([margin.top, margin.top + 20 * data.names.length]); let bar = svg.append("g") .attr("fill", "steelblue") .selectAll("rect") .data(agedata, d => d.name) .join("rect") .style("mix-blend-mode", "multiply") .attr("x", x(0)) .attr("y", d => y(d.name)) .attr("width", d => x(d.value) - x(0)) .attr("height", y.bandwidth() - 1); const gx = svg.append("g") .call(xAxis, x); const gy = svg.append("g") .call(yAxis, y); return Object.assign(svg.node(), { update(agedata) { const t = svg.transition().duration(750); gx.transition(t) .call(xAxis, x.domain([0, d3.max(agedata, d => d.value)])); gy.transition(t) .call(yAxis, y.domain(agedata.map(d => d.name))); bar = bar .data(agedata, d => d.name) .call(bar => bar.transition(t) .attr("width", d => x(d.value) - x(0)) .attr("y", d => y(d.name))); } }); }

【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

只有前 10 条可见,因为其余的隐藏在图表下方。因此,selection.join 是不需要的,因为没有条形加入或移除,它们只会更新。这不仅简化了代码,而且使过渡更有意义,因为进入或退出条形图的速度现在暗示它们在展示外的位置。
动画过渡通常由读者点击或在寻找答案时点击触发。接下来,让我们看看如何让图表响应此类查询。
Next
附录 【译】Learn|【译】Learn D3 入门文档(Joins)
文章图片

附 根据源码,去除了平台依赖,提取了主要代码,有以下示例:
  • 示例 1
  • 示例 2
参考资料
  • Learn D3: Joins

    推荐阅读