使用新的地图和数据重建D3动画等值线

时间:2017-04-21 17:02:28

标签: javascript json d3.js

我正在尝试重新创建D3.js的例子:"又一个动画的等值线图": http://bl.ocks.org/rgdonohue/9280446

只是,我试图使用具有不同数据的不同地图。我使用的地图是美国各县,数据是按县分列的2年家庭中值。我在显示地图时遇到错误。错误: TypeError:counties [i] .properties未定义

出现以下函数(请参阅上面的原始函数链接):

function processData(error, us, countyData) {
  var counties = us.objects.counties.geometries;  // store the path in variable for ease
  for (var i in counties) {    // for each geometry object
    for (var j in countyData) {  // for each row in the CSV
      if(counties[i].properties.id == countyData[j].id) {   // if they match
        for(var k in countyData[i]) {   // for each column in the a row within the CSV
          if(k != 'name' && k != 'id') {  // let's not add the name or id as props since we already have them
            if(attributeArray.indexOf(k) == -1) { 
               attributeArray.push(k);  // add new column headings to our array for later
            }
            counties[i].properties[k] = Number(countyData[j][k])  // add each CSV column key/value to geometry object
          } 
        }
        break;  // stop looking through the CSV since we made our match
      }
    }
  }
  d3.select('#clock').html(attributeArray[currentAttribute]);  // populate the clock initially with the current year
  drawMap(us);  // let's mug the map now with our newly populated data object
}    

我相信正在发生的事情是我们'由于某种原因未定义。原始示例使用地图world-topo.json,我无法找到源代码。我的地图是http://d3js.org/us-10m.v1.json

原始代码:

world.objects.countries.geometries

我改为:

us.objects.counties.geometries

在Bostock的原始Choropleth例子中,他使用我使用的相同美国地图并将其称为“我们”,但它似乎并不适合我:< / p>

svg.append("g")
.data(topojson.feature(us, us.objects.counties).features)

我意识到我对D3没有很好的理解 - 我更多的是代码黑客,而不是开发人员。我只想尝试一些可视化,并且对javascript / d3没什么经验。任何帮助,将不胜感激。

我的整个计划:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>d3 choropleth map</title>
    <style>
      #wrapper {
          width: 960px;
          margin: -30px auto 0;
      }
      #map {
          width: 960px;
          height: 580px;
          position: relative;
      }
      .stroke {
        fill: none;
        stroke: #888;
        stroke-width: 2px;
      }

      .fill {
        fill: #fff;
      }

      .graticule {
        fill: none;
        stroke: #777;
        stroke-width: .5px;
        stroke-opacity: .5;
      }

      .land {
        fill: #222;
      }

      .boundary {
        fill: none;
        stroke: #fff;
        stroke-width: .5px;
      }
      .county {
          fill: steelblue;
          stroke: white;
      }
      #play, #clock {
        position: absolute;
        top: 15px;
      }
      #play {
        left: 15px;
      }
      #clock {
        left: 65px;
      }
    </style>
</head>

<body>

    <div id="wrapper">  
        <div id="map"></div>
      <button id="play">play</button>
      <span id="clock">year</span>
    </div>


<script src="d3.v3.min.js"></script>
<script src="d3.geo.projection.v0.min.js"></script>
<script src="queue.v1.min.js"></script>
<script src="topojson.v1.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>   
<script src="https://d3js.org/topojson.v2.min.js"></script>

<script>
  //globals
var width, height, projection, path, graticule, svg, attributeArray = [], currentAttribute = 0, playing = false;

function init() {

  setMap();
  animateMap();

}

function setMap() {

  width = 960, height = 580;  // map width and height, matches 

  projection = d3.geo.eckert5()   // define our projection with parameters
    .scale(170)
    .translate([width / 2, height / 2])
    .precision(.1);

  path = d3.geo.path()  // create path generator function
      .projection(projection);  // add our define projection to it

  graticule = d3.geo.graticule(); // create a graticule

  svg = d3.select("#map").append("svg")   // append a svg to our html div to hold our map
      .attr("width", width)
      .attr("height", height);

  svg.append("defs").append("path")   // prepare some svg for outer container of svg elements
      .datum({type: "Sphere"})
      .attr("id", "sphere")
      .attr("d", path);

  svg.append("use")   // use that svg to style with css
      .attr("class", "stroke")
      .attr("xlink:href", "#sphere");

  svg.append("path")    // use path generator to draw a graticule
      .datum(graticule)
      .attr("class", "graticule")
      .attr("d", path);

  loadData();  // let's load our data next

}

function loadData() {

  queue()   // queue function loads all external data files asynchronously 
    .defer(d3.json, "us-10m.v1.json")  // our geometries
    .defer(d3.csv, "countiesRandom.csv")  // and associated data in csv file
    .await(processData);   // once all files are loaded, call the processData function passing
                           // the loaded objects as arguments
}

