likes
comments
collection
share

单线性插值算法,实现虚线连接echarts折线图

作者站长头像
站长
· 阅读数 23

我们来思考这么一个需求,使用echarts加载一个折线图。我想这个问题,对绝大多数的前端开发者来说都是不用加以思考就能解决的,只要将后端返回的数据处理好,再使用echarts官网中的折线图示例代码,就能实现。

在此基础上,我们发现如果加载折线图的数据中有断断续续的null值,那最终呈现的折线效果就是断断续续的。例如加载如下代码的折线图

  series: [
      {
        name: '测试',
        type: 'line',
        data: [150, 230, null, null, 134, 147, 260, 150, 230, null, null, 134, 147, 260],
      }
    ]

单线性插值算法,实现虚线连接echarts折线图 假设还有一个需求,要用虚线连接上面折线图断开的地方,该如何实现呢?其实我们只要在上述折线图的基础上,再加载一条显示样式为虚线的折线就好了,问题就在于如何构造这条虚线的数据,来让上面折线的断开处平滑连接呢。我们拿上面折线第一处断开地方的数据为例

[150, 230, null, null, 134,...]
要想让折线断开处平滑连接,就要使230134这段的数据是平滑下降的
已知元素230134的数组下标之差为1-4=-3230134的数据之差为96
那么可得96/-3 =-32,如果230后面的数据都是前面数据减去32后所得
那么我们就能获得这么一个数组,这个数组在230134之间是平滑下降的
[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],
  }
]

单线性插值算法,实现虚线连接echarts折线图 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
评论
请登录