D3地图:缩放到路径组

时间:2017-10-11 21:15:45

标签: d3.js

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

  1. 创建单个州的地图。
  2. 必须显示县边界。
  3. 州,北部,东部,西部和西部地区。中央必须填充不同的颜色。每个地区都由县组成。
  4. 当用户点击某个区域时,地图必须放大该区域。
  5. 到目前为止我能够取得的成就: 我有前3个要求。问题是,当我点击一个县时,它会缩放到该县而不是该地区。

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

    缩放到边界框

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

    纽约州与县修剪

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

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

     .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>
    

    以下是地图的外观: enter image description here

    问题在于它缩放到县而不是地区: enter image description here

2 个答案:

答案 0 :(得分:1)

首先,您不需要那些大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类设置为所有县。

答案 1 :(得分:1)

这是我的解决方案,似乎运作得很好。我做的重要改变是:

  • 仅包括处于感兴趣状态的县(使其运行得更快)
  • 表示&#34;有效&#34;区域而不是县
  • 每当点击一个县时,迭代该区域内的所有县以获得缩放的最大和最小边界(这是关键)

我注意到的一点奇怪是,当过滤掉不适用的县时,一个适用的县会一直过滤掉,所以我手动将其重新加入(粗略)。

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", "");
}