echarts+大数据量。这是个无解的问题!

大数据量,什么样的数据才算大呢?在echart 4.5.0版本中,画折线图,数据线一共1001000条,每条数据5002200个数据点,即最小数据50000个点(五万个点),最大数据2200000个点(两百二十万个点)。在不同数据线、不同数据点的情况下,任意情况下出现不能够使图表进行流畅的放大缩小的现象,即可认为其数据量大。当放大缩小无法流畅地进行时,也意味着图表的数据交互,包括数据点的拖拽、图表平移、显示鼠标悬浮处的点的数据(tooltip)等都会有很明显的卡顿。

需要注意的是,echarts的图表会卡顿一般是分两种情况,一种是对于数据过多地绑定了事件(尤其是一些容易频繁触发的事件,比如数据点拖拽、监听鼠标移动事件等)或者进行大量图形的操作(比如换算数据单位,从g转kg之类的操作,然后重新画图)时交互体验较差,一种是展示大量的数据,导致做任何交互动作都会有明显的卡顿,甚至浏览器会提示页面无响应的情况。当然,两者混合的可能是比较常见的,很多需求都是既要求数据量大又要求交互体验好。老实说,很难实现,除非js的性能能进一步提高。

图表加载大数据量出现的问题以其影响因子

问题

数据从后台传到前端,前端再渲染成图表。这个流程很简单易懂。问题在于,当数据量达到一定量时(为了方便,数据量用后台传过来的json数据的实际大小表示),比如50M大小数据,以网速5M/s来算,需要10s以上(毕竟后台可能需要处理一下数据才能发过来),渲染图表的时间随着数据量的增大而加长,50M数据需要15s以上才能渲染出来。而且渲染出来后,进行图表的缩放或者其它交互仍然会有明显的卡顿。(卡顿现象是否明显、渲染时间是否缩短或延长,有很大一部分取决于电脑硬件配置不同)

影响因子

1.后台处理数据所需的时间

2.被传输数据的文件大小

3.网络状况

4.前端图表框架

5.图表交互

6.客户使用的电脑硬件配置

图表加载大量数据的一些解决思路

尽管可以使用一些方式来减轻卡顿,但是卡顿现象是无法彻底解决的。以下是一些解决思路(默认图表框架:echarts):

1.如非必要,取消一切动画效果

2.通过设置echarts的配置属性,如silent属性(是否响应和触发鼠标事件),减少不必要的数据触发事件的频次;showSymbol属性(是否显示点),不画数据点能减少部分卡顿压力,建议showSymbol和tooltip不一起使用。

3.通过提高硬件性能(这个难度是最高的,不太现实,很难。毕竟成本很高。)

4.和后台开发人员商量讨论,对数据进行压缩,减少数据量(这比较有效,特别是在需求中一定要有大量交互的情况下有显著效果)

5.如果echart框架实现效果实在不理想,可以尝试其它框架。就个人实践而言,在画大数据量的彩图(百万级别)时,echarts需要画的时间按分钟计,而plotly.js花的时间按秒计(具体看电脑性能),远比echart强。当然,使用其它框架会面临各种问题,比如文档不完善(尤其没有中文文档)、官网需要翻墙访问、出现错误在百度很难找到解决方案、有些交互难以实现(可能没有相应的API可用)、影响整个项目状态(打包后文件大小)等。所以请谨慎使用,迫不得已,实在没有办法了,再尝试这个最终方案。

我自己的解决方案(勉强使用)

使用场景

事先说明,我这个解决方案的使用场景极为有限,而且并不能解决全部的问题,瑕疵较多。但总体而言,这算是”总的来说还行吧“这种感觉的解决方案。

需求:图表加载大量数据,并在缩放时出现明显的卡顿现象;对特定部分数据图形有交互的需求,允许其它数据不进行任何交互;需求要求交互顺畅,没有明显卡顿,且数据量20W~500W不等。

比如,画一个折线图,要求数据线共201条,200条数据线(参考线)用于参考,1条数据线(调整线)用于调整,需要根据参考线对调整线进行数值设置。

解决思路

核心:我只关注我需要交互的数据,其它数据只是背景。

