HeatmapLayer.js
5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* @file defines echarts Heatmap Chart
* @author Ovilia (me@zhangwenli.com)
* Inspired by https://github.com/mourner/simpleheat
*
* @module
*/
define(function (require) {
var GRADIENT_LEVELS = 256;
var zrUtil = require('zrender/core/util');
/**
* Heatmap Chart
*
* @class
*/
function Heatmap() {
var canvas = zrUtil.createCanvas();
this.canvas = canvas;
this.blurSize = 30;
this.pointSize = 20;
this.maxOpacity = 1;
this.minOpacity = 0;
this._gradientPixels = {};
}
Heatmap.prototype = {
/**
* Renders Heatmap and returns the rendered canvas
* @param {Array} data array of data, each has x, y, value
* @param {number} width canvas width
* @param {number} height canvas height
*/
update: function(data, width, height, normalize, colorFunc, isInRange) {
var brush = this._getBrush();
var gradientInRange = this._getGradient(data, colorFunc, 'inRange');
var gradientOutOfRange = this._getGradient(data, colorFunc, 'outOfRange');
var r = this.pointSize + this.blurSize;
var canvas = this.canvas;
var ctx = canvas.getContext('2d');
var len = data.length;
canvas.width = width;
canvas.height = height;
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 = normalize(value);
// 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 offset = 0;
var pixelLen = pixels.length;
var minOpacity = this.minOpacity;
var maxOpacity = this.maxOpacity;
var diffOpacity = maxOpacity - minOpacity;
while(offset < pixelLen) {
var alpha = pixels[offset + 3] / 256;
var gradientOffset = Math.floor(alpha * (GRADIENT_LEVELS - 1)) * 4;
// Simple optimize to ignore the empty data
if (alpha > 0) {
var gradient = isInRange(alpha) ? gradientInRange : gradientOutOfRange;
// Any alpha > 0 will be mapped to [minOpacity, maxOpacity]
alpha > 0 && (alpha = alpha * diffOpacity + minOpacity);
pixels[offset++] = gradient[gradientOffset];
pixels[offset++] = gradient[gradientOffset + 1];
pixels[offset++] = gradient[gradientOffset + 2];
pixels[offset++] = gradient[gradientOffset + 3] * alpha * 256;
}
else {
offset += 4;
}
}
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() {
var brushCanvas = this._brushCanvas || (this._brushCanvas = zrUtil.createCanvas());
// set brush size
var r = this.pointSize + this.blurSize;
var d = r * 2;
brushCanvas.width = d;
brushCanvas.height = d;
var ctx = brushCanvas.getContext('2d');
ctx.clearRect(0, 0, d, d);
// 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.blurSize;
// draw the shadow in black, and use alpha and shadow blur to generate
// color in color map
ctx.shadowColor = '#000';
// draw circle in the left to the canvas
ctx.beginPath();
ctx.arc(-r, r, this.pointSize, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
return brushCanvas;
},
/**
* get gradient color map
* @private
*/
_getGradient: function (data, colorFunc, state) {
var gradientPixels = this._gradientPixels;
var pixelsSingleState = gradientPixels[state] || (gradientPixels[state] = new Uint8ClampedArray(256 * 4));
var color = [];
var off = 0;
for (var i = 0; i < 256; i++) {
colorFunc[state](i / 255, true, color);
pixelsSingleState[off++] = color[0];
pixelsSingleState[off++] = color[1];
pixelsSingleState[off++] = color[2];
pixelsSingleState[off++] = color[3];
}
return pixelsSingleState;
}
};
return Heatmap;
});