【问题标题】:D3 map: zoom to group of pathsD3 地图:缩放到一组路径
【发布时间】:2017-10-11 21:15:44
【问题描述】:

我是 D3 的新手。我需要做什么:

  1. 创建单个州的地图。
  2. 必须显示县界。
  3. 州北部、南部、东部、西部和中部必须使用不同的颜色填充。每个地区都由县组成。
  4. 当用户点击一个区域时,地图必须放大到该区域。

到目前为止,我能够实现的目标: 我有前三个要求。问题是当我点击一个县时,它会放大到那个县而不是区域。

我编写的代码基于以下示例:

缩放到边界框

https://bl.ocks.org/mbostock/4699541

县被删减的纽约州

https://bl.ocks.org/gregdevs/a73f8a16f129757c037e72ecdebdd8f2

我自己创建的代码的唯一部分(以及我认为需要更改的部分)是区域的着色。这是使用 if then else 语句来设置以下类

 .attr('class', function (d) {
                  if (d.id == "51105" || d.id == "51169" || d.id == "51191" || d.id == "51520" || d.id == "51077" || d.id == "51035" ||
                      d.id == "51141" || d.id == "51089" || d.id == "51143" || d.id == "51590" || d.id == "51195" || d.id == "51051" ||
                      d.id == "51027" || d.id == "51167" || d.id == "51185" || d.id == "51173" || d.id == "51021" || d.id == "51197" ||
                      d.id == "51071" || d.id == "51590" || d.id == "51155" || d.id == "51063" || d.id == "51067" || d.id == "51121" ||
                      d.id == "51161" || d.id == "51770") {
                      return "WesternRegion";
                  }
                  else if (d.id == "51083" || d.id == "51117" || d.id == "51025" || d.id == "51081" || d.id == "51037" || d.id == "51011" ||
                           d.id == "51590" || d.id == "51029" || d.id == "51049" || d.id == "51145" || d.id == "51041" || d.id == "51111" ||
                           d.id == "51147" || d.id == "51183" || d.id == "51181" || d.id == "51007" || d.id == "51135" || d.id == "51053" ||
                           d.id == "51149" || d.id == "51087" || d.id == "51760") {
                      return "SouthernRegion";
                  }
                  else if (d.id == "51175" || d.id == "51800" || d.id == "51550" || d.id == "51810" || d.id == "51710" || d.id == "51093" ||
                           d.id == "51001" || d.id == "51131") {
                      return "EasternRegion";
                  }
                  else if (d.id == "51165" || d.id == "51171" || d.id == "51069" || d.id == "51043" || d.id == "51107" || d.id == "51059" ||
                           d.id == "51013" || d.id == "51510" || d.id == "51139" || d.id == "51187" || d.id == "51157" || d.id == "51061" || d.id == "51153") {
                      return "NorthernRegion";
                  }
                  else return "CentralRegion";
              })
              ;

以下是完整代码。为了让它工作,需要从https://bl.ocks.org/mbostock/raw/4090846/us.json 下载 us.json 并将其复制到一个名为 scripts 的文件夹中。

<!DOCTYPE html>
<meta charset="utf-8">
<style>

    .outline {
  stroke: #000;
  stroke-width: 1.5px;
}

path {
  fill: #ccc;
  stroke: #fff;
  stroke-width: .5px;
}
.background {
  fill: none;
  pointer-events: all;
}

.feature {
  fill: #ccc;
  cursor: pointer;
}

.county.active {
  fill: orange !important;
}

.WesternRegion 
{
    fill:Green;   
}

.EasternRegion 
{
    fill:Blue;   
}

.SouthernRegion 
{
    fill:#efce43;   
}
.NorthernRegion 
{
    fill:Purple;   
}

.mesh {
  fill: none;
  stroke: #fff;
  stroke-linecap: round;
  stroke-linejoin: round;
}  


