功能需求背景:
分布式光纖振動(dòng)系統(tǒng)DVS,需要實(shí)現(xiàn)實(shí)時(shí)頻譜曲線圖跟瀑布圖;DVS系統(tǒng)能夠感知光纖上的振動(dòng),DVS底層系統(tǒng)把一根光纜分成N個(gè)點(diǎn)(可看成N個(gè)傳感器),假設(shè)一根40KM的光纜,我們按5m一個(gè)點(diǎn)位,那么就可以采集到8000個(gè)點(diǎn)的數(shù)據(jù)信息。在一個(gè)周期內(nèi)把這些點(diǎn)位的振動(dòng)數(shù)值進(jìn)行采集,就可以繪制出一個(gè)周期內(nèi)的頻譜圖,頻譜曲線圖效果如下:
當(dāng)我們定期把這8000個(gè)點(diǎn)位的數(shù)據(jù)值根據(jù)其振動(dòng)數(shù)值的大小繪制成不同的顏色(值大就顏色深,值小就顏色淺)并顯示出來,就形一副瀑布圖。效果如下:
功能實(shí)現(xiàn):
1、曲線圖的實(shí)現(xiàn). 曲線圖,我們直接采用echarts進(jìn)行繪制,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、瀑布圖效果的實(shí)現(xiàn)
瀑布圖可以考慮直接用echarts的熱力圖來實(shí)現(xiàn),不過經(jīng)過測試,熱力圖在快速刷新下效果不是很好,非???。所以只能采用canvas進(jìn)行自己繪制了。canvas是一個(gè)畫布,繪制算法是什么樣的呢?
我們定期把這8000個(gè)點(diǎn)位的數(shù)據(jù)值根據(jù)其大小繪制成不同的顏色(值大就顏色深,值小就顏色淺)并顯示出來。就形一副瀑布圖。
怎么根據(jù)值(整形)獲取不同樣的顏色?我們這里用colormap這個(gè)現(xiàn)成的npm模塊。 生成72個(gè)顏色。然后通過下標(biāo)0,1,2...71我們就可以取到對(duì)應(yīng)的顏色。每一個(gè)顏色是一個(gè)數(shù)組:[255,255,255,1] 代表紅,綠,藍(lán)的顏色值跟alpha值。
獲取顏色值代碼:
this.colormap = colormap({
colormap: 'jet',
nshades: 72,
format: 'rba',
alpha: 1
})
映射振動(dòng)值到具體顏色的下標(biāo)。
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為我們的振動(dòng)值,outMin為colormap顏色其始下標(biāo),outMax為colormap顏色值的終止下標(biāo)。minDb為data的范圍,最小值,maxDb為data的最大值(可以設(shè)置比最大值大一些)。 好,下面我們?yōu)?000個(gè)點(diǎn)依次著色,并繪制到canvas畫布上。代碼如下。
var n = 8000;
const imgData = _this.waterFallCtx.createImageData(n, 1);//創(chuàng)建一個(gè)寬8000,高1像素的圖。
for (let i = 0; i < imgData.data.length; i += 4) {
const cindex = _this.squeeze(data['valueData'][i / 4], 0, 71)//獲取顏色下標(biāo)
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實(shí)時(shí)接收數(shù)據(jù),并設(shè)置到曲線圖跟 瀑布圖上。
var _this = this
var ws = new WebSocket('ws://xxxx:8082/dvs/xxx/ui');
// Web Socket 已連接上,使用 send() 方法發(fā)送數(shù)據(jù)
// 這里接受服務(wù)器端發(fā)過來的消息
ws.onmessage = function(e) {
_this.parseBlob(e.data, function (e) {
_this.$refs.waterFall.addData(e);
})
}
這里的數(shù)據(jù)我們采用二進(jìn)制輸出,所以獲取到的數(shù)據(jù)是二進(jìn)制,需要解析一下才能拿到8000個(gè)點(diǎn)的數(shù)據(jù)。
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)
});
},
最終效果: