最近在使用d3和threejs结合来做些3D特效,对D3不是很了解,所以通过这个用D3绘制中国地图并标点的例子来加深下对D3的理解。
准备
我使用的是目前最新版的D3v5.7.0
。除过D3,我们还需要给地图着色,我们使用D3的库:d3-scale-chromatic。
我们是通过svg来绘制地图的,需要一个中国地理信息的geojson文件,关于geojson文件规范参考:geojson,可以去Natural Earth上下载,这里我们使用现成的中国🇨🇳geojson文件:查看json文件
在地图绘制出来后,我们需要给一些城市标点,所以还需要整理一些城市的经纬度。
因为定位用到经纬度的转换,所以我们还需要了解下经纬度的知识。
- 经线 地球上的一个点离本初子午线的南北方向走线以西(西经)或以东(东经)的度数。本初子午线的经度是0°,地球上其他地点的经度是向东到180°或向西180°。东经180°即西经180°,月等同于国际日期变更线,国际日期变更线的两边,日期相差一日。
- 纬度 地球上的一个点与地球球心的连线和地球赤道面所成的线面角。其数值在[0, 90]之间,位于赤道以北的叫做北纬,记做N;位于赤道以南的叫做南纬,记做S。
东经为正数,西经为负数;北纬为正数,南纬为负数(上北下南,左西右东,东北为正,西南为负)。
开始
我是在angular项目里面开发的,所以只记录多出的步骤。
安装类库
安装d3.js:
1 | npm i d3 --save |
1 | npm i d3-scale-chromatic --save |
安装d3-geo(从球体到平面的投影):
1 | npm i d3-geo --save |
安装d3-array(d3-geo依赖):
1 | npm i d3-array --save |
页面元素
由于使用的angular框架,动态插入元素在组件里面会有问题,所以直接将元素放在页面上。我们需要绘制地图,然后当鼠标移上去的时候出现一个tooltips提示,所以我们的html为这样:
1 | <div id="world"> |
对应的css文件:
1 | #world { |
js准备
导入类库:
1 | import * as THREE from 'three'; |
定义绘制的svg的大小:
1 | const width = 1024; |
设置投影函数:
1 | const projection = geo.geoMercator() |
我们这里通过d3-geo
的“墨卡托投影”来创建了一个投影方法,用来将球体投影到平面上。按行看看投影的配置:
.scale(550)
投影的比例因子,可以按比例放大投影。.center([105, 38])
将中心点设置为经度105,纬度38,这里正好是中国地图的中心点。.translate([width / 2, height / 2])
将投影的中心设置为svg的中心。
综合一下,上述的设置可以保证我们将中国地图的投影正好投影在svg的中心,并且放大一定倍率。
绘制地图
前面我们准备好了基本页面框架,也设置好了投影的配置,现在可以用svg来绘制地图了
创建路径生成器
创建地理路径生成器,使用当前设置的投影:
1 | const path = geo.geoPath(projection); |
创建颜色比例尺
1 | const colors = d3.scaleOrdinal(d3Color.schemeBrBG[11]); |
加载地图json并生成地图
首先来加载json文件,我们使用d3内置的json请求方法:
1 | async getJson() { |
读取svg元素,并设置svg的宽高:
1 | const svg = d3.select('#svg') |
这里使用异步方法,可以减少回调嵌套
1 | this.getJson().then(data => { |
逐行来分析代码:
.selectAll('path')
选中svg中所有匹配path
的元素节点.data(data.features)
绑定当前选择器和数据。data的操作是“update”,表明选择的dom元素已经和数据进行了绑定.enter()
返回输入(enter)选择:当前选择中存在,但是当前dom元素中还不存在的每个数据元素的占位符节点。此方法只在由data()
方法返回的更新选择中定义。此外,输入选择(enter)只定义了append
、insert
、select
和call
操作符.append('path')
在当前选择的每个元素最后追加具有指定名称的新元素,返回包含追加元素的新选择.attr('d', path)
为所有选中元素设置名称为”d”的属性,值为path里面的每个值。即给svg添加的path元素设置d属性,d属性的值是需要绘制的路径。.attr('fill', function(d, i) {return colors(i)})
为前面设置的svg的path元素填充颜色,颜色是从前面的设置的颜色比例尺中读取。.attr('stroke', 'rgba(255, 255, 255, 1'))
和上面一样,为svg的path元素根据路径描边,颜色为白色。.attr('stroke-width', 1)
同上,为svg的path元素设置路径描边的宽度。
具体d3的选择器方法可以参考这个wiki。
综合一下:从json中得到地图的路径,然后挨个添加到svg中的path标签,然后填充地图,并给路径描边。运行一下,可以看到已经生成了中国地图:
nice~😆
ps:发现颜色比例尺不是很够,导致有些地方有重复色块,目前没什么解决方案。写了个随机生成颜色的方案,刷新一下都不一样,哈哈哈~
1 | return '#' + (Math.floor(Math.random() * 0xFFFFFF)).toString(16); |
定位城市坐标
我们需要通过已知的城市的经纬度来在地图上定点。经纬度是球面的描述,而我们的地图是二维平面,我们需要有个转换的方法,这就要用到前面定义的墨卡托投影方法了。转换方法:
1 | // log 经度,lat 纬度 |
城市坐标json
偷懒一点,这里没请求json文件,而是直接定义好的经纬度数组:
1 | const places = [ |
标点并画圆
通过转换的坐标来给svg添加g元素进行定点:
1 | const location = svg.selectAll('.location') |
通过定的点给svg的g元素添加circle元素,并填充颜色画圆。
1 | location.append('circle') |
添加鼠标互动
获得tooltip的dom节点:
1 | const tooltip = d3.select('#tooltip'); |
给svg的g标签添加鼠标效果,鼠标一上去出现tooltip文字,并将圆圈放大二倍,且伴随着延时动画;鼠标移走也是同样相反的动画。
1 | location.on('mouseover', function (d) { |
ok,完成~