d3.js 入門學習記錄(十一) 地圖上的視覺化

WanFengZ發表於2019-12-08

地圖投影

這裡使用的是 topoJSON 而非是 geoJSON 資料。網站也有許多網站能夠轉化檔案。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        path {
            fill: #ccc;
            stroke: #fff;
            stroke-width: .5px;
        }
        .state:hover {
            fill: red;
        }
    </style>
</head>
<body>
<script src="../d3.js"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script>
    const width = 960,
          height = 500

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

    const svg = d3.select('body').append('svg')
        .attr('width', width)
        .attr('height', height)

    const g  = svg.append('g').call(d3.zoom().scaleExtent([1, 10]).on('zoom', zoomHandle))

    function zoomHandle() {
        const transform = d3.event.transform
        svg.select('g').attr('transform', `translate(${transform.x}, ${transform.y}) scale(${transform.k})`)
    }

    d3.json('./us.json').then(data => {

      g.selectAll('path.state')
        .data(topojson.feature(data, data.objects.states).features)
        .enter()
        .append('path')
            .attr('class', 'state')
            .attr('d', d => path(d))

    })
</script>
</body>
</html>
複製程式碼

效果如下:

d3.js 入門學習記錄(十一) 地圖上的視覺化

topoJSON 文件見:github.com/topojson/to…

我們先給 svg 定義了平移縮放,然後通過以下關鍵程式碼渲染了地圖:

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

g.selectAll('path.state')
    .data(topojson.feature(data, data.objects.states).features)
    .enter()
    .append('path')
        .attr('class', 'state')
        .attr('d', d => path(d))
複製程式碼

我們定義了一個地圖的 path 生成器,通過 projection 方法設定生成器的投影方式。然後我們通過 topojson 拿到 地圖資料中各個州的資料,通過 path 元素和生成器渲染到頁面上。

d3 中常見的投影方式見下例。

投影方式

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        body {
            background: #fcfcfa;
        }
        .map {
            float: left;
            margin: 20px;
            text-align: center;
        }
        .land {
            fill: #222;
        }
        .boundary {
            fill: none;
            stroke: #fff;
            stroke-width: .5px;
        }
    </style>
</head>

<body>
<script src="../d3.js"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script>
    const width = 300,
          height = 300,
          translate = [width / 2, height / 2],
          projections = [
            {name: 'geoAzimuthalEqualArea', fn: d3.geoAzimuthalEqualArea().scale(60).translate(translate)},
            {name: 'geoAzimuthalEquidistant', fn: d3.geoAzimuthalEquidistant().scale(50).translate(translate)},
            {name: 'geoGnomonic', fn: d3.geoGnomonic().scale(60).translate(translate)},
            {name: 'geoOrthographic', fn: d3.geoOrthographic().scale(80).translate(translate)},
            {name: 'geoStereographic', fn: d3.geoStereographic().scale(50).translate(translate)},
            {name: 'geoAlbers', fn: d3.geoAlbers().scale(50).translate(translate)},
            {name: 'geoConicConformal', fn: d3.geoConicConformal().scale(40).translate(translate)},
            {name: 'geoConicEqualArea', fn: d3.geoConicEqualArea().scale(50).translate(translate)},
            {name: 'geoConicEquidistant', fn: d3.geoConicEquidistant().scale(35).translate(translate)},
            {name: 'geoEquirectangular', fn: d3.geoEquirectangular().scale(50).translate(translate)},
            {name: 'geoMercator', fn: d3.geoMercator().scale(50).translate(translate)},
            {name: 'geoTransverseMercator', fn: d3.geoTransverseMercator().scale(50).translate(translate)},
            {name: 'geoNaturalEarth1', fn: d3.geoNaturalEarth1().scale(80).translate(translate)}
          ]

    d3.json('./world-50m.json').then(data => {
        projections.forEach(projection => {
            const path = d3.geoPath().projection(projection.fn)

            const div = d3.select('body')
                .append('div')
                    .attr('class', 'map')

            const svg = div.append('svg')
                .attr('width', width)
                .attr('height', height)

            svg.append('path')
                .datum(topojson.feature(data, data.objects.land))
                    .attr('class', 'land')
                    .attr('d', d => path(d))

            svg.append('path')
                .datum(topojson.mesh(data, data.objects.countries))
                    .attr('class', 'boundary')
                    .attr('d', path)

            div.append('h3').text(projection.name)
        })
    })
</script>
</body>
</html>
複製程式碼

各投影方式的效果如下:

d3.js 入門學習記錄(十一) 地圖上的視覺化

等值區域圖(熱度地圖)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .states {
            fill: none;
            stroke: #fff;
            stroke-width: .5px;
        }
    </style>
</head>
<body>
<script src="../d3.js"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script>
    const width = 960,
          height = 600,
          colors = d3.scaleLinear()
            .domain([0.02, 0.04, 0.06, 0.08, 0.10])
            .range(d3.schemeRdBu[6].reverse()),
          path = d3.geoPath().projection(d3.geoAlbersUsa()),
          svg = d3.select('body').append('svg')
            .attr('width', width)
            .attr('height', height),
          g = svg.append('g').call(
            d3.zoom()
                .scaleExtent([1, 10])
                .on('zoom', zoomHandle)
          )

    function zoomHandle() {
        const transform = d3.event.transform
        g.attr('transform', `translate(${transform.x}, ${transform.y}) scale(${transform.k})`)
    }

    svg.append('text').text('2008年美國失業熱度圖').attr('transform', 'translate(400, 550)')

    d3.json('./us.json').then(us => {
        d3.tsv('./unemployment.tsv').then(unemployment => {
          const map = new Map()

          unemployment.forEach(d => {
                map.set(Number(d.id), Number(d.rate))
          })

          g.append('g').selectAll('path')
                .data(topojson.feature(us, us.objects.counties).features)
                .enter()
                .append('path')
                    .attr('d', d => path(d))
                    .attr('fill', d => colors(map.get(d.id)))

          g.append('path')
                .datum(topojson.mesh(us, us.objects.states))
                .attr('class', 'states')
                .attr('d', d => path(d))
        })
    })
</script>
</body>
</html>
複製程式碼

效果如下:

d3.js 入門學習記錄(十一) 地圖上的視覺化

在請求到地圖資料和每個區域的失業值後,我們先是把失業資料置入一個 map 中,

const map = new Map()
unemployment.forEach(d => {
    map.set(Number(d.id), Number(d.rate))
})
複製程式碼

這便於我們通過每個區域的 id 值找到其對應的失業率。

我們先通過

g.append('g').selectAll('path')
    .data(topojson.feature(us, us.objects.counties).features)
    .enter()
    .append('path')
        .attr('d', d => path(d))
        .attr('fill', d => colors(map.get(d.id)))
複製程式碼

繪製出各個區,並以失業率值對應的顏色填充,然後再通過下面的程式碼繪製出各個州的輪廓:

 g.append('path')
    .datum(topojson.mesh(us, us.objects.states))
    .attr('class', 'states')
    .attr('d', d => path(d))
複製程式碼

相關文章