将数据分为两部分,一部分是参考数据,一部分是调整数据。在HTML文件中使用两个div标签,一个是参考数据使用,另一个是调整数据使用。将两个div块在页面上重叠,画调整数据的div放置最上方,画不需要进行任何交互行为的数据的div放置最下方。之后使用框架分别加载数据到特定div块中,每次交互有且只能操作被置于最上方的div块中的canvas画布(默认使用echarts图表框架)。

优点和缺点

优点:

1.需要关注的数据量总是较少的,无论图形需要什么交互,都能较为顺畅地进行。

2.在一定程度上能够像操作一个图表一样操纵两个重叠的图表(比如缩放,下载成图片等)

缺点:

1.很难对参考数据添加交互功能,参考数据的更多是一个背景的角色。

2.图表缩放时,仍会出现卡顿问题

3.仍然存在内存崩溃的风险

实践

1.准备数据

数据函数(用于生成数据使用,来自highChart图表框架):

function getData (n) {var arr = []var ivar avar bvar cvar spikefor (i = 0; i < n; i = i + 1) {if (i % 100 === 0) {a = 2 * Math.random()}if (i % 1000 === 0) {b = 2 * Math.random()}if (i % 10000 === 0) {c = 2 * Math.random()}if (i % 50000 === 0) {spike = 10} else {spike = 0}arr.push([i,2 * Math.sin(i / 100) + a + b + c + spike + Math.random()])}return arr}

为了方便,我们可以先用node.js来生成数据,代码如下–

const fs = require('fs')
function getData (n) {var arr = []var ivar avar bvar cvar spikefor (i = 0; i < n; i = i + 1) {if (i % 100 === 0) {a = 2 * Math.random()}if (i % 1000 === 0) {b = 2 * Math.random()}if (i % 10000 === 0) {c = 2 * Math.random()}if (i % 50000 === 0) {spike = 10} else {spike = 0}arr.push([i,2 * Math.sin(i / 100) + a + b + c + spike + Math.random()])}return arr
}
const allData = []
for (let i = 0; i < 200; i++) {allData.push(getData(1000))
}
// 为了使调整数据和参考数据有明显区别,在这一步对数据的值进行加法,最好同时在图表中把参考数据和调整数据的数据线颜色进行不同设置
const oneData = getData(1000).map(item => {item[1] = item[1] + 2return item
})fs.writeFile('oneData.json', JSON.stringify(oneData), (err) => {if (err) {throw err}console.log('Write Success')
})fs.writeFile('allData.json', JSON.stringify(allData), (err) => {if (err) {throw err}console.log('Write Success')
})

2.html代码

<!--
vue框架+echarts图表框架
-->
<template><div><div style="z-index:99;position:absolute;"><div id="chart1" style="width:1000px;height:750px;"></div></div><div style="z-index:100;position:absolute;"><div id="chart2" style="width:1000px;height:750px;"></div></div></div>
</template>

3.js代码

