Advertisement
Guest User

heatmap.js

a guest
Aug 23rd, 2011
367
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * heatmap.js 1.0 - JavaScript Heatmap Library
  3.  *
  4.  * Copyright (c) 2011, Patrick Wied (http://www.patrick-wied.at)
  5.  * Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
  6.  * and the Beerware (http://en.wikipedia.org/wiki/Beerware) license.
  7.  */
  8.  
  9. (function(w){
  10.     // the heatmapFactory creates heatmap instances
  11.     var heatmapFactory = (function(){
  12.    
  13.     // store object constructor
  14.     // a heatmap contains a store
  15.     // the store has to know about the heatmap in order to trigger heatmap updates when datapoints get added
  16.     function store(hmap){
  17.  
  18.         var _ = {
  19.             // data is a two dimensional array
  20.             // a datapoint gets saved as data[point-x-value][point-y-value]
  21.             // the value at [point-x-value][point-y-value] is the occurrence of the datapoint
  22.             data: [],
  23.             // tight coupling of the heatmap object
  24.             heatmap: hmap
  25.         };
  26.         // the max occurrence - the heatmaps radial gradient alpha transition is based on it
  27.         this.max = 0;
  28.        
  29.         this.get = function(key){
  30.             return _[key];
  31.         },
  32.         this.set = function(key, value){
  33.             _[key] = value;
  34.         };
  35.     };
  36.    
  37.     store.prototype = {
  38.         // function for adding datapoints to the store
  39.         // datapoints are usually defined by x and y but could also contain a third parameter which represents the occurrence
  40.         addDataPoint: function(x, y){
  41.             if(x < 0 || y < 0)
  42.                 return;
  43.                
  44.             var heatmap = this.get("heatmap"),
  45.             data = this.get("data");
  46.            
  47.             if(!data[x]) data[x] = [];
  48.             if(!data[x][y]) data[x][y] = 1;
  49.             // if count parameter is set increment by count otherwise by 1
  50.             data[x][y]+=(arguments.length<3)?1:arguments[2];
  51.            
  52.             // do we have a new maximum?
  53.             if(this.max < data[x][y]){
  54.                 this.max = data[x][y];
  55.                 // max changed, we need to redraw all existing(lower) datapoints
  56.                 heatmap.get("actx").clearRect(0,0,heatmap.get("width"),heatmap.get("height"));
  57.                 for(var one in data)                   
  58.                     for(var two in data[one])
  59.                         heatmap.drawAlpha(one, two, data[one][two]);
  60.  
  61.                 // @TODO
  62.                 // implement feature
  63.                 // heatmap.drawLegend(); ?
  64.                 return;
  65.             }
  66.             heatmap.drawAlpha(x, y, data[x][y]);
  67.         },
  68.         setDataSet: function(obj){
  69.            
  70.             this.max = obj.max;
  71.             var heatmap = this.get("heatmap"),
  72.             data = this.get("data"),
  73.             d = obj.data,
  74.             dlen = d.length;
  75.             // clear the heatmap before the data set gets drawn
  76.             heatmap.clear();
  77.            
  78.             while(dlen--){
  79.                 var point = d[dlen];
  80.                 heatmap.drawAlpha(point.x, point.y, point.count);
  81.                 if(!data[point.x]) data[point.x] = [];
  82.                 if(!data[point.x][point.y]) data[point.x][point.y] = 1;
  83.                 data[point.x][point.y]+=point.count;
  84.             }
  85.         },
  86.         exportDataSet: function(){
  87.             var data = this.get("data");
  88.             var exportData = [];
  89.             for(var one in data){
  90.                 // jump over undefined indexes
  91.                 if(one === undefined)
  92.                     continue;
  93.                 for(var two in data[one]){
  94.                     if(two === undefined)
  95.                         continue;
  96.                     // if both indexes are defined, push the values into the array
  97.                     exportData.push({x: parseInt(one, 10), y: parseInt(two, 10), count: data[one][two]});
  98.                 }
  99.             }
  100.                    
  101.             return exportData;
  102.         },
  103.         generateRandomDataSet: function(points){
  104.             var heatmap = this.get("heatmap"),
  105.             w = heatmap.get("width"),
  106.             h = heatmap.get("height");
  107.             var randomset = {},
  108.             max = Math.floor(Math.random()*1000+1);
  109.             randomset.max = max;
  110.             var data = [];
  111.             while(points--){
  112.                 data.push({x: Math.floor(Math.random()*w+1), y: Math.floor(Math.random()*h+1), count: Math.floor(Math.random()*max+1)});
  113.             }
  114.             randomset.data = data;
  115.             this.setDataSet(randomset);
  116.         }
  117.     };
  118.    
  119.    
  120.     // heatmap object constructor
  121.     function heatmap(config){
  122.         // private variables
  123.         var _ = {
  124.             radiusIn : 20,
  125.             radiusOut : 40,
  126.             zoom: 1,
  127.             element : {},
  128.             canvas : {},
  129.             acanvas: {},
  130.             ctx : {},
  131.             actx : {},
  132.             visible : true,
  133.             width : 0,
  134.             height : 0,
  135.             max : false,
  136.             gradient : false,
  137.             opacity: 180
  138.         };
  139.         // heatmap store containing the datapoints and information about the maximum
  140.         // accessible via instance.store
  141.         this.store = new store(this);
  142.        
  143.         this.get = function(key){
  144.             return _[key];
  145.         },
  146.         this.set = function(key, value){
  147.             _[key] = value;
  148.         };
  149.         // configure the heatmap when an instance gets created
  150.         this.configure(config);
  151.         // and initialize it
  152.         this.init();
  153.     };
  154.    
  155.     // public functions
  156.     heatmap.prototype = {
  157.         configure: function(config){
  158.                 if(config.radius){
  159.                     var rout = config.radius,
  160.                     rin = parseInt(rout/2);                
  161.                 }
  162.                 this.set("radiusIn", rin || 15),
  163.                 this.set("radiusOut", rout || 40),
  164.                 this.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element));
  165.                 this.set("visible", config.visible);
  166.                 this.set("max", config.max || false);
  167.                 this.set("gradient", config.gradient || { 0.45: "rgb(0,0,255)", 0.55: "rgb(0,255,255)", 0.65: "rgb(0,255,0)", 0.95: "yellow", 1.0: "rgb(255,0,0)"});    // default is the common blue to red gradient
  168.                 this.set("opacity", parseInt(255/(100/config.opacity), 10) || 180);
  169.                 this.set("width", config.width || 0);
  170.                 this.set("height", config.height || 0);
  171.                 if(config.palette != undefined){
  172. //                  this.set("gradient", config.palette);
  173.                 }
  174.         },
  175.         init: function(){
  176.                 this.initColorPalette();
  177.                 var canvas = document.createElement("canvas"),
  178.                 acanvas = document.createElement("canvas"),
  179.                 element = this.get("element");
  180.                 this.set("canvas", canvas);
  181.                 this.set("acanvas", acanvas);
  182.                 canvas.width = acanvas.width = element.style.width.replace(/px/,"") || this.getWidth(element);
  183.                 this.set("width", canvas.width);
  184.                 canvas.height = acanvas.height = element.style.height.replace(/px/,"") || this.getHeight(element);
  185.                 this.set("height", canvas.height);
  186.                 canvas.style.position = acanvas.style.position = "absolute";
  187.                 canvas.style.top = acanvas.style.top = "0";
  188.                 canvas.style.left = acanvas.style.left = "0";
  189.                 canvas.style.zIndex = 1000000;
  190.                 if(!this.get("visible"))
  191.                     canvas.style.display = "none";
  192.  
  193.                 this.get("element").appendChild(canvas);
  194.                 this.set("ctx", canvas.getContext("2d"));
  195.                 this.set("actx", acanvas.getContext("2d"));
  196.         },
  197.         initColorPalette: function(){
  198.                
  199.             var canvas = document.createElement("canvas");
  200.             canvas.width = "1";
  201.             canvas.height = "256";
  202.             var ctx = canvas.getContext("2d");
  203.             var grad = ctx.createLinearGradient(0,0,1,256),
  204.             gradient = this.get("gradient");
  205.             for(var x in gradient){
  206.                 grad.addColorStop(x, gradient[x]);
  207.             }
  208.            
  209.             ctx.fillStyle = grad;
  210.             ctx.fillRect(0,0,1,256);
  211.            
  212.             this.set("gradient", ctx.getImageData(0,0,1,256).data);
  213.             delete canvas;
  214.             delete grad;
  215.             delete ctx;
  216.         },
  217.         getWidth: function(element){
  218.             var width = element.offsetWidth;
  219.             if(element.style.paddingLeft)
  220.                 width+=element.style.paddingLeft;
  221.             if(element.style.paddingRight)
  222.                 width+=element.style.paddingRight;
  223.            
  224.             return width;
  225.         },
  226.         getHeight: function(element){
  227.             var height = element.offsetHeight;
  228.             if(element.style.paddingTop)
  229.                 height+=element.style.paddingTop;
  230.             if(element.style.paddingBottom)
  231.                 height+=element.style.paddingBottom;
  232.            
  233.             return height;
  234.         },
  235.         colorize: function(x, y){
  236.                 // get the private variables
  237.                 var width = this.get("width"),
  238.                 radiusOut = this.get("radiusOut"),
  239.                 height = this.get("height"),
  240.                 actx = this.get("actx"),
  241.                 ctx = this.get("ctx");
  242.                
  243.                 var x2 = radiusOut*2;
  244.                
  245.                 if(x+x2>width)
  246.                     x=width-x2;
  247.                 if(x<0)
  248.                     x=0;
  249.                 if(y<0)
  250.                     y=0;
  251.                 if(y+x2>height)
  252.                     y=height-x2;
  253.                 // get the image data for the mouse movement area
  254.                 var image = actx.getImageData(x,y,x2,x2),
  255.                 // some performance tweaks
  256.                     imageData = image.data,
  257.                     length = imageData.length,
  258.                     palette = this.get("gradient"),
  259.                     opacity = this.get("opacity");
  260.                 // loop thru the area
  261.                 for(var i=3; i < length; i+=4){
  262.                    
  263.                     // [0] -> r, [1] -> g, [2] -> b, [3] -> alpha
  264.                     var alpha = imageData[i],
  265.                     offset = alpha*4;
  266.                    
  267.                     if(!offset)
  268.                         continue;
  269.    
  270.                     // we ve started with i=3
  271.                     // set the new r, g and b values
  272.                     imageData[i-3]=palette[offset];
  273.                     imageData[i-2]=palette[offset+1];
  274.                     imageData[i-1]=palette[offset+2];
  275.                     // we want the heatmap to have a gradient from transparent to the colors
  276.                     // as long as alpha is lower than the defined opacity (maximum), we'll use the alpha value
  277.                     imageData[i] = (alpha < opacity)?alpha:opacity;
  278.                 }
  279.                 // the rgb data manipulation didn't affect the ImageData object(defined on the top)
  280.                 // after the manipulation process we have to set the manipulated data to the ImageData object
  281.                 image.data = imageData;
  282.                 ctx.putImageData(image,x,y);   
  283.         },
  284.         drawAlpha: function(x, y, count){
  285.                 // storing the variables because they will be often used
  286.                 var r1 = this.get("radiusIn"),
  287.                 r2 = this.get("radiusOut"),
  288.                 ctx = this.get("actx"),
  289.                 max = this.get("max"),
  290.                 // create a radial gradient with the defined parameters. we want to draw an alphamap
  291.                 rgr = ctx.createRadialGradient(x,y,r1,x,y,r2),
  292.                 xb = x-r2, yb = y-r2, mul = 2*r2;
  293.                 // the center of the radial gradient has .1 alpha value
  294.                 rgr.addColorStop(0, 'rgba(0,0,0,'+((count)?(count/this.store.max):'0.1')+')');  
  295.                 // and it fades out to 0
  296.                 rgr.addColorStop(1, 'rgba(0,0,0,0)');
  297.                 // drawing the gradient
  298.                 ctx.fillStyle = rgr;  
  299.                 ctx.fillRect(xb,yb,mul,mul);
  300.                 // finally colorize the area
  301.                 this.colorize(xb,yb);
  302.  
  303.         },
  304.         toggleDisplay: function(){
  305.                 var visible = this.get("visible"),
  306.                 canvas = this.get("canvas");
  307.                
  308.                 if(!visible)
  309.                     canvas.style.display = "block";
  310.                 else
  311.                     canvas.style.display = "none";
  312.                    
  313.                 this.set("visible", !visible);
  314.         },
  315.         // Hide canvas
  316.         hide: function(){
  317.                 canvas = this.get("canvas");       
  318.                 canvas.style.display = "none";
  319.                 this.set("visible", false);
  320.         },
  321.         // Display the canvas
  322.         show: function(){
  323.                 canvas = this.get("canvas");       
  324.                 canvas.style.display = "block";
  325.                 this.set("visible", true);         
  326.         },
  327.         // dataURL export
  328.         getImageData: function(){
  329.                 return this.get("canvas").toDataURL();
  330.         },
  331.         changeZoom: function(scale){
  332.                 var zoom = this.get("zoom");
  333.                 var ctx = this.get("ctx");
  334.                 var width = this.get("width");
  335.                 var height = this.get("height");
  336.                
  337.                 var newWidth = width * scale;
  338.                 var newHeight = height * scale;
  339.              
  340.                 ctx.save();
  341.                 ctx.translate(-((newWidth-width)/2), -((newHeight-height)/2));
  342.                 ctx.scale(scale, scale);
  343.                 ctx.clearRect(0, 0, width, height);
  344.                 ctx.drawImage(copiedCanvas, 0, 0);
  345.                 ctx.restore(); 
  346.         },
  347.         clear: function(){
  348.             var w = this.get("width"),
  349.             h = this.get("height");
  350.             this.store.set("data",[]);
  351.             // @TODO: reset stores max to 1
  352.             //this.store.max = 1;
  353.             this.get("ctx").clearRect(0,0,w,h);
  354.             this.get("actx").clearRect(0,0,w,h);
  355.         },
  356.         destroy: function(){
  357.             var element = this.get("element");
  358.             var canvas = this.get("canvas");
  359. //          this.get("element").removeNode(true);
  360.             element.removeChild(canvas);
  361. //          this.get("canvas").removeNode(true);
  362.         }
  363.     };
  364.        
  365.     return {
  366.             create: function(config){
  367.                 return new heatmap(config);
  368.             },
  369.             util: {
  370.                 mousePosition: function(ev){
  371.                     // this doesn't work right
  372.                     // rather use
  373.                     /*
  374.                         // this = element to observe
  375.                         var x = ev.pageX - this.offsetLeft;
  376.                         var y = ev.pageY - this.offsetTop;
  377.                        
  378.                     */
  379.                     var x, y;
  380.                    
  381.                     if (ev.layerX) { // Firefox
  382.                         x = ev.layerX;
  383.                         y = ev.layerY;
  384.                     } else if (ev.offsetX) { // Opera
  385.                         x = ev.offsetX;
  386.                         y = ev.offsetY;
  387.                     }
  388.                     if(typeof(x)=='undefined')
  389.                         return;
  390.                    
  391.                     return [x,y];
  392.                 }
  393.             }
  394.         };
  395.     })();
  396.     w.h337 = w.heatmapFactory = heatmapFactory;
  397. })(window);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement