首页 > 解决方案 > d3 geo 在单击/缩放时更改 topojson 文件

问题描述

我在 D3.js 中构建了世界地图出于性能考虑,我使用的是 world-atlas 110m 版本,但我想在放大时增加地图细节。

当我单击美国时,我还想更改投影,因此也想更改 topojson 文件。(即使用geoAlbersUsa(),渲染美国各州)

我有一些非常基本的功能来改变点击时的地图投影,但我正在努力改变如何改变 topojson 文件。

import * as d3 from "d3";
import { feature } from 'topojson';

var projections = [
  {name: "mercator", value: d3.geoMercator()},
  {name: "orthograpic", value: d3.geoOrthographic()},
  {name: "Us", value: d3.geoAlbersUsa()} ]

let projection = projections[1].value;
const svg = d3.select('svg');
const graticule = d3.geoGraticule();
let pathGenerator = d3.geoPath(projection);
let country;

const g = svg.append('g');
          

g.append('path')
    .attr('class', 'sphere')
    .attr('d', pathGenerator({type: 'Sphere'}));

g.append('path')
    .datum(graticule)
    .attr("class", "graticule")
    .attr("d", pathGenerator);

g.call(d3.drag()
.on("drag", (event, d) => {
  //if (projection === projections[1].value){
  const rotate = projection.rotate()
    const k = 75 / projection.scale()
    projection.rotate([
      rotate[0] + event.dx * k,
      rotate[1] - event.dy * k
    ])
    pathGenerator = d3.geoPath().projection(projection)
    svg.selectAll(".graticule").attr("d", pathGenerator)
    svg.selectAll(".country").attr("d", pathGenerator)
  //}
  //else{return}
}));

function update(){
  pathGenerator = d3.geoPath().projection(projection)
  svg.selectAll("path").attr("d", pathGenerator)
  svg.selectAll("path").transition().duration(750)
  svg.selectAll(".sphere").attr("d", pathGenerator({type:'Sphere'}))
    
}

Promise.all([
  d3.tsv('https://unpkg.com/world-atlas@1.1.4/world/110m.tsv'),
  d3.json('https://unpkg.com/world-atlas@1.1.4/world/110m.json'),
  d3.json("states-albers-10m.json")
]).then(([tsvData, topoJSONdata, usTopoJson]) => {
const countryName = tsvData.reduce((accumulator, d) => { //for each..
    accumulator[d.iso_n3] = d.name;
    return accumulator;
  })

const countries = feature(topoJSONdata, topoJSONdata.objects.countries);
  console.log(projection)
g.selectAll('.country').data(countries.features)
    .enter().append('path')
      .attr('class', 'country')
      .attr('d', pathGenerator)
      .on("click", function(event,d) {
        country = countryName[d.id];
        if(country === "United States" && projection !== projections[0].value){
          console.log(projection);
          projection = projections[0].value
          update();
        }
        else{
          projection = projections[1].value
          update();
        }
      })
})

g.call(d3.zoom().on('zoom', (event) => {
  g.attr('transform', event.transform, )
}))

现场演示:https ://vizhub.com/Glebenator/810613a0a8584310abdcbcdaa78a368a

标签: javascriptd3.js

解决方案


如果您想通过缩放更改功能/数据源/任何内容,您需要获取当前缩放状态,该状态包含在传递给缩放事件侦听器的事件中(或 d3 v6 之前的 d3.event)。

这为我们提供了一个相对简单的缩放监听器:

 .on("zoom", function(event) {

   // if zoomed in sufficiently:
   if(event.transform.k > 4) {
     /*  draw the detailed features */
   }
   // otherwise:
   else {
    /* draw the undetailed features */
   }
   // update the paths:      
   features.attr("d",path)
 })

下面我将它与一些语义缩放相结合,您可以更轻松地应用投影剪辑范围,这将阻止路径生成器在所需范围之外绘制要素。它仍然会投射他们的观点,但不必绘制它们。还有其他方法可以获得性能提升,但我将把它们留到另一天。语义缩放使用缩放的平移和原始投影平移(后者需要缩放缩放比例),并结合缩放和投影比例:

  projection.translate([t.x+240*t.k,t.y+120*t.k]).scale(baseScale*t.k);

我下面的示例并不喜欢它如何加载数据,它加载一个基础层,然后加载一个更详细的文件。加载更详细的文件后,它会应用缩放行为,允许您放大更详细的功能集(大陆与国家)。有很多方法你可能想要做到这一点,我的例子只是说明性的。

对于优化,您可以通过存储先前的缩放值并仅在新值超过阈值时调用重新渲染/更新函数来查看缩放级别是否超过阈值。

var svg = d3.select("body").append("svg")
  .attr("width", 480)
  .attr("height", 240);
  
var baseScale = 480/Math.PI/2;  
var projection = d3.geoMercator()
  .scale(baseScale)
  .translate([240,120])
  .clipExtent([[0,0],[480,240]])
 
  
var path = d3.geoPath(projection);
  
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-110m.json").then(function(land) {

   // draw world initially:
   var world = topojson.feature(land, land.objects.land);
   
   var features = update([world]).attr("d",path);

   d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json").then(function(countries) {
   
   // get a more detailed set of features:
   var detail = topojson.feature(countries, countries.objects.countries).features;
   
   // apply a zoom behavior:
   var zoom = d3.zoom()
     .on("zoom", function(event) {
      var t = event.transform;
      
      // Semantic zoom using the projection:
      projection.translate([t.x+240*t.k,t.y+120*t.k]).scale(baseScale*t.k);
      
      // if zoomed in sufficiently:
      if(t.k > 1) {
        // draw the detailed features:
        features = update(detail);
      }
      // otherwise:
      else {
        // draw the undetailed features:
        features = update([world]);
      }
      // update the paths:      
      features.attr("d",path)
     })
    svg.call(zoom);

   })
})

function update(data) {
  return svg.selectAll("path").data(data).join("path");
}
path {
 stroke-width: 1px;
 stroke: black;
 fill: #aaa;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>


推荐阅读