541 lines
17 KiB
JavaScript
541 lines
17 KiB
JavaScript
|
(function (global, factory) {
|
|||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|||
|
typeof define === 'function' && define.amd ? define(factory) :
|
|||
|
(global.LineGradient = factory());
|
|||
|
}(this, (function () { 'use strict';
|
|||
|
|
|||
|
/**
|
|||
|
* @author https://github.com/chengquan223
|
|||
|
* @Date 2017-02-27
|
|||
|
* */
|
|||
|
function CanvasLayer(options) {
|
|||
|
this.options = options || {};
|
|||
|
this.paneName = this.options.paneName || 'labelPane';
|
|||
|
this.zIndex = this.options.zIndex || 0;
|
|||
|
this._map = options.map;
|
|||
|
this._lastDrawTime = null;
|
|||
|
this.show();
|
|||
|
}
|
|||
|
|
|||
|
CanvasLayer.prototype = new BMap.Overlay();
|
|||
|
|
|||
|
CanvasLayer.prototype.initialize = function (map) {
|
|||
|
this._map = map;
|
|||
|
var canvas = this.canvas = document.createElement('canvas');
|
|||
|
var ctx = this.ctx = this.canvas.getContext('2d');
|
|||
|
canvas.style.cssText = 'position:absolute;' + 'left:0;' + 'top:0;' + 'z-index:' + this.zIndex + ';';
|
|||
|
this.adjustSize();
|
|||
|
this.adjustRatio(ctx);
|
|||
|
map.getPanes()[this.paneName].appendChild(canvas);
|
|||
|
var that = this;
|
|||
|
map.addEventListener('resize', function () {
|
|||
|
that.adjustSize();
|
|||
|
that._draw();
|
|||
|
});
|
|||
|
return this.canvas;
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.adjustSize = function () {
|
|||
|
var size = this._map.getSize();
|
|||
|
var canvas = this.canvas;
|
|||
|
canvas.width = size.width;
|
|||
|
canvas.height = size.height;
|
|||
|
canvas.style.width = canvas.width + 'px';
|
|||
|
canvas.style.height = canvas.height + 'px';
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.adjustRatio = function (ctx) {
|
|||
|
var backingStore = ctx.backingStorePixelRatio || ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
|
|||
|
var pixelRatio = (window.devicePixelRatio || 1) / backingStore;
|
|||
|
var canvasWidth = ctx.canvas.width;
|
|||
|
var canvasHeight = ctx.canvas.height;
|
|||
|
ctx.canvas.width = canvasWidth * pixelRatio;
|
|||
|
ctx.canvas.height = canvasHeight * pixelRatio;
|
|||
|
ctx.canvas.style.width = canvasWidth + 'px';
|
|||
|
ctx.canvas.style.height = canvasHeight + 'px';
|
|||
|
// console.log(ctx.canvas.height, canvasHeight);
|
|||
|
ctx.scale(pixelRatio, pixelRatio);
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.draw = function () {
|
|||
|
var self = this;
|
|||
|
var args = arguments;
|
|||
|
|
|||
|
clearTimeout(self.timeoutID);
|
|||
|
self.timeoutID = setTimeout(function () {
|
|||
|
self._draw();
|
|||
|
}, 15);
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype._draw = function () {
|
|||
|
var map = this._map;
|
|||
|
var size = map.getSize();
|
|||
|
var center = map.getCenter();
|
|||
|
if (center) {
|
|||
|
var pixel = map.pointToOverlayPixel(center);
|
|||
|
this.canvas.style.left = pixel.x - size.width / 2 + 'px';
|
|||
|
this.canvas.style.top = pixel.y - size.height / 2 + 'px';
|
|||
|
this.dispatchEvent('draw');
|
|||
|
this.options.update && this.options.update.call(this);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.getContainer = function () {
|
|||
|
return this.canvas;
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.show = function () {
|
|||
|
if (!this.canvas) {
|
|||
|
this._map.addOverlay(this);
|
|||
|
}
|
|||
|
this.canvas.style.display = 'block';
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.hide = function () {
|
|||
|
this.canvas.style.display = 'none';
|
|||
|
//this._map.removeOverlay(this);
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.setZIndex = function (zIndex) {
|
|||
|
this.canvas.style.zIndex = zIndex;
|
|||
|
};
|
|||
|
|
|||
|
CanvasLayer.prototype.getZIndex = function () {
|
|||
|
return this.zIndex;
|
|||
|
};
|
|||
|
|
|||
|
var tool = {
|
|||
|
merge: function merge(settings, defaults) {
|
|||
|
Object.keys(settings).forEach(function (key) {
|
|||
|
defaults[key] = settings[key];
|
|||
|
});
|
|||
|
},
|
|||
|
//计算两点间距离
|
|||
|
getDistance: function getDistance(p1, p2) {
|
|||
|
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
|
|||
|
},
|
|||
|
//判断点是否在线段上
|
|||
|
containStroke: function containStroke(x0, y0, x1, y1, lineWidth, x, y) {
|
|||
|
if (lineWidth === 0) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
var _l = lineWidth;
|
|||
|
var _a = 0;
|
|||
|
var _b = x0;
|
|||
|
// Quick reject
|
|||
|
if (y > y0 + _l && y > y1 + _l || y < y0 - _l && y < y1 - _l || x > x0 + _l && x > x1 + _l || x < x0 - _l && x < x1 - _l) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if (x0 !== x1) {
|
|||
|
_a = (y0 - y1) / (x0 - x1);
|
|||
|
_b = (x0 * y1 - x1 * y0) / (x0 - x1);
|
|||
|
} else {
|
|||
|
return Math.abs(x - x0) <= _l / 2;
|
|||
|
}
|
|||
|
var tmp = _a * x - y + _b;
|
|||
|
var _s = tmp * tmp / (_a * _a + 1);
|
|||
|
return _s <= _l / 2 * _l / 2;
|
|||
|
},
|
|||
|
|
|||
|
//是否在矩形内
|
|||
|
isPointInRect: function isPointInRect(point, bound) {
|
|||
|
var wn = bound.wn; //西北
|
|||
|
var es = bound.es; //东南
|
|||
|
return point.x >= wn.x && point.x <= es.x && point.y >= wn.y && point.y <= es.y;
|
|||
|
},
|
|||
|
|
|||
|
//是否在圆内
|
|||
|
isPointInCircle: function isPointInCircle(point, center, radius) {
|
|||
|
var dis = this.getDistanceNew(point, center);
|
|||
|
return dis <= radius;
|
|||
|
},
|
|||
|
|
|||
|
//两点间距离
|
|||
|
getDistanceNew: function getDistanceNew(point1, point2) {
|
|||
|
return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var global = typeof window === 'undefined' ? {} : window;
|
|||
|
|
|||
|
var requestAnimationFrame = global.requestAnimationFrame || global.mozRequestAnimationFrame || global.webkitRequestAnimationFrame || global.msRequestAnimationFrame || function (callback) {
|
|||
|
return global.setTimeout(callback, 1000 / 60);
|
|||
|
};
|
|||
|
|
|||
|
var LineGradient = function LineGradient(map, userOptions) {
|
|||
|
var self = this;
|
|||
|
|
|||
|
self.map = map;
|
|||
|
self.lines = [];
|
|||
|
self.pixelList = [];
|
|||
|
|
|||
|
//默认参数
|
|||
|
var options = {
|
|||
|
//线条宽度
|
|||
|
lineWidth: 1
|
|||
|
};
|
|||
|
|
|||
|
//全局变量
|
|||
|
var baseLayer = null,
|
|||
|
animationLayer = null,
|
|||
|
animationFlag = true,
|
|||
|
width = map.getSize().width,
|
|||
|
height = map.getSize().height;
|
|||
|
|
|||
|
function Line(opts) {
|
|||
|
this.name = opts.name;
|
|||
|
this.label = opts.label;
|
|||
|
this.labelColor = opts.labelColor;
|
|||
|
this.path = opts.path;
|
|||
|
this.step = 0;
|
|||
|
}
|
|||
|
|
|||
|
Line.prototype.getPointList = function () {
|
|||
|
var points = [],
|
|||
|
path = this.path;
|
|||
|
if (path && path.length > 0) {
|
|||
|
path.forEach(function (p) {
|
|||
|
points.push({
|
|||
|
code: p.code,
|
|||
|
name: p.name,
|
|||
|
location: p.location,
|
|||
|
pixel: map.pointToPixel(p.location),
|
|||
|
height: p.height,
|
|||
|
value: p.value,
|
|||
|
time: p.time,
|
|||
|
color: p.color
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
return points;
|
|||
|
};
|
|||
|
|
|||
|
Line.prototype.draw = function (context) {
|
|||
|
var pointList = this.pixelList || this.getPointList();
|
|||
|
|
|||
|
for (var i = 0, len = pointList.length; i < len - 1; i++) {
|
|||
|
context.save();
|
|||
|
context.beginPath();
|
|||
|
// context.lineCap = "round";
|
|||
|
// context.lineJoin = "round";
|
|||
|
context.lineWidth = options.lineWidth;
|
|||
|
context.strokeStyle = pointList[i].color;
|
|||
|
context.moveTo(pointList[i].pixel.x, pointList[i].pixel.y);
|
|||
|
context.lineTo(pointList[i + 1].pixel.x, pointList[i + 1].pixel.y);
|
|||
|
context.stroke();
|
|||
|
context.closePath();
|
|||
|
context.restore();
|
|||
|
}
|
|||
|
|
|||
|
var lastpoint = pointList[pointList.length - 1];
|
|||
|
context.font = 'bold 14px Arial';
|
|||
|
context.textAlign = 'left';
|
|||
|
context.textBaseline = 'middle';
|
|||
|
context.fillStyle = this.labelColor;
|
|||
|
context.fillText(this.label, lastpoint.pixel.x, lastpoint.pixel.y);
|
|||
|
};
|
|||
|
|
|||
|
Line.prototype.drawMoveCircle = function (context) {
|
|||
|
var pointList = this.pixelList || this.getPointList();
|
|||
|
|
|||
|
context.save();
|
|||
|
context.fillStyle = '#fff';
|
|||
|
context.shadowColor = '#fff';
|
|||
|
context.shadowBlur = 5;
|
|||
|
context.beginPath();
|
|||
|
context.arc(pointList[this.step].pixel.x, pointList[this.step].pixel.y, 3, 0, Math.PI * 2, true);
|
|||
|
context.fill();
|
|||
|
context.closePath();
|
|||
|
context.restore();
|
|||
|
this.step += 1;
|
|||
|
if (this.step >= pointList.length) {
|
|||
|
this.step = 0;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
Line.prototype.drawArrow = function (context, map) {
|
|||
|
var pointList = this.pixelList || this.getPointList();
|
|||
|
// for (var i = 0, len = pointList.length; i < len - 1; i++) {
|
|||
|
if (this.step >= pointList.length - 1) {
|
|||
|
this.step = 0;
|
|||
|
}
|
|||
|
context.beginPath();
|
|||
|
// context.lineWidth = options.animateLineWidth;
|
|||
|
context.lineWidth = 5;
|
|||
|
context.strokeStyle = '#fff';
|
|||
|
context.moveTo(pointList[this.step].pixel.x, pointList[this.step].pixel.y);
|
|||
|
context.lineTo(pointList[this.step + 1].pixel.x, pointList[this.step + 1].pixel.y);
|
|||
|
context.stroke();
|
|||
|
|
|||
|
context.save();
|
|||
|
context.translate(pointList[this.step + 1].pixel.x, pointList[this.step + 1].pixel.y);
|
|||
|
//我的箭头本垂直向下,算出直线偏离Y的角,然后旋转 ,rotate是顺时针旋转的,所以加个负号
|
|||
|
var ang = (pointList[this.step + 1].pixel.x - pointList[this.step].pixel.x) / (pointList[this.step + 1].pixel.y - pointList[this.step].pixel.y);
|
|||
|
ang = Math.atan(ang);
|
|||
|
pointList[this.step + 1].pixel.y - pointList[this.step].pixel.y >= 0 ? context.rotate(-ang) : context.rotate(Math.PI - ang); //加个180度,反过来
|
|||
|
context.lineTo(-6, -6);
|
|||
|
context.lineTo(0, 6);
|
|||
|
context.lineTo(6, -6);
|
|||
|
context.lineTo(0, 0);
|
|||
|
context.fillStyle = '#fff';
|
|||
|
context.fill(); //箭头是个封闭图形
|
|||
|
context.restore(); //用来恢复Canvas之前保存的状态,否则会影响后续绘制
|
|||
|
|
|||
|
this.step += 1;
|
|||
|
// }
|
|||
|
};
|
|||
|
|
|||
|
//底层canvas渲染,标注,线条
|
|||
|
var brush = function brush() {
|
|||
|
var baseCtx = baseLayer.canvas.getContext('2d');
|
|||
|
if (!baseCtx) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
addLine();
|
|||
|
|
|||
|
baseCtx.clearRect(0, 0, width, height);
|
|||
|
|
|||
|
self.pixelList = [];
|
|||
|
self.lines.forEach(function (line) {
|
|||
|
self.pixelList.push({
|
|||
|
name: line.name,
|
|||
|
label: line.label,
|
|||
|
labelColor: line.labelColor,
|
|||
|
data: line.getPointList()
|
|||
|
});
|
|||
|
line.draw(baseCtx);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
//中层canvas渲染,动画效果
|
|||
|
var render = function render() {
|
|||
|
var animationCtx = animationLayer.canvas.getContext('2d');
|
|||
|
if (!animationCtx) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (!animationFlag) {
|
|||
|
animationCtx.clearRect(0, 0, width, height);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
animationCtx.fillStyle = 'rgba(0,0,0,.2)';
|
|||
|
var prev = animationCtx.globalCompositeOperation;
|
|||
|
animationCtx.globalCompositeOperation = 'destination-in';
|
|||
|
animationCtx.fillRect(0, 0, width, height);
|
|||
|
animationCtx.globalCompositeOperation = prev;
|
|||
|
|
|||
|
var lines = self.lines;
|
|||
|
for (var i = 0; i < lines.length; i++) {
|
|||
|
var line = lines[i];
|
|||
|
// line.drawMoveCircle(animationCtx); //移动圆点
|
|||
|
line.drawArrow(animationCtx); //画箭头
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
//鼠标事件
|
|||
|
var mouseInteract = function mouseInteract() {
|
|||
|
map.addEventListener('movestart', function () {
|
|||
|
animationFlag = false;
|
|||
|
});
|
|||
|
|
|||
|
map.addEventListener('moveend', function () {
|
|||
|
animationFlag = true;
|
|||
|
self.lines = []; //解决拖动后多余的小圆点bug,没想明白,暂时这样
|
|||
|
});
|
|||
|
|
|||
|
map.addEventListener('zoomstart', function () {
|
|||
|
animationFlag = false;
|
|||
|
});
|
|||
|
|
|||
|
map.addEventListener('zoomend', function () {
|
|||
|
animationFlag = true;
|
|||
|
self.lines = [];
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
var addLine = function addLine() {
|
|||
|
if (self.lines && self.lines.length > 0) return;
|
|||
|
var dataset = options.data;
|
|||
|
var legend = new Legend();
|
|||
|
|
|||
|
dataset.forEach(function (l, i) {
|
|||
|
var line = new Line({
|
|||
|
name: l.name,
|
|||
|
label: l.label,
|
|||
|
labelColor: l.labelColor,
|
|||
|
path: []
|
|||
|
});
|
|||
|
l.data.forEach(function (p, j) {
|
|||
|
line.path.push({
|
|||
|
code: p.code,
|
|||
|
name: p.name,
|
|||
|
location: new BMap.Point(p.Longitude, p.Latitude),
|
|||
|
height: p.height,
|
|||
|
value: p.value,
|
|||
|
time: p.time,
|
|||
|
color: legend.getColor(p.value).color
|
|||
|
});
|
|||
|
});
|
|||
|
self.lines.push(line);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
var Legend = function Legend() {
|
|||
|
var options = {
|
|||
|
width: 400,
|
|||
|
height: 15,
|
|||
|
range: [700, 750, 800, 850, 900, 950, 1000, 1050, 1100],
|
|||
|
// gradient: {
|
|||
|
// 0: 'rgba(100,255,51,1)',
|
|||
|
// 0.167: 'rgba(153,255,51,1)',
|
|||
|
// 0.333: 'rgba(204,255,51,1)',
|
|||
|
// 0.5: 'rgba(255,255,71,0.8)',
|
|||
|
// 0.667: 'rgba(255,250,150,1',
|
|||
|
// 0.833: 'rgba(255,187,102,0.9)',
|
|||
|
// 1: 'rgba(255,119,68,0.9)'
|
|||
|
// }
|
|||
|
gradient: {
|
|||
|
0.1: '#fe0000',
|
|||
|
0.4: '#ffaa01',
|
|||
|
0.7: '#b0e000',
|
|||
|
1.0: '#38a702'
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var canvas = document.createElement('canvas');
|
|||
|
var context = canvas.getContext('2d');
|
|||
|
canvas.width = options.width;
|
|||
|
canvas.height = options.height;
|
|||
|
var grad = context.createLinearGradient(0, 0, canvas.width, canvas.height);
|
|||
|
for (var gradient in options.gradient) {
|
|||
|
grad.addColorStop(gradient, options.gradient[gradient]);
|
|||
|
}
|
|||
|
context.fillStyle = grad;
|
|||
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
|||
|
|
|||
|
this.d2Hex = function (d) {
|
|||
|
// Converts a decimal number to a two digit Hex value
|
|||
|
var hex = Number(d).toString(16);
|
|||
|
while (hex.length < 2) {
|
|||
|
hex = "0" + hex;
|
|||
|
}
|
|||
|
return hex.toUpperCase();
|
|||
|
};
|
|||
|
this.getRgbColor = function (point) {
|
|||
|
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|||
|
var data = imageData.data;
|
|||
|
var i = (point.Y * canvas.width + point.X) * 4;
|
|||
|
var rgb = [],
|
|||
|
color = '#',
|
|||
|
objRgbColor = {
|
|||
|
"rgb": rgb,
|
|||
|
"color": color
|
|||
|
};
|
|||
|
for (var j = 0; j < 3; j++) {
|
|||
|
rgb.push(data[i + j]);
|
|||
|
color += this.d2Hex(data[i + j]);
|
|||
|
}
|
|||
|
objRgbColor.color = color;
|
|||
|
return objRgbColor;
|
|||
|
};
|
|||
|
this.getColor = function (value) {
|
|||
|
var colorValue = value - options.range[0];
|
|||
|
var point = Math.round(colorValue * canvas.width / (options.range[options.range.length - 1] - options.range[0]));
|
|||
|
return this.getRgbColor({
|
|||
|
X: point,
|
|||
|
Y: 1
|
|||
|
});
|
|||
|
};
|
|||
|
};
|
|||
|
|
|||
|
self.init(userOptions, options);
|
|||
|
|
|||
|
baseLayer = new CanvasLayer({
|
|||
|
map: map,
|
|||
|
update: brush
|
|||
|
});
|
|||
|
|
|||
|
animationLayer = new CanvasLayer({
|
|||
|
map: map,
|
|||
|
update: render
|
|||
|
});
|
|||
|
|
|||
|
mouseInteract();
|
|||
|
|
|||
|
(function drawFrame() {
|
|||
|
self.timer = setTimeout(function () {
|
|||
|
self.animationId = requestAnimationFrame(drawFrame);
|
|||
|
render();
|
|||
|
}, 1000 / 10);
|
|||
|
|
|||
|
// requestAnimationFrame(drawFrame);
|
|||
|
// render();
|
|||
|
})();
|
|||
|
|
|||
|
this.clickEvent = this.clickEvent.bind(this);
|
|||
|
|
|||
|
this.bindEvent();
|
|||
|
};
|
|||
|
|
|||
|
LineGradient.prototype.init = function (settings, defaults) {
|
|||
|
//合并参数
|
|||
|
tool.merge(settings, defaults);
|
|||
|
this.options = defaults;
|
|||
|
};
|
|||
|
|
|||
|
LineGradient.prototype.bindEvent = function (e) {
|
|||
|
var map = this.map;
|
|||
|
if (this.options.methods) {
|
|||
|
if (this.options.methods.click) {
|
|||
|
map.setDefaultCursor("default");
|
|||
|
map.addEventListener('click', this.clickEvent);
|
|||
|
}
|
|||
|
if (this.options.methods.mousemove) {
|
|||
|
map.setDefaultCursor("default");
|
|||
|
map.addEventListener('mousemove', this.clickEvent);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
LineGradient.prototype.clickEvent = function (e) {
|
|||
|
var self = this,
|
|||
|
flag = false,
|
|||
|
lines = self.pixelList;
|
|||
|
|
|||
|
if (lines.length > 0) {
|
|||
|
lines.forEach(function (line, i) {
|
|||
|
for (var j = 0; j < line.data.length; j++) {
|
|||
|
var beginPt = line.data[j].pixel;
|
|||
|
if (line.data[j + 1] == undefined) {
|
|||
|
return;
|
|||
|
}
|
|||
|
var endPt = line.data[j + 1].pixel;
|
|||
|
var curPt = e.pixel;
|
|||
|
var inCircle = tool.isPointInCircle(curPt, beginPt, self.options.lineWidth);
|
|||
|
if (inCircle) {
|
|||
|
// self.options.methods.click(e, line.name);
|
|||
|
self.options.methods.mousemove(e, line.data[j]);
|
|||
|
flag = true;
|
|||
|
return;
|
|||
|
}
|
|||
|
// var isOnLine = tool.containStroke(beginPt.x, beginPt.y, endPt.x, endPt.y, self.options.lineWidth, curPt.x, curPt.y);
|
|||
|
// if (isOnLine) {
|
|||
|
// // self.options.methods.click(e, line.name);
|
|||
|
// self.options.methods.mousemove(e, line);
|
|||
|
// return;
|
|||
|
// }
|
|||
|
}
|
|||
|
});
|
|||
|
if (!flag) {
|
|||
|
document.getElementById('tooltips').style.visibility = 'hidden';
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
return LineGradient;
|
|||
|
|
|||
|
})));
|