iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

這個網站也太嗨!30 個網頁動態提案系列 第 31

#28- D3.js 地圖動起來!用SVG viewbox/D3 fitExtent讓地圖置中

今天來讓地圖動起來!
老樣子,先看成果~神秘的非洲大陸~

相信沒幾個人認識這些國家吧XDDD

我自己覺得地圖只要格式對的話,其實用D3.js去繪製不難,
我遇到的難點是要讓地圖置中 XD
下面就會講解一下

1.格式 & 地圖投影
2.利用SVG或是D3內建fitExtent置中
3.上code

今天很多內容主要參考這一篇:使用d3.js繪製地圖

D3 地圖格式: GeoJason

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);

上code!

// 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,點到文字會一直閃 :

試了一些方法沒辦法解決,但不要強求將文字放在地圖旁邊就是一個做法哈
有任何想法歡迎留言!


上一篇
#27-微互動折線圖動態!就是要比較才看得出結果啊 (D3.js)
下一篇
#29-網站Tips動起來!用Tailwind自訂動畫&Hover動畫~
系列文
這個網站也太嗨!30 個網頁動態提案33

1 則留言

0
DannyChen
iT邦新手 4 級 ‧ 2021-10-14 22:55:06

我在撰寫d3地圖的主題處理on事件的時候也有遇到類似的問題
方法一:
原先是使用一個變數設定0,如果滑鼠移到當下的地圖區域被觸發了就設定成1,如果值等於1就把tooltip顯示出來,直到離開地圖區域才變回0,避免滑鼠指標在同一個地圖區域被多次觸發。
方法二:
之後我再撰文的時候想到另一個更簡單的方式,在css可以添加svg text{
pointer-events:none;
},避免文字成為事件觸發目標。

另外可能還有方法三是跟事件冒泡或事件捕捉有關,由於on的原理是Javascript的事件處理,所以可能去調整它的觸發順序能夠解決吧,但這個方法我就沒有實際嘗試過就是了。/images/emoticon/emoticon37.gif

Rachel ? iT邦新手 5 級 ‧ 2021-10-15 16:49:35 檢舉

救世主!!!!!!
方法二 1行搞定,太美了。

/images/emoticon/emoticon07.gif

太笨了,方法一其實我不是很懂...
我有試過Event.preventDefault()沒有成功,有機會調整一下順序看看

我要留言

立即登入留言