基於chart.js繪製熱力圖

alex2wong發表於2018-06-04

最近玩了玩圖表的熱力圖,因為公司有同事想做無線裝置的訊號強度視覺化,在不同頻段(x 軸: MHz)的訊號強度 ( y軸: dbm)本身就是一個兩維的資料,加上隨著時間的訊號強度變化,在二維空間中會累加出熱區效果,我們可以計算出熱力值作為第三維資料。

ps: 以下圖表顯示的都是隨機的模擬資料。

剛開始,訊號強度密集的區域不是很明顯

隨著時間推移,可以看出訊號強度集中在40-60dbm 的部分

計算思路

利用chart.js 我們可以完成基本的座標軸和訊號強度線條的繪製,但是熱力圖是chart.js 本身不支援的,所以需要二次開發。 熱力圖實質上可以認為是點密度圖,就是資料點在空間中的密集程度,越密集值越高。具體的演算法可以根據自己的需求來定,但是主流的做法還是點密度。這種演算法可以是截斷的,也就是搜尋半徑內有多少資料點,就作為熱力值。也可以是隨著距離衰減的,比如IDW。

距離越遠的點對於當前單元格的熱力值影響相對弱,這也是地理學第一定律的典型應用。

static computeDensity(heatSets: any[], lineSets: number[], maxValueY: number) {
    if (!heatSets) {
      mat = this.genMat(matY, matX, 0);  // 初始化 Y*X 的矩陣
    } else {
      mat = heatSets;  // 上一次累加後的熱力值矩陣
    }
    // 
    for (let x = 0; x < matX; x += 1) {
      try {
        // 把當前的訊號強度點直接累加到原有的熱力值矩陣上,如果想要把訊號的其他屬性作為權重,那麼就把1 替換成當前點的某屬性值
        mat[lineSets[x] - 1][x] += 1;
        this.addBuffer(mat, lineSets, x, radius);  // 搜尋半徑為 radius, 對於當前資料點,我們要把ta 累加到附近的熱力矩陣單元格內。
      }
    }
}

// 根據熱力矩陣的統計結果(最大最小值)來限定邊界顏色,從紅色漸變到背景色
static setColor(densityData: DensityData) { 
    // 使得每個熱力值都對應 不同的漸變色。
}
複製程式碼

效能

效能在實時性要求較高的熱力圖中很重要,包括 heatmap.js 這種著名的熱力相簿是具有很高效能的,因為ta 直接在canvas 的渲染函式裡面 putImageDate, 利用漸變函式直接上色,效能是非常高的, 毫秒級別。

而我最開始的熱力計算函式是很笨的,遍歷整個矩陣(假如n * n)去搜尋要計算熱力的資料點或者線的節點(m個點),複雜度很高,最多需要執行 n * n * m 次累加函式。 但是後來逆向思維了一下,直接遍歷資料點(m個),最多再加上遍歷周邊半徑(rad)內的單元格,至多執行 m * rad 次累加函式。 這個複雜度大大降低,耗時從1000ms左右降到10ms 以內。 原諒我搞不清楚大O 演算法複雜度。。哈哈

草圖

關於點密度的計算還是挺有趣的,後面整理後再把關鍵程式碼放到Github 上。對,就是那個已經被微軟收購的Github..

相關文章