D3地图:放大到路径组

问题描述:

我是D3的新手。我需要做的事情:D3地图:放大到路径组

  1. 创建一个单一状态的地图。
  2. 必须显示县边界。
  3. 国家地区北,南,东,西&*必须填充不同的颜色。每个地区都由县组成。
  4. 当用户点击一个区域时,地图必须放大到该区域。

我到目前为止所能达到的效果如下: 我有第一个3的要求。问题是当我点击一个县时,它会放大该县而不是该区域。

我写的代码是从下面的例子基于:

缩放边界框

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

纽约州与县削峰

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

的唯一部分我自己创建的代码(以及我认为需要更改的部分)是注册表的颜色离子。这是通过使用如果然后其他语句设置以下类别

.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并将其复制到名为脚本的文件夹中。

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

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

+0

接近我的答案!如果你有时间,看看我的解决方案,有什么想法为什么一个县不断过滤掉? – Nick

这是我的解决方案,它似乎工作得很好。我所做的重要变化是:

  • 只有包括在关注状态的县(使得它运行得更快)
  • 指示“活动”区域,而不是县
  • 如果点击了一个县,通过在该地区获得的最大和最小边界缩放所有县迭代(这是关键)

奇怪的事情,我注意到的是,过滤掉不适用的县时,一个适用全县保持过滤掉,所以我手动将它添加回(毛重)。

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