【 視覺化】熱力圖繪製原理

linghuam發表於2018-11-14

網上那些炫酷的熱力圖是如何繪製的? 相信你也很好奇,本文將以 canvas 作為繪圖示例來講解熱力圖的原理。

透明度

我們經常遇到透明度的概念,如 CSS 中的 opacity 屬性、rgba 顏色中的 alpha 變數、canvas 中的 globalAlpha 屬性等。

它們的取值範圍一般是 0-1 之間,0 表示完全透明,1 表示不透明,值越小,越透明。

透明度疊加

思考一個問題: 透明度為 0.2 的矩形跟透明度為 0.6 的矩形疊加後的透明度為多少?

結果可以看以下示例,通過 canvas 的 getImageData 方法輸出了疊加後的透明度(值除以 255 即可)

See the Pen canvas-opacity by linghuam (@linghuam) on CodePen.

很多人的第一感覺也許是 0.8,其實這是一種想當然的理解。正確的思路如下:

假設把透明度理解成玻璃的透光性,這樣 alpha=0.2 表示一束光照射到玻璃上,有 20% 的光線被反射回來(這一部分光線會進入你的眼睛),80% 穿透過去,這時我們看到的東西就會很模糊。同理,alpha=0 表示光線全部穿透過去,所以我們什麼都看不見,alpha=1 表示光線全部被反射,所以我們能看見全部。

那麼 alpha=0.2 和 alpha=0.6 的疊加相當於兩塊玻璃疊加,第一塊玻璃有 80% 光線穿透, 第二塊在第一塊穿透過的光線中,有 40% 光線穿透,這樣穿過兩塊玻璃後被反射多少光線呢,計算方法如下:1*0.2 + (1-0.2)*0.6 = 0.68, 所以最後的透明度是 0.68 而不是 0.8。

下面一篇文章總結了其計算公式:兩個半透明顏色色的疊加計算方法

熱力圖原理

其實熱力圖就是根據透明度的大小和疊加來渲染的。

首先我們的資料集是一個物件陣列,每個元素包含了 { x, y, value } 屬性。我們首先從這一組值中找出 value 最大值 max,然後用 value/max 的值來表示透明度,這樣我們可以在畫布中繪製不同透明度的小圓圈,起初這些圓圈都是黑白的,所以下一步需要根據不同透明度來進行著色處理。

通過檢視 mapv 原始碼發現,它實現了一個 Intensity 類用來對不同透明度實現一個漸變色。

首先他建立了一個 256 * 1 的 canvas ,然後利用 canvas 的 createLinearGradient 來將漸變色填充進去,這樣一個透明度值就可以對應 canvas 上的一個顏色值,通過 getImageData 方法就可以根據透明度來取對應的顏色值。

擷取部分核心程式碼:

// 建立一個 256*1 的 canvas 並填充漸變色
Intensity.prototype.initPalette = function () {

    var gradient = this.gradient;

    var canvas = new Canvas(256, 1);

    var paletteCtx = this.paletteCtx = canvas.getContext('2d');

    var lineGradient = paletteCtx.createLinearGradient(0, 0, 256, 1);

    for (var key in gradient) {
        lineGradient.addColorStop(parseFloat(key), gradient[key]);
    }

    paletteCtx.fillStyle = lineGradient;
    paletteCtx.fillRect(0, 0, 256, 1);

}

// 通過透明度值取到對應的顏色
Intensity.prototype.getImageData = function (value) {

    var imageData = this.paletteCtx.getImageData(0, 0, 256, 1).data;

    if (value === undefined) {
        return imageData;
    }

    var max = this.max;
    var min = this.min;

    if (value > max) {
        value = max;
    }

    if (value < min) {
        value = min;
    }

    var index = Math.floor((value - min) / (max - min) * (256 - 1)) * 4;

    return [imageData[index], imageData[index + 1], imageData[index + 2], imageData[index + 3]];
}
複製程式碼

當然,為了熱力圖更好看,作者用了 canvas 的 shadowBlur 來實現一個邊緣模糊效果。

至此,熱力圖的實現原理就介紹完了,下面是我根據這個原理做的一個小 Demo:

See the Pen canvas-heatmap by linghuam (@linghuam) on CodePen.

參考資料

相關文章