7

ECharts海量数据渲染解决卡顿的4种方式 - 南风晚来晚相识

 1 week ago
source link: https://www.cnblogs.com/IwishIcould/p/18154373
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ECharts海量数据渲染解决卡顿的4种方式

周五进行需求评审的时候;
出现了一个图表,本身一个图表本没有什么稀奇的;
可是产品经理在图表的上的备注,让我觉得这个事情并不简单;
那个图表的时间跨度可以是月,年,而且时间间隔很短;
这让我意识到事情并不是想的那样简单;
然后经过简单的询问:如果选择的范围是年;数据可能会上万;
我们都知道;出现上万的数据;
在渲染的时候肯定会出现白屏,操作的时候卡顿;
今天周末,没有事干;来研究研究 Echarts 渲染海量数据

file模块用来写文件

我们首先使用node来生成10万条数据;
借助node的fs模块就行;
如果不会的小伙伴;也不要担心;超级简单
// 引入模块
let fs = require('fs');
// 数据内容
let fileCont='我是文件内容'
/**
 * 第一个参数是文件名
 * 第二个参数是文件内容,这个文件的内容必须是字符串哈(特别注意)
 * 第三个参数是回调函数, 回调函数中有两个参数,
 * 第一个参数是错误信息,
 * 第二个参数是写入成功后的返回值
 * */ 
fs.writeFile('./demodata.txt',fileCont, (error, data) => {
  if (!error) {
    console.log('写入成功了',data)
  } else {
    console.log('写入失败了',error)
  }
})
1425695-20240422230618356-1762652414.png

现在我们需要创建一个指定类型的数据格式

我们等会从2022.1.1开始;每条数据间隔5分钟;产生10万条数据。
time的值是时间戳,我们可以通过 new Date().getTime() 来获取
value的值是温度,我们通过Math.random() * 50+10来获取
time的时间间隔是每隔5分钟
数据格式如下
 [
  {"time":1640966400000,"value":36.57},
  {"time":1640966700000,"value":31.68},
]
// 引入模块
let fs = require('fs');
// 生成100000条符合要求的数据格式
function timeFn(total){
  // 获取2022年1月1日的时间戳
  let dateTimeStamp = new Date(2022, 0, 1).getTime(); // 2022年1月1日 
  // 5分钟的时间戳是多少
  let oneHourStamp = 1000 * 60*5;
  let newArr = []
  for(let i= 0;i<total; i++){
    // 构造我们需要的数据格式
    newArr.push(
      { time: dateTimeStamp + oneHourStamp* i,
        value:Math.random() * 50+10 
      }
    )
  }
  return newArr
}
let needData = timeFn(100000)

fs.writeFile('./demodata.js',JSON.stringify(needData), (error, data) => {
  // JSON.stringify(needData) 将数组转为字符串
  if (!error) {
    console.log('写入成功了',data)
  } else {
    console.log('写入失败了',error)
  }
})
1425695-20240422230650960-925481771.png

10万条数据渲染耗时10秒,且页面非常卡顿

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../allechart/echarts.js"></script>
  <script src="./demodata.js"></script>
</head>

<body>
  <div style="width: 598px;height: 400px;" id="box"></div>