</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>

    var width = 960,
        height = 500;
    active = d3.select(null);

    var projection = d3.geo.albers()
        .scale(1000)
        .translate([width / 2, height / 2]);

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

    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

    svg.append("rect")
        .attr("class", "background")
        .attr("width", width)
        .attr("height", height)
        .on("click", reset);

    var g = svg.append("g")
        .style("stroke-width", "1.5px");

    d3.json("/Scripts/us.json", function (error, us) {
        if (error) throw error;

        var states = topojson.feature(us, us.objects.states),
            state = states.features.filter(function (d) { return d.id === 51; })[0];

        projection.scale(1)
            .translate([0, 0]);

        var b = path.bounds(state),
            s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
            t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

        projection.scale(s)
            .translate(t);

        g.selectAll("path")
            .datum(topojson.mesh(us, us.objects.states, function (a, b) { return a !== b; }))
            .attr("class", "mesh")
            .attr("d", path)
            .on("click", clicked);

        g.append("path")
            .datum(state)
            .attr("class", "outline")
            .attr("d", path)
            .attr('id', 'land');

        g.append("clipPath")
            .attr("id", "clip-land")
            .append("use")
        .attr("xlink:href", "#land");

        g.selectAll("path")
            .data(topojson.feature(us, us.objects.counties).features)
            .enter().append("path")
            .attr("d", path)
            .attr('countyId', function (d) {
                return d.id
                })
            .attr("clip-path", "url(#clip-land)")
            .on("click", clicked)
            .attr('class', function (d) {
                  if (d.id == "51105" || d.id == "51169" || d.id == "51191" || d.id == "51520" || d.id == "51077" || d.id == "51035" ||
                      d.id == "51141" || d.id == "51089" || d.id == "51143" || d.id == "51590" || d.id == "51195" || d.id == "51051" ||
                      d.id == "51027" || d.id == "51167" || d.id == "51185" || d.id == "51173" || d.id == "51021" || d.id == "51197" ||
                      d.id == "51071" || d.id == "51590" || d.id == "51155" || d.id == "51063" || d.id == "51067" || d.id == "51121" ||
                      d.id == "51161" || d.id == "51770") {
                      return "WesternRegion";
                  }
                  else if (d.id == "51083" || d.id == "51117" || d.id == "51025" || d.id == "51081" || d.id == "51037" || d.id == "51011" ||
                           d.id == "51590" || d.id == "51029" || d.id == "51049" || d.id == "51145" || d.id == "51041" || d.id == "51111" ||
                           d.id == "51147" || d.id == "51183" || d.id == "51181" || d.id == "51007" || d.id == "51135" || d.id == "51053" ||
                           d.id == "51149" || d.id == "51087" || d.id == "51760") {
                      return "SouthernRegion";
                  }
                  else if (d.id == "51175" || d.id == "51800" || d.id == "51550" || d.id == "51810" || d.id == "51710" || d.id == "51093" ||
                           d.id == "51001" || d.id == "51131") {
                      return "EasternRegion";
                  }
                  else if (d.id == "51165" || d.id == "51171" || d.id == "51069" || d.id == "51043" || d.id == "51107" || d.id == "51059" ||
                           d.id == "51013" || d.id == "51510" || d.id == "51139" || d.id == "51187" || d.id == "51157" || d.id == "51061" || d.id == "51153") {
                      return "NorthernRegion";
                  }
                  else return "CentralRegion";
              })
              ;

    });

    function clicked(d) {
        //     debugger;
        if (d3.select(this).classed("NorthernRegion")) {
            alert("You selected Northern Region");
        }
        else if (d3.select(this).classed("SouthernRegion")) {
            alert("You selected Southern Region");
        }
        else if (d3.select(this).classed("EasternRegion")) {
            alert("You selected Eastern Region");
        }
        else if (d3.select(this).classed("WesternRegion")) {
            alert("You selected Western Region");
        }
        else if (d3.select(this).classed("CentralRegion")) {
            alert("You selected Central Region");
        }

        if (active.node() === this) return reset();
        active.classed("active", false);
        active = d3.select(this).classed("active", true);

        var bounds = path.bounds(d  ),
      dx = bounds[1][0] - bounds[0][0],
      dy = bounds[1][1] - bounds[0][1],
      x = (bounds[0][0] + bounds[1][0]) / 2,
      y = (bounds[0][1] + bounds[1][1]) / 2,
      scale = .9 / Math.max(dx / width, dy / height),
      translate = [width / 2 - scale * x, height / 2 - scale * y];

        g.transition()
      .duration(750)
      .style("stroke-width", 1.5 / scale + "px")
      .attr("transform", "translate(" + translate + ")scale(" + scale + ")");

    }

    function reset() {

        active.classed("active", false);
        active = d3.select(null);

        g.transition()
      .duration(750)
      .style("stroke-width", "1.5px")
      .attr("transform", "");
    }
