功能需求背景:
分布式光纤振动系统DVS,需要实现实时频谱曲线图跟瀑布图;DVS系统能够感知光纤上的振动,DVS底层系统把一根光缆分成N个点(可看成N个传感器),假设一根40KM的光缆,我们按5m一个点位,那么就可以采集到8000个点的数据信息。在一个周期内把这些点位的振动数值进行采集,就可以绘制出一个周期内的频谱图,频谱曲线图效果如下:
当我们定期把这8000个点位的数据值根据其振动数值的大小绘制成不同的颜色(值大就颜色深,值小就颜色浅)并显示出来,就形一副瀑布图。效果如下:
功能实现:
1、曲线图的实现. 曲线图,我们直接采用echarts进行绘制,option配置如下:
refreshData(data){
var option = {
animation: false,
title: {
text: '',
left: 10
},
toolbox: {
show: false,
feature: {
dataZoom: {
yAxisIndex: false
},
saveAsImage: {
pixelRatio: 2
}
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
backgroundColor:'#ccc',
show: true,
top: 30,
left: 40,
right: 10,
bottom: 60
},
dataZoom: [{
type: 'inside'
}, {
type: 'slider',
xAxisIndex: [0, 1]
}],
xAxis: [
{
data: data.categoryData,
splitLine: {
show: false
},
splitArea: {
show: false
}
}
],
yAxis: {
show: true,
max: 5000,
min: 10,
silent: false,
splitArea: {
show: false
}
},
series: [{
type: 'bar',
data: data.valueData,
large: true
}]
};
...
}
绘制出来的效果如图:
2、瀑布图效果的实现
瀑布图可以考虑直接用echarts的热力图来实现,不过经过测试,热力图在快速刷新下效果不是很好,非常卡。所以只能采用canvas进行自己绘制了。canvas是一个画布,绘制算法是什么样的呢?
我们定期把这8000个点位的数据值根据其大小绘制成不同的颜色(值大就颜色深,值小就颜色浅)并显示出来。就形一副瀑布图。
怎么根据值(整形)获取不同样的颜色?我们这里用colormap这个现成的npm模块。 生成72个颜色。然后通过下标0,1,2...71我们就可以取到对应的颜色。每一个颜色是一个数组:[255,255,255,1] 代表红,绿,蓝的颜色值跟alpha值。
获取颜色值代码:
this.colormap = colormap({
colormap: 'jet',
nshades: 72,
format: 'rba',
alpha: 1
})
映射振动值到具体颜色的下标。
squeeze (data, outMin, outMax) {
if (!data) {
return outMin
}
if (data <= this.minDb) {
return outMin
} else if (data >= this.maxDb) {
return outMax
} else {
return Math.round((data - this.minDb) / (this.maxDb - this.minDb) * outMax)
}
}
data为我们的振动值,outMin为colormap颜色其始下标,outMax为colormap颜色值的终止下标。minDb为data的范围,最小值,maxDb为data的最大值(可以设置比最大值大一些)。 好,下面我们为8000个点依次着色,并绘制到canvas画布上。代码如下。
var n = 8000;
const imgData = _this.waterFallCtx.createImageData(n, 1);//创建一个宽8000,高1像素的图。
for (let i = 0; i < imgData.data.length; i += 4) {
const cindex = _this.squeeze(data['valueData'][i / 4], 0, 71)//获取颜色下标
const color = _this.colormap[cindex]//获取颜色
imgData.data[i + 0] = color[0]
imgData.data[i + 1] = color[1]
imgData.data[i + 2] = color[2]
imgData.data[i + 3] = 255
}
_this.waterFallCtx.drawImage(_this.waterFallCtx.canvas, 0, 0, n, 199, 0, 1, n, 199)//把画布往下移一行,留出顶部空白。
_this.waterFallCtx.putImageData(imgData, 0, 0)//把顶部留出的一行画上刚刚生成的图片
_this.ctx.drawImage(_this.waterFallCtx.canvas, 0, 0, n, 200);//显示到界面上。
通过websocket实时接收数据,并设置到曲线图跟 瀑布图上。
var _this = this
var ws = new WebSocket('ws://xxxx:8082/dvs/xxx/ui');
// Web Socket 已连接上,使用 send() 方法发送数据
// 这里接受服务器端发过来的消息
ws.onmessage = function(e) {
_this.parseBlob(e.data, function (e) {
_this.$refs.waterFall.addData(e);
})
}
这里的数据我们采用二进制输出,所以获取到的数据是二进制,需要解析一下才能拿到8000个点的数据。
parseBlob(blob, callback) {
var _this = this
var reader = {
readAs: function(type,blob,cb){
var r = new FileReader();
r.onloadend = function(){
if(typeof(cb) === 'function') {
cb.call(r,r.result);
}
}
try{
r['readAs'+type](blob);
}catch(e){}
}
}
var shortVar, intVar, stringVar;
reader.readAs('ArrayBuffer',blob.slice(6, blob.size-2),function(result){
shortVar = new Int16Array(result);
var list = [], val = [];
for(var i=0; i<shortVar.length; i ++) {
list.push(i)
val.push(shortVar[i])
}
var obj = {
'categoryData': list,
'valueData': val
}
_this.refreshData(obj)//绘制曲线图
callback(obj)
});
},
最终效果: