Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Learn D3:Joins #98

Open
XXHolic opened this issue Aug 29, 2021 · 0 comments
Open

Learn D3:Joins #98

XXHolic opened this issue Aug 29, 2021 · 0 comments

Comments

@XXHolic
Copy link
Owner

XXHolic commented Aug 29, 2021

引子

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

正文

如果你熟悉此教程中的 D3 ,你可能会惊讶于 D3 选择器被提及的如此之少。

那是因为你可能不需要它们!

D3 选择器适用一个特殊的定位:快速,动态图表的增量更新。如果你关注的是静态图表,或者每帧都可以从头开始重新绘制的图表,那么你可能更喜欢不同的抽象。另一方面,如果你想要动画过渡或从现代浏览器中榨取最佳性能,选择器适合你。

(即使你决定不使用选择器,请记住 D3 还有大量其它有用的可视化工具。比例尺、形状、插值器、颜色、地图投影和许多其它功能可用于 Canvas、WebGL 或其它 DOM 抽象,如 Observable 的 HTML 标记模板文字、React 或 Svelte 。D3 还可以使用统计、分组和聚合、时间序列和解析的方法,协助数据清理和分析。)

本质上,D3 选择器指定的是转换而不是表示。你可以指定将当前状态转换为所需状态所做的更改(插入、更新和删除),而不是表示图表(DOM)的所需状态。这有时很乏味,但允许你设置过渡动画,并最小化对 DOM 的更改,从而提高性能。

让我们看看如何做。

假设我们想显示字母表中的字母。虽然不是很直观,但我们将保持简单,以专注于技术(有关实际的示例,请参见 D3 图库 的动画和交互部分。)

92-1

这个图表是静态的,因为它是在每次单元运行时从头开始创建的,这使得 D3 代码在本质上等同于 HTML 文本。

92-2

那么,何必要用选择器呢?对,对于静态图表,没有什么理由这样做。

但假设你想更新此图表以响应不断变化的数据。并且你不想从头开始重新绘制,希望应用最小的更新集来反映新数据。你希望重用现有元素,添加需要的元素,删除不需要的元素。只需将上述代码移动到一个在数据更改时调用的方法中,你就可以获得高性能的增量更新!😲

92-3

让我们把代码分析一下。

text 是一组文本元素,最初为空,其父元素是 SVG 元素。这个父项决定了输入的文本元素稍后将附加到何处。

通过调用 selection.data ,text 被绑定到一个新的数据数组 letters 。这将计算 text 选择集的三个子集:enter 选择集表示在新数据中,去除已存在元素与新数据的交集之后,剩下的数据;update 选择集表示新数据与已存在元素的交集;exit 选择集表示在已存在元素中,去除已存在元素与新数据的交集之后,剩下的元素。

作为图示如下:

92-4

(这些选择集隐藏在代码中:selection.data 返回 update 选择集,从中可以调用 selection.enter 或 selection.exit 访问其它选择集。)

我们可以手动处理这三种情况,但 selection.join 本就能方便的处理。enter 选择集添加进来;exit 选择集被移除;最后, updateenter 选择集被合并、排序并返回。然后,我们可以为这些加入或更新元素分配属性和文本。

我们观察到只要字母和文本元素之间的关联保持不变,就不需要在更新元素时重新分配某些属性和文本内容,这样就可以更高效。要保留此关联,selection.data 需要一个键函数;为了对 enterupdateexit 的精确操作,selection.join 需要相应的函数。如果 update 比 enter 和 exit 更常见,这将大大提高性能!

92-5

与前面一样,selection.join 返回合并的 enter 和 update 选择集,因此我们可以共享应用于两者的代码,例如设置 x 属性。

传递给 selection.data 的键函数用于计算每个新数据和已选元素数据的(字符串)键,确定哪个数据绑定到哪个元素:如果元素和数据具有相同的键,则该数据绑定到元素,并且元素被放入 update 选择集。字母是很好的键,因此标识函数(d => d)在这里是合适的。

(如果未指定键函数,则数据由索引绑定:第一个数据绑定到第一个元素,依此类推。作为练习,尝试重写上面的代码以通过索引联接。你将会也想把设置 x 属性与设置文本内容交换!)

然而,选择器真正闪耀的地方是过渡!✨

在下面,字母从顶部进入,更新时水平滑动,然后从底部退出。这比上面的瞬时转变更容易理解。

92-6

好的过渡不仅仅是为了吸引注意力而让图表“舞动”;它们帮助观看者通过变动了解数据是如何变化的

良好的过渡保持对象的恒定性:在过渡之前表示特定事物(如字母 C )的图表元素应在整个过渡过程中和过渡之后表示相同的事物,从而允许观看者连续跟踪。相反,如果给定元素的含义在过渡期间发生变化,那么变动将毫无意义。

举个更实际的例子怎么样?

下图显示了按特定年龄组人口百分比排列的前十个州(以及华盛顿特区,外地居住者)。这表明犹他州的年轻人口比例过高,反映了 LDS 教会对养育家庭的重视。相比之下,佛罗里达州退休人口众多,许多人都在 70 岁或以上。

当你更改选定的年龄组时,请观察图表如何重新排序以反映排名的变化。x 轴同时重新缩放以适应新数据。

92-7

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 = 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)));
    }
  });
}

92-9

只有前 10 条可见,因为其余的隐藏在图表下方。因此,selection.join 是不需要的,因为没有条形加入或移除,它们只会更新。这不仅简化了代码,而且使过渡更有意义,因为进入或退出条形图的速度现在暗示它们在展示外的位置。

动画过渡通常由读者点击或在寻找答案时点击触发。接下来,让我们看看如何让图表响应此类查询。

Next

附录

92-10

根据源码,去除了平台依赖,提取了主要代码,有以下示例:

参考资料

🗑️

《巫师 3》主线剧情一般难度完成,最后触发的是个悲剧结局。

再去看了别人的攻略,才发现前面很多任务的选择都影响到了这个结局。

自己之前自认为正当的选择,到最后却导致悲剧收场。

心情还是有些郁闷的,在做选择的时候明显的代入了自己的想法。

巫师3 游戏本地文件艺术设定集。

92-poster

This was referenced Aug 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant