D3.js 是一个可视化的 Javascript 库,使用它可以方便的将数据以图形化的方式展示在 HTML 页面中。

现在我们的需求是根据行政区域的地理显示不同区域的状态(如温度、人口等)。

首先需要获取区域的边界数据,以大武汉为例,市下面划分出了 13 个区,

首先获得这些区的名称,然后利用百度地图的 API 获取区域边界数据,方法是:

http://developer.baidu.com/map/jsdemo.htm#c1_10 页面的源码编辑器中修改 getBoundary 函数为

function getBoundary(){
    var bdary = new BMap.Boundary();
    bdary.get("武汉市 洪山区", function(rs){       //获取行政区域
        map.clearOverlays();        //清除地图覆盖物       
        var count = rs.boundaries.length; //行政区域的点有多少个
        for(var i = 0; i < count; i++){
            var ply = new BMap.Polygon(rs.boundaries[i], {strokeWeight: 2, strokeColor: "#ff0000"}); //建立多边形覆盖物
            map.addOverlay(ply);  //添加覆盖物
            map.setViewport(ply.getPath());    //调整视野     
            console.log(rs.boundaries[i]);
        }                
    });   
}

运行这段代码,会将对应的区域的坐标路径输出到控制台中,注意一个行政区域可能会由多个区域组成

获取完数据后,需要将它保存为 D3.js 需要的格式 GeoJSON feature collection,参见 http://geojson.org/geojson-spec.html

(要注意的是 Baidu 获取的貌似和我们需要的是反的,如果不倒序,则显示的每个区域都几乎覆盖了整个地球,剩下没有覆盖的恰好是我们需要的区域范围)

然后再定义对应的区域的数据(如温度,人口等),形如

name,x,y,value
黄陂区,114.323981, 30.888003,24
东西湖区,114.037116, 30.683843,5
蔡甸区,113.965582, 30.466047,1
汉南区,113.941924, 30.313071,22
江夏区,114.328789, 30.220889,4
洪山区,114.349486, 30.50543,2
江岸区,114.266716, 30.645693,4
江汉区,114.200334, 30.609433,11
硚口区,114.160993, 30.588782,3
汉阳区,114.183867, 30.531172,15
武昌区,114.322753, 30.559431,17
青山区,114.391168, 30.626713,6
新洲区,114.625446, 30.72895,3

其中 x,y 为文字显示的位置,value 为要显示的数据,显示时会根据此数据自动套用 d3.scale.quantize 定义的颜色

最后在 HTML 页面用 D3.js 将两种数据载入、关联起来,关键代码如下

<script type="text/javascript">
var height = window.innerHeight;
var width = window.innerWidth;

var svg = d3.select('#container')
    .append('svg')
    .attr('width', width).attr('height', height)
    .append('g');

var projection = d3.geo.mercator();
var oldScala = projection.scale();
var oldTranslate = projection.translate();

xy = projection.scale(21000)
    .translate([width / 2, height / 2]).center([114.235521, 30.631975]);

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

var color = d3.scale.quantize()
    .range(["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"])

svg.attr('width', width).attr('height', height);

d3.csv("wuhan_data.csv", function(data){
    color.domain([
        d3.min(data, function(d){return d.value;}),
        d3.max(data, function(d){return d.value;})
    ]);

    d3.json("wuhan_map.json", function(json){
        for(var i = 0; i < data.length; i++)
        {
            var name  = data[i].name;
            var value = data[i].value;
            for(var j = 0; j < json.features.length; j++)
            {
                
                var map_name = json.features[j].properties.name;
                if(name == map_name) {
                    
                    json.features[j].properties.value = value;
                    break;
                }
            }
        }
        
        svg.selectAll("path")
            .data(json.features)
            .enter()
            .append("path")
            .attr("d", path)
            .on('mouseover', function(data) {
              d3.select(this).style('opacity', 1);
            })
            .on('mouseout', function(data) {
              d3.select(this).style('opacity', 0.7);
            })
            //.attr('fill', 'rgba(128,124,139,0.61)')
            .attr('fill', function(d) {
                var value = d.properties.value;
                if(value) {
                    return color(value);
                }
                return "rgba(128,124,139,0.61)";
            })
            .attr('stroke', 'rgba(255,255,255,1)')
            .attr('stroke-width', 1)
            .style('opacity', 0.7);

        svg.selectAll(".place-label")
            .data(data)
            .enter()
            .append("text")
            .attr("class", "place-label")
            .attr("transform", function(d) { return "translate(" + projection([d.x, d.y]) + ")"; })
            .text(function(d) { return d.name + ":" + d.value; });
    });
});
</script>