</script>

以下是地图的外观:

问题在于它缩放到县而不是区域:

【问题讨论】:

    标签: d3.js


    【解决方案1】:

    这是我的解决方案,似乎效果很好。我所做的重要更改是:

    • 仅包括感兴趣的州的那些县(使其运行速度更快)
    • 表示“活动”区域而不是县
    • 每当单击某个县时,遍历该区域中的所有县以获得缩放的最大和最小边界(这是关键)

    我注意到的一件奇怪的事情是,在过滤掉不适用的县时,一个适用的县一直在过滤掉,所以我手动将其添加回(总)。

    var width = 960,
    height = 500,
    active = "";
    
    var projection = d3.geo.albers().scale(1000).translate([width / 2, height / 2]);
    var path = d3.geo.path().projection(projection);
    var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
    svg.append("rect").attr("class", "background").attr("width", width).attr("height", height).on("click", reset);
    var g = svg.append("g").style("stroke-width", "1.5px");
    d3.json("scripts/us.json", function(error, us) {
        if (error) throw error;
        var states = topojson.feature(us, us.objects.states),
            state = states.features.filter(function(d) {
                return d.id === 51;
            })[0];
        projection.scale(1).translate([0, 0]);
        var b = path.bounds(state),
            s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
            t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
        projection.scale(s).translate(t);
        g.selectAll("path").datum(topojson.mesh(us, us.objects.states, function(a, b) {
            return a !== b;
        })).attr("class", "mesh").attr("d", path).on("click", clicked);
        g.append("path").datum(state).attr("class", "outline").attr("d", path).attr('id', 'land');
        g.append("clipPath").attr("id", "clip-land").append("use").attr("xlink:href", "#land");
        us.objects.counties.geometries = us.objects.counties.geometries.filter(function(county) {
            return county.id >= 51000 && county.id < 52000
        });
        // Not sure why this one needs to be re-added
        us.objects.counties.geometries.push(us.objects.counties.geometries.find(function(d) {
            return d.id == 51069
        }));
        console.log(us.objects);
        g.selectAll("path").data(topojson.feature(us, us.objects.counties).features).enter().append("path").attr("d", path).attr('countyId', function(d) {
            return d.id
        }).attr("clip-path", "url(#clip-land)").on("click", clicked).attr('class', function(d) {
            if (d.id == "51105" || d.id == "51169" || d.id == "51191" || d.id == "51520" || d.id == "51077" || d.id == "51035" || d.id == "51141" || d.id == "51089" || d.id == "51143" || d.id == "51590" || d.id == "51195" || d.id == "51051" || d.id == "51027" || d.id == "51167" || d.id == "51185" || d.id == "51173" || d.id == "51021" || d.id == "51197" || d.id == "51071" || d.id == "51590" || d.id == "51155" || d.id == "51063" || d.id == "51067" || d.id == "51121" || d.id == "51161" || d.id == "51770") {
                return "WesternRegion";
            } else if (d.id == "51083" || d.id == "51117" || d.id == "51025" || d.id == "51081" || d.id == "51037" || d.id == "51011" || d.id == "51590" || d.id == "51029" || d.id == "51049" || d.id == "51145" || d.id == "51041" || d.id == "51111" || d.id == "51147" || d.id == "51183" || d.id == "51181" || d.id == "51007" || d.id == "51135" || d.id == "51053" || d.id == "51149" || d.id == "51087" || d.id == "51760") {
                return "SouthernRegion";
            } else if (d.id == "51175" || d.id == "51800" || d.id == "51550" || d.id == "51810" || d.id == "51710" || d.id == "51093" || d.id == "51001" || d.id == "51131") {
                return "EasternRegion";
            } else if (d.id == "51165" || d.id == "51171" || d.id == "51069" || d.id == "51043" || d.id == "51107" || d.id == "51059" || d.id == "51013" || d.id == "51510" || d.id == "51139" || d.id == "51187" || d.id == "51157" || d.id == "51061" || d.id == "51153") {
                return "NorthernRegion";
            } else {
                return "CentralRegion";
            }
        });
    });
    
    function clicked(d) {
        var selected = d3.select(this).attr('class');
        var dxAll = [];
        var dyAll = [];
        var xAll = [];
        var yAll = [];
        // Iterate through all in class and find max values
        d3.selectAll('.' + selected).each(function(data) {
            var bounds = path.bounds(data);
            dxAll.push(bounds[1][0], bounds[0][0]);
            dyAll.push(bounds[1][1], bounds[0][1]);
            xAll.push(bounds[0][0], bounds[1][0]);
            yAll.push(bounds[0][1], bounds[1][1]);
        });
        dx = Math.max.apply(null, dxAll) - Math.min.apply(null, dxAll);
        dy = Math.max.apply(null, dyAll) - Math.min.apply(null, dyAll);
        x = (Math.max.apply(null, xAll) + Math.min.apply(null, xAll)) / 2;
        y = (Math.max.apply(null, yAll) + Math.min.apply(null, yAll)) / 2;
        if (active === selected) return reset();
        active = selected;
        scale = .9 / Math.max(dx / width, dy / height),
            translate = [width / 2 - scale * x, height / 2 - scale * y];
        g.transition().duration(750).style("stroke-width", 1.5 / scale + "px").attr("transform", "translate(" + translate + ")scale(" + scale + ")");
    }
    
    function reset() {
        active = "";
        g.transition().duration(750).style("stroke-width", "1.5px").attr("transform", "");
    }
    

    【讨论】:

      【解决方案2】:

      首先,您不需要那些大的if...else 语句。它们可以大大简化。例如,要获取 clicked 函数中的类:

      var thisClass = d3.select(this).attr("class");
      

      回到你的问题。

      一个解决方案是获取所有带有被点击元素的类的路径,并将它们的边界推入一个数组:

      var allBounds = [];
      
      var allPaths = d3.selectAll("path." + thisClass).each(function(d) {
          allBounds.push(path.bounds(d))
      });
      

      然后计算所有这些边界的角:

      var bound0 = d3.min(allBounds, function(d) {
          return d[0][0]
      });
      var bound1 = d3.min(allBounds, function(d) {
          return d[0][1]
      });
      var bound2 = d3.max(allBounds, function(d) {
          return d[1][0]
      });
      var bound3 = d3.max(allBounds, function(d) {
          return d[1][1]
      });
      
      var bounds = path.bounds(d),
          dx = bound2 - bound0,
          dy = bound3 - bound1,
          x = (bound0 + bound2) / 2,
          y = (bound1 + bound3) / 2,
          scale = .9 / Math.max(dx / width, dy / height),
          translate = [width / 2 - scale * x, height / 2 - scale * y];
      

      这里是更新的 bl.ocks:https://bl.ocks.org/anonymous/3e473b01de29cb7a3c0a6d8807b8b247/f6675e001dc7dcdb7ffd4c437944bb3233b417ca

      PS:如果您点击中心区域(灰色区域),它将不起作用。原因很简单:在您的代码中,您将centralRegion 类设置为在if...else 语句中没有给定ID 的所有路径。你必须改变它。

      PPS:您必须重构代码以将 active 类设置为所有县。

      【讨论】:

      • 接近我的答案!如果您有时间看看我的解决方案,知道为什么一个县会一直被过滤掉吗?
      猜你喜欢
      • 2015-10-12
      • 2017-07-11
      • 2021-09-02
      • 1970-01-01
      • 2016-02-27
      • 2019-11-29
      • 2013-09-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多