d3图谱绘制的小技巧

背景介绍

在使用 d3 绘制图谱的时候,由于 svg 无法像其他 html 元素一样静态布局,所有的元素都需要按照绝对定位计算位置,如果遇到比较复杂的元素就非常麻烦,往往需要计算大量的坐标,还特别容易计算错误(尤其会遗漏掉间距)。

其实可以通过一些小技巧来减轻计算的工作量,且让代码逻辑变得非常清晰。

案例说明

举一个常见的例子说明:

这个节点由三个部分组成,分别是蓝色的边框,黑色的主文本和红色的副文本。

如何计算这三个元素的坐标并正确的绘制出来,以 X 轴为例,主要要考虑以下两个问题:

  1. 坐标的起点在哪里?
  2. 各个元素的长度分别是多少?

关于坐标的起点有几种情况:

  1. 起点在元素最左侧
  2. 起点在整个元素中点
  3. 起点在元素的最右侧

情况 1 最符合人的思维习惯,所有元素都是正向排列,最为简单。

情况 2 下整个元素的左边界的坐标和右边界的坐标往往随着元素的宽度变化,在整体长度可变,或者内部元素非常多的情况下计算非常复杂,不建议使用

情况 3 和情况 1 类似,只要稍微做处理,以最右边为起点,则左侧起点为所有元素的长度取负即可

业务中如果出现左右两棵树的情况,往往需要情况 1 和情况 3 同时考虑。

假设我们已经获取到了各个元素的长度如下:

主文本的宽度:strWidth

副文本的宽度:subTextWidth

边框的左右间隙:CPNodeHPadding

主文本和副文本之间的间隙:CPNodeHPaddingSubText

元素总长:d.width = strWidth + subTextWidth + 2 * CPNodeHPadding + CPNodeHPaddingSubText

接下来用伪代码来快速编写几个元素的位置,已左右两棵树的情况为例:

假设左边的树 direction=-1,右边的树 direction=1,部分代码省略。

蓝色的边框为整体元素的最外层,最先绘制。

1
2
3
4
CPNode.append("rect").attr("x", (d) => {
d.rectX = direction === 1 ? 0 : -d.width; // 记录边框的起始点,即整个元素的起点
return d.rectX;
});

然后按照顺序从左往右绘制主文本:

1
2
3
4
CPNode.append("text").attr("x", (d) => {
d.textX = d.rectX + CPNodeHPadding; // 文字起点为边框起点+边框与文字的间距
return d.textX;
});

然后绘制副文本:

1
2
3
CPNode.append("text").attr("x", (d) => {
return d.textX + d.strWidth + CPNodeHPaddingSubText; // 副文本起点为文字起点加上文本之间的间距
});

所有的图形就全部绘制完毕了,我们回过头来看下原本的代码是什么样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CPNode.append("rect").attr("x", (d) => {
d.rectX = direction === 1 ? 0 : -d.width;
return d.rectX;
});
CPNode.append("text").attr("x", (d) => {
let x = null;
if (d.tagsWidth > d.strWidth) {
x = d.tagsWidth - d.strWidth / 2;
} else {
x = d.strWidth / 2;
}
return direction * x;
});
CPNode.attr("x", (d) => {
return direction * (d.strWidth + d.subStrWidth / 2) - CPNodeHPadding;
});

原本的代码算法存在 bug,导致副文本的位置无法准确定位,即使没有 bug,这样的写法也非常难以理解,后续维护也很困难。

新的方案其实只是将绘制的过程按照人的习惯的思维按顺序绘制,并且将每一步的坐标轴起点保存起来,将“计算每个元素相对于起点的偏移”简化成“每个相关元素之间的偏移”,并利用 d3 数据绑定的特性保存起来,在后续的绘制当中能够重复使用,非常有用!

总结

在多次开发 d3 相关的业务之后,对 d3 或者说数据可视化也总结了一些小技巧,其中有两个非常大的技巧:

第一就是将图层分层,不仅仅是将不同的组件进行分层,还包括将交互层和表现层进行分层

第二就是将基于整体坐标轴的数值转化成基于关系的偏移量。

两者结合能够快速定位到每个节点的坐标,在解决动画,交互等复杂问题上也有很大的帮助。


d3图谱绘制的小技巧
https://www.wobushi.top/2021/d3图谱绘制的小技巧/
作者
Pride Su
发布于
2021年4月15日
许可协议