</body>
<script>
  function backTime(value){
    let date = new Date(value);  
    // 获取年份、月份和日期  
    let year = date.getFullYear();  
    // 月份从 0 开始,需要加 1  
    let month = date.getMonth() + 1; 
    let day = date.getDate();  
    let hours = date.getHours();  
    let minutes = date.getMinutes();  
    let seconds = date.getSeconds(); 
    // 格式化月份和日期为两位数(不足两位时补零)  
    month = month < 10 ? '0' + month : month;  
    day = day < 10 ? '0' + day : day;  
    hours = hours < 10 ? '0' + hours : hours;  
    minutes = minutes < 10 ? '0' + minutes : minutes;  
    seconds = seconds < 10 ? '0' + seconds : seconds;  
    // 返回格式化后的字符串  
    return year + '-' + month + '-' + day +  '\n' + hours + ':' + minutes + ':' + seconds;
  }
 
  console.log(backData)
  let myChart = echarts.init(document.querySelector('#box'))
  let option = {
    legend: {
      data: ['某城市']
    },
    tooltip: {
      trigger: 'axis',
      triggerOn: 'mousemove',
      confine: true,
      extraCssText: 'white-space: pre-wrap'
    },
    xAxis: {
      type: 'category',
      // 返回时间
      data: backData.map(v=> backTime(v.time)),
      splitLine: { show: false },
      lineStyle: {
        width: 2
      },
      axisTick: {
        show: false
      },
      axisLabel:{
        // 更改x轴文字颜色的配置
        textStyle: {
          color: '#717782'
        },
        showMinLabel: true,
        showMaxLabel: true // 固定显示X轴的最后一条数据
      },
    },
    yAxis: {
      type: 'value',
      axisLine: {
        show: false
      },
      axisTick: {
        show: false
      },
      splitLine: {
        lineStyle: {
          color: '#D2DBE6'
        }
      },
      axisLabel: {
        formatter: '{value}',
        color: '#717782'
      }
    },
    grid: {
      left: '30',
      right: '35',
      bottom: '10',
      top: '20',
      containLabel: true
    },
  
    series: [
      {
        data:backData.map(v=>v.value),
        type: 'line',
        smooth: true
      }
    ]
  };
  myChart.setOption(option);
</script>

</html>
1425695-20240422230714480-1481132420.gif

发现的问题

我们发现渲染时间非常久(需要10多秒),而且页面卡顿;
有没有好的办法来解决这个问题呢;
是有的,最好的使用echarts的dataZoom用于区域缩放;
通过滑块看指定区域的数据,我们来尝试一下

dataZoom的常见属性介绍

type: "slider" || "inside",
  slider:这种类型会在图表的一侧添加一个滑动条,
  用户可以通过拖动滑动条来改变数据窗口的范围,从而实现数据的缩放。
  inside:这种类型缩放组件是内置于坐标系中的,
  用户可以通过鼠标滚轮、触屏手指滑动等方式来操作数据的缩放。
  简单点说:slider会产生一个滚动条,inside不会

xAxisIndex: 可以是一个数字,表示特定的X轴索引;
  也可以是一个数组,表示同时控制多个X轴。

xAxisIndex: 0, 控制第1条数据开始

start: 0, 数据窗口范围的起始百分比。范围是:0 ~ 100。表示 0% ~ 100%。

end: 1, 数据窗口范围的结束百分比。范围是:0 ~ 100。

minSpan: 0, 用于限制窗口大小的最小值(百分比值),取值范围是 0 ~ 100。

maxSpan: 10, 用于限制窗口大小的最大值(百分比值),取值范围是 0 ~ 100。

特别提醒:start: 设置为0;说明是从第1条数据开始的;
那么xAxisIndex就必须是0;
因为xAxisIndex不是0,他们就互相矛盾了;
minSpan 和 maxSpan一般配合使用;主要是用于只展示某一个区间;
无论用户怎么缩放;都会在这个区间

我们使用 dataZoom 来处理海量的数据

... 其他配置项
dataZoom: [
  {
    type: "slider", // 滑块类型 值有slider和inside
    xAxisIndex: [0],
    start: 0,
    end: 1,
    minSpan: 0, // 用于限制窗口大小的最小值(百分比值),取值范围是 0 ~ 100。
    maxSpan: 10,
  },
],
series: [
  {
    data:backData.map(v=>v.value),
    type: 'line',
    smooth: true
  }
]
1425695-20240422230724310-1122826753.gif

配置之后,我们发现渲染非常流畅

通过配置前和配置后的图的对比
我们发现配置之后;页面渲染速度非常快;
打开页面就渲染完成,压根想不到是10万条数据;
说明通过 dataZoom 是非常有效的

dataZoom处理海量数据的优缺点

优点:配置简单;
缺点:只能看指定局部的数据;无法看整体数据

其他办法 sampling 降采样策略 sampling: 'average'

series: [
  {
    data:backData.map(v=>v.value),
    type: 'line',
    smooth: true,
    sampling: 'average',//' 表示采用平均值采样策略
  }
]
1425695-20240422230731696-1875543437.gif

sampling的几个值

'lttb': 采用 Largest-Triangle-Three-Bucket 算法,
      可以最大程度保证采样后线条的趋势,形状和极值。
      不过有可能会造成页面渲染时间长
