D3.js-坐标轴

数据结构

D3 画的 SVG 坐标轴的 HTML 结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<g
transform="translate(20, 300)"
class="myXaxis"
fill="none"
font-size="10"
font-family="sans-serif"
text-anchor="middle"
>
<path
class="domain"
stroke="currentColor"
d="M20.5,0.5H800.5"
style="color: rgb(181, 181, 183);"
></path>
<g
class="tick"
opacity="1"
transform="translate(71.3695652173846,0)"
style="font-weight: bold; font-size: 15px; opacity: 1; color: rgb(112, 113, 125);"
>
<line stroke="currentColor" y2="0"></line>
<text fill="currentColor" y="10" dy="0.71em">2015</text></g
>
<g
class="tick"
opacity="1"
transform="translate(240.9347826086923,0)"
style="font-weight: bold; font-size: 15px; opacity: 1; color: rgb(112, 113, 125);"
>
<line stroke="currentColor" y2="0"></line>
<text fill="currentColor" y="10" dy="0.71em">2016</text></g
>
</g>

从上述结构可以得出如下结论:

  • 每个坐标轴都包含在一个 g 标签中
  • 每个坐标轴有一个 path 元素,对应轴线,且该元素默认有一个.domain 的样式
  • 坐标轴的.domain,包含有内刻度和外刻度(通过 tickSizeOuter(123)设置)
  • 每个坐标轴有 N 个 g 元素,对应刻度,每个刻度默认有一个.tick 的样式
  • 每个刻度由一个 line 元素(刻度线)和一个 text 元素(每个刻度的值文本)构成
  • path 的颜色:stroke 和 color 都可以设置
  • path 的宽度:通过 stroke-width 设置,且不能带 px 单位,只能是纯数值
  • axis 的样式设置,必须在 call 之后才能生效

实现方案

网格线

按常规思路来看,网格线就是直线/虚线,那么我们只需要计算好位置,然后通过 line 或者 path 画线条即可。不过这里有一个更精妙的方法,就是将刻度长度设置为负数,即反向延长坐标轴的刻度线:

1
2
3
4
5
6
7
// 设置显示的刻度数量
yAxis
.ticks(5)
.tickSizeOuter(0)
// 将刻度长度设置为负数,就可以实现网格线了
.tickSizeInner(-1 * this.maxXPosition + this.minXPosition)
.tickPadding(10);

指定坐标轴显示的数值

可以通过tickValues()这个方法来指定。但是注意,这个方法对于比例尺有要求,必须是序数比例尺,比如 band、point 比例尺。

1
2
3
4
// 显示指定的刻度
xAxis
// 指定坐标轴显示的数值
.tickValues([...dates]);

For ordinal scales, such as band and point scales, the scale does not implement scale.ticks because an ordinal scale has no way of knowing which ordinal values from the scale’s domain to prioritize. For an ordinal axis, use axis.tickValues to instead specify which tick values you want.

疑问:序数比例尺可以自动计算显示哪些数值么?

设置坐标轴显示的数值数量

针对 date 类型的 Scale,tickNumber 是无效的。

坐标轴自动填补范围

不能直接用数据的最大值和最小值作为比例尺的范围,否则有些点会超出绘图区域;因此应该需要向上和向下再取一段数值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 假设我们认为Y轴一般显示tickNumber个数值,那么先计算出每两个数值之间的间隔,然后对这个间隔取10的对数,再四舍五入得到一个对数值
* 然后通过该对数值计算一个最终的间隔数值,给真实数据的最大值和最小值补齐
* @param {*} minValue 真实轴线数据的最小值
* @param {*} maxValue 真实轴线数据的最大值
* @param {*} tickNumber 轴线上需要显示的数值个数
* @returns
*/
_computeAxisRange(minValue, maxValue, tickNumber) {
let gap = (maxValue - minValue) / tickNumber;
const exponent = Math.round(Math.log10(gap));
gap = 10 ** exponent;
const max = (Math.floor(maxValue / gap) + 1) * gap;
const min = (Math.ceil(minValue / gap) - 1) * gap;
return {
min, max,
}
}

刻度数值的对齐方式

通过设置 text-anchor 属性来实现:

1
2
3
4
5
6
7
8
9
10
this.group.selectAll('text').style('text-anchor', () => {
if (
this.option?.style?.textAnchor &&
typeof this.option?.style?.textAnchor === 'function'
) {
return this.option?.style?.textAnchor();
}

return this.option?.style?.textAnchor;
});

常用代码示例

1
2
3
4
5
6
7
8
9
10
11
12
// 显示指定的刻度
xAxis
// 指定坐标轴显示的数值
.tickValues([...dates])
// 不显示外侧刻度
.tickSizeOuter(0)
// 内侧的刻度,设置为0就不显示了
.tickSizeInner(0)
// 文字和刻度的padding,即文字和刻度与坐标轴轴线的距离
.tickPadding(10);
// 文本格式化
// .tickFormat('');

经验教训

将 X 轴的比例尺设置为了 d3.scaleTime()

这样导致的问题就是组件适配性非常差,因为 X 轴的值很可能并不是一个时间,比如是一个股票代码。

合适的比例尺是 d3.scalePoint()

参考资料

坐标轴的介绍:

http://www.qiutianaimeili.com/html/page/2020/04/2020457heuxpj58al.html

https://juejin.im/post/6844903998772740103