Heatmap.js熱力圖實(shí)現(xiàn)原理

Heatmap.js是基于canvas開源的熱力圖框架携取,使用該框架可以方便的實(shí)現(xiàn)熱力圖,其效果圖如下所示:

熱力圖.png

最近正好在學(xué)習(xí)canvas,順便研究了一下Heatmap的源碼诲宇,Heatmap的代碼還是比較好理解的取逾,代碼結(jié)構(gòu)清晰逆趣,有很多值得學(xué)習(xí)的荆几,比如代碼的架構(gòu)吓妆,熱力圖渲染原理、著色等吨铸。下面來說下具體的實(shí)現(xiàn)過程行拢。講的不是很清楚,可以看下精簡版的代碼實(shí)現(xiàn)诞吱。

架構(gòu)

Heatmap其架構(gòu)主要由兩部分組成舟奠,分別是RendererStore竭缝。
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)行研究泥兰,至少要自己能快速的模仿一個出來才算真正的吃透了。
個人博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末题禀,一起剝皮案震驚了整個濱河市鞋诗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迈嘹,老刑警劉巖削彬,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異江锨,居然都是意外死亡吃警,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門啄育,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酌心,“玉大人,你說我怎么就攤上這事挑豌“踩” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵氓英,是天一觀的道長侯勉。 經(jīng)常有香客問我,道長铝阐,這世上最難降的妖魔是什么址貌? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上练对,老公的妹妹穿的比我還像新娘遍蟋。我一直安慰自己,他們只是感情好螟凭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布虚青。 她就那樣靜靜地躺著,像睡著了一般螺男。 火紅的嫁衣襯著肌膚如雪棒厘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天下隧,我揣著相機(jī)與錄音奢人,去河邊找鬼。 笑死汪拥,一個胖子當(dāng)著我的面吹牛达传,可吹牛的內(nèi)容都是我干的篙耗。 我是一名探鬼主播迫筑,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宗弯!你這毒婦竟也來了脯燃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蒙保,失蹤者是張志新(化名)和其女友劉穎辕棚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邓厕,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逝嚎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了详恼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片补君。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昧互,靈堂內(nèi)的尸體忽然破棺而出挽铁,到底是詐尸還是另有隱情,我是刑警寧澤敞掘,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布叽掘,位于F島的核電站,受9級特大地震影響玖雁,放射性物質(zhì)發(fā)生泄漏更扁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浓镜。 院中可真熱鬧赊堪,春花似錦、人聲如沸竖哩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽相叁。三九已至遵绰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間增淹,已是汗流浹背椿访。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虑润,地道東北人成玫。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像拳喻,于是被迫代替她去往敵國和親哭当。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容