D3.js:为具有多个 geoJSON 文件的地图设置 fitExtent 或 fitSize(或以编程方式居中和缩放)

D3.js: Set fitExtent or fitSize (or center and scale programmatically) for map with multiple geoJSON files

我想绘制数据来自各个 topoJSON 文件的多个国家/地区。这些国家的地图应该在屏幕中间(居中),并且应该设置适当的比例,使地图填满整个 window.

我已经尝试过的:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="https://d3js.org/d3.v5.min.js"></script>
        <script src="https://d3js.org/topojson.v2.min.js"></script>
        <style type="text/css">

    .svg-container {
        display: inline-block;
        position: relative;
        width: 100%;
        padding-bottom: 100%;
        vertical-align: top;
        overflow: hidden;
    }
    .svg-content {
        display: inline-block;
        position: absolute;
        margin: 0;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

        </style>
    </head>
    <body>
        <div id="container" class="svg-container"> </div>

        <script type="text/javascript">
                var width = 600; //for simplicity set to fixed value
                var height = 600; //for simplicity set to fixed value

                var projection = d3.geoMercator();

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

                var svg = d3.select("#container").append("svg")
                      .classed("svg-content", true)
                      .attr("width", width)
                      .attr("height", height);

            //Example data
            var countries = ['germany', 'italy', 'switzerland', 'france']; 

            function loadJsonFiles() {

                  var files = ["https://api.myjson.com/bins/1b0ddz", 
                       "https://api.myjson.com/bins/11jkvb",
                       "https://api.myjson.com/bins/96x1j",
                       "https://api.myjson.com/bins/sspzr"];

                  var promises = [];
                  var allCountryData = [];

                  files.forEach(function(url) {
                          promises.push(d3.json(url))
                  });

                  return Promise.all(promises)
                      .then(function(countryData) {

                      for (var i = 0; i < countryData.length; i++) {

                        allCountryData.push(countryData[i]);

                      }
                    return allCountryData;
                  });
            }

            loadJsonFiles().then(function(allCountryData) {

                  var allBounds = [];
                  var objName; 
                  var countryData;

                  for (var i = 0; i < allCountryData.length; i++) {

                      objName = allCountryData[i].objects[countries[i]];
                      countryData = topojson.feature(allCountryData[i], objName);

                      //How can I use the right projection parameters for all country data loaded from topojson (outside the loop)?
                      projection
                          .scale(1000) //How to set this programmatically?
                          .center([8,47]) //How to set this programmatically?
                          .translate([width / 2, height / 2]);
                      //How can I append all the country data to the svg (outside the loop)?
                      svg.append("path")
                          .datum(countryData)
                          .attr("d", path);
                  }

            });

        </script>
    </body>
</html>   

我觉得按照Andrew Reid在post中提供的建议和fitSizefitExtent应该可以解决这个问题。但是我无法将其应用于我的问题。这也是我解决问题的首选方法。

或者,是否可以使用 center()scale() 来解决这个问题? 我将如何确定必须向 center() 提供哪些坐标以及向 scale() 提供的值? 非常感谢您的帮助,我试了几天都没有成功。

projection.fitSizeprojection.fitExtent 都采用单个 geojson 对象,而不是一组 geojson 对象。幸运的是,geojson 有一个对象类型,可以包含我们需要的尽可能多的子对象:a featureCollection.

我看到您正在使用 topojson,topojson.feature return 是一个 geojson 对象。在您的情况下,您每次使用 topojson 时 return 一个特征集合。

为了使用fitSize/fitExtent我们需要创建一个包含所有独立特征的geojson对象,一个特征集合就是这样做的,结构如下:

featureCollection = {
  "type":"FeatureCollection",
  "features": [ ... ]
}

我们可以构建一个特征集合,类似于:

var featureCollection = {type:"FeatureCollection","features":[]}

for (var i = 0; i < allCountryData.length; i++) {

     objName = allCountryData[i].objects[countries[i]];
     countryData = topojson.feature(allCountryData[i], objName);

     featureCollection.features.push(...countryData.features)

}

现在我们可以使用

将这个特征集合传递给fitSize/fitExtent
projection.fitSize([width,height],featureCollection);

应该如下所示:

                var width = 400; //for simplicity set to fixed value
                var height = 200; //for simplicity set to fixed value

                var projection = d3.geoMercator();

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

                var svg = d3.select("#container").append("svg")
                      .classed("svg-content", true)
                      .attr("width", width)
                      .attr("height", height);

            //Example data
            var countries = ['germany', 'italy', 'switzerland', 'france']; 

            function loadJsonFiles() {

                  var files = ["https://api.myjson.com/bins/1b0ddz", 
                       "https://api.myjson.com/bins/11jkvb",
                       "https://api.myjson.com/bins/96x1j",
                       "https://api.myjson.com/bins/sspzr"];

                  var promises = [];
                  var allCountryData = [];

                  files.forEach(function(url) {
                          promises.push(d3.json(url))
                  });

                  return Promise.all(promises)
                      .then(function(countryData) {

                      for (var i = 0; i < countryData.length; i++) {

                        allCountryData.push(countryData[i]);

                      }
                    return allCountryData;
                  });
            }

            loadJsonFiles().then(function(allCountryData) {

                var allBounds = [];
                var objName; 
                var countryData;


    var featureCollection = {type:"FeatureCollection","features":[]}
    for (var i = 0; i < allCountryData.length; i++) {

      objName = allCountryData[i].objects[countries[i]];
      countryData = topojson.feature(allCountryData[i], objName);

      featureCollection.features.push(...countryData.features)

    }
    
    projection.fitSize([400,200],featureCollection)
    
    svg.selectAll("path")
      .data(featureCollection.features)
      .enter()
      .append("path")
      .attr("d",path);

            });
svg {
   border: 1px solid black;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<div id="container" class="svg-container"> </div>

我保留了您的代码,只是我使用了不同的 for 循环(如上所示)来创建要素集合,而不是循环附加每个路径我使用了输入选择。最后,我更改了大小和位置,以使其更适合片段视图。