2017-10-11 93 views
1

我是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

回答

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類設置爲所有縣。

+0

接近我的答案!如果你有時間,看看我的解決方案,有什麼想法爲什麼一個縣不斷過濾掉? – Nick

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