<script>
import echarts from 'echarts'
import allData from '../../static/allData.json' // 使用getData()函数生成的参考数据
import oneData from '../../static/oneData.json' // 使用getData()函数生成的调整数据
export default {data () {return {mychart1: null,mychart2: null,selectData: [], // myChart2图表记录的需要进行拖拽的数据点selsectIndex: null, // myChart2图表用户点击位置最靠近的左侧的点imgUrl: '' // 用于图表下载功能的路径}},mounted () {this.drawLineOS()this.limitOS()},methods: {async limitOS () {const fun = async function onPointDragging (dataIndex, dx, dy) {console.log(dataIndex, 'dataIndex')const arr = self.myChart2.convertFromPixel('grid', this.position)Data[Number(dataIndex)][1] = arr[1] // 将坐标值(x, y)还原为数组的项[a,b]// 更新图表await self.myChart2.setOption({series: [{id: 'a',data: Data}]})self.myChart2.setOption({// 声明一个 graphic component,里面有若干个 type 为 'circle' 的 graphic elements。// 这里使用了 echarts.util.map 这个帮助方法,其行为和 Array.prototype.map 一样,但是兼容 es5 以下的环境。// 用 map 方法遍历 data 的每项,为每项生成一个圆点。// graphic: echarts.util.map(this.selectData, (dataItem, dataIndex) => {graphic: echarts.util.map(oneData, (dataItem, dataIndex) => {return {// 'circle' 表示这个 graphic element 的类型是圆点。type: 'circle',$action: 'merge',shape: {// 圆点的半径。r: 5},style: {fill: '#FFFFFF',stroke: '#000000'},// 用 transform 的方式对圆点进行定位。position: [x, y] 表示将圆点平移到 [x, y] 位置。// 这里使用了 convertToPixel 这个 API 来得到每个圆点的位置,下面介绍。position: self.myChart2.convertToPixel('grid', dataItem),// 这个属性让圆点不可见(但是不影响他响应鼠标事件)。// invisible: true,invisible: true,// 这个属性让圆点可以被拖拽。draggable: true,// 把 z 值设得比较大,表示这个圆点在最上方,能覆盖住已有的折线图的圆点。z: 201,// 此圆点的拖拽的响应事件,在拖拽过程中会不断被触发。下面介绍详情。// 这里使用了 echarts.util.curry 这个帮助方法,意思是生成一个与 onPointDragging// 功能一样的新的函数,只不过第一个参数永远为此时传入的 dataIndex 的值。ondrag: echarts.util.curry(onPointDragging, dataIndex)}})})const maxAndMin = self.myChart2.getModel().getComponent('yAxis').axis.scale._extentif (self.max !== null || self.min !== null) {if (self.max !== maxAndMin[1] || self.min !== maxAndMin[0]) {self.myChart1.setOption({yAxis: {name: `yAxis`,type: 'value',axisLine: {onZero: false},max: maxAndMin[1],min: maxAndMin[0]}})}self.max = maxAndMin[1]self.min = maxAndMin[0]} else {self.max = maxAndMin[1]self.min = maxAndMin[0]}}// 基于准备好的dom,初始化echarts实例this.myChart2 = this.$echarts.init(document.getElementById('chart2'))// 数据联动--用于将两个图表的数据在一张图上显示(下载功能)this.myChart1.group = 'group1'this.myChart2.group = 'group1'echarts.connect('group1')const arrdata = []let Data = oneDataarrdata.push({id: 'a',type: 'line', // 数据类型,画一条折线animation: false, // 是否开启动画showSymbol: true, // 是否画点symbolSize: 6, // 数据点大小// 降采样策略,详情可查echarts文档。主要用于在数据量过大,甚至屏幕上一个像素点存在// 多个数据点时能够过滤数据使一个像素点只画一个点。sampling: 'lttb',color: 'blue', // 数据线颜色data: Data // 要渲染的数据})// 设置图表配置项const option = {// 图表位置调整grid: {left: '5%', // 距离div左边的距离right: '20%', // 距离div右边的距离bottom: '10%', // 距离div下边的距离top: '10%' // 距离div上边的距离},// y轴yAxis: {name: `yAxis`,type: 'value'},// x轴xAxis: {name: `xAxis`,type: 'value',scale: true // 是否是脱离 0 值比例。设置成 true 后坐标刻度不会强制包含零刻度},// 工具栏toolbox: {show: true, // 是否显示工具栏feature: {// 自定义下载图片工具myDownloadImage: {show: true,title: '下载图片',iconStyle: {color: '#7d7d7d',borderColor: '#7d7d7d'},// 显示的图标icon: 'path://M884.736 897.024H139.264c-18.432 0-32.768 14.336-32.768 32.768 0 18.432 14.336 32.768 32.768 32.768h745.472c18.432 0 32.768-14.336 32.768-32.768 0-18.432-14.336-32.768-32.768-32.768z m-397.312-69.632c12.288 12.288 32.768 12.288 47.104 0l313.344-313.344c6.144-6.144 10.24-14.336 10.24-24.576 0-18.432-14.336-32.768-32.768-32.768-8.192 0-16.384 4.096-22.528 10.24L544.768 722.944V94.208c0-16.384-14.336-32.768-32.768-32.768-18.432 0-32.768 14.336-32.768 32.768v628.736L221.184 466.944c-6.144-6.144-14.336-10.24-22.528-10.24-18.432 0-34.816 14.336-34.816 32.768 0 8.192 4.096 18.432 10.24 24.576l313.344 313.344z',// 点击的回调函数onclick: function () {// 获取两个重叠图表的图片数据并调用下载函数const imgUrl = self.myChart2.getConnectedDataURL({type: 'png',excludeComponents: ['yAxis', 'xAxis']})self.download(imgUrl)}},// 缩放功能dataZoom: {show: true,xAxisIndex: 0,yAxisIndex: 0}}},series: arrdata}// 绘制图表// 等待调整数据的图表画完,将参考数据图表的x、y轴最大值和最小值跟// 调整数据的图表的x、y轴最大值和最小值保持一致await this.myChart2.setOption(option)this.myChart1.setOption({yAxis: {show: false,name: `yAxis`,type: 'value',axisLine: {onZero: false},max: this.myChart2.getModel().getComponent('yAxis').axis.scale._extent[1],min: this.myChart2.getModel().getComponent('yAxis').axis.scale._extent[0]},xAxis: {show: false,name: `xAxis`,type: 'value',scale: true,max: this.myChart2.getModel().getComponent('xAxis').axis.scale._extent[1],min: this.myChart2.getModel().getComponent('xAxis').axis.scale._extent[0]}})const self = thisthis.myChart2.setOption({// 声明一个 graphic component,里面有若干个 type 为 'circle' 的 graphic elements。// 这里使用了 echarts.util.map 这个帮助方法,其行为和 Array.prototype.map 一样,但是兼容 es5 以下的环境。// 用 map 方法遍历 data 的每项,为每项生成一个圆点。graphic: echarts.util.map(oneData, (dataItem, dataIndex) => {console.log(dataIndex)return {// 'circle' 表示这个 graphic element 的类型是圆点。type: 'circle',shape: {// 圆点的半径。r: 5},style: {fill: '#FFFFFF',stroke: '#000000'},// 用 transform 的方式对圆点进行定位。position: [x, y] 表示将圆点平移到 [x, y] 位置。// 这里使用了 convertToPixel 这个 API 来得到每个圆点的位置,下面介绍。position: self.myChart2.convertToPixel('grid', dataItem),// 这个属性让圆点不可见(但是不影响他响应鼠标事件)。invisible: true,// 这个属性让圆点可以被拖拽。draggable: true,// 把 z 值设得比较大,表示这个圆点在最上方,能覆盖住已有的折线图的圆点。z: 201,// 此圆点的拖拽的响应事件,在拖拽过程中会不断被触发。下面介绍详情。// 这里使用了 echarts.util.curry 这个帮助方法,意思是生成一个与 onPointDragging// 功能一样的新的函数,只不过第一个参数永远为此时传入的 dataIndex 的值。ondrag: echarts.util.curry(fun, dataIndex)}})})// 监听图表缩放事件,每一次缩放都需要对调整数据图表拖拽功能进行更新,同时对参考数据图表的坐标轴范围进行更新this.myChart2.on('dataZoom', (params) => {self.myChart2.setOption({// 声明一个 graphic component,里面有若干个 type 为 'circle' 的 graphic elements。// 这里使用了 echarts.util.map 这个帮助方法,其行为和 Array.prototype.map 一样,但是兼容 es5 以下的环境。// 用 map 方法遍历 data 的每项,为每项生成一个圆点。// graphic: echarts.util.map(this.selectData, (dataItem, dataIndex) => {graphic: echarts.util.map(oneData, (dataItem, dataIndex) => {return {position: self.myChart2.convertToPixel('grid', dataItem)}})})this.myChart1.setOption({yAxis: {show: false,name: `yAxis`,type: 'value',axisLine: {onZero: false},max: this.myChart2.getModel().getComponent('yAxis').axis.scale._extent[1],min: this.myChart2.getModel().getComponent('yAxis').axis.scale._extent[0]},xAxis: {show: false,name: `xAxis`,type: 'value',scale: true,max: this.myChart2.getModel().getComponent('xAxis').axis.scale._extent[1],min: this.myChart2.getModel().getComponent('xAxis').axis.scale._extent[0]}})})},drawLineOS () {// 基于准备好的dom,初始化echarts实例this.myChart1 = this.$echarts.init(document.getElementById('chart1'))const arrdata = []let obj = allDataobj.map((item, index) => {let data = []item.map((e, index) => {data.push(e)})arrdata.push({silent: false,clip: true,type: 'line',name: `${item.name}`,animation: false,showSymbol: false,sampling: 'lttb',itemStyle: {normal: {symbol: 'none',lineStyle: {width: 2 // 设置线条粗细}}},data: data})})const option = {grid: {left: '5%', // 距离div左边的距离right: '20%', // 距离div右边的距离bottom: '10%', // 距离下面top: '10%'},yAxis: {show: false,name: `yAxis`,type: 'value'},xAxis: {show: false,name: `xAxis`,type: 'value',scale: true},series: arrdata}// 绘制图表this.myChart1.setOption(option)},// 无闪现下载图片download (srcData) {const aElement = document.createElement('a')aElement.style.display = 'none'aElement.download = 'echarts.png'aElement.href = srcDataaElement.click()aElement.remove()}}
}
</script>

以上示例的数据量为20万1千。这个数据量不算大,后续有兴趣的可以自己调整下数据量,我这个方案是为了实现在200万这个数据量下进行能够实现需求所要的交互效果走的偏路。可以参考,但是不能一条路走到底,毕竟使用场景真的少,而且问题确实不少。

比如,

1.对图表进行缩放后再进行数据点的拖拽。如果放大图表时选中的范围高度较小,当拖拽点超出这个高度时,会发现这个点在图表背消失了,同时之前与这个点相连的左右两个点,会直接相连。但是一旦把图表进行缩放还原,你会发现那个之前消失的点出现了,确确实实那个点的y值是你之前拖拽的到的位置的值。

2.如果单条数据量较大,比如一条1万个点的数据,那么进行拖拽交互时,会出现明显的卡顿。同时进行图表缩放也会有一定程度的卡顿,但是这个是可以接受的,不是特别明显。

3.当数据量较大时会内存崩溃。在我自己的电脑上,差不多300万到500万的数据进行图表渲染时,浏览器有可能会出现内存崩溃的提示。

4.如果需要对参考数据进行一些交互,那么这个是难以实现的。

5.虽然下载图片时能够把两个图表数据重叠输出为图片。但是必须数据输出时必须限制图片是没有背景的,不能设置任何背景颜色。有时无法满足客户下载图片用来做报告用的实际需求。需要通过PS等工具或其它手段实现背景设置。

6.因为图表的缩放完全按照调整数据的图表来,导致参考数据的数据往往不能按照用户想要的显示。比如用户想看参考数据的一些细节,专门放大了这部分,但是有部分数据是看不到的。例子:

按照用户的想法,应该是要显示所有的参考数据的,但是实际上有部分参考数据被遮住了。

题外话

图表组件切换会卡顿

在vue+echart中,切换渲染不同的图表组件,比如line.vue和bar.vue两个图表组件(每个组件都有单独的echart实例),在切换组件时,开发者很容易忘掉,需要把组件内的echart实例销毁的事情。如果在切换组件(销毁组件)时,不进行echart实例的删除,可能导致内存中echart的实例一直存在,CPU占比会比较高。这种情况,可以在vue的生命周期函数beforeDestroy中使用this.chart.clear()或者this.chart.dispose()进行内存的释放。

echarts框架下大数据量展示的解决方案相关推荐

  1. 使用ECharts加载大数据量数据

    使用ECharts加载大数据量数据 言归正传,本次研究目的是通过echarts加载大数据量数据,测试数据点为24w左右,最终调试结果,加载一条曲线在2.5s左右,同时加载两条曲线为5s以为,8条曲线为 ...

  2. MySql下大数据量级别(1000万+)优化查询和操作方法

    MySql下大数据量级别(1000万+)优化查询和操作方法 一.[原则一]: insert into tb (...) values(...),(...)...; 要比 insert into tb ...

  3. 大数据导出excel大小限制_EXCEL大数据量导出的解决方案

    将web页面上显示的报表导出到excel文件里是一种很常见的需求.润乾报表的类excel模型,支持excel文件数据无失真的导入导出,使用起来非常的方便.然而,当数据量较大的情况下,excel本身的支 ...

  4. 大数据量分页查询方法(转)

    本文旨在介绍一种对数据库中的大数据量表格进行分页查询的实现方法,该方法对应用服务器.数据库服务器.查询客户端的cpu和内存占用都较低,查询速度较快,是一个较为理想的分页查询实现方案. 1.问题的提出  ...

  5. AJAX 大数据量处理

    看来一篇淘宝网关于大数据量处理的解决方案的文章,感觉思路不错,确实值得学习,下面总结成自己的经验.主要是思想. 思路如下: 将大数据量数据进行分割返回,例如需要返回100条数据,我们可以通过10次请求 ...

  6. 数据可视化大屏电商数据展示平台开发实录(Echarts柱图曲线图、mysql筛选统计语句、时间计算、大数据量统计)

    数据可视化大屏电商数据展示平台 一.前言 二.项目介绍 三.项目展示 四.项目经验分享 4.1 翻牌器 4.1.1 翻牌器-今日实时交易 4.1.2.翻牌器后端统计SUM函数的使用 4.2 不同时间指 ...

  7. 数据库查询经常卡死?面对亿级大数据量,我是这么展示分析的

    建议你们看到文末,不会亏待你们 日常一提数据分析和可视化,就想到这个工具操作要多简单易用,图表要多美多炫,然而总是忽略背后的数据支撑. excel 几十万行数据就卡死崩,谈何数据透视表.可视化? 近千 ...

  8. web 折线图大数据量拉取展示方案_【第2010期】QQ音乐Android客户端Web页面通用性能优化实践...

    前言 今日早读文章由QQ音乐客户端开发工程师@关岳分享,公号:云加社区(ID:QcloudCommunity,腾讯云官方开发者社区)授权分享. 正文从这开始~~ QQ音乐 Android 客户端的 W ...

  9. 大数据量树形数据表格展示, 虚拟表格,el-table, umy-ui, 表格懒加载

    1. 出现的问题 要展示树形数据表格,根据当前点击的表格行去请求新的数据并展示, 基于这种情况遇到以下问题 1). 当树形表格数据层级大于五级且数据量较多时, 浏览器崩溃 2). 当数据条数展示超出5 ...

