单线性插值算法,实现虚线连接echarts折线图
我们来思考这么一个需求,使用echarts加载一个折线图。我想这个问题,对绝大多数的前端开发者来说都是不用加以思考就能解决的,只要将后端返回的数据处理好,再使用echarts官网中的折线图示例代码,就能实现。
在此基础上,我们发现如果加载折线图的数据中有断断续续的null值,那最终呈现的折线效果就是断断续续的。例如加载如下代码的折线图
series: [
{
name: '测试',
type: 'line',
data: [150, 230, null, null, 134, 147, 260, 150, 230, null, null, 134, 147, 260],
}
]
假设还有一个需求,要用虚线连接上面折线图断开的地方,该如何实现呢?其实我们只要在上述折线图的基础上,再加载一条显示样式为虚线的折线就好了,问题就在于如何构造这条虚线的数据,来让上面折线的断开处平滑连接呢。我们拿上面折线第一处断开地方的数据为例
[150, 230, null, null, 134,...]
要想让折线断开处平滑连接,就要使230到134这段的数据是平滑下降的
已知元素230和134的数组下标之差为1-4=-3,230和134的数据之差为96
那么可得96/-3 =-32,如果230后面的数据都是前面数据减去32后所得
那么我们就能获得这么一个数组,这个数组在230到134之间是平滑下降的
[150, 230, 198, 166, 134,...]
同理,我们也能求出上面折线第二处断开处数据null值应该为多少
最终得到这样一个用于展示虚线的数组
[150, 230, 198, 166, 134, 147, 260, 150, 230, 198, 166, 134, 147, 260]
我们将上面计算所得的虚线数据数组展示后效果如下(要注意两条折线的name要设置相同,这样在echarts中才能用一个图例来表示它们)
series: [
{
name: '测试',
type: 'line',
data: [150, 230, null, null, 134, 147, 260, 150, 230, null, null, 134, 147, 260],
},
{
name: '测试',
type: 'line',
lineStyle:{
type: 'dashed',
},
data: [150, 230, 198, 166, 134, 147, 260, 150, 230, 198, 166, 134, 147, 260],
}
]
OK,那我们也是终于实现用虚线连接折线断开处的需求了,只是虚线的数据是我们自己手动构造的,如果用代码来灵活构造虚线的数据,就要用到单线性插值算法了。通过下面简单的数学例子,来讲解单线性插值算法吧
假如有两个点(x, y),分别是(1, 230)、(3, 166)
那当x为2时,它的y值应该是多少呢?
(这问题一看就是小数学题啊,斜眼法秒了~)
y = (230-166) / (1-3) + 230 = 198
代码的核心,就是要实现上面的算法,话不多说,直接上代码瞅一瞅~
const linearInterpolate = (arr = []) => {
let result = [...arr]; // 创建一个副本以避免修改原数组
// 遍历数组,进行线性插值
for (let i = 0; i < arr.length; i++) {
if (arr[i] === null) {
// 找到前后两个非 null 的值
let prevIndex = i - 1;
let nextIndex = i + 1;
while (prevIndex >= 0 && arr[prevIndex] === null) {
prevIndex--;
}
while (nextIndex < arr.length && arr[nextIndex] === null) {
nextIndex++;
}
// 如果前后都有非 null 的值,进行线性插值
if (prevIndex >= 0 && nextIndex < arr.length) {
let x1 = prevIndex;
let y1 = arr[prevIndex];
let x2 = nextIndex;
let y2 = arr[nextIndex];
let x = i;
let y = y1 + ((x - x1) * (y2 - y1)) / (x2 - x1);
result[i] = y;
result[prevIndex] = arr[prevIndex];
result[nextIndex] = arr[nextIndex];
} else {
// 如果没有前后两个非 null 的值,保持 null
result[i] = null;
}
}
}
return result;
};
console.log(linearInterpolate([150, 230, null, null, 134, 147, 260, 150, 230, null, null, 134, 147, 260]));
// [150, 230, 198, 166, 134, 147, 260, 150, 230, 198, 166, 134, 147, 260];
首先,我们遍历数组找到为null元素的下标和前后两个非null值的下标,如果这两个非null值的下标是合法的,再用线性插值算法计算出null元素应有的值,等数组遍历完,就能得到null值被填补后的目标数组。
再进一步,我们考虑目标数组是否能只填补null值和保留跟null值紧相邻的元素呢,其它在原数组非null的部分在目标数组中却为null值呢?只需修改linearInterpolate函数中的一行代码即可达到需求
let result = new Array(arr.length).fill(null);
// 替换 let result = [...arr];
// 再运行 linearInterpolate([150, 230, null, null, 134, 147, 260, 150, 230, null, null, 134, 147, 260])
// 得到目标数组 [null, 230, 198, 166, 134, null, null, null, 230, 198, 166, 134, null, null];
最后,我们就能开心的拿着目标数组在折线图中加载这条虚线,美滋滋的完成用虚线连接折线断开处的需求啦~
作者公众号:程序的艺术,欢迎关注!谢谢~
转载自:https://juejin.cn/post/7395043012466753551