2016-04-21
使用 d3.js 绘制地图和飞线动效。涉及内容:GeoJSON、地图投影、贝塞尔曲线、中间帧动画、蒙板等
一、绘制地图
以绘制海南省为例:
1. 拿到原始数据,比如构成海南省边界的一系列经纬点。
2. 转成适合 d3 识别的格式,如 GeoJSON
- GeoJSON 是一种专门用于描述地理数据且基于 JSON 的公开标准。
- 如海南省边界 GeoJSON 格式如下:
3. 将经纬度地理坐标(三维)转换成平面直角坐标(二维),即 “地图投影”。
为什么称之为 “投影”?
以圆柱投影为例,假想球中心有一处光源,球体的影子印在圆柱上,再把圆柱展开。
图片来源自 wikipedia
d3 中内置了多种地球投影函数,如 d3.geo.mercator()
等,调用非常方便。
4. 生成 SVG 路径
调用 d3 中的路径生成器 —— d3.geo.path()
,生成 SVG 路径。
// mercator 投影法,即正轴等角圆柱投影
var projection = d3.geo.mercator()
// 调用路径生成器,加入投影函数,生成路径。
d3.geo.path().projection(projection);
最后将生成的 SVG 路径数值,放入
的 d
属性中,即可渲染出海南省
二、绘制飞线
1. 找到城市
原始数据
[{
"from": {
"name":"拉萨",
"coordinate": [116.4551,40.2539]
},
"to": {
"name":"北京",
"coordinate": [91.1865,30.1465]
}
}]
三维转二维坐标
与绘制地图时相似,使用 projection()
,把经纬度转为直角坐标。
2. 绘制路径
二次贝塞尔曲线
// 起始点为(50,50),控制点在(50,100),结束点为(100,100)
起始点拉萨坐标,结束点北京坐标,控制点由计算得出,如下:
三、飞线动画
使用 attrTween()
,插入中间帧函数,不断变更
中的 d
属性,呈现出线条在“一点点绘制出来”的效果。
// 过渡动画
flyline.transition()
// 动画时长
.duration(1800)
// 为属性 d ,设置中间帧过渡
.attrTween('d', function(d){
var l = $path.getTotalLength();
return function(t){
var p = $path.getPointAtLength(t * l)
return '最终返回的值'
}
});
说明:
- $path 变量为完整的飞线路径,即最终效果的飞线;
-
getTotalLength()
得出该
的总长度; - 此时的 t,即是中间帧的时刻。值范围为[0, 1],总数量大概会有 100 帧左右(为何是 100 帧左右,而不是个确切的数?暂没搞懂..)
-
getPointAtLength()
传入路径上距离,返回该点的 x,y 坐标
新的控制点如何确定?
通过起始点和原控制点,求出新的控制点
图片来源 cnblogs
取 p01 为新的控制点。
通过新控制点和终点(变量),起始点不变,动态一次次绘制飞线。
function valueTween(d){
var $path = d3.select(this.parentNode).select('.line-basic');
// 基路径
var coord = $path.attr('d').replace(/(M|Q)/g, '').match(/((\d|\.)+)/g);
var x1 = +coord[0], y1 = +coord[1], // 起点
x2 = +coord[2], y2 = +coord[3], // 控制点
x3 = +coord[4], y3 = +coord[5]; // 终点
var l = $path.node().getTotalLength();
return function(t){
// 新的终点
var p = $path.node().getPointAtLength(t * l);
// 新的控制点
var x = (1-t) * x1 + t * x2;
var y = (1-t) * y1 + t * y2;
return 'M'+x1+','+y1+' Q'+x+','+y+' '+p.x+','+p.y;
}
}
四、飞线样式
1. 使用 svg 蒙板,渲染飞线“头粗尾巴细”的效果
(1) 添加圆形蒙板
- 圆心 cy,cx 为飞线终点;
- 设置的半径即为可视区域;
- 蒙板动态跟随飞线变化。
(2) svg 中
标签
(3) 为蒙板添加径向渐变,使得飞线有“头部深,尾部浅至透明”的效果
2. 为飞线添加一个亮色的头部
3. 优化
原因是蒙板半径没有自适应。当半径为一个固定数值时,将导致长度小于此值的飞线没掉了尾部渐变效果。如下图,白色圆圈为蒙板范围:
优化:使蒙板半径随着两点(起点与终点)的距离而变化
五、总结
整个流程如下:
- 加载地图数据,绘制出地图;
- 轮询飞线数据,保存在数据中心;
- 飞线池 FlylinePond 初始化 生成飞线实体;
- 启动飞线数据运输带 - 不断绘制(只要数据池中有数据)
- draw() -已知起点和终点,二次贝塞尔曲线
- 绘制飞线基本路线
- 飞线动画,不断改变 d 属性; attrTween
- 飞线头部
- 蒙板
- 结束圆圈
- 终点文字
效果图:
效果图.gif
六、资料:
- eChart(百度)-地图-模拟迁徙
- 贝塞尔曲线原理
- D3.js 入门系列 — 地图的制作
- Marker animation along SVG path element with D3.js
- D3.js 官网 Tutorials
- wikipedia: 麥卡托投影法