/**
|
* @file defines echarts Heatmap Chart
|
* @author Ovilia (me@zhangwenli.com)
|
* Inspired by https://github.com/mourner/simpleheat
|
*
|
* @module
|
*/
|
define(function (require) {
|
var defaultOptions = {
|
blurSize: 30,
|
|
// gradientColors is either shaped of ['blue', 'cyan', 'lime', 'yellow', 'red']
|
// or [{
|
// offset: 0.2,
|
// color: 'blue'
|
// }, {
|
// offset 0.8,
|
// color: 'cyan'
|
// }]
|
gradientColors: ['blue', 'cyan', 'lime', 'yellow', 'red'],
|
minAlpha: 0.05,
|
valueScale: 1,
|
opacity: 1
|
};
|
|
var BRUSH_SIZE = 20;
|
var GRADIENT_LEVELS = 256;
|
|
/**
|
* Heatmap Chart
|
*
|
* @class
|
* @param {Object} opt options
|
*/
|
function Heatmap(opt) {
|
this.option = opt;
|
if (opt) {
|
for (var i in defaultOptions) {
|
if (opt[i] !== undefined) {
|
this.option[i] = opt[i];
|
} else {
|
this.option[i] = defaultOptions[i];
|
}
|
}
|
} else {
|
this.option = defaultOptions;
|
}
|
}
|
|
Heatmap.prototype = {
|
/**
|
* Renders Heatmap and returns the rendered canvas
|
* @param {Array} [x, y, value] array of data
|
* @param {number} canvas width
|
* @param {number} canvas height
|
* @return {Object} rendered canvas
|
*/
|
getCanvas: function(data, width, height) {
|
var brush = this._getBrush();
|
var gradient = this._getGradient();
|
var r = BRUSH_SIZE + this.option.blurSize;
|
|
var canvas = document.createElement('canvas');
|
canvas.width = width;
|
canvas.height = height;
|
var ctx = canvas.getContext('2d');
|
|
var len = data.length;
|
for (var i = 0; i < len; ++i) {
|
var p = data[i];
|
var x = p[0];
|
var y = p[1];
|
var value = p[2];
|
|
// calculate alpha using value
|
var alpha = Math.min(1, Math.max(value * this.option.valueScale
|
|| this.option.minAlpha, this.option.minAlpha));
|
|
// draw with the circle brush with alpha
|
ctx.globalAlpha = alpha;
|
ctx.drawImage(brush, x - r, y - r);
|
}
|
|
// colorize the canvas using alpha value and set with gradient
|
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
var pixels = imageData.data;
|
var len = pixels.length / 4;
|
while(len--) {
|
var id = len * 4 + 3;
|
var alpha = pixels[id] / 256;
|
var colorOffset = Math.floor(alpha * (GRADIENT_LEVELS - 1));
|
pixels[id - 3] = gradient[colorOffset * 4]; // red
|
pixels[id - 2] = gradient[colorOffset * 4 + 1]; // green
|
pixels[id - 1] = gradient[colorOffset * 4 + 2]; // blue
|
pixels[id] *= this.option.opacity; // alpha
|
}
|
ctx.putImageData(imageData, 0, 0);
|
|
return canvas;
|
},
|
|
/**
|
* get canvas of a black circle brush used for canvas to draw later
|
* @private
|
* @returns {Object} circle brush canvas
|
*/
|
_getBrush: function() {
|
if (!this._brushCanvas) {
|
this._brushCanvas = document.createElement('canvas');
|
|
// set brush size
|
var r = BRUSH_SIZE + this.option.blurSize;
|
var d = r * 2;
|
this._brushCanvas.width = d;
|
this._brushCanvas.height = d;
|
|
var ctx = this._brushCanvas.getContext('2d');
|
|
// in order to render shadow without the distinct circle,
|
// draw the distinct circle in an invisible place,
|
// and use shadowOffset to draw shadow in the center of the canvas
|
ctx.shadowOffsetX = d;
|
ctx.shadowBlur = this.option.blurSize;
|
// draw the shadow in black, and use alpha and shadow blur to generate
|
// color in color map
|
ctx.shadowColor = 'black';
|
|
// draw circle in the left to the canvas
|
ctx.beginPath();
|
ctx.arc(-r, r, BRUSH_SIZE, 0, Math.PI * 2, true);
|
ctx.closePath();
|
ctx.fill();
|
}
|
return this._brushCanvas;
|
},
|
|
/**
|
* get gradient color map
|
* @private
|
* @returns {array} gradient color pixels
|
*/
|
_getGradient: function() {
|
if (!this._gradientPixels) {
|
var levels = GRADIENT_LEVELS;
|
var canvas = document.createElement('canvas');
|
canvas.width = 1;
|
canvas.height = levels;
|
var ctx = canvas.getContext('2d');
|
|
// add color to gradient stops
|
var gradient = ctx.createLinearGradient(0, 0, 0, levels);
|
var len = this.option.gradientColors.length;
|
for (var i = 0; i < len; ++i) {
|
if (typeof this.option.gradientColors[i] === 'string') {
|
gradient.addColorStop((i + 1) / len,
|
this.option.gradientColors[i]);
|
} else {
|
gradient.addColorStop(this.option.gradientColors[i].offset,
|
this.option.gradientColors[i].color);
|
}
|
}
|
|
ctx.fillStyle = gradient;
|
ctx.fillRect(0, 0, 1, levels);
|
this._gradientPixels = ctx.getImageData(0, 0, 1, levels).data;
|
}
|
return this._gradientPixels;
|
}
|
};
|
|
return Heatmap;
|
});
|