cesium 热力图
什么是热力图
热力图,是一种通过对色块着色来显示数据的统计图表
热力图在各领域用途
- 一场世界杯足球竞赛----评委们通常利用热图了解到冠军队伍中门将、后卫、中场和前锋的跑位,让我们一目了然地看到多名球员在比赛中跑位的差异
- 生物学热图----通常用在分子生物学范畴,可以显示从DNA微阵列获得的大量可比较样本(不同状态下的细胞、不同患者的样本)中的很多基因的表达水平
- 天气、地震预测----气象局还可利用热图判断地震震源位置,可清楚看出哪些地方是地震高发区(频率最高)
heatmap.js
使用heatmap.js库可以快速生成热力图
cesium中结合heatmap集成热力图

heatmap的使用很简单,但是他是在一个二维平面区域内绘制热力图,x,y是二维平面内的坐标,比如有个200*200的区域,x:10,y:15就是在此位置添加一个热力。但是cesium中是GIS,使用是的WGS84坐标系,而且使用经纬度进行标记位置,所以这里需要一个转换,转换好后用heatmap生成热力图,然后取出热力图图片,在cesium中绘制一个多边形,贴图采用热力图图片,就将热力图结合到cesium模型、地形上了。
坐标转换流程(重点)
-
先确定要绘制热力图的区域
js// 要绘制热力图区域数据 const polygon = [ { longitude: 120.68281005, latitude: 30.51066356 }, { longitude: 120.68231251, latitude: 30.51353815 }, { longitude: 120.69327431, latitude: 30.51833967 }, { longitude: 120.69607266, latitude: 30.51186293 } ]; const bpoint = []; for (const coord of polygon) { bpoint.push(new Cartographic(coord.longitude, coord.latitude, 0)); } // 创建尽可能小的Rectangle,该矩形包含所提供数组中的所有位置 const bound = Rectangle.fromCartographicArray(bpoint);
-
将WGS84边界框转换为墨卡托边界框(即可理解成将经纬度坐标转换成平面xy坐标)
js/** * 将WGS84边界框转换为墨卡托边界框(即可理解成将经纬度坐标转换成平面xy坐标) * @param {*} bb WGS84边界框,如{north, east, south, west} * @returns */ CesiumHeatmap.wgs84ToMercatorBB = function (bb) { const WMP = new Cesium.WebMercatorProjection(); const sw = WMP.project(Cesium.Cartographic.fromDegrees(bb.west, bb.south)); const ne = WMP.project(Cesium.Cartographic.fromDegrees(bb.east, bb.north)); return { north: ne.y, east: ne.x, south: sw.y, west: sw.x }; };
-
根据墨卡托边界框计算宽高
js/** * 根据墨卡托边界框计算宽高 * @param {*} mbb 墨卡托边界框 */ _setWidthAndHeight(mbb) { const width = mbb.east > 0 && mbb.west < 0 ? mbb.east + Math.abs(mbb.west) : Math.abs(mbb.east - mbb.west); const height = mbb.north > 0 && mbb.south < 0 ? mbb.north + Math.abs(mbb.south) : Math.abs(mbb.north - mbb.south); this.width = Math.round(width); this.height = Math.round(height); }
-
根据宽高生成DOM渲染区域
js/** * 获取动态生成的DOM渲染区域 * @param {*} width 宽度 * @param {*} height 高度 * @returns DOM */ CesiumHeatmap._getContainer = function (width, height) { const div = document.createElement('div'); div.setAttribute('id', 'heatmap-cesium-div'); div.setAttribute('style', 'width: ' + width + 'px; height: ' + height + 'px; margin: 0px; display: none;'); document.body.appendChild(div); return div; };
-
创建热力图h337
jsthis._heatmap = h337.create(this._options);
-
生成热力图heatmap库所需的数据
js
/**
* 将WGS84坐标点转换成热力图上的点
* @param {*} p WGS84经纬度坐标点数据
* @returns
*/
wgs84PointToHeatmapPoint(p) {
return this.mercatorPointToHeatmapPoint(CesiumHeatmap.wgs84ToMercator(p));
}
/**
* 将WGS84位置转换为墨卡托位置
* @param {*} p WGS84位置 如{lon: longitude, lat: latitude}
* @returns
*/
CesiumHeatmap.wgs84ToMercator = function (p) {
const WMP = new Cesium.WebMercatorProjection();
const mp = WMP.project(Cesium.Cartographic.fromDegrees(p.lon, p.lat));
return {
x: mp.x,
y: mp.y
};
};
/**
* 将墨卡托坐标点转换成热力图上的点
* @param {*} p 墨卡托坐标点数据
* @returns
*/
mercatorPointToHeatmapPoint(p) {
var pn = {};
pn.x = Math.round(p.x - this._mbounds.west);
pn.y = Math.round(p.y - this._mbounds.south);
pn.y = this.height - pn.y;
return pn;
}
其基本思路就是:将热力值的WGS坐标转换成墨卡托坐标,然后与之前的墨卡托边界框计算得到x,y,然后就可以使用heatmap库进行生成热力图
cesiumHeatMap封装
将上述代码进行封装
js
import * as Cesium from 'cesium';
import h337 from './heatmap.js';
var CesiumHeatmap = {
defaults: {
maxOpacity: 0.8, // 如果在热图选项对象中没有给出,则使用的最大不透明度
minOpacity: 0.1, // 如果在热图选项对象中没有给出,则使用的最小不透明度
blur: 0.85, // 如果在热图选项对象中没有给出,则使用的模糊
gradient: {
'.3': 'blue',
'.65': 'yellow',
'.8': 'orange',
'.95': 'red'
} // 如果热图选项对象中没有给出,则使用的梯度
}
};
/**
* 创建CesiumHeatmap实例
* @param {*} bound
* @param {*} options
* @returns
*/
CesiumHeatmap.create = function (bound, options) {
var instance = new HeatmapInstance(bound, options);
return instance;
};
/**
* 获取动态生成的DOM渲染区域
* @param {*} width 宽度
* @param {*} height 高度
* @returns DOM
*/
CesiumHeatmap._getContainer = function (width, height) {
const div = document.createElement('div');
div.setAttribute('id', 'heatmap-cesium-div');
div.setAttribute('style', 'width: ' + width + 'px; height: ' + height + 'px; margin: 0px; display: none;');
document.body.appendChild(div);
return div;
};
/**
* 删除动态生成的DOM渲染区域
*/
CesiumHeatmap._removeContainer = function () {
let div = document.getElementById('heatmap-cesium-div');
document.body.removeChild(div);
};
/**
* 将WGS84位置转换为墨卡托位置
* @param {*} p WGS84位置 如{lon: longitude, lat: latitude}
* @returns
*/
CesiumHeatmap.wgs84ToMercator = function (p) {
const WMP = new Cesium.WebMercatorProjection();
const mp = WMP.project(Cesium.Cartographic.fromDegrees(p.lon, p.lat));
return {
x: mp.x,
y: mp.y
};
};
/**
* 将WGS84边界框转换为墨卡托边界框(即可理解成将经纬度坐标转换成平面xy坐标)
* @param {*} bb WGS84边界框,如{north, east, south, west}
* @returns
*/
CesiumHeatmap.wgs84ToMercatorBB = function (bb) {
const WMP = new Cesium.WebMercatorProjection();
const sw = WMP.project(Cesium.Cartographic.fromDegrees(bb.west, bb.south));
const ne = WMP.project(Cesium.Cartographic.fromDegrees(bb.east, bb.north));
return {
north: ne.y,
east: ne.x,
south: sw.y,
west: sw.x
};
};
class HeatmapInstance {
constructor(bound, heatmapOptions) {
if (!bound) return null;
if (!heatmapOptions) heatmapOptions = {};
this._options = heatmapOptions;
this._options.gradient = this._options.gradient ? this._options.gradient : CesiumHeatmap.defaults.gradient;
this._options.maxOpacity = this._options.maxOpacity ? this._options.maxOpacity : CesiumHeatmap.defaults.maxOpacity;
this._options.minOpacity = this._options.minOpacity ? this._options.minOpacity : CesiumHeatmap.defaults.minOpacity;
this._options.blur = this._options.blur ? this._options.blur : CesiumHeatmap.defaults.blur;
this._mbounds = CesiumHeatmap.wgs84ToMercatorBB(bound);
this._setWidthAndHeight(this._mbounds);
this._options.radius = Math.round(
this._options.radius ? this._options.radius : this.width > this.height ? this.width : this.height
);
this._options.container = CesiumHeatmap._getContainer(this.width, this.height);
this._heatmap = h337.create(this._options);
}
/**
* 根据墨卡托边界框计算宽高
* @param {*} mbb 墨卡托边界框
*/
_setWidthAndHeight(mbb) {
const width = mbb.east > 0 && mbb.west < 0 ? mbb.east + Math.abs(mbb.west) : Math.abs(mbb.east - mbb.west);
const height = mbb.north > 0 && mbb.south < 0 ? mbb.north + Math.abs(mbb.south) : Math.abs(mbb.north - mbb.south);
this.width = Math.round(width);
this.height = Math.round(height);
}
/**
* 设置WGS84位置数组
* @param {*} min 数据值允许的最小值
* @param {*} max 数据值允许的最大值
* @param {*} data WGS84坐标下的数据点数组,值如{lon, lat, value}
* @returns 返回热力图生成后的图片
*/
setWGS84Data(min, max, data) {
if (!(data && data.length > 0 && min !== null && min !== false && max !== null && max !== false)) return false;
var convdata = [];
for (var i = 0; i < data.length; i++) {
var gp = data[i];
var hp = this.wgs84PointToHeatmapPoint(gp);
if (gp.value || gp.value === 0) {
hp.value = gp.value;
}
convdata.push(hp);
}
this._heatmap.setData({
min: min,
max: max,
data: convdata
});
CesiumHeatmap._removeContainer();
return this._heatmap.getDataURL();
}
/**
* 将WGS84坐标点转换成热力图上的点
* @param {*} p WGS84经纬度坐标点数据
* @returns
*/
wgs84PointToHeatmapPoint(p) {
return this.mercatorPointToHeatmapPoint(CesiumHeatmap.wgs84ToMercator(p));
}
/**
* 将墨卡托坐标点转换成热力图上的点
* @param {*} p 墨卡托坐标点数据
* @returns
*/
mercatorPointToHeatmapPoint(p) {
var pn = {};
pn.x = Math.round(p.x - this._mbounds.west);
pn.y = Math.round(p.y - this._mbounds.south);
pn.y = this.height - pn.y;
return pn;
}
}
export { CesiumHeatmap };
写一个例子调用
js
// 热力图数据
const heatPoints = [
{ lon: 120.69530288, lat: 30.51613188, value: 10 },
{ lon: 120.68330288, lat: 30.51213188, value: 50 },
{ lon: 120.68350288, lat: 30.51413188, value: 20 },
{ lon: 120.68830288, lat: 30.51213188, value: 10 }
];
// 要绘制热力图区域数据
const box = [
{ longitude: 120.68281005, latitude: 30.51066356 },
{ longitude: 120.68231251, latitude: 30.51353815 },
{ longitude: 120.69327431, latitude: 30.51833967 },
{ longitude: 120.69607266, latitude: 30.51186293 }
];
const points = [];
const bpoint = [];
for (const coord of box) {
points.push(coord.longitude, coord.latitude);
bpoint.push(new Cartographic(coord.longitude, coord.latitude, 0));
}
// 创建尽可能小的 Rectangle,该矩形包含所提供数组中的所有位置
const bound = Rectangle.fromCartographicArray(bpoint);
const heatMap = CesiumHeatmap.create(bound, {
// heatmap相应参数
backgroundColor: 'rgba(0,0,0,0)',
radius: 150,
maxOpacity: 0.5,
minOpacity: 0,
blur: 0.85
});
const img = heatMap.setWGS84Data(0, 60, heatPoints);
this.app.viewerCesium.entities.add({
polygon: {
hierarchy: Cartesian3.fromDegreesArray(points), // 多边形
material: new ImageMaterialProperty({
image: img
})
}
});
heatmap.js 代码修改
如果你采用es6模块化的方式引入heatmap,运行时会报错

