D3.js制作带悬浮提示框的渐变色中国地图(使用node.js提供服务)
一.效果图
使用D3来制作中国地图主要有几个地方需要注意:
- 需要用到投影函数,并挂在在路径生成器上。
- 由于同源策略限制的原因,需要通过服务器来返回地图文件,比如
china.json
这种。 - 如果需要做渐变色渲染或者显示标注,需要额外的数据,并通过服务器返回。
- 要区分开
topojson
和geojson
两种格式的数据的不同,他们的加载模式也有所不同,相对于geojson
数据,topojson
文件更小,渲染时更节省Dom空间。
二.代码示例
我把代码部分分为前端和后端,咱们先看前端的部分。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>topojsonMAP</title>
<style>
/* Tooltip CSS */
.d3-tip {
line-height: 1.5;
font-weight: 400;
font-family: "avenir next", Arial, sans-serif;
padding: 6px;
background: rgba(0, 0, 0, 0.6);
color: #FFA500;
border-radius: 1px;
pointer-events: none;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 8px;
width: 100%;
line-height: 1.5;
color: rgba(0, 0, 0, 0.6);
position: absolute;
pointer-events: none;
}
.d3-tip.n:after {
content: "\25BC";
margin: -1px 0 0 0;
top: 100%;
left: 0;
text-align: center;
}
</style>
</head>
<body>
</body>
</html>
<script src="/static/js/d3-v4.js"></script>
<script src="/static/js/topojson.js"></script> <!-- 用来处理topojson格式的地图文件 -->
<script src="/static/js/d3-tip.js"></script> <!-- 用来生成tip提示框 -->
<script>
const width = 1200;
const height = 1000;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html((d) => {
return "<strong>Country: </strong><span class='details'>" + d.properties.name + "<br></span>"
});
//创建投影函数
var projection = d3.geoMercator()
.center([107, 31])
.scale(950)
.translate([width / 2, height / 2 + height / 8])
//创建地理路径生成器
var path = d3.geoPath()
.projection(projection);
svg.call(tip);
//读取json文件并创建地图
d3.json("/static/china.topojson", (error, topoRoot) => {
if (error)
return console.error(error);
//将TopoJSON对象转换成GeoJSON,保存在georoot中
var geoRoot = topojson.feature(topoRoot, topoRoot.objects.china);
//输出GeoJSON对象
console.log('geoRoot', geoRoot);
//添加中国各种的路径元素
var provinces = svg.append("g")
.attr("class", "countries")
.selectAll("path")
.data(geoRoot.features)
.enter()
.append("path")
.attr("class", "province")
.style("fill", "#ccc")
.attr("d", path)
.on("mouseover", (d) => {
tip.show(d);
d3.select(this)
.style("opacity", 0.8)
})
.on("mouseout", (d) => {
tip.hide(d);
d3.select(this)
.style("opacity", 1)
});
var $ = {
ajax: (url) => {
var request = new XMLHttpRequest();
request.open('get', url);
request.send();
request.onreadystatechange = () => {
if (request.readyState === 4 && request.status === 200) {
var json = JSON.parse(request.responseText);
//将读取到的数据存到数组values,令其索引号为各省的名称
var cityArr = [];
for (var i = 0; i < json.provinces.length; i++) {
var name = json.provinces[i].name;
var value = json.provinces[i].value;
cityArr[name] = value;
}
//求最大值和最小值
var maxvalue = d3.max(json.provinces, function (d) {
return d.value;
});
var minvalue = 0;
//定义一个线性比例尺,将最小值和最大值之间的值映射到[0, 1]
var linear = d3.scaleLinear()
.domain([minvalue, maxvalue])
.range([0, 1]);
//创建颜色插值函数
var colorFunc = d3.interpolate("#395988", "#4D8DC3");
//设定各省份的填充色
provinces.style("fill", function (d, i) {
var t = linear(cityArr[d.properties.name]);
var color = colorFunc(t);
return color.toString();
})
}
}
}
}
$.ajax('/stats/data');
});
</script>
我在上述代码中已经添加了很清晰的注释,但是需要注意的仍然有几点:
-
d3-tip.js
这个文件大家在github上下载的时候,如果使用最新版本的话以上代码是会报错的,如果你严格按照github上这个项目作者所提示的一样去引用,不好意思,还是会报错,但是你如果换成2013这个版本就可以运行起来,如下图版本: -
d3.json()
这个高阶函数并不是必须要使用的。你们可以在上面看到,我用原生js封装了一个ajax函数$.ajax()
用来处理get请求,也能得到相应数据,当然,你如果通过服务器获取数据,记住要将它格式化成json格式的数据。 -
TopoJSON
是GeoJSON
按拓扑学编码后的扩展形式,是由 D3 的作者 Mike Bostock 制定的。相比GeoJSON
直接使用 Polygon、Point 之类的几何体来表示图形的方法,TopoJSON
中的每一个几何体都是通过将共享边(被称为arcs)整合后组成的。TopoJSON
消除了冗余,文件大小缩小了 80%,因为:1.边界线只记录一次(例如广西和广东的交界线只记录一次)2.地理坐标使用整数,不使用浮点数。
下面我们看看后端部分,后端由node.js提供服务器。
const express = require('express');
const path = require('path')
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
/*返回静态资源 public是我存放静态资源的文件夹,static是我准备返回静态资源的url*/
app.use('/static', express.static(path.join('public')));
app.get('/map/faster', (req, res) => {
res.render('map_faster');
});
app.get('/stats/data', (req, res) => {
const data = {
"name": "中国",
"provinces": [{
"name": "北京",
"value": 14149
},
{
"name": "天津",
"value": 2226.41
},
{
"name": "河北",
"value": 1544.94
},
{
"name": "山西",
"value": 3720.24
},
{
"name": "内蒙古",
"value": 2771.96
},
{
"name": "辽宁",
"value": 6263.69
},
{
"name": "吉林",
"value": 4494.77
},
{
"name": "黑龙江",
"value": 3835.48
},
{
"name": "上海",
"value": 5493.23
},
{
"name": "江苏",
"value": 12299.72
},
{
"name": "浙江",
"value": 14151.74
},
{
"name": "安徽",
"value": 1562.67
},
{
"name": "福建",
"value": 14225.67
},
{
"name": "江西",
"value": 384.73
},
{
"name": "山东",
"value": 9923.65
},
{
"name": "河南",
"value": 1611.41
},
{
"name": "湖北",
"value": 1202.97
},
{
"name": "湖南",
"value": 928.36
},
{
"name": "广东",
"value": 15610.67
},
{
"name": "广西",
"value": 9278.87
},
{
"name": "海南",
"value": 13348.02
},
{
"name": "重庆",
"value": 1168.32
},
{
"name": "四川",
"value": 7798.15
},
{
"name": "贵州",
"value": 168.94
},
{
"name": "云南",
"value": 8947.08
},
{
"name": "西藏",
"value": 13405.7
},
{
"name": "陕西",
"value": 1597.47
},
{
"name": "甘肃",
"value": 4522.35
},
{
"name": "青海",
"value": 5424.32
},
{
"name": "宁夏",
"value": 545.45
},
{
"name": "新疆",
"value": 13150.57
}
]
}
res.send(data);
})
var server = app.listen('7002', function (req, res) {
var port = server.address().port;
console.log('Start with port: ' + port);
})
至此,整个绘制就完成了。如果大家有疑问,欢迎在底下留言。