'average': 取过滤点的平均值
'min': 取过滤点的最小值
'max': 取过滤点的最大值
'minmax': 取过滤点绝对值的最大极值 (从 v5.5.0 开始支持)
'sum': 取过滤点的和

sampling 降采样策略 sampling: 'lttb'

series: [
  {
    data:backData.map(v=>v.value),
    type: 'line',
    smooth: true,
    // 采用 Largest-Triangle-Three-Bucket 算法,
    // 可以最大程度保证采样后线条的趋势,形状和极值。
    // 不过有可能会造成页面渲染时间长
    sampling: 'lttb'
  }
]
1425695-20240422230741751-1960948706.gif

降采样策略 sampling 的优缺点

优点:可以看见整体的趋势;
缺点:部分数据丢失;tooltip功能可能实现不了
1425695-20240422230751938-1139772437.png

使用 large 属性

series: [
  {
    data:backData.map(v=>v.value),
    type: 'line',
    smooth: true,
    //开启大数据量优化,在数据特别多而出现图形卡顿时候开启
    large:true, 
  }
]
1425695-20240422230801913-1095417355.gif

发现使用large属性没有效果

为什么我们使用large没有效果呢?
我们去官网看看怎么说的;
https://echarts.apache.org/zh/option.html#series-bar.type
在上面这个文档中心,我发现折线图[type: 'line']没有 large 属性
large支持的图表:柱状图(bar), K 线图 (candlestick), 路径图(lines),散点图(scatter)
其他类型的图表暂时不支持;
所以我们可以把折线图更改为柱状图看下是否可以解决卡顿问题;
series: [
  {
    data:backData.map(v=>v.value),
    type:'bar',
    //开启大数据量优化,在数据特别多而出现图形卡顿时候开启
    large:true,
  }
]
1425695-20240422230810174-1832613913.gif

large 属性的介绍以及优缺点

large:是否开启大数据量优化;
在【数据图形】特别多而出现卡顿时候可以开启。
开启后配合 largeThreshold 在数据量大于指定阈值的时候对绘制进行优化。
优点:解决图形卡数据量大而产生的卡顿问题。
缺点:优化后不能自定义设置单个数据项的样式;
【特别提醒】: 
large支持的图表:柱状图(bar), K 线图 (candlestick), 路径图(lines),散点图(scatter)

"辅助"large属性最佳配角 largeThreshold

有些时候;数据量并不是一开始就是大量;
而是经过时间的变化;数据才变成了海量数据;
如果我们一开始就使用large开启优化;这显然是不适合的;
这个时候;我们的最佳配角 largeThreshold 就闪亮登场了;
largeThreshold:开启绘制优化的阈值。
当数据量(即data长度)大于这个阀值的时候,会开启高性能模式。
低于这个阀值;不会开启高性能模式;
比如我们希望:数据量(即data长度)大于1万时开启高性能模式,你可以这样设置:
series: [
  {
    data:backData.map(v=>v.value),
    type:'bar',
    //开启大数据量优化,在数据特别多而出现图形卡顿时候开启
    large:true,
    // 数据量大于1万时开启高性能模式,如果没有大于1万;不会开启
    // 此时我们的数据是10万;会开启;渲染非常快
    largeThreshold: 10000,
  }
]
1425695-20240422230820038-583726289.gif

appendData 属性的简单介绍

根据官网的介绍;
appendData属性会分片加载数据和增量渲染;
在大数据量的场景下(例如地理数的打点);
此时,数据量将会非常的大;
在互联网环境下,往往需要分片加载;
appendData 接口提供了分片加载后增量渲染的能力;
渲染新加入的数据块时,不会清除原有已经渲染的部分数据。
但是并不是所有图表类型都支持这个属性;
目前不支持series系列的;如柱状图,折线图,饼状图这些都不支持;
目前支持的图有: 散点图(scatter),线图(lines)。
ECharts GL的散点图(scatterGL)、线图(linesGL) 和 可视化建筑群(polygons3D)。
上面这段参考:https://echarts.apache.org/zh/api.html#echartsInstance.appendData

series系列的图表

1425695-20240422230828215-1851169510.png
最近股票亏麻了;哎;好难受;
如果觉得我的文章写的还不错;给我点个推荐;
感谢了

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK