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来实现。

演示示例实例效果图如下:

轨迹绘制方法如下:

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实现平面地图迁徙图

下载地址:canvas实现平面地图迁徙图