function processData(error, us, countyData) {
  var counties = us.objects.counties.geometries;  // store the path in variable for ease
  for (var i in counties) {    // for each geometry object
    for (var j in countyData) {  // for each row in the CSV
      if(counties[i].properties.id == countyData[j].id) {   // if they match
        for(var k in countyData[i]) {   // for each column in the a row within the CSV
          if(k != 'name' && k != 'id') {  // let's not add the name or id as props since we already have them
            if(attributeArray.indexOf(k) == -1) { 
               attributeArray.push(k);  // add new column headings to our array for later
            }
            counties[i].properties[k] = Number(countyData[j][k])  // add each CSV column key/value to geometry object
          } 
        }
        break;  // stop looking through the CSV since we made our match
      }
    }
  }
  d3.select('#clock').html(attributeArray[currentAttribute]);  // populate the clock initially with the current year
  drawMap(us);  // let's mug the map now with our newly populated data object
}

function drawMap(us) {

    svg.selectAll(".county")   // select country objects (which don't exist yet)
      .data(topojson.feature(us, us.objects.counties).features)  // bind data to these non-existent objects
      .enter().append("path") // prepare data to be appended to paths
      .attr("class", "county") // give them a class for styling and access later
      .attr("id", function(d) { return "code_" + d.properties.id; }, true)  // give each a unique id for access later
      .attr("d", path); // create them using the svg path generator defined above

    var dataRange = getDataRange(); // get the min/max values from the current year's range of data values
    d3.selectAll('.county')  // select all the countries
    .attr('fill-opacity', function(d) {
        return getColor(d.properties[attributeArray[currentAttribute]], dataRange);  // give them an opacity value based on their current value
    });
}

function sequenceMap() {

    var dataRange = getDataRange(); // get the min/max values from the current year's range of data values
    d3.selectAll('.county').transition()  //select all the countries and prepare for a transition to new values
      .duration(750)  // give it a smooth time period for the transition
      .attr('fill-opacity', function(d) {
        return getColor(d.properties[attributeArray[currentAttribute]], dataRange);  // the end color value
      })

}

function getColor(valueIn, valuesIn) {

  var color = d3.scale.linear() // create a linear scale
    .domain([valuesIn[0],valuesIn[1]])  // input uses min and max values
    .range([.3,1]);   // output for opacity between .3 and 1 %

  return color(valueIn);  // return that number to the caller
}

function getDataRange() {
  // function loops through all the data values from the current data attribute
  // and returns the min and max values

  var min = Infinity, max = -Infinity;  
  d3.selectAll('.county')
    .each(function(d,i) {
      var currentValue = d.properties[attributeArray[currentAttribute]];
      if(currentValue <= min && currentValue != -99 && currentValue != 'undefined') {
        min = currentValue;
      }
      if(currentValue >= max && currentValue != -99 && currentValue != 'undefined') {
        max = currentValue;
      }
  });
  return [min,max];  //boomsauce
}

function animateMap() {

  var timer;  // create timer object
  d3.select('#play')  
    .on('click', function() {  // when user clicks the play button
      if(playing == false) {  // if the map is currently playing
        timer = setInterval(function(){   // set a JS interval
          if(currentAttribute < attributeArray.length-1) {  
              currentAttribute +=1;  // increment the current attribute counter
          } else {
              currentAttribute = 0;  // or reset it to zero
          }
          sequenceMap();  // update the representation of the map 
          d3.select('#clock').html(attributeArray[currentAttribute]);  // update the clock
        }, 2000);

        d3.select(this).html('stop');  // change the button label to stop
        playing = true;   // change the status of the animation
      } else {    // else if is currently playing
        clearInterval(timer);   // stop the animation by clearing the interval
        d3.select(this).html('play');   // change the button label to play
        playing = false;   // change the status again
      }
  });
}


window.onload = init();  // magic starts here

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

来自countiesRandom.csv的示例数据:

id,2010,2015
01001,141300,133900

1 个答案:

答案 0 :(得分:0)

关于:

  

我在显示地图时遇到错误。错误:TypeError:   县[i] .properties未定义

我们需要检查引用的世界topojson和US topojson。

world topojson中,几何定义如下:

"geometries":[
{"type":"Polygon","id":1,"arcs":[[0,1,2,3,4,5,6,7]],"properties":{"admin":"Afghanistan","id":"AFG"}},
{"type":"Polygon","id":4,"arcs":[[16,17,18,19,20]],"properties":{"admin":"Albania","id":"ALB"}},

每个几何体都有一个属性属性。

在您引用的US topojson中,几何定义如下:

"geometries":[
{"type":"Polygon","arcs":[[0,1,2,3,4]],"id":"05089"},
{"type":"Polygon","arcs":[[5,6,7,8,9]],"id":"06079"},
{"type":"Polygon","arcs":[[10,11,12,13,14,15,16]],"id":"17111"},

没有为每个几何体分配属性属性。因此,当您调用几何的属性时,它将返回undefined。

因此,您可能会在counties[i].properties.id找到更多成功,而不是使用counties[i].id

如果您要比较示例,也可能值得注意的是,从topojson到geojson(topojson.feature(us, us.objects.counties).features)的转换也将修改数据的结构:

d3.json("//d3js.org/us-10m.v1.json",function(error,us) {
  console.log("Number of county geometries:");
  console.log(us.objects.counties.geometries.length);
  console.log("Example county id:");
  console.log(us.objects.counties.geometries[0].id);  
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>

d3.json("//d3js.org/us-10m.v1.json",function(error,us) {
  var geojson = topojson.feature(us, us.objects.counties).features;
  console.log("Number of features");
  console.log(geojson.length);
  console.log("Example county ID");
  console.log(geojson[0].id);
  
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>