**
前言
**
最近在做自己維護的一個可視化工具的時候,在添加基於echart的雷達圖的時候,按照echart官網案例寫完發現在自己項目中無法正常運行,排查了一番發現是我項目中echart的版本太低。找到問題原因之後就升級echart,但是升級echart之後發現原本正常運行的echart地圖組件又無法使用,百度了一番發現echart在最新的版本中地圖數據進行了切換,原先的數據由於不符合規範被砍掉,導致2.0以前的echart地圖都無法正常使用了。既然出現這樣的情況,那就沒辦法了,項目中使用的echart地圖有三種類型,遷徙圖、標記圖和熱力圖。思來想去,echart最終還是要升級,所以就決定自己開發項目中需要的基於canvas的遷徙圖,標記圖和熱力圖。這篇穩重主要就闡述canvas如何實現類似於echart中的遷徙圖。
原理說明
1、軌跡開始位置和結束位置之間的軌跡通過二次貝塞爾曲線quadraticCurveTo來實現,其中繪製貝塞爾曲線的控制點需要根據開始位置和結束位置來確定;
2、軌跡上運行的標記通過二次貝塞爾曲線反推獲取貝塞爾曲線不同位置的x,y座標,然後通過不斷設置軌跡上點位置來實現軌跡上點;
3、軌跡上點移動和開始和結束位置動畫通過requestAnimationFrame來實現,切換重回canvas的時候需要調用cancelAnimationFrame來實現。
演示示例實例效果圖如下:
軌跡繪製方法
function drawTravel (start,end) {
var middleX = 0;
var middleY = 0;
var gnt1 = ctx.createLinearGradient(start.pos[0],start.pos[1],end.pos[0],end.pos[1]);
gnt1.addColorStop(0,start.color);
gnt1.addColorStop(1,end.color);
if (start.pos[0] > end.pos[0] && start.pos[1] > end.pos[1]) {
middleX = (start.pos[0] + end.pos[0]) / 2 * rate;
middleY = (start.pos[1] + end.pos[1]) / 2 * (2 - rate);
}
if (start.pos[0] > end.pos[0] && start.pos[1] < end.pos[1]) {
middleX = (start.pos[0] + end.pos[0]) / 2 * rate;
middleY = (start.pos[1] + end.pos[1]) / 2 * rate;
}
if (start.pos[0] < end.pos[0] && start.pos[1] > end.pos[1]) {
middleX = (start.pos[0] + end.pos[0]) / 2 * (2 - rate);
middleY = (start.pos[1] + end.pos[1]) / 2 * (2 - rate);
}
if (start.pos[0] < end.pos[0] && start.pos[1] < end.pos[1]) {
middleX = (start.pos[0] + end.pos[0]) / 2 * (2 - rate);
middleY = (start.pos[1] + end.pos[1]) / 2 * rate;
}
ctx.strokeStyle = gnt1;
ctx.beginPath();
ctx.moveTo(start.pos[0],start.pos[1]);
ctx.quadraticCurveTo(middleX,middleY,end.pos[0],end.pos[1]);
ctx.stroke();
// 獲取貝塞爾曲線上的點
for (var i = 0; i < dotNumber; i++) {
var _t = (t - animationDotSpeed * i * 2) >= 0 ? (t - animationDotSpeed * i * 2) : 1 + (t - animationDotSpeed * i * 2);
var x = Math.pow(1-_t, 2) * start.pos[0] + 2 * _t * (1-_t) * middleX + Math.pow(_t, 2) * end.pos[0];
var y = Math.pow(1-_t, 2) * start.pos[0] + 2 * _t * (1-_t) * middleY + Math.pow(_t, 2) * end.pos[1];
ctx.fillStyle = 'rgba(' + dotColor.split('(')[1].split(')')[0] + ',' + (1 - 1 / dotNumber * i) + ')'
ctx.beginPath();
ctx.arc(x,y,dotRadius,0,2*Math.PI);
ctx.fill();
ctx.closePath()
}
}
開始位置和結束位置標記繪製方法
function drawCoordinate (coordinate) {
ctx.fillStyle = centerColor;
ctx.beginPath();
ctx.arc(coordinate.pos[0], coordinate.pos[1], radiusCenter,0,2*Math.PI);
ctx.closePath();
ctx.fill()
ctx.fillStyle = ringColor.split(',').slice(0,3).join(',') + ',0.5)';
ctx.beginPath();
ctx.arc(coordinate.pos[0], coordinate.pos[1], radiusCenter + 5,0,2*Math.PI);
ctx.closePath();
ctx.fill()
if (radiusRing >= radiusRingMax) {
radiusRing = radiusRingMin;
}
ctx.fillStyle = ringColor;
ctx.beginPath();
ctx.arc(coordinate.pos[0], coordinate.pos[1], radiusRing,0,2*Math.PI);
ctx.closePath();
ctx.fill()
radiusRing += animationSpeed;
ringColor = ringColor.split(',').slice(0,3).join(',') + ',' + (0.5 - (radiusRing - radiusRingMin) * 0.02) + ')';
}
執行canvas繪製方法
function draw () {
cancelAnimationFrame(requestAnimationFrameName);
ctx.clearRect(0,0,width,height)
array.forEach(function (item, index) {
drawCoordinate(item);
if (index > 0) {
drawTravel(array[0],item)
}
})
if (t >= 1) {
t = 0;
}
t += animationDotSpeed;
requestAnimationFrameName = requestAnimationFrame(draw)
}
實例預覽地址:canvas實現平面地圖遷徙圖
希望上述說明能夠幫助到您