最新文章

  1. 从windows上传文件到linux,中文名乱码解决方法
  2. Spark 1.4连接mysql诡异的问题及解决
  3. 【译】Build Knowledge Graph from unstructured corpus using Machine Learning
  4. builtins.ModuleNotFoundError: No module named ‘’scrapy.contrib‘’
  5. cocos2d 屏幕適配_Cocos2d-x 3.1 一步步做屏幕适配
  6. 吃鸡服务器不接受响应,绝地求生:蓝洞优化服务器性能,从此告别掉帧延迟!...
  7. java多线程调用nsq消费_spark-streaming连接消费nsq
  8. List集合多线程并发条件下不安全,如何解决?
  9. python 中locals() 和 globals()
  10. C运行时库(CRT)
  11. 微信小程序开发:学习笔记[5]——JavaScript脚本
  12. Docker contanier comunication with route
  13. Pycharm主题,彩虹猫进度条,翻译插件
  14. Python数据结构与算法(3.3)——队列
  15. turtle库进阶练习
  16. mysql删除日志文件_mysql删除日志文件,定时清理日志
  17. 项目实战之物联网智能鱼缸
  18. 模拟线上应用cpu100%解决方法
  19. git中patch的用法
  20. Java中四个访问修饰符public private protected 和默认(package-private)的用法详解

热门文章

  1. openwrt多wan限上下行速脚本,基于qosv4,imq模块替换成ifb模块
  2. SQL语句中not in 和not exist的区别
  3. pyspark steaming 连接kafka数据实时处理(也可以对接flume+kafka+spark)
  4. [网络工程师]-路由协议-IGRP协议
  5. 端游服务器文件转成手游,苦等三年,当年画面最好的端游终于要做成手游了!...
  6. 手机配件市场上的“隐形巨头”:80后长沙夫妻创办,IPO首日市值逼近600亿
  7. hibernate报错could not insert
  8. python读取lst文件
  9. 计算机视觉会议(CVPR,ECCV,ICCV,NIPS,AAAI,ICLR等)
  10. 2022/5/1 Mybatis框架动态SQL