mainwp-child/js/heatmap.js
Thang Hoang Van 5106760db7 First commit
2014-03-19 23:58:52 +07:00

362 lines
11 KiB
JavaScript

/*
* heatmap.js 1.0 - JavaScript Heatmap Library
*
* Copyright (c) 2011, Patrick Wied (http://www.patrick-wied.at)
* Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and the Beerware (http://en.wikipedia.org/wiki/Beerware) license.
*/
(function(w){
// the heatmapFactory creates heatmap instances
var heatmapFactory = (function(){
// 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 = 0;
this.get = function(key){
return _[key];
},
this.set = function(key, value){
_[key] = value;
};
};
store.prototype = {
// function for adding datapoints to the store
// datapoints are usually defined by x and y but could also contain a third parameter which represents the occurrence
addDataPoint: function(x, y){
if(x < 0 || y < 0)
return;
var heatmap = this.get("heatmap"),
data = this.get("data");
if(!data[x]) data[x] = [];
if(!data[x][y]) data[x][y] = 1;
// if count parameter is set increment by count otherwise by 1
data[x][y]+=(arguments.length<3)?1:arguments[2];
// do we have a new maximum?
if(this.max < data[x][y]){
this.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]);
},
setDataSet: function(obj){
this.max = obj.max;
var heatmap = this.get("heatmap"),
data = this.get("data"),
d = obj.data,
dlen = d.length;
// clear the heatmap before the data set gets drawn
heatmap.clear();
while(dlen--){
var point = d[dlen];
heatmap.drawAlpha(point.x, point.y, point.count);
if(!data[point.x]) data[point.x] = [];
if(!data[point.x][point.y]) data[point.x][point.y] = 1;
data[point.x][point.y]+=point.count;
}
// Add event after all done
// 10-22-2011 by Jeffri Hong
if ( typeof(obj.callback) == 'function' )
obj.callback();
},
exportDataSet: function(){
var data = this.get("data");
var exportData = [];
for(var one in data){
// jump over undefined indexes
if(one === undefined)
continue;
for(var two in data[one]){
if(two === undefined)
continue;
// if both indexes are defined, push the values into the array
exportData.push({x: parseInt(one, 10), y: parseInt(two, 10), count: data[one][two]});
}
}
return exportData;
},
generateRandomDataSet: function(points){
var heatmap = this.get("heatmap"),
w = heatmap.get("width"),
h = heatmap.get("height");
var randomset = {},
max = Math.floor(Math.random()*1000+1);
randomset.max = max;
var data = [];
while(points--){
data.push({x: Math.floor(Math.random()*w+1), y: Math.floor(Math.random()*h+1), count: Math.floor(Math.random()*max+1)});
}
randomset.data = data;
this.setDataSet(randomset);
}
};
// heatmap object constructor
function heatmap(config){
// private variables
var _ = {
radiusIn : 20,
radiusOut : 40,
element : {},
canvas : {},
acanvas: {},
ctx : {},
actx : {},
visible : true,
width : 0,
height : 0,
max : false,
gradient : false,
opacity: 180
};
// heatmap store containing the datapoints and information about the maximum
// accessible via instance.store
this.store = new store(this);
this.get = function(key){
return _[key];
},
this.set = function(key, value){
_[key] = value;
};
// configure the heatmap when an instance gets created
this.configure(config);
// and initialize it
this.init();
};
// public functions
heatmap.prototype = {
configure: function(config){
if(config.radius){
var rout = config.radius,
rin = parseInt(rout/2);
}
this.set("radiusIn", rin || 15),
this.set("radiusOut", rout || 40),
this.set("element", (config.element instanceof Object)?config.element:document.getElementById(config.element));
this.set("visible", config.visible);
this.set("max", config.max || false);
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
this.set("opacity", parseInt(255/(100/config.opacity), 10) || 180);
this.set("width", config.width || 0);
this.set("height", config.height || 0);
},
init: function(){
this.initColorPalette();
var canvas = document.createElement("canvas"),
acanvas = document.createElement("canvas"),
element = this.get("element");
this.set("canvas", canvas);
this.set("acanvas", acanvas);
canvas.width = acanvas.width = element.style.width.replace(/px/,"") || this.getWidth(element);
this.set("width", canvas.width);
canvas.height = acanvas.height = element.style.height.replace(/px/,"") || this.getHeight(element);
this.set("height", canvas.height);
canvas.style.position = acanvas.style.position = "absolute";
canvas.style.top = acanvas.style.top = "0";
canvas.style.left = acanvas.style.left = "0";
canvas.style.zIndex = 1000000;
if(!this.get("visible"))
canvas.style.display = "none";
this.get("element").appendChild(canvas);
this.set("ctx", canvas.getContext("2d"));
this.set("actx", acanvas.getContext("2d"));
},
initColorPalette: function(){
var canvas = document.createElement("canvas");
canvas.width = "1";
canvas.height = "256";
var ctx = canvas.getContext("2d");
var grad = ctx.createLinearGradient(0,0,1,256),
gradient = this.get("gradient");
for(var x in gradient){
grad.addColorStop(x, gradient[x]);
}
ctx.fillStyle = grad;
ctx.fillRect(0,0,1,256);
this.set("gradient", ctx.getImageData(0,0,1,256).data);
delete canvas;
delete grad;
delete ctx;
},
getWidth: function(element){
var width = element.offsetWidth;
if(element.style.paddingLeft)
width+=element.style.paddingLeft;
if(element.style.paddingRight)
width+=element.style.paddingRight;
return width;
},
getHeight: function(element){
var height = element.offsetHeight;
if(element.style.paddingTop)
height+=element.style.paddingTop;
if(element.style.paddingBottom)
height+=element.style.paddingBottom;
return height;
},
colorize: function(x, y){
// get the private variables
var width = this.get("width"),
radiusOut = this.get("radiusOut"),
height = this.get("height"),
actx = this.get("actx"),
ctx = this.get("ctx");
var x2 = radiusOut*2;
if(x+x2>width)
x=width-x2;
if(x<0)
x=0;
if(y<0)
y=0;
if(y+x2>height)
y=height-x2;
// get the image data for the mouse movement area
var image = actx.getImageData(x,y,x2,x2),
// some performance tweaks
imageData = image.data,
length = imageData.length,
palette = this.get("gradient"),
opacity = this.get("opacity");
// loop thru the area
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;
}
// the rgb data manipulation didn't affect the ImageData object(defined on the top)
// after the manipulation process we have to set the manipulated data to the ImageData object
image.data = imageData;
ctx.putImageData(image,x,y);
},
drawAlpha: function(x, y, count){
// storing the variables because they will be often used
var r1 = this.get("radiusIn"),
r2 = this.get("radiusOut"),
ctx = this.get("actx"),
max = this.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/this.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
this.colorize(xb,yb);
},
toggleDisplay: function(){
var visible = this.get("visible"),
canvas = this.get("canvas");
if(!visible)
canvas.style.display = "block";
else
canvas.style.display = "none";
this.set("visible", !visible);
},
// dataURL export
getImageData: function(){
return this.get("canvas").toDataURL();
},
clear: function(){
var w = this.get("width"),
h = this.get("height");
this.store.set("data",[]);
// @TODO: reset stores max to 1
//this.store.max = 1;
this.get("ctx").clearRect(0,0,w,h);
this.get("actx").clearRect(0,0,w,h);
}
};
return {
create: function(config){
return new heatmap(config);
},
util: {
mousePosition: function(ev){
// this doesn't work right
// rather use
/*
// this = element to observe
var x = ev.pageX - this.offsetLeft;
var y = ev.pageY - this.offsetTop;
*/
var x, y;
if (ev.layerX) { // Firefox
x = ev.layerX;
y = ev.layerY;
} else if (ev.offsetX) { // Opera
x = ev.offsetX;
y = ev.offsetY;
}
if(typeof(x)=='undefined')
return;
return [x,y];
}
}
};
})();
w.h337 = w.heatmapFactory = heatmapFactory;
})(window);