D3.js 應用

目次

使用地圖資料

地圖資料格式

要在 D3.js 中使用地圖資料,需要使用 GeoJSON 格式,GeoJSON 主要是基於 JSON 編寫的一種地理交換資料格式,其格式如下:

{
    "type" : "FeatureCollection",
    "features" : [
        {
          "type": "Feature",
          "geometry": {
            "type": "Point",
            "coordinates": [51.507351, -0.127758]
          },
          "properties": {
            "name": "London"
          }
        },
        {
            ...
        }
    ]
}

TopoJSON

為 GeoJSON 的改良版格式,將 GeoJSON 中的重複邊或點做簡化處理,能夠大幅減少使用空間,
但是 TopoJSON 不能直接為 D3.js 所接受,必須經過轉換才能使用,轉換可使用 Topojson 函式庫

引入 D3.js

取得地圖資料

參考以下網址可以取得臺灣的地圖資料
繪製臺灣地圖(ithelp.ithome.com.tw)

這裡使用已經轉換成 TopoJSON 的地圖資料
https://raw.githubusercontent.com/Zovjsra/taiwan-map/main/COUNTY_MOI_1090820.json

使用 d3.json() 讀取檔案

完整程式碼

程式說明

html

這個範例的 html 主要是一個 canvas,裡面有一個 svg,但是這個 svg 是作為 tooltip 使用的,
畫布的 svg 會由 D3.js 來增加

Tooltip 的 svg 中,有一個 text,是用來顯示縣市名稱的,後面會由程式修改裡面的文字。

<div id='canvas'>
    <svg id='tooltip'>
        <text x='50%' y='50%' style='text-anchor: middle; fill: white'>hello</text>
    </svg>
</div>

JavaScript

首先我們要在 canvas 中加入 svg

let svg = d3.select('#canvas').append('svg').style('height', 500).style('width', 500)

設定 tooltip 的性質

.style('position', 'absolute') 使得 tooltip 的位置不影響網頁的其他元素 .style('display', 'none') 使得 tooltip 預設為不顯示

let tooltip = d3.select('#tooltip').style('position', 'absolute')
    .style('background', 'blue')
    .style('width', 100)
    .style('height', 50)
    .style('display', 'none')

接著使 tooltip 的位置跟隨滑鼠,其中 e.layerXe.layerY 代表滑鼠在 div 中的相對位置

d3.select('#canvas').on('mousemove', function(e){
    tooltip.style('left', e.layerX + 20).style('top', e.layerY + 35)
})

d3.json() 讀取外部地圖檔案

d3.json('https://raw.githubusercontent.com/Zovjsra/taiwan-map/main/COUNTY_MOI_1090820.json').then((data) => {
    ...    
})

使用 topojson 將 TopoJSON 格式轉換為 GeoJSON 格式

const counties = topojson.feature(data, data.objects.COUNTY_MOI_1090820)

設定地圖的投影法,這裡使用常用的麥卡托投影法,並使用 .center([X, Y]) 來指定中心的經緯度,.scale() 指定縮放的大小
D3.geo(github)

const projection = d3.geoMercator().center([123, 24]).scale(5000);

生成 geoPath 物件,內容是地圖的外框座標

const path = d3.geoPath().projection(projection);

用 geoPath 物件在 svg 上畫上地圖,並使用剛才轉換的 counties 作為資料

const geoPath = svg.selectAll('.geo-path')
    .data(counties.features)
    .join('path')
    .attr('class', 'geo-path')

使用 path 設定外框的座標

.attr('d', path)
    .style('fill', 'green')

設定滑鼠進入的動作

.on('mouseover', function(e){
        d3.select(this).style('stroke', 'white')
        d3.select(this).select(function(d){
            tooltip.select('text').html(d.properties.COUNTYNAME)
            tooltip.style('display', 'block')     //顯示 tooltip
        })
    })

設定滑鼠離開的動作

.on('mouseleave', function(e){
        d3.select(this).style('stroke', 'none')
        tooltip.style('display', 'none')          //隱藏 tooltip
    })

使用 Leaflet 建立地圖

QuickStart(leafletjs.com)

Leaflet 提供了方便的函式庫,讓我們可以快速地建立一個地圖並在地圖上添加內容

首先必須載入 Leaflet 的 CSS 以及 JS(順序不能顛倒)

接著定義畫布

接著就可以用 L.map(id).setView(經緯度, 縮放) 設定地圖的初始位置

接著需要載入地圖資料,這裡的 accessToken 需要到 www.mapbox.com 申請帳號取得。
如果有自己常用的地圖來源,也可以使用其他的來源

接著我們就可以在地圖上面加入圖釘、圓圈等標記

加入圖釘

使用 L.marker(經緯度).addTo(mymap) 可以加入圖釘

加入圓圈

使用 L.circle(經緯度, 屬性) 可以加入圓圈

加入 Popup

使用 marker.bindPopup(html內容) 可以加入 Popup

再加入 .openPopup() 可以使 popup 預設為打開的狀態

完整程式碼

製作 Force-Directed Graph

Force-Directed Graph(bl.ocks.org)

Force-Directed Graph 是一種由 node 和 link 組成的圖形,其中 link 的長度愈大代表兩個 node 之間的力量愈大,反之則愈小

要製作 Force-Directed Graph,首先我們必須先準備一份資料,其格式如下:

此範例要使用的資料如下(如參考網站之資料)

設定畫布及一些CSS性質

設定顏色函式,以自動選擇顏色
d3-scale-chromatic(github)

創建 forceSimulation 物件

繪製線條

繪製圓點

由於要將圓點與文字放在一起,因此首先需要建立多個 group

繪製圓點,並設定拖動時的動作

接著加入文字,文字的內容就是資料的 id

設定 simulation 物件的更新

設定 simulation 中的 link 的力

完整程式碼(codepen)