引言
现存许多图表产品在双 Y 轴混合图刻度线对齐上还存在不足,例如下图展示的 Ant Design Charts 和 Echarts。但 BizCharts 不仅可以自动实现双 Y 轴刻度线对齐,还能做到两侧 Y 轴刻度线随交互变化而变化。
因此,开始探索 BizCharts 源码寻找答案,本期关注线性刻度线(相邻刻度间,刻度值差值大小相同)对齐方法。
实现猜想
在引入 BizCharts 实现双 Y 轴刻度线对齐方法前,我们不妨设想下,如果我们需要实现这个功能,应该如何实现?
分析问题:双 Y 轴统计图坐标轴对齐,本质是需要在页面上绘制 2 条坐标轴,坐标轴刻度条目和位置一致。从实现逻辑上看,我们需要获取 2 个等长的一维数组(设为 leftTicks 和 rightTicks),每一项都对应坐标轴的刻度值。
解决思路:
1、确定刻度值数目 n(可以从视觉美观上确定一个值,也可以支持外部传入,即由用户确定刻度值数目);
2、确定两侧 Y 轴上数值的最大和最小值;
3、小幅度调整左右 Y 轴数值的最值(在满足数值美观度和数据可覆盖的条件下,使数值范围能等间距地划分成 n 份);
4、基于最值,得到刻度值序列 leftTicks, rightTicks。
但如何小幅度调整 Y 轴两侧数值的最值呢?
BizCharts 实现方法
BizCharts 的实现方法和上文“实现猜想”列出的解决思路不同,它没有把解决“小幅度调整左右 Y 轴数值的最值”作为目标,而是提出了好的相邻刻度值间距(线性刻度下,刻度值是等差数列)nickTickList,并遍历寻找最接近真实数据平均刻度值(下文代码块的 avgInterval)的刻度间距,最终确定刻度最小值和刻度值序列。
简而言之,就是在理想刻度间距 nickTickList 中去找最接近真实数据刻度间距 avgInterval 的间距值 interval。
// min和max为图数据的最值,tickCount 为刻度数目
const avgInterval = (max - min) / (tickCount - 1);
最佳刻度值间距和刻度值数目
BizCharts 提出最佳刻度间距集合为下文代码块 SNAP\_COUNT\_ARRAY 及其以 10 等比例缩放的数据,即100,1000,...,0.1,0.01,0.001 等等都是最佳刻度间距。
const SNAP_COUNT_ARRAY = [1, 1.2, 1.5, 2, 2.2, 2.4, 2.5, 3, 4, 5, 6, 7.5, 8, 10];
对于刻度值数目,BizCharts 实现时默认刻度值数目为 5。
内部实现
我们将内部实现大致分为以下几步:归一化数据、计算平均刻度值、查找理想间距 interval、计算最小刻度值、范围校验。程序流程图如下所示:
归一化数据
此处采用归一化数据目的是将数据映射在 1~10 的范围,并采用 factor 记录数据缩放比例,方便后续查找刻度间距。
function getFactor(number) {
// 取正数
number = Math.abs(number);
let factor = 1;
if (number === 0) {
return factor;
}
// 小于1,逐渐放大
if (number < 1) {
let count = 0;
while (number < 1) {
factor = factor / 10;
number = number * 10;
count++;
}
// 浮点数计算出现问题
if (factor.toString().length > DECIMAL_LENGTH) {
factor = parseFloat(factor.toFixed(count));
}
return factor;
}
// 大于10逐渐缩小
while (number > 10) {
factor = factor * 10;
number = number / 10;
}
return factor;
}
计算平均刻度间距
平均刻度间距是指以真实数据最值作为线性刻度最值时的刻度间距,计算方法如下:
/**
* max和min为真实数据的最值
* tickCount为刻度数目
*/
const avgInterval = (max - min) / (tickCount - 1);
查找理想间距 interval
按照从小到大的顺序遍历理想间距数组,定位最接近平均刻度间距的理想间距。
// 第一轮查找理想间距
let similarityIndex = 0;
for (let index = 0; index < SNAP_COUNT_ARRAY.length; index++) {
const item = SNAP_COUNT_ARRAY[index];
if (calInterval <= item) {
similarityIndex = index;
break;
}
}
const similarityInterval = getInterval(similarityIndex, tickCount, calMin, calMax);
// 递归函数:查找理想间距
function getInterval(startIndex, tickCount, min, max) {
let verify = false;
let interval = SNAP_COUNT_ARRAY[startIndex];
// 刻度值校验,如果不满足,循环下去
for (let i = startIndex; i < SNAP_COUNT_ARRAY.length; i++) {
if (intervalIsVerify({ interval: SNAP_COUNT_ARRAY[i], tickCount, max, min })) {
// 有符合条件的interval
interval = SNAP_COUNT_ARRAY[i];
verify = true;
break;
}
}
// 如果不满足, 依次缩小10倍,再计算
if (!verify) {
return 10 * getInterval(0, tickCount, min / 10, max / 10);
}
return interval;
}
计算最小刻度值
/**
* min 真实数据的最小值
* interval 为理想刻度间距
*/
const minTick = Math.floor(min / interval) * interval;
刻度范围校验
判断刻度值范围(基于当前刻度数、理想刻度间距和最小刻度值计算得到)是否覆盖了真实数据。
/**
* minTick 为最小刻度值
* tickCount 为刻度数目
* interval 为理想刻度间距
* max 真实数据的最大值
*/
if (minTick + (tickCount - 1) * interval >= max) {
return true;
}
总结
BizCharts 在双 Y 轴刻度线对齐方法上主要是依赖了他们提出 nice 的刻度差值,将“小幅度调整最值”等范围无限化的问题给具体化了,解决问题的方式很新颖。
参考信息
https://github.com/alibaba/BizCharts/blob/master
作者:ES2049 / 小李爱吃车厘子
文章可随意转载,但请保留此 原文链接。
非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj@alibaba-inc.com
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。