基于echarts的带流动光效的折线图

222 阅读1分钟

前沿

今天给大家分享基于echarts的带流动光效的折线图, 首先看一下要做的效果如下图:

44225df8a64b444aa867af0b0ba5dfe1~tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0 (2).webp

刚开始以为很容易实现,但是echart只支持非平滑曲线,效果实现代码可查看juejin.cn/post/712918…

d2fdf98098364715bbc605007e86b80b~tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0 (1).webp

实现原理

实现原理其实也很简单,我们基于上面非平滑曲线,通过算法处理图表数据,增加图表数据的同时让线条呈现曲线。下面是算法源码

/**
 * 图表增加采样
 */

// 二次贝塞尔曲线
function bezier2(p0, p1, p2, t) {
  const t2 = t * t
  const m = 1 - t
  const m2 = m * (1 - t)
  return p0 * m2 + 2 * p1 * t * m + p2 * t2
}

// 三次贝塞尔曲线
function bezier3(p0, p1, p2, p3, t) {
  const t2 = t * t
  const t3 = t * t2
  const m = 1 - t
  const m2 = m * (1 - t)
  const m3 = m2 * (1 - t)
  return p0 * m3 + 3 * p1 * t * m2 + 3 * p2 * t2 * m + p3 * t3
}

function getMidPoint(p0, p1) {
  const x0 = p0[0]
  const y0 = p0[1]
  const x1 = p1[0]
  const y1 = p1[1]
  return [(x0 + x1) / 2, (y0 + y1) / 2]
}

function movePoint(p, v) {
  const px = p[0]
  const py = p[1]
  const vx = v[0]
  const vy = v[1]
  return [px + vx, py + vy]
}

function getVector(p0, p1) {
  const x0 = p0[0]
  const y0 = p0[1]
  const x1 = p1[0]
  const y1 = p1[1]
  return [x1 - x0, y1 - y0]
}

export function smoothLine(points, smooth = 50) {
  const len = points.length
  if (len < 3) {
    return points
  }
  const newPoints = []
  const controls = []
  for (let i = 0; i < len - 2; i++) {
    const p0 = points[i]
    const p1 = points[i + 1]
    const p2 = points[i + 2]
    const m1 = getMidPoint(p0, p1)
    const m2 = getMidPoint(p1, p2)
    const m = getMidPoint(m1, m2)
    const v = getVector(m, p1)
    const _m1 = movePoint(m1, v)
    const _m2 = movePoint(m2, v)
    // 起点
    if (i === 0) {
      for (let j = 0; j < smooth; j++) {
        const x = bezier2(p0[0], _m1[0], p1[0], j / smooth)
        const y = bezier2(p0[1], _m1[1], p1[1], j / smooth)
        newPoints.push([x, y, ...p0])
      }
      controls.push(_m1)
      controls.push(_m2)
    } else {
      for (let j = 0; j < smooth; j++) {
        const prev = controls[controls.length - 1]
        const x = bezier3(p0[0], prev[0], _m1[0], p1[0], j / smooth)
        let y = bezier3(p0[1], prev[1], _m1[1], p1[1], j / smooth)
        if (y < 0) {
          y = 0
        }
        newPoints.push([x, y, ...p0])
      }
      controls.push(_m1)
      controls.push(_m2)
      // 结束
      if (i === len - 3) {
        for (let j = 0; j <= smooth; j++) {
          const x = bezier2(p1[0], _m2[0], p2[0], j / smooth)
          const y = bezier2(p1[1], _m2[1], p2[1], j / smooth)
          newPoints.push([x, y, ...p0])
        }
      }
    }
  }
  return newPoints
}

处理数据

接下来我们使用算法处理原始的图表数据

// 原始图表数据
const respData = [
        {
            "STATUS":0,
            "COUNT":"4",
            "NAME":"[0,1)"
        },
        {
            "STATUS":0,
            "COUNT":"0",
            "NAME":"[2,4)"
        },
        {
            "STATUS":1,
            "COUNT":"4",
            "NAME":"[1,1)"
        },
        {
            "STATUS":1,
            "COUNT":"0",
            "NAME":"[1,4)"
        },
        {
            "STATUS":2,
            "COUNT":"5",
            "NAME":"[2,1)"
        },
        {
            "STATUS":2,
            "COUNT":"0",
            "NAME":"[2,4)"
        },
        {
            "STATUS":3,
            "COUNT":"5",
            "NAME":"[3,1)"
        },
        {
            "STATUS":3,
            "COUNT":"0",
            "NAME":"[3,4)"
        }
    ],
let smoothLinesCoordsData = [
  {
    coords: [],
  },
]
 // 处理折线数据
const smoothLineData = smoothLine(respData)
// 再通过折线数据获取光线数据
smoothLinesCoordsData[0].coords = smoothLineData.map((item) => [
  item[0],
  item[1],
])

处理X轴

我们增加数据采样后,会导致数据量很大,如果不处理X轴的话X轴会密密麻麻一大堆。 所以我们修改下x轴interval属性,让他有一个间隔值,可根据自己实际情况来设置

xAxis: {
      type: 'value',
      name: '',
      min: 1,
      interval: 1,
      axisTick: {
        show: false,
      },
      axisLabel: {
        rotate: 35,
        formatter: (params) => {
          return lineArrData[params - 1] ? lineArrData[params - 1][2] : ''
        },
      },
      position: 'bottom',
    },

以上就是所有步骤,如果你是用react, 已经有相应的插件了。可查看juejin.cn/post/709056…