今天來讓地圖動起來!
老樣子,先看成果~神秘的非洲大陸~
相信沒幾個人認識這些國家吧XDDD
我自己覺得地圖只要格式對的話,其實用D3.js去繪製不難,
我遇到的難點是要讓地圖置中 XD
下面就會講解一下
1.格式 & 地圖投影
2.利用SVG或是D3內建fitExtent置中
3.上code
今天很多內容主要參考這一篇:使用d3.js繪製地圖
D3讀取地圖資料有特定格式,要花時間找一下。
這次用地圖用的GeoJSON
標準格式,回傳讓我們可以繪製多邊形polygon
的點位置
他會長得像這樣:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Algeria",
"cartodb_id": 1,
"created_at": "2013-11-12T16:15:59+0100",
"updated_at": "2013-11-12T16:15:59+0100"
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [ //利用這邊的座標畫地圖
[
[
[
8.621624,
36.941797
],
]
]
}
},...
D3有很多投影方式,今天採用的是像Google地圖常用的麥卡倫投影
詳細原理有點複雜XD 就先略過了
這邊有兩個方法讓地圖漂亮置中~
1.SVG viewbox & preserveAspectRatio
用SVG的viewbox
: SVG可視範圍,想像SVG是一張大白紙,
viewbox就是告訴SVG
1.我要裁的開始座標
2.我要裁的寬高
而preserveAspectRatio進階設定比例
這次範例就是設定參數:xMidyMid
讓他取中間
我的案例就會是
//html
<svg viewBox="420 130 150 350" //這邊我已經算好要裁減的位置了
preserveAspectRatio="xMinYMin"
></svg>
這樣D3讀取資料後,也不用特別設定translate,
就可以置中囉。
不過手機版還要另外設定。
對於SVG viewbox & preserveAspectRatio有興趣可以參考這一篇(圖文精美!)
SVG 研究之路 (23) - 理解 viewport 與 viewbox
2. D3內建fitExtent API
這個簡單, 回傳地圖左上角座標
&右下叫座標
D3就會幫我們放入中心點囉
projection = d3
.geoMercator()
.fitExtent([[0, 0],[width, height],], data);
// SVG 基本設定
const width = window.innerWidth * 0.7;
const height = window.innerHeight * 0.6;
const svg = d3.select("svg");
// 先把變數設好
let projection
let geoGenerator
let path
//讀資料
d3.json( "https://raw.githubusercontent.com/codeforgermany/click_that_hood/master/public/data/africa.geojson"
).then(function (data) {
//投影基本設定,還有置中
projection = d3
.geoMercator()
.fitExtent([[0, 0],[width, height],], data);
geoGenerator = d3.geoPath().projection(projection);
// 畫地圖
svg
.append("g")
.selectAll("path")
.data(data.features)
.enter()
.append('g')
.append("path")
.attr("fill", "#69b3a2")
.attr("d", geoGenerator)
.style("stroke", "#fff")
.on("mouseover", handleMouseOver)
.on("mouseout", function (d, i) {
d3.select(this).transition().duration(300).attr("fill", "#69b3a2");
d3.selectAll("text")
.transition()
.delay(function(d, i) { return 100; })
.text("");
});
});
//處理滑鼠hover效果
function handleMouseOver(e, d) {
let centroid = geoGenerator.centroid(d); //算出中間點
svg
.append("text")
.text(d.properties.name)
.style("font-size", 30)
.style("font-weight", "bold")
.style("display", "inline")
.attr("transform", "translate(" + centroid + ")")
.style("fill", "black")
.transition()
.delay(function(d, i) { return 100; });
d3.select(this).transition().duration(300).attr("fill", "yellow");
}
D3可以做地圖資料好好玩!
今天的code在這裡,但還是有小bug,點到文字會一直閃 :
試了一些方法沒辦法解決,但不要強求將文字放在地圖旁邊就是一個做法哈
有任何想法歡迎留言!
我在撰寫d3地圖的主題處理on事件的時候也有遇到類似的問題
方法一:
原先是使用一個變數設定0,如果滑鼠移到當下的地圖區域被觸發了就設定成1,如果值等於1就把tooltip顯示出來,直到離開地圖區域才變回0,避免滑鼠指標在同一個地圖區域被多次觸發。
方法二:
之後我再撰文的時候想到另一個更簡單的方式,在css可以添加svg text{
pointer-events:none;
},避免文字成為事件觸發目標。
另外可能還有方法三是跟事件冒泡或事件捕捉有關,由於on的原理是Javascript的事件處理,所以可能去調整它的觸發順序能夠解決吧,但這個方法我就沒有實際嘗試過就是了。
救世主!!!!!!
方法二 1行搞定,太美了。
太笨了,方法一其實我不是很懂...
我有試過Event.preventDefault()沒有成功,有機會調整一下順序看看