blob: 7894bb6684d05904fd20169d3465d888c7a0144d [file] [log] [blame]
/*
* Copyright 2013 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var HeatGrid = function(el) {
this.el = $(el);
var rgb = function(r, g, b) {
return 'rgb(' + parseInt(r) + ',' + parseInt(g) + ',' + parseInt(b) + ')';
}
var frac = function(minval, maxval, fraction) {
return minval + fraction * (maxval - minval);
}
var gradient = function(mincolor, zerocolor, maxcolor,
ofs) {
if (ofs == 0) {
return zerocolor;
} else if (ofs < 0) {
return [frac(zerocolor[0], mincolor[0], -ofs/3),
frac(zerocolor[1], mincolor[1], -ofs/3),
frac(zerocolor[2], mincolor[2], -ofs/3)];
} else if (ofs > 0) {
return [frac(zerocolor[0], maxcolor[0], ofs/3),
frac(zerocolor[1], maxcolor[1], ofs/3),
frac(zerocolor[2], maxcolor[2], ofs/3)];
}
}
this.draw = function(grid) {
console.debug('heatgrid.draw', grid);
this.el.html('<div id="heatgrid"><canvas></canvas>' +
'<div id="heatgrid-popover"></div></div>');
var heatgrid = this.el.find('#heatgrid');
heatgrid.css({
position: 'relative',
overflow: 'scroll',
width: '100%',
height: '100%',
});
var popover = this.el.find('#heatgrid-popover');
popover.css({
position: 'absolute',
top: 0, left: 0,
background: '#aaa',
border: '1px dotted black',
'white-space': 'pre'
});
var canvas = this.el.find('canvas');
var xmult = parseInt(1000 / grid.headers.length);
if (xmult < 1) xmult = 1;
var xsize = grid.headers.length * xmult;
var ysize = grid.data.length;
canvas.attr({width: xsize, height: ysize});
canvas.css({
background: '#fff',
width: '100%',
height: ysize //'100%',
});
console.debug('heatgrid canvas size is: x y =', xsize, ysize);
var ctx = canvas[0].getContext('2d');
if (!grid.data.length || !grid.data[0].length) {
return;
}
// TODO(apenwarr): offsetX/Y are flakey, use something else
var movefunc = function(offX, offY) {
var x = parseInt(offX / canvas.width() * grid.headers.length);
var y = parseInt(offY / canvas.height() * grid.data.length);
if (x > grid.headers.length || y > grid.data.length) return;
var info = [];
for (var i = 0; i < grid.headers.length; i++) {
if (grid.types[i] != 'number') {
info.push(grid.data[y][i]);
} else {
break;
}
}
info.push(grid.headers[x]);
info.push('value=' + grid.data[y][x]);
popover.css({
left: (x + 0.4) / grid.headers.length * canvas.width(),
top: (y + 0.4) / grid.data.length * canvas.height(),
});
popover.text(info.join('\n'));
};
heatgrid.mousemove(function(ev) {
var pos = canvas.position();
movefunc(ev.pageX - pos.left, ev.pageY - pos.top);
});
heatgrid.mouseleave(function() {
popover.hide();
});
heatgrid.mouseenter(function() {
popover.show();
});
var total = 0, count = 0;
for (var y = 0; y < grid.data.length; y++) {
for (var x = 0; x < grid.data[y].length; x++) {
if (grid.types[x] != 'number') continue;
var cell = parseFloat(grid.data[y][x]);
if (!isNaN(cell)) {
total += cell;
count++;
}
}
}
var avg = total / count;
var tdiff = 0;
for (var y = 0; y < grid.data.length; y++) {
for (var x = 0; x < grid.data[y].length; x++) {
if (grid.types[x] != 'number') continue;
var cell = parseFloat(grid.data[y][x]);
if (!isNaN(cell)) {
tdiff += (cell - avg) * (cell - avg);
}
}
}
var stddev = Math.sqrt(tdiff / count);
console.debug('total,count,mean,stddev = ', total, count, avg, stddev);
var img = ctx.createImageData(xsize, ysize);
for (var y = 0; y < grid.data.length; y++) {
for (var x = 0; x < grid.data[y].length; x++) {
if (grid.types[x] != 'number') continue;
var cell = parseFloat(grid.data[y][x]);
if (isNaN(cell)) continue;
var ofs = stddev ? (cell - avg) / stddev : 3;
var color = gradient([192,192,192], [192,192,255], [0,0,255], ofs);
if (!color) continue;
var pix = (y * xsize + x*xmult) * 4;
for (var i = 0; i < xmult; i++) {
img.data[pix + 0] = color[0];
img.data[pix + 1] = color[1];
img.data[pix + 2] = color[2];
img.data[pix + 3] = 255;
pix += 4;
}
}
}
ctx.putImageData(img, 0, 0);
}
};