看看官方的issues给出的解决方案github.com/pa7/heatmap...
其实并没有解决,这个热力图的库上次维护还是7年前。所以我的解决方案就是,拷贝heatmap.js到本地,删除相关无用代码,以下是代码
js
// Heatmap Config stores default values and will be merged with instance config
var HeatmapConfig = {
defaultRadius: 40,
defaultRenderer: 'canvas2d',
defaultGradient: { 0.25: 'rgb(0,0,255)', 0.55: 'rgb(0,255,0)', 0.85: 'yellow', 1.0: 'rgb(255,0,0)' },
defaultMaxOpacity: 1,
defaultMinOpacity: 0,
defaultBlur: 0.85,
defaultXField: 'x',
defaultYField: 'y',
defaultValueField: 'value',
plugins: {}
};
var Store = (function StoreClosure() {
var Store = function Store(config) {
this._coordinator = {};
this._data = [];
this._radi = [];
this._min = 10;
this._max = 1;
this._xField = config['xField'] || config.defaultXField;
this._yField = config['yField'] || config.defaultYField;
this._valueField = config['valueField'] || config.defaultValueField;
if (config['radius']) {
this._cfgRadius = config['radius'];
}
};
var defaultRadius = HeatmapConfig.defaultRadius;
Store.prototype = {
// when forceRender = false -> called from setData, omits renderall event
_organiseData: function (dataPoint, forceRender) {
var x = dataPoint[this._xField];
var y = dataPoint[this._yField];
var radi = this._radi;
var store = this._data;
var max = this._max;
var min = this._min;
var value = dataPoint[this._valueField] || 1;
var radius = dataPoint.radius || this._cfgRadius || defaultRadius;
if (!store[x]) {
store[x] = [];
radi[x] = [];
}
if (!store[x][y]) {
store[x][y] = value;
radi[x][y] = radius;
} else {
store[x][y] += value;
}
var storedVal = store[x][y];
if (storedVal > max) {
if (!forceRender) {
this._max = storedVal;
} else {
this.setDataMax(storedVal);
}
return false;
} else if (storedVal < min) {
if (!forceRender) {
this._min = storedVal;
} else {
this.setDataMin(storedVal);
}
return false;
} else {
return {
x: x,
y: y,
value: value,
radius: radius,
min: min,
max: max
};
}
},
_unOrganizeData: function () {
var unorganizedData = [];
var data = this._data;
var radi = this._radi;
for (var x in data) {
for (var y in data[x]) {
unorganizedData.push({
x: x,
y: y,
radius: radi[x][y],
value: data[x][y]
});
}
}
return {
min: this._min,
max: this._max,
data: unorganizedData
};
},
_onExtremaChange: function () {
this._coordinator.emit('extremachange', {
min: this._min,
max: this._max
});
},
addData: function () {
if (arguments[0].length > 0) {
var dataArr = arguments[0];
var dataLen = dataArr.length;
while (dataLen--) {
this.addData.call(this, dataArr[dataLen]);
}
} else {
// add to store
var organisedEntry = this._organiseData(arguments[0], true);
if (organisedEntry) {
// if it's the first datapoint initialize the extremas with it
if (this._data.length === 0) {
this._min = this._max = organisedEntry.value;
}
this._coordinator.emit('renderpartial', {
min: this._min,
max: this._max,
data: [organisedEntry]
});
}
}
return this;
},
setData: function (data) {
var dataPoints = data.data;
var pointsLen = dataPoints.length;
// reset data arrays
this._data = [];
this._radi = [];
for (var i = 0; i < pointsLen; i++) {
this._organiseData(dataPoints[i], false);
}
this._max = data.max;
this._min = data.min || 0;
this._onExtremaChange();
this._coordinator.emit('renderall', this._getInternalData());
return this;
},
removeData: function () {
// TODO: implement
},
setDataMax: function (max) {
this._max = max;
this._onExtremaChange();
this._coordinator.emit('renderall', this._getInternalData());
return this;
},
setDataMin: function (min) {
this._min = min;
this._onExtremaChange();
this._coordinator.emit('renderall', this._getInternalData());
return this;
},
setCoordinator: function (coordinator) {
this._coordinator = coordinator;
},
_getInternalData: function () {
return {
max: this._max,
min: this._min,
data: this._data,
radi: this._radi
};
},
getData: function () {
return this._unOrganizeData();
} /*,
TODO: rethink.
getValueAt: function(point) {
var value;
var radius = 100;
var x = point.x;
var y = point.y;
var data = this._data;
if (data[x] && data[x][y]) {
return data[x][y];
} else {
var values = [];
// radial search for datapoints based on default radius
for(var distance = 1; distance < radius; distance++) {
var neighbors = distance * 2 +1;
var startX = x - distance;
var startY = y - distance;
for(var i = 0; i < neighbors; i++) {
for (var o = 0; o < neighbors; o++) {
if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
if (data[startY+i] && data[startY+i][startX+o]) {
values.push(data[startY+i][startX+o]);
}
} else {
continue;
}
}
}
}
if (values.length > 0) {
return Math.max.apply(Math, values);
}
}
return false;
}*/
};
return Store;
})();
var Canvas2dRenderer = (function Canvas2dRendererClosure() {
var _getColorPalette = function (config) {
var gradientConfig = config.gradient || config.defaultGradient;
var paletteCanvas = document.createElement('canvas');
var paletteCtx = paletteCanvas.getContext('2d');
paletteCanvas.width = 256;
paletteCanvas.height = 1;
var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
for (var key in gradientConfig) {
gradient.addColorStop(key, gradientConfig[key]);
}
paletteCtx.fillStyle = gradient;
paletteCtx.fillRect(0, 0, 256, 1);
return paletteCtx.getImageData(0, 0, 256, 1).data;
};
var _getPointTemplate = function (radius, blurFactor) {
var tplCanvas = document.createElement('canvas');
var tplCtx = tplCanvas.getContext('2d');
var x = radius;
var y = radius;
tplCanvas.width = tplCanvas.height = radius * 2;
if (blurFactor == 1) {
tplCtx.beginPath();
tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
tplCtx.fillStyle = 'rgba(0,0,0,1)';
tplCtx.fill();
} else {
var gradient = tplCtx.createRadialGradient(x, y, radius * blurFactor, x, y, radius);
gradient.addColorStop(0, 'rgba(0,0,0,1)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
tplCtx.fillStyle = gradient;
tplCtx.fillRect(0, 0, 2 * radius, 2 * radius);
}
return tplCanvas;
};
var _prepareData = function (data) {
var renderData = [];
var min = data.min;
var max = data.max;
var radi = data.radi;
var data = data.data;
var xValues = Object.keys(data);
var xValuesLen = xValues.length;
while (xValuesLen--) {
var xValue = xValues[xValuesLen];
var yValues = Object.keys(data[xValue]);
var yValuesLen = yValues.length;
while (yValuesLen--) {
var yValue = yValues[yValuesLen];
var value = data[xValue][yValue];
var radius = radi[xValue][yValue];
renderData.push({
x: xValue,
y: yValue,
value: value,
radius: radius
});
}
}
return {
min: min,
max: max,
data: renderData
};
};
function Canvas2dRenderer(config) {
var container = config.container;
var shadowCanvas = (this.shadowCanvas = document.createElement('canvas'));
var canvas = (this.canvas = config.canvas || document.createElement('canvas'));
var renderBoundaries = (this._renderBoundaries = [10000, 10000, 0, 0]);
var computed = getComputedStyle(config.container) || {};
canvas.className = 'heatmap-canvas';
this._width = canvas.width = shadowCanvas.width = config.width || +computed.width.replace(/px/, '');
this._height = canvas.height = shadowCanvas.height = config.height || +computed.height.replace(/px/, '');
this.shadowCtx = shadowCanvas.getContext('2d');
this.ctx = canvas.getContext('2d');
// @TODO:
// conditional wrapper
canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;';
container.style.position = 'relative';
container.appendChild(canvas);
this._palette = _getColorPalette(config);
this._templates = {};
this._setStyles(config);
}
Canvas2dRenderer.prototype = {
renderPartial: function (data) {
if (data.data.length > 0) {
this._drawAlpha(data);
this._colorize();
}
},
renderAll: function (data) {
// reset render boundaries
this._clear();
if (data.data.length > 0) {
this._drawAlpha(_prepareData(data));
this._colorize();
}
},
_updateGradient: function (config) {
this._palette = _getColorPalette(config);
},
updateConfig: function (config) {
if (config['gradient']) {
this._updateGradient(config);
}
this._setStyles(config);
},
setDimensions: function (width, height) {
this._width = width;
this._height = height;
this.canvas.width = this.shadowCanvas.width = width;
this.canvas.height = this.shadowCanvas.height = height;
},
_clear: function () {
this.shadowCtx.clearRect(0, 0, this._width, this._height);
this.ctx.clearRect(0, 0, this._width, this._height);
},
_setStyles: function (config) {
this._blur = config.blur == 0 ? 0 : config.blur || config.defaultBlur;
if (config.backgroundColor) {
this.canvas.style.backgroundColor = config.backgroundColor;
}
this._width = this.canvas.width = this.shadowCanvas.width = config.width || this._width;
this._height = this.canvas.height = this.shadowCanvas.height = config.height || this._height;
this._opacity = (config.opacity || 0) * 255;
this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255;
this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255;
this._useGradientOpacity = !!config.useGradientOpacity;
},
_drawAlpha: function (data) {
var min = (this._min = data.min);
var max = (this._max = data.max);
var data = data.data || [];
var dataLen = data.length;
// on a point basis?
var blur = 1 - this._blur;
while (dataLen--) {
var point = data[dataLen];
var x = point.x;
var y = point.y;
var radius = point.radius;
// if value is bigger than max
// use max as value
var value = Math.min(point.value, max);
var rectX = x - radius;
var rectY = y - radius;
var shadowCtx = this.shadowCtx;
var tpl;
if (!this._templates[radius]) {
this._templates[radius] = tpl = _getPointTemplate(radius, blur);
} else {
tpl = this._templates[radius];
}
// value from minimum / value range
// => [0, 1]
var templateAlpha = (value - min) / (max - min);
// this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData
shadowCtx.globalAlpha = templateAlpha < 0.01 ? 0.01 : templateAlpha;
shadowCtx.drawImage(tpl, rectX, rectY);
// update renderBoundaries
if (rectX < this._renderBoundaries[0]) {
this._renderBoundaries[0] = rectX;
}
if (rectY < this._renderBoundaries[1]) {
this._renderBoundaries[1] = rectY;
}
if (rectX + 2 * radius > this._renderBoundaries[2]) {
this._renderBoundaries[2] = rectX + 2 * radius;
}
if (rectY + 2 * radius > this._renderBoundaries[3]) {
this._renderBoundaries[3] = rectY + 2 * radius;
}
}
},
_colorize: function () {
var x = this._renderBoundaries[0];
var y = this._renderBoundaries[1];
var width = this._renderBoundaries[2] - x;
var height = this._renderBoundaries[3] - y;
var maxWidth = this._width;
var maxHeight = this._height;
var opacity = this._opacity;
var maxOpacity = this._maxOpacity;
var minOpacity = this._minOpacity;
var useGradientOpacity = this._useGradientOpacity;
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (x + width > maxWidth) {
width = maxWidth - x;
}
if (y + height > maxHeight) {
height = maxHeight - y;
}
var img = this.shadowCtx.getImageData(x, y, width, height);
var imgData = img.data;
var len = imgData.length;
var palette = this._palette;
for (var i = 3; i < len; i += 4) {
var alpha = imgData[i];
var offset = alpha * 4;
if (!offset) {
continue;
}
var finalAlpha;
if (opacity > 0) {
finalAlpha = opacity;
} else {
if (alpha < maxOpacity) {
if (alpha < minOpacity) {
finalAlpha = minOpacity;
} else {
finalAlpha = alpha;
}
} else {
finalAlpha = maxOpacity;
}
}
imgData[i - 3] = palette[offset];
imgData[i - 2] = palette[offset + 1];
imgData[i - 1] = palette[offset + 2];
imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
}
this.ctx.putImageData(img, x, y);
this._renderBoundaries = [1000, 1000, 0, 0];
},
getValueAt: function (point) {
var value;
var shadowCtx = this.shadowCtx;
var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
var data = img.data[3];
var max = this._max;
var min = this._min;
value = (Math.abs(max - min) * (data / 255)) >> 0;
return value;
},
getDataURL: function () {
return this.canvas.toDataURL();
}
};
return Canvas2dRenderer;
})();
var Renderer = (function RendererClosure() {
var rendererFn = false;
if (HeatmapConfig['defaultRenderer'] === 'canvas2d') {
rendererFn = Canvas2dRenderer;
}
return rendererFn;
})();
var Util = {
merge: function () {
var merged = {};
var argsLen = arguments.length;
for (var i = 0; i < argsLen; i++) {
var obj = arguments[i];
for (var key in obj) {
merged[key] = obj[key];
}
}
return merged;
}
};
// Heatmap Constructor
var Heatmap = (function HeatmapClosure() {
var Coordinator = (function CoordinatorClosure() {
function Coordinator() {
this.cStore = {};
}
Coordinator.prototype = {
on: function (evtName, callback, scope) {
var cStore = this.cStore;
if (!cStore[evtName]) {
cStore[evtName] = [];
}
cStore[evtName].push(function (data) {
return callback.call(scope, data);
});
},
emit: function (evtName, data) {
var cStore = this.cStore;
if (cStore[evtName]) {
var len = cStore[evtName].length;
for (var i = 0; i < len; i++) {
var callback = cStore[evtName][i];
callback(data);
}
}
}
};
return Coordinator;
})();
var _connect = function (scope) {
var renderer = scope._renderer;
var coordinator = scope._coordinator;
var store = scope._store;
coordinator.on('renderpartial', renderer.renderPartial, renderer);
coordinator.on('renderall', renderer.renderAll, renderer);
coordinator.on('extremachange', function (data) {
scope._config.onExtremaChange &&
scope._config.onExtremaChange({
min: data.min,
max: data.max,
gradient: scope._config['gradient'] || scope._config['defaultGradient']
});
});
store.setCoordinator(coordinator);
};
function Heatmap() {
var config = (this._config = Util.merge(HeatmapConfig, arguments[0] || {}));
this._coordinator = new Coordinator();
if (config['plugin']) {
var pluginToLoad = config['plugin'];
if (!HeatmapConfig.plugins[pluginToLoad]) {
throw new Error("Plugin '" + pluginToLoad + "' not found. Maybe it was not registered.");
} else {
var plugin = HeatmapConfig.plugins[pluginToLoad];
// set plugin renderer and store
this._renderer = new plugin.renderer(config);
this._store = new plugin.store(config);
}
} else {
this._renderer = new Renderer(config);
this._store = new Store(config);
}
_connect(this);
}
// @TODO:
// add API documentation
Heatmap.prototype = {
addData: function () {
this._store.addData.apply(this._store, arguments);
return this;
},
removeData: function () {
this._store.removeData && this._store.removeData.apply(this._store, arguments);
return this;
},
setData: function () {
this._store.setData.apply(this._store, arguments);
return this;
},
setDataMax: function () {
this._store.setDataMax.apply(this._store, arguments);
return this;
},
setDataMin: function () {
this._store.setDataMin.apply(this._store, arguments);
return this;
},
configure: function (config) {
this._config = Util.merge(this._config, config);
this._renderer.updateConfig(this._config);
this._coordinator.emit('renderall', this._store._getInternalData());
return this;
},
repaint: function () {
this._coordinator.emit('renderall', this._store._getInternalData());
return this;
},
getData: function () {
return this._store.getData();
},
getDataURL: function () {
return this._renderer.getDataURL();
},
getValueAt: function (point) {
if (this._store.getValueAt) {
return this._store.getValueAt(point);
} else if (this._renderer.getValueAt) {
return this._renderer.getValueAt(point);
} else {
return null;
}
}
};
return Heatmap;
})();
// core
var heatmapFactory = {
create: function (config) {
return new Heatmap(config);
},
register: function (pluginKey, plugin) {
HeatmapConfig.plugins[pluginKey] = plugin;
}
};
export default heatmapFactory;
效果
