283 lines
7.5 KiB
Java
283 lines
7.5 KiB
Java
|
|
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @fileOverview heatmap
|
|||
|
|
* @author leungwensen@gmail.com
|
|||
|
|
*/
|
|||
|
|
var _require = require('@antv/attr/lib'),
|
|||
|
|
ColorUtil = _require.ColorUtil; // TODO: ColorUtil 独立成包,从 attr 包中抽离
|
|||
|
|
|
|||
|
|
|
|||
|
|
var GeomBase = require('./base');
|
|||
|
|
|
|||
|
|
var Util = require('../util');
|
|||
|
|
|
|||
|
|
var ORIGIN_FIELD = '_origin';
|
|||
|
|
var SHADOW_CANVAS = 'shadowCanvas';
|
|||
|
|
var VALUE_RANGE = 'valueRange';
|
|||
|
|
var IMAGE_SHAPE = 'imageShape';
|
|||
|
|
var MAPPED_DATA = 'mappedData';
|
|||
|
|
var GRAY_SCALE_BLURRED_CANVAS = 'grayScaleBlurredCanvas';
|
|||
|
|
var HEATMAP_SIZE = 'heatmapSize';
|
|||
|
|
|
|||
|
|
var Heatmap = /*#__PURE__*/function (_GeomBase) {
|
|||
|
|
_inheritsLoose(Heatmap, _GeomBase);
|
|||
|
|
|
|||
|
|
function Heatmap() {
|
|||
|
|
return _GeomBase.apply(this, arguments) || this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var _proto = Heatmap.prototype;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* get default configuration
|
|||
|
|
* @protected
|
|||
|
|
* @return {Object} configuration
|
|||
|
|
*/
|
|||
|
|
_proto.getDefaultCfg = function getDefaultCfg() {
|
|||
|
|
var cfg = _GeomBase.prototype.getDefaultCfg.call(this);
|
|||
|
|
|
|||
|
|
cfg.type = 'heatmap';
|
|||
|
|
cfg.paletteCache = {}; // cfg.shapeType = 'heatmap';
|
|||
|
|
|
|||
|
|
return cfg;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._prepareRange = function _prepareRange() {
|
|||
|
|
var self = this;
|
|||
|
|
var data = self.get(MAPPED_DATA);
|
|||
|
|
var colorAttr = self.getAttr('color');
|
|||
|
|
var colorField = colorAttr.field;
|
|||
|
|
var min = Infinity;
|
|||
|
|
var max = -Infinity;
|
|||
|
|
data.forEach(function (row) {
|
|||
|
|
var value = row[ORIGIN_FIELD][colorField];
|
|||
|
|
|
|||
|
|
if (value > max) {
|
|||
|
|
max = value;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (value < min) {
|
|||
|
|
min = value;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (min === max) {
|
|||
|
|
min = max - 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var range = [min, max];
|
|||
|
|
self.set(VALUE_RANGE, range);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._prepareSize = function _prepareSize() {
|
|||
|
|
var self = this;
|
|||
|
|
var radius = self.getDefaultValue('size');
|
|||
|
|
|
|||
|
|
if (!Util.isNumber(radius)) {
|
|||
|
|
radius = self._getDefaultSize();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var styleOptions = self.get('styleOptions');
|
|||
|
|
var blur = styleOptions && Util.isObject(styleOptions.style) ? styleOptions.style.blur : null;
|
|||
|
|
|
|||
|
|
if (!Util.isFinite(blur) || blur === null) {
|
|||
|
|
blur = radius / 2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.set(HEATMAP_SIZE, {
|
|||
|
|
blur: blur,
|
|||
|
|
radius: radius
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._getDefaultSize = function _getDefaultSize() {
|
|||
|
|
var self = this;
|
|||
|
|
var position = self.getAttr('position');
|
|||
|
|
var coord = self.get('coord');
|
|||
|
|
var radius = Math.min(coord.width / (position.scales[0].ticks.length * 4), coord.height / (position.scales[1].ticks.length * 4));
|
|||
|
|
return radius;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._colorize = function _colorize(img) {
|
|||
|
|
var self = this;
|
|||
|
|
var colorAttr = self.getAttr('color');
|
|||
|
|
var pixels = img.data;
|
|||
|
|
var paletteCache = self.get('paletteCache');
|
|||
|
|
|
|||
|
|
for (var i = 3; i < pixels.length; i += 4) {
|
|||
|
|
var alpha = pixels[i]; // get gradient color from opacity value
|
|||
|
|
|
|||
|
|
if (alpha) {
|
|||
|
|
var palette = void 0;
|
|||
|
|
|
|||
|
|
if (paletteCache[alpha]) {
|
|||
|
|
palette = paletteCache[alpha];
|
|||
|
|
} else {
|
|||
|
|
palette = ColorUtil.rgb2arr(colorAttr.gradient(alpha / 256));
|
|||
|
|
paletteCache[alpha] = palette;
|
|||
|
|
} // const palette = colorUtil.rgb2arr(colorAttr.gradient(alpha / 256));
|
|||
|
|
|
|||
|
|
|
|||
|
|
pixels[i - 3] = palette[0];
|
|||
|
|
pixels[i - 2] = palette[1];
|
|||
|
|
pixels[i - 1] = palette[2];
|
|||
|
|
pixels[i] = alpha;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._prepareGreyScaleBlurredCircle = function _prepareGreyScaleBlurredCircle(r, blur) {
|
|||
|
|
var self = this;
|
|||
|
|
var circleCanvas = self.get(GRAY_SCALE_BLURRED_CANVAS);
|
|||
|
|
|
|||
|
|
if (!circleCanvas) {
|
|||
|
|
circleCanvas = document.createElement('canvas');
|
|||
|
|
self.set(GRAY_SCALE_BLURRED_CANVAS, circleCanvas);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var r2 = r + blur;
|
|||
|
|
var ctx = circleCanvas.getContext('2d');
|
|||
|
|
circleCanvas.width = circleCanvas.height = r2 * 2;
|
|||
|
|
ctx.clearRect(0, 0, circleCanvas.width, circleCanvas.height); // ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2;
|
|||
|
|
|
|||
|
|
ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2;
|
|||
|
|
ctx.shadowBlur = blur;
|
|||
|
|
ctx.shadowColor = 'black';
|
|||
|
|
ctx.beginPath();
|
|||
|
|
ctx.arc(-r2, -r2, r, 0, Math.PI * 2, true);
|
|||
|
|
ctx.closePath();
|
|||
|
|
ctx.fill();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._drawGrayScaleBlurredCircle = function _drawGrayScaleBlurredCircle(x, y, r, alpha, ctx) {
|
|||
|
|
var self = this;
|
|||
|
|
var circleCanvas = self.get(GRAY_SCALE_BLURRED_CANVAS);
|
|||
|
|
ctx.globalAlpha = alpha;
|
|||
|
|
ctx.drawImage(circleCanvas, x - r, y - r);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._getShadowCanvasCtx = function _getShadowCanvasCtx() {
|
|||
|
|
var self = this;
|
|||
|
|
var canvas = self.get(SHADOW_CANVAS);
|
|||
|
|
|
|||
|
|
if (!canvas) {
|
|||
|
|
canvas = document.createElement('canvas');
|
|||
|
|
self.set(SHADOW_CANVAS, canvas);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var coord = self.get('coord');
|
|||
|
|
|
|||
|
|
if (coord) {
|
|||
|
|
canvas.width = coord.width;
|
|||
|
|
canvas.height = coord.height;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return canvas.getContext('2d');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._clearShadowCanvasCtx = function _clearShadowCanvasCtx() {
|
|||
|
|
var ctx = this._getShadowCanvasCtx();
|
|||
|
|
|
|||
|
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto._getImageShape = function _getImageShape() {
|
|||
|
|
var self = this;
|
|||
|
|
var imageShape = self.get(IMAGE_SHAPE);
|
|||
|
|
|
|||
|
|
if (imageShape) {
|
|||
|
|
return imageShape;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var container = self.get('container');
|
|||
|
|
imageShape = container.addShape('Image', {});
|
|||
|
|
self.set(IMAGE_SHAPE, imageShape);
|
|||
|
|
return imageShape;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto.clear = function clear() {
|
|||
|
|
// @2019-02-28 by blue.lb 由于设置了SHADOW_CANVAS作为像素缓存canvas,每次销毁chart时,也需要清除该缓冲区
|
|||
|
|
this._clearShadowCanvasCtx();
|
|||
|
|
|
|||
|
|
_GeomBase.prototype.clear.call(this);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto.drawWithRange = function drawWithRange(range) {
|
|||
|
|
var self = this; // canvas size
|
|||
|
|
|
|||
|
|
var _self$get = self.get('coord'),
|
|||
|
|
start = _self$get.start,
|
|||
|
|
end = _self$get.end,
|
|||
|
|
width = _self$get.width,
|
|||
|
|
height = _self$get.height; // value, range, etc
|
|||
|
|
|
|||
|
|
|
|||
|
|
var valueField = self.getAttr('color').field;
|
|||
|
|
var size = self.get(HEATMAP_SIZE); // prepare shadow canvas context
|
|||
|
|
|
|||
|
|
self._clearShadowCanvasCtx();
|
|||
|
|
|
|||
|
|
var ctx = self._getShadowCanvasCtx(); // filter data
|
|||
|
|
|
|||
|
|
|
|||
|
|
var data = self.get(MAPPED_DATA);
|
|||
|
|
|
|||
|
|
if (range) {
|
|||
|
|
data = data.filter(function (row) {
|
|||
|
|
return row[ORIGIN_FIELD][valueField] <= range[1] && row[ORIGIN_FIELD][valueField] >= range[0];
|
|||
|
|
});
|
|||
|
|
} // step1. draw points with shadow
|
|||
|
|
|
|||
|
|
|
|||
|
|
var scale = self._getScale(valueField);
|
|||
|
|
|
|||
|
|
for (var i = 0; i < data.length; i++) {
|
|||
|
|
var obj = data[i];
|
|||
|
|
var cfg = self.getDrawCfg(obj);
|
|||
|
|
var alpha = scale.scale(obj[ORIGIN_FIELD][valueField]);
|
|||
|
|
|
|||
|
|
self._drawGrayScaleBlurredCircle(cfg.x - start.x, cfg.y - end.y, size.radius + size.blur, alpha, ctx);
|
|||
|
|
} // step2. convert pixels
|
|||
|
|
|
|||
|
|
|
|||
|
|
var colored = ctx.getImageData(0, 0, width, height);
|
|||
|
|
|
|||
|
|
self._clearShadowCanvasCtx();
|
|||
|
|
|
|||
|
|
self._colorize(colored);
|
|||
|
|
|
|||
|
|
ctx.putImageData(colored, 0, 0);
|
|||
|
|
|
|||
|
|
var imageShape = self._getImageShape();
|
|||
|
|
|
|||
|
|
imageShape.attr('x', start.x);
|
|||
|
|
imageShape.attr('y', end.y);
|
|||
|
|
imageShape.attr('width', width);
|
|||
|
|
imageShape.attr('height', height);
|
|||
|
|
imageShape.attr('img', ctx.canvas);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
_proto.draw = function draw(data
|
|||
|
|
/* , container, shapeFactory, index */
|
|||
|
|
) {
|
|||
|
|
var self = this;
|
|||
|
|
self.set(MAPPED_DATA, data);
|
|||
|
|
|
|||
|
|
self._prepareRange();
|
|||
|
|
|
|||
|
|
self._prepareSize();
|
|||
|
|
|
|||
|
|
var size = self.get(HEATMAP_SIZE);
|
|||
|
|
|
|||
|
|
self._prepareGreyScaleBlurredCircle(size.radius, size.blur);
|
|||
|
|
|
|||
|
|
var range = self.get(VALUE_RANGE);
|
|||
|
|
self.drawWithRange(range); // super.draw(data, container, shapeFactory, index);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return Heatmap;
|
|||
|
|
}(GeomBase);
|
|||
|
|
|
|||
|
|
GeomBase.Heatmap = Heatmap;
|
|||
|
|
module.exports = Heatmap;
|