轉載自:http://1.aisensiy.sinaapp.com/2011/12/heatmapjs/
最近做的一個東西,需要以熱力圖的形式去展示數據。於是就想找一找熱力圖的算法。找到了很多生成熱力圖的工具,它們的算法幾乎是一致的,都是首先用alpha透明度方式畫發散的圓在頁面上,然后利用一個調色板,把對應的透明度改成相應的顏色即可。
一個很不錯的中文的算法介紹在這里 淺談Heatmap
一個英文的在這里 How to Make Heat Map
雖說我本來打算的是找到算法自己去實現一下的,但是一不小心我發現萬能的google在搜索記錄里面給了我一個heatmap.js的鏈接。我好奇的點進去發現這就是我所需要實現的東西…
heatmap.js可以使用canvas畫出來一張漂亮的heatmap。更重要的是它支持數據的動態添加。比如,上圖的演示就是一個利用mousemove事件生成heatmap的例子。它會自動的刷新canvas,實時顯示鼠標運動的heatmap。
打開heatmap.js發現里面的代碼並不多,但是真的很精悍。
頁面代碼請點擊這里[heatmap.js],下面我做一個code的分析吧,看了那么久了,寫下來一方面是自己加深記憶,另一方面就是可以更好的理清思路吧。[寫就是為了更好的思考]么。
code中包含兩個主要的對象,store heatmap。store是heatmap的數據部分,算是model吧。而heatmap則是真正繪制圖像的對象。heatmap部分可以被配置,可以自定義很多的內容,尤其是配色也是可以配置的,那么我們除了做出來正真的heatmap的效果之外還可以做出來各種各樣不錯的效果的。
首先看看存儲部分吧,比較簡單,注釋也比較清楚。
12345678910111213141516171819202122232425 | // store object constructor // a heatmap contains a store // the store has to know about the heatmap // in order to trigger heatmap updates when // datapoints get added function store(hmap){ var _ = { // data is a two dimensional array // a datapoint gets saved as data[point-x-value][point-y-value] // the value at [point-x-value][point-y-value] // is the occurrence of the datapoint data: [], // tight coupling of the heatmap object heatmap: hmap }; // the max occurrence - the heatmaps radial gradient // alpha transition is based on it this .max = 1; this .get = function (key){ return _[key]; }, this .set = function (key, value){ _[key] = value; }; }; |
在model里面,支持一次添加一個數據點。這也是heatmapjs支持實時繪制的關鍵。一旦max值有變化就會重新繪制整個canvas。
123456789101112131415161718192021222324252627 | addDataPoint: function (x, y){ if (x < 0 || y < 0) return ; var me = this , heatmap = me.get( "heatmap" ), data = me.get( "data" ); if (!data[x]) data[x] = []; if (!data[x][y]) data[x][y] = 0; // if count parameter is set increment by count otherwise by 1 data[x][y]+=(arguments.length me.set( "data" , data); // do we have a new maximum? if (me.max < data[x][y]){ me.max = data[x][y]; // max changed, we need to redraw all existing(lower) datapoints heatmap.get( "actx" ).clearRect(0,0,heatmap.get( "width" ),heatmap.get( "height" )); for ( var one in data) for ( var two in data[one]) heatmap.drawAlpha(one, two, data[one][two]); // @TODO // implement feature // heatmap.drawLegend(); ? return ; } heatmap.drawAlpha(x, y, data[x][y]); }, |
下面就是畫的部分了。這里是最重要的兩個方法,drawAlpha colorize
123456789101112131415161718192021 | drawAlpha: function (x, y, count){ // storing the variables because they will be often used var me = this , r1 = me.get( "radiusIn" ), r2 = me.get( "radiusOut" ), ctx = me.get( "actx" ), max = me.get( "max" ), // create a radial gradient with the defined parameters. // we want to draw an alphamap rgr = ctx.createRadialGradient(x,y,r1,x,y,r2), xb = x-r2, yb = y-r2, mul = 2*r2; // the center of the radial gradient has .1 alpha value rgr.addColorStop(0, 'rgba(0,0,0,' +((count)?(count/me.store.max): '0.1' )+ ')' ); // and it fades out to 0 rgr.addColorStop(1, 'rgba(0,0,0,0)' ); // drawing the gradient ctx.fillStyle = rgr; ctx.fillRect(xb,yb,mul,mul); // finally colorize the area me.colorize(xb,yb); }, |
策略很簡單,
123 | rgr.addColorStop(0, 'rgba(0,0,0,' +((count)?(count/me.store.max): '0.1' )+ ')' ); // and it fades out to 0 rgr.addColorStop(1, 'rgba(0,0,0,0)' ); |
利用當前點的count除以最大的count獲取的結果做為alpha值。然后做一個RadialGradient畫出來這個圖就可以了。那么由於多個相近的點aphla效果的疊加就可以獲取想要的效果了。這里就是canvas的nb之處了,看別的語言實現都是采用將一個這樣的png圖片畫到畫板上,但是canvas就可以直接實現這個效果。
有了這幅aphla版本的heatmap 我們利用一個配送版做着色就大功告成了。
這里又用到了上面所說的canvas的nb之處,在通常需要一個圖片作為配色板的時候canvas可以自己做出來一個緩存起來。
12345678910111213141516171819 | initColorPalette: function (){ var me = this , canvas = document.createElement( "canvas" ); canvas.width = "1" ; canvas.height = "256" ; var ctx = canvas.getContext( "2d" ), grad = ctx.createLinearGradient(0,0,1,256), gradient = me.get( "gradient" ); for ( var x in gradient){ grad.addColorStop(x, gradient[x]); } ctx.fillStyle = grad; ctx.fillRect(0,0,1,256); //這里太強大了,緩存了我的畫板數據,然后刪除了畫板 me.set( "gradient" , ctx.getImageData(0,0,1,256).data); delete canvas; delete grad; delete ctx; }, |
這種方式也給我們實現各種各樣的配色提供了方便,我們只需要改變那個 **gradient** 就可以了。
12345678910111213141516 | for ( var i=3; i < length; i+=4){ // [0] -> r, [1] -> g, [2] -> b, [3] -> alpha var alpha = imageData[i], offset = alpha*4; if (!offset) continue ; // we ve started with i=3 // set the new r, g and b values // 根據透明度選擇配色板上的配色 imageData[i-3]=palette[offset]; imageData[i-2]=palette[offset+1]; imageData[i-1]=palette[offset+2]; // we want the heatmap to have a gradient from transparent to the colors // as long as alpha is lower than the defined opacity (maximum), // we'll use the alpha value imageData[i] = (alpha < opacity)?alpha:opacity; } |
還是很簡練的吧,看到heatmap.js的風格,真的像是在看一個不錯的藝術品一樣。強烈推薦一看~
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。