1. 程式人生 > >canvas實現平面遷徙圖

canvas實現平面遷徙圖

前言

最近在做自己維護的一個視覺化工具的時候,在新增基於echart的雷達圖的時候,按照echart官網案例寫完發現在自己專案中無法正常執行,排查了一番發現是我專案中echart的版本太低。找到問題原因之後就升級echart,但是升級echart之後發現原本正常執行的echart地圖元件又無法使用,百度了一番發現echart在最新的版本中地圖資料進行了切換,原先的資料由於不符合規範被砍掉,導致2.0以前的echart地圖都無法正常使用了。既然出現這樣的情況,那就沒辦法了,專案中使用的echart地圖有三種類型,遷徙圖、標記圖和熱力圖。思來想去,echart最終還是要升級,所以就決定自己開發專案中需要的基於canvas的遷徙圖,標記圖和熱力圖。這篇穩重主要就闡述canvas如何實現類似於echart中的遷徙圖。

原理說明

1、軌跡開始位置和結束位置之間的軌跡通過二次貝塞爾曲線quadraticCurveTo來實現,其中繪製貝塞爾曲線的控制點需要根據開始位置和結束位置來確定;

2、軌跡上執行的標記通過二次貝塞爾曲線反推獲取貝塞爾曲線不同位置的x,y座標,然後通過不斷設定軌跡上點位置來實現軌跡上點;

3、軌跡上點移動和開始和結束位置動畫通過requestAnimationFrame來實現,切換重回canvas的時候需要呼叫cancelAnimationFrame來實現。

演示示例例項效果圖如下:

軌跡繪製方法

 1 function drawTravel (start,end) {
 2   var middleX = 0;
 3   var middleY = 0;
 4   var gnt1 = ctx.createLinearGradient(start.pos[0],start.pos[1],end.pos[0],end.pos[1]);
 5   gnt1.addColorStop(0,start.color);
 6   gnt1.addColorStop(1,end.color);
 7   if (start.pos[0] > end.pos[0] && start.pos[1] > end.pos[1]) {
 8      middleX = (start.pos[0] + end.pos[0]) / 2 * rate;
 9      middleY = (start.pos[1] + end.pos[1]) / 2 * (2 - rate);
10   } 
11   if (start.pos[0] > end.pos[0] && start.pos[1] < end.pos[1]) {
12      middleX = (start.pos[0] + end.pos[0]) / 2 * rate;
13      middleY = (start.pos[1] + end.pos[1]) / 2 * rate;
14   } 
15   if (start.pos[0] < end.pos[0] && start.pos[1] > end.pos[1]) {
16      middleX = (start.pos[0] + end.pos[0]) / 2 * (2 - rate);
17      middleY = (start.pos[1] + end.pos[1]) / 2 * (2 - rate);
18   } 
19   if (start.pos[0] < end.pos[0] && start.pos[1] < end.pos[1]) {
20      middleX = (start.pos[0] + end.pos[0]) / 2 * (2 - rate);
21      middleY = (start.pos[1] + end.pos[1]) / 2 * rate;
22   }
23   ctx.strokeStyle = gnt1;
24   ctx.beginPath();
25   ctx.moveTo(start.pos[0],start.pos[1]);
26   ctx.quadraticCurveTo(middleX,middleY,end.pos[0],end.pos[1]);
27   ctx.stroke();
28   // 獲取貝塞爾曲線上的點
29   for (var i = 0; i < dotNumber; i++) {
30     var _t = (t - animationDotSpeed * i * 2) >= 0 ? (t - animationDotSpeed * i * 2) : 1 + (t - animationDotSpeed * i * 2);
31     var x = Math.pow(1-_t, 2) * start.pos[0] + 2 * _t * (1-_t) * middleX + Math.pow(_t, 2) * end.pos[0];
32     var y = Math.pow(1-_t, 2) * start.pos[0] + 2 * _t * (1-_t) * middleY + Math.pow(_t, 2) * end.pos[1];
33     ctx.fillStyle = 'rgba(' + dotColor.split('(')[1].split(')')[0] + ',' + (1 - 1 / dotNumber * i) + ')'
34     ctx.beginPath();
35     ctx.arc(x,y,dotRadius,0,2*Math.PI);
36     ctx.fill();
37     ctx.closePath()
38   }
39 }

開始位置和結束位置標記繪製方法

 1 function drawCoordinate (coordinate) {
 2   ctx.fillStyle = centerColor;
 3   ctx.beginPath();
 4   ctx.arc(coordinate.pos[0], coordinate.pos[1], radiusCenter,0,2*Math.PI);
 5   ctx.closePath();
 6   ctx.fill()
 7   ctx.fillStyle = ringColor.split(',').slice(0,3).join(',') + ',0.5)';
 8   ctx.beginPath();
 9   ctx.arc(coordinate.pos[0], coordinate.pos[1], radiusCenter + 5,0,2*Math.PI);
10   ctx.closePath();
11   ctx.fill()
12   if (radiusRing >= radiusRingMax) {
13       radiusRing = radiusRingMin;
14   }
15   ctx.fillStyle = ringColor;
16   ctx.beginPath();
17   ctx.arc(coordinate.pos[0], coordinate.pos[1], radiusRing,0,2*Math.PI);
18   ctx.closePath();
19   ctx.fill()
20   radiusRing += animationSpeed;
21   ringColor = ringColor.split(',').slice(0,3).join(',') + ',' + (0.5 - (radiusRing - radiusRingMin) * 0.02) + ')';
22 }

執行canvas繪製方法

 1 function draw () {
 2   cancelAnimationFrame(requestAnimationFrameName);
 3   ctx.clearRect(0,0,width,height)
 4   array.forEach(function (item, index) {
 5     drawCoordinate(item);
 6     if (index > 0) {
 7         drawTravel(array[0],item)
 8     }
 9   })
10   if (t >= 1) {
11       t = 0;
12   }
13   t += animationDotSpeed;
14   requestAnimationFrameName = requestAnimationFrame(draw)
15 }

例項預覽地址:canvas實現平面地圖遷徙圖

希望上述說明能夠幫助到您。

&n