作者:codexu_废话不多说,先上传成品图:再放个小动画:可能很多同学不知道频谱图和瀑布图,在其实我也不懂。。。但是我们前端负责把数据按照规则展示出来(上面的折线图是频谱图,下面的是瀑布图)。技术选型框架:Vue(这个不重要,反正不说)数据传输:WebSocket谱图:HighCharts瀑布图:Canvas为什么要用WebSocket?因为服务器需要实时传输数据,所以要求达到30帧,每帧动画由1024个点组成,绝对比Ajax轮询舒服,而且本项目对浏览器兼容性没有要求。为什么要用HighCharts画频谱图?我做了一个测试,HighCharts和ECharts,canvas的性能虽然比svg好,但是同时渲染感觉HighCharts更流畅(HighCharts需要付费)。为什么瀑布图使用Canvas?虽然使用数据可视化图表库很方便,但是考虑到本项目对性能要求苛刻,所以只使用瀑布图这样的大范围热图:使用热图,请放心,PPT不会卡,而且浏览器会准时5秒后直接Crash。组件功能拆分整个组件拆分为三部分:父组件:负责WebSocket与服务器的实时通信,处理二进制数据,控制渲染频率,控制启动暂停,刷新组件。子组件>频谱图(HighCharts):提供addData方法,获取渲染一帧的数据,提供触发缩放事件,发送给父组件。Subcomponent>WaterfallChart(Canvas):提供和频谱图一样的addData方法。缩放事件发生后,频谱图将根据其选定的位置进行缩放。父组件WebSocket链接服务器操作不多,直接使用native方法:this.socket=newWebSocket('ws://192.168.2.250:8100/socket')this.socket.onopen=()=>{...}this.socket.onclose=()=>{...}连接后端并发送指令,这里我们定义三个://开始获取数据this.socket.send('start')//暂停获取数据this.socket.send('pause')//恢复获取数据this.socket.send('resume')监听onmessage事件:this.socket.onmessage=(event)=>{constreader=newFileReader()reader.readAsArrayBuffer(event.data)reader.onload=e=>{if(e.target.readyState===FileReader.DONE){//处理二进制数据}}}处理二进制数据本来想用大篇幅来写,不过前几天看到《为什么视频网站的视频链接地址是blob?》,写的很好,惭愧,请转载看懂这篇,别忘了再来。控制渲染频率。服务器每秒发送大约400条数据。每秒400帧肯定不现实,直接导致丢帧。解决方法:创建一个数组保存数据,每渲染一帧就删除这条数据,不足100条时发送resume继续采集,超过400条时发送pause暂停采集。服务端发送频率方面,cpu使用率会超过100%,获取的时候会有一点卡顿,不过可以接受,毕竟一次可以渲染好几秒时间。this.renderInterval=setInterval(()=>{if(this.data.length<=100&&this.socketPause===true){this.socket.send('resume')this.socketPause=false}if(this.data.length>=400&&this.socketPause===false){this.socket.send('pause')this.socketPause=true}if(this.data.length<=0)returnconstresult=this.data[0]this.$refs.frequency.addData(result.data)this.$refs.waterFall.addData(result.data.map(item=>item[1]))this.data.shift()},this.refreshInterval)使用setInterval定时渲染的另一个好处是可以控制渲染频率。注意组件右上角的拖动条,在低端电脑上可以降低渲染频率。SpectrogramHighCharts和ECharts在配置项上有一些差异,但都是配置问题。查文档,很简单,记得关掉所有动画。addData()this.chart.series[0].setData(data,true,false)父组件可以通过$ref.addData()触发渲染一帧缩放在配置中chart.zoomType设置为'x',设置为X轴选择缩放。chart.events.selection配置选择事件:selection(event){constpointWidth=(this.xAxisMax-this.xAxisMin)/1024constponitStart=Math.floor((event.xAxis[0].min-this.xAxisMin)/pointWidth)constponitEnd=Math.floor((event.xAxis[0].max-this.xAxisMin)/pointWidth)this.$emit('frequencySelect',[ponitStart,ponitEnd])},将选中的发送给parent组件点,然后通过父组件传递给瀑布图组件。由于性能原因,此处的瀑布图与某些库分离。很多朋友在这里不知道怎么操作。这是本文的重点。先了解几个概念。很多人都接触过Canvas,但是这几个大概没怎么关注过它(像素操作):createImageData()putImageData()drawImage()这个应该知道先创建两个canvas,一个用来显示整个效果(this.canvas),另一个保存生成的图片(this.waterFallDom,不会插入到dom上)。this.canvas=document.createElement('canvas')this.ctx=this.canvas.getContext('2d')this.waterFallDom=document.createElement('canvas')this.waterFallCtx=this.waterFallDom.getContext('2d')createImageDatacreateImageData(width,height)方法创建一个新的空白ImageData对象,两个参数,设置图片的宽高,这里工程一共需要1024个点:constimageData=this.waterFallCtx.createImageData(data.length,1)这时候生成了一张1024*1的空白图片,我们继续给每个像素点上色:for(leti=0;i
