Echarts作为一个强大的开源可视化库,在日常开发中,广泛应用于数据可视化、数据分析、信息展示和数据报告等多种场景,然而,当数据量达到十万甚至更多时,图表的加载和渲染效率成为挑战。本文将介绍如何使用 ECharts 的数据分段加载和降采样策略来优化大数据图表的显示,以提升性能和用户体验。
(一)正常加载十万条数据:
如下图所示:
- 首先我们先看下这十万条数据加载页面的情况,海量数据已经连成一片,都不知道加载的是什么东西了。
- 其次我们再看下这十万条数据的性能,总加载时间用了12s,绘制时间用了4s, 如果等12s去看这个图表,这对于用户体验来说必然是不可接受的。
(二)优化方案:
遇到上述的渲染瓶颈,我们将使用数据分段加载(DataZoom)和降采样策略(Sampling)来优化大数据图表的显示,以提升性能和用户体验。
方案一:Datazoom(数据分段加载):主要是用于分区域渲染,然后自然的关注数据的细节,或者浏览整体,他提供了几个属性可以控制这个图标渲染时的性能问题,比如start数据范围起始百分比,比如end范围结束的百分比
dataZoom: [{
type: 'slider', // 值:inside slider
start: 0, // 数据窗口范围的起始百分比
end: 100, // 数据窗口范围的结束百分比
realtime: true, // 是否实时更新 true/false
xAxisIndex: 0, // 表示特定的X轴索引
minSpan: 0, // 用于限制窗口大小的最小值(百分比值),取值范围是 0 ~ 100。
maxSpan: 100, // // 用于限制窗口大小的最大值(百分比值),取值范围是 0 ~ 100。
// 特别提醒:start: 设置为0, 说明是从第1条数据开始的; 那么xAxisIndex就必须是0; 因为xAxisIndex不是0,他们就互相矛盾了。
filterMode: 'filter'
}, {
type: 'slider',
start: 0,
end: 100,
realtime: true,
yAxisIndex: 0, // 表示特定的y轴索引
minSpan: 0,
maxSpan: 100,
filterMode: 'empty'
}]
加入优化方案后,再来感受一下渲染性能:
- 很明显页面图表渲染流畅很多,因为他只加载了区域部分数据。
- 执行时间从12秒降到1.3秒,绘制时间从4s降到0.3s,页面性能有了大幅提升。
方案二:Sampling(降采样策略): 减少数据点的数量,减少渲染负担,以提高性能或提供更清晰的视觉表现。降采样可以通过多种方式实现,例如简单地跳过一些数据点,或使用算法如平均值、最大值、最小值等来聚合数据点。
series: [{
name: '销量',
data: sampledData,
type: 'bar',
sampling: 'lttb',
// sampling的值:
// 'lttb' 采用 Largest-Triangle-Three-Bucket 算法,可以最大程度保证采样后线条的趋势,形状和极值。
// 'average' 取过滤点的平均值
// 'min' 取过滤点的最小值
// 'max' 取过滤点的最大值
// 'minmax' 取过滤点绝对值的最大极值 (从 v5.5.0 开始支持)
// 'sum' 取过滤点的和
}, {
name: '金额',
data: sampledData,
type: 'line'
}],
看效果:
- 这个渲染效果是保留了原图表渲染的趋势,原本什么样,这个渲染出来的这个趋势图和原来差不多,但他是做了过滤的。
- 执行时间从12秒降到1.3秒,绘制时间从4s降到0.3s,明显也不卡顿了。
(三)总结上述两种优化方案的优缺点
DataZoom(数据分段加载)
优点:
- 配置简单:DataZoom 的配置相对简单,通过设置
start
和end
属性,可以控制图表渲染的数据范围,从而提高性能。 - 流畅渲染:使用 DataZoom 后,页面渲染速度非常快,能够实现快速渲染大量数据而不影响用户体验。
缺点:
- 局部数据展示:DataZoom 只能查看指定局部的数据,无法查看整体数据,这可能限制了用户对数据整体趋势的把握。
Sampling(降采样策略)
优点:
- 整体趋势可见:与 DataZoom 不同,Sampling 策略允许用户看到数据的整体趋势,而不是仅限于局部数据。
- 减少数据点:通过减少数据点的数量,Sampling 策略可以减轻渲染负担,提高性能,尤其是在数据点非常多的折线图中效果明显。
缺点:
- 数据丢失:Sampling 策略可能会导致部分数据丢失,这对于需要精确数据的用户来说可能是一个问题。
- tooltip功能可能受限:由于数据点的减少,一些图表的交互功能,如 tooltip,可能无法实现或受到影响。
综上所述,DataZoom 和 Sampling 是处理 ECharts 大数据渲染的两种有效策略,它们各有优势和局限。DataZoom 适合于需要快速渲染大量数据的场景,而 Sampling 更适用于需要观察数据整体趋势的情况。在实际应用中,可以根据具体需求选择合适的策略来优化图表的显示和性能。
(四)上完整代码
新建ChartComponent.vue子组件
<template>
<div ref="chart" style="width: 100%; height: 400px;"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'ChartComponent',
props: {
echartsdata: Array // 假设传入的数据是数组
},
mounted () {
this.initChart()
},
watch: {
echartsdata (newval, oldval) {
this.initChart()
}
},
methods: {
initChart () {
const chart = echarts.init(this.$refs.chart)
const sampledData = this.echartsdata
const option = {
// ECharts 配置项
xAxis: {
type: 'category'
// data: Array.from({ length: sampledData.length }, (_, i) => i)
},
yAxis: {
type: 'value'
},
series: [{
name: '销量',
data: sampledData,
type: 'bar',
sampling: 'min',
// 'lttb' 采用 Largest-Triangle-Three-Bucket 算法,可以最大程度保证采样后线条的趋势,形状和极值。
// 'average' 取过滤点的平均值
// 'min' 取过滤点的最小值
// 'max' 取过滤点的最大值
// 'minmax' 取过滤点绝对值的最大极值 (从 v5.5.0 开始支持)
// 'sum' 取过滤点的和
}, {
name: '金额',
data: sampledData,
type: 'line'
// sampling: 'lttb', // 最大程度保证采样后的线条趋势。
// sampleSize: 10
}],
dataZoom: [{
type: 'slider', // 值:inside slider
start: 0, // 数据窗口范围的起始百分比
end: 100, // 数据窗口范围的结束百分比
realtime: true, // 是否实时更新 true/false
xAxisIndex: 0, // 表示特定的X轴索引
minSpan: 0, // 用于限制窗口大小的最小值(百分比值),取值范围是 0 ~ 100。
maxSpan: 100, // // 用于限制窗口大小的最大值(百分比值),取值范围是 0 ~ 100。
// 特别提醒:start: 设置为0, 说明是从第1条数据开始的; 那么xAxisIndex就必须是0; 因为xAxisIndex不是0,他们就互相矛盾了。
filterMode: 'filter'
}, {
type: 'slider',
start: 0,
end: 100,
realtime: true,
yAxisIndex: 0, // 表示特定的y轴索引
minSpan: 0,
maxSpan: 100,
filterMode: 'empty'
}]
}
chart.setOption(option)
}
}
}
</script>
父组件:
<template>
<div>
<button @click="loadinglargeData">加载大数据</button>
<chart-component :echartsdata="largeData" :sample-rate="6"></chart-component>
{{largeData}}
</div>
</template>
<script>
import ChartComponent from './components/ChartComponent.vue'
export default {
components: {
ChartComponent
},
data () {
return {
largeData: [950, 230, 12, 618, 35, 147, 260, 1100, 90, 510, 20, 0, 1018, 135, 947, 760, 100, 90, 150]
}
},
created () {
},
methods: {
// 点击加载数据
loadinglargeData () {
this.createLargeData()
},
// 随机生成100000条数据
createLargeData () {
let largeData = []
for (let i = 0; i < 3000; i++) {
largeData.push(Math.floor(Math.random() * 1000))
}
console.log(largeData)
this.largeData = largeData
}
}
}
</script>