Heatmap.js
是基于canvas
開源的熱力圖框架携取,使用該框架可以方便的實(shí)現(xiàn)熱力圖,其效果圖如下所示:
最近正好在學(xué)習(xí)
canvas
,順便研究了一下Heatmap
的源碼诲宇,Heatmap
的代碼還是比較好理解的取逾,代碼結(jié)構(gòu)清晰逆趣,有很多值得學(xué)習(xí)的荆几,比如代碼的架構(gòu)吓妆,熱力圖渲染原理、著色等吨铸。下面來說下具體的實(shí)現(xiàn)過程行拢。講的不是很清楚,可以看下精簡版的代碼實(shí)現(xiàn)诞吱。
架構(gòu)
Heatmap
其架構(gòu)主要由兩部分組成舟奠,分別是Renderer
和Store
竭缝。
Renderer
:渲染器,主要用于熱力圖畫布的創(chuàng)建沼瘫,繪制抬纸、著色等。
Store
:數(shù)據(jù)管理器晕鹊,主要用于管理熱力圖的數(shù)據(jù)松却,包括添加數(shù)據(jù)暴浦、更新數(shù)據(jù)溅话、刪除數(shù)據(jù)、組裝渲染器需要的數(shù)據(jù)格式等歌焦。
Heatmap對象
Heatmap
對象主要用于構(gòu)造一個外部調(diào)用的對象飞几,將公共方法寫在原型對象中,在構(gòu)造函數(shù)中独撇,創(chuàng)建對象的時候屑墨,初始化了一個_renderer
對象和_store
對象,分別用于渲染熱力和管理數(shù)據(jù)纷铣。
// 核心代碼卵史,創(chuàng)建heatmap對象
var heatmapFactory = {
create: function(config) {
return new Heatmap(config);
},
register: function(pluginKey, plugin) {
HeatmapConfig.plugins[pluginKey] = plugin;
}
};
return heatmapFactory;
//默認(rèn)屬性
var HeatmapConfig = {
defaultRadius: 40,//半徑
defaultRenderer: 'canvas2d',//默認(rèn)為2D畫布
defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"}, //熱度默認(rèn)顏色值,從外到里的顏色
defaultMaxOpacity: 1,//最大透明度
defaultMinOpacity: 0,//最小透明度
defaultBlur: .85,//模糊度
defaultXField: 'x',//x坐標(biāo)字段名
defaultYField: 'y',//主坐標(biāo)字段名
defaultValueField: 'value', //熱度值字段名
plugins: {}//插件
};
數(shù)據(jù)管理器
Store
是一個數(shù)據(jù)管理器搜立,用于管理熱力圖所需要的數(shù)據(jù)以躯。主要方法如下所示:
_organiseData
用于構(gòu)造渲染器所需要的數(shù)據(jù)格式,主要用于查找出熱力值的最大值和最小值啄踊,通過這兩個值來決定各熱力圖的顏色渲染比例忧设。其構(gòu)造的每個對象如下所示:
{
x: x,
y: y,
value: value,
radius: radius,
min: min,
max: max
}
_unOrganizeData
:用于還原_organiseData
組裝的數(shù)據(jù),方便外部獲取颠通。其對象格式如下所示:
{
x: x,
y: y,
radius: radi[x][y],
value: data[x][y]
}
_onExtremaChange
:min
或者max
有變化時通知_coordinator
執(zhí)行extremachange
方法進(jìn)行重新渲染址晕。
addData
:動態(tài)添加數(shù)據(jù)進(jìn)行渲染。
setDataMax
:設(shè)置最大值顿锰。
setDataMin
:設(shè)置最小值谨垃。
getData
:獲取原始數(shù)據(jù)。
渲染器
Renderer
是整個框架中的核心代碼硼控,通過該渲染器可以完美的創(chuàng)建出熱力圖乘客。通過Canvas2dRenderer
來創(chuàng)建畫布和創(chuàng)建調(diào)色板等。
調(diào)色板
調(diào)色板是通過使用defaultGradient
設(shè)置的顏色來創(chuàng)建一個256*1的畫布淀歇,使用createLinearGradient
創(chuàng)建漸變的圖像易核,最后將返回一個Uint8ClampedArray類型的圖片數(shù)據(jù),該是一個包含RGBA像素信息的Uint8ClampedArray浪默,數(shù)組中所有的值都是整數(shù)牡直,范圍是0~255缀匕。
//獲取顏色調(diào)色板
var _getColorPalette = function(config) {
var gradientConfig = config.gradient || config.defaultGradient;
var paletteCanvas = document.createElement('canvas');//創(chuàng)建畫布
var paletteCtx = paletteCanvas.getContext('2d');//獲取畫布的上下文
paletteCanvas.width = 256;
paletteCanvas.height = 1;
//線性漸變對象
var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
for (var key in gradientConfig) {
//插入斷點(diǎn)使?jié)u變變成一段一段的色塊
gradient.addColorStop(key, gradientConfig[key]);//添加漸變點(diǎn)
}
paletteCtx.fillStyle = gradient;//上下文的填充樣式
paletteCtx.fillRect(0, 0, 256, 1);//繪制一個寬為256,高為1的矩形
//返回一個Uint8ClampedArray類型的圖片數(shù)據(jù)
return paletteCtx.getImageData(0, 0, 256, 1).data;
};
繪制熱力圖
熱力圖的繪制是使用_drawAlpha
方法來完成的碰逸,通過獲取到數(shù)據(jù)的x坐標(biāo)和y坐標(biāo)乡小、半徑等值來生成一個canvas,然后再更新渲染邊界饵史。
//繪制熱點(diǎn)圖
_drawAlpha: function(data) {
var min = this._min = data.min;
var max = this._max = data.max;
var data = data.data || [];
var dataLen = data.length;
// on a point basis?
var blur = 1 - this._blur;
while(dataLen--) {
var point = data[dataLen];
var x = point.x;
var y = point.y;
var radius = point.radius;
// 如果point的值大于最大值满钟,選用max作為繪制的值
var value = Math.min(point.value, max);
var rectX = x - radius;
var rectY = y - radius;
var shadowCtx = this.shadowCtx;
var tpl;//獲取圖片對象
if (!this._templates[radius]) {//半徑相同時,不需要重新生成圖片
this._templates[radius] = tpl = _getPointTemplate(radius, blur);
} else {
tpl = this._templates[radius];
}
// 設(shè)置透明度
var templateAlpha = (value-min)/(max-min);
// 小于0.01的圖片將無法顯示
shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha;
//繪制圖片
shadowCtx.drawImage(tpl, rectX, rectY);
// 更新渲染邊界
if (rectX < this._renderBoundaries[0]) {
this._renderBoundaries[0] = rectX;
}
if (rectY < this._renderBoundaries[1]) {
this._renderBoundaries[1] = rectY;
}
if (rectX + 2*radius > this._renderBoundaries[2]) {
this._renderBoundaries[2] = rectX + 2*radius;
}
if (rectY + 2*radius > this._renderBoundaries[3]) {
this._renderBoundaries[3] = rectY + 2*radius;
}
}
},
著色器
著色器算是渲染器中最核心的代碼胳喷,熱力圖顯示不同的顏色都是通過_colorize
來完成后湃番,其核心原理是獲取畫布的RGBA像素信息,再使用調(diào)色板的顏色對畫布上的RGBA像素信息進(jìn)行修改吭露,就可以將熱力圖渲染出不同的顏色了吠撮。
_colorize: function() {
var x = this._renderBoundaries[0];
var y = this._renderBoundaries[1];
var width = this._renderBoundaries[2] - x;
var height = this._renderBoundaries[3] - y;
var maxWidth = this._width;
var maxHeight = this._height;
var opacity = this._opacity;
var maxOpacity = this._maxOpacity;
var minOpacity = this._minOpacity;
var useGradientOpacity = this._useGradientOpacity;
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (x + width > maxWidth) {
width = maxWidth - x;
}
if (y + height > maxHeight) {
height = maxHeight - y;
}
var img = this.shadowCtx.getImageData(x, y, width, height);
var imgData = img.data;
var len = imgData.length;
var palette = this._palette;
for (var i = 3; i < len; i+= 4) {
var alpha = imgData[i];
var offset = alpha * 4;
if (!offset) {
continue;
}
debugger
var finalAlpha;
if (opacity > 0) {
finalAlpha = opacity;
} else {
if (alpha < maxOpacity) {
if (alpha < minOpacity) {
finalAlpha = minOpacity;
} else {
finalAlpha = alpha;
}
} else {
finalAlpha = maxOpacity;
}
}
imgData[i-3] = palette[offset];
imgData[i-2] = palette[offset + 1];
imgData[i-1] = palette[offset + 2];
imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
}
img.data = imgData;
this.ctx.putImageData(img, x, y);
this._renderBoundaries = [1000, 1000, 0, 0];
},
以上只是對代碼的初步了解,要想完全了解期思想和原理讲竿,還需要花更多的時間來進(jìn)行研究泥兰,至少要自己能快速的模仿一個出來才算真正的吃透了。
個人博客