一.效果圖
使用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);
})
至此,整個繪製就完成了。如果大家有疑問,歡迎在底下留言。