D3 - 在美国地图上渲染多年的州级数据

4

我刚开始学习编程,正在阅读 O'Reilly Interactive Data Visualization 书中的例子。

目前,我正在尝试将五年电动汽车的数据按州进行可视化。每个州的颜色根据该特定年份注册的电动汽车数量而变化。

问题:

每次我在下拉菜单中选择一个 year,它都会在其下方创建另一个地图svg。如何使其重新渲染现有的地图svg上的数据呢?

背景:

我有全局变量来定义svg的尺寸。我还有两个函数:

  • drawMap 带有一个年份参数,构建svg,从csv加载数据到 us_map.json,并创建所有州的边界。
  • clickEventMap 为每个年份按钮附加事件侦听器,然后调用 drawMap 函数,传递与按钮上的年份相匹配的年份。最后,我调用 drawMap(2013),以便页面默认显示2013年的数据,然后调用 clickEventMap()

我已经尝试过的方法:

我在 d3 color.domain 方法之后使用 console.log(year),此时正确的年份存储在 year 变量中。

此外,如果我手动更改调用 drawMap() 时的参数,我可以成功地更改现有地图上呈现的数据。

我的代码:

var w = 1200;
var h = 800;
var barpadding = 1;

var drawMap = function(year) {

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

  var projection = d3.geo.albersUsa()
                         .translate([w/2, h/2]) 
                         .scale([1600]);   

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

  var color = d3.scale.quantize()   
                    .range(["rgb(237, 248, 233)", "rgb(186, 228, 179)", "rgb(116,196,118)", "rgb(49, 163, 84)", "rgb(0, 109, 44)"]);


  color.domain([
    d3.min(data, function(d) {return d["electric_vehicles_" + year];}),
    d3.max(data, function(d) { return d["electric_vehicles_" + year];})

  ]);

  console.log(year);

  d3.json("us-states.json", function(json) {  
    for (var i = 0; i < data.length; i ++) {  
      var dataState = data[i].state;         
      var dataValue = parseFloat(data[i]["electric_vehicles_" + year]);  
      for (var j = 0; j < json.features.length; j++) {   
        var jsonState = json.features[j].properties.name;
        if (dataState === jsonState) {                    
          json.features[j].properties.value = dataValue;
          break;
        }
      }
    }

    var stateView = function() {
    console.log("State clicked.");
    location.href="/states/1";  

    svg.selectAll("path")
      .data(json.features)
      .enter()
      .append("path")
      .attr("d", path)
      .style("fill", function(d) {
        var value = d.properties.value;
        if (value) {
          return color(value);
        }
        else {
          return "#ccc";
        }
      })
      .on("mouseover", function(d, i) {
        d3.select(this)
        .style("fill-opacity", 0.5);
      })
      .on("mouseout", function(d, i) {
        d3.selectAll("path")
        .style("fill-opacity", 1);
      })
      .on("click", function() {
        stateView();
      });
  });
});
};

var clickEventMap = function() {
  $("#map2013").on("click", function() {
    // var year = 2013;
    drawMap(2013);
  });

  $("#map2012").on("click", function() {
    // var year = 2012;
    drawMap(2012);
  });

  $("#map2011").on("click", function() {
    // var year = 2011;
    drawMap(2011);
  });

  $("#map2010").on("click", function() {
    // var year = 2010;
    drawMap(2010);
  });

  $("#map2009").on("click", function() {
    // var year = 2009;
    drawMap(2009);
  });
};

drawMap(2013);
clickEventMap();

感谢您的反馈。

可以提供一个可以工作的实例/网址吗? - iulian
1个回答

1
您的主要问题是在drawMap函数内部做了太多的事情。其中很多内容只需要执行一次,而不是每次切换年份都要执行。这就是为什么您会得到多个地图的原因。每次调用drawMap时,此代码会创建一个新的svg。
var svg = d3.select("body")
  .append("svg")
  .attr("height", h)
  .attr("width", w);

如果你将其从drawMap函数中移出,你只会得到一个svg。
每次绘制地图时也会获取'us-states.json'文件。这可能在技术上可以工作,但会导致很多缓慢的网络请求,而这些请求其实是不必要的。只需在页面加载时进行一次请求即可。这是对代码的相当大的重构更改,因为基本上在此之后的所有内容都需要从回调函数内部调用。
最后,在您进行这些更改之后,您需要更改绘制州的代码以使其正确更新。现在,您只是在“enter”选择上执行所有操作,这仅适用于新路径。由于您将编辑现有路径,因此您的代码需要像这样:
var paths = svg.selectAll("path")
    .data(json.features)
    .enter()
    .append("path")
    .attr("d", path)
    .on("mouseover", function(d, i) {
        d3.select(this)
            .style("fill-opacity", 0.5);
    })
    .on("mouseout", function(d, i) {
        d3.selectAll("path")
            .style("fill-opacity", 1);
    })
    .on("click", function() {
        stateView();
    });

paths.style("fill", function(d) {
    var value = d.properties.value;
    if (value) {
        return color(value);
    } else {
        return "#ccc";
    }
});

Mike Bostock的文章Thinking with JoinsThree Little Circles都是理解d3中更新的很好的资源。

基本上,只需考虑哪些事情您只需要执行一次,以及每次单击新年份时需要更改什么(更新颜色比例尺并为每个州设置填充样式)。尝试将所有仅发生一次的内容提取到init函数中,并保持您的drawMap函数较小。


感谢您的回复,詹姆斯。 - arsm800
我将一些变量移到了drawMap()函数上面,并创建了一个paths变量。尽管每次并没有创建一个新的svg,但初始地图svg并未更改。最终我做的是将地图svg放在一个id为“usmap”的div中,然后执行var svg = d3.select("#usmap")。此外,在声明svg之前,在drawMap()函数的开头添加了$("#usmap").empty()。虽然清空div并创建新的svg可能不是最有效或优雅的方法,但似乎有用。 - arsm800
我想清空div可能是让它工作的最简单方法。但我认为你没有充分利用d3的更新模式。特别是加载地图json会使重绘变慢,所以你至少应该尝试只发出一次请求。 - James Stewart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接