渐变的地图

制作中国地图里,各省份的颜色值都是随意的。如果要将一些值反映在地图上,可以利用颜色的变化来表示值的变化。

思路

例如,有值域的范围为:

[10, 500]

现希望10用浅绿表示,500用深绿表示,10到500之间的值用浅绿和深绿之间的颜色表示。显然,此处需要一个函数,传入的参数是10到500之间的值,返回值是浅绿到深绿之间的颜色值。

定义一个颜色插值如下。

var palegreen = d3.rgb(66,251,75);    //浅绿
var darkgreen = d3.rgb(2,100,7);        //深绿

var color = d3.interpolate(a,b);        //颜色插值函数

这段代码最后得到的color可作为函数使用,参数的范围为[0, 1],当参数为0时,返回浅绿,当参数为1时,返回深绿。但是,现在的值域是[10, 500],范围不是[0, 1]。因此,先定义一个线性比例尺,将[10, 500]按线性关系映射到[0, 1]。

var linear = d3.scale.linear()
        .domain([10, 500])
        .range([0, 1]);

如此一来,便可结合比例尺来使用颜色插值函数。

color( linear(10) );      //返回浅绿RGB(66,251,75)
color( linear(250) );       //返回浅绿和深绿之间的值
color( linear(500) );       //返回深绿RGB(2,100,7)

绘制完整的中国地图

本例使用 TopoJSON 文件作为地理数据,这种文件比 GeoJSON 小,能提高读取速度。

要使用 TopoJSON 的相关函数,需要引用:

<script src="http://d3js.org/topojson.v1.min.js" charset="utf-8"></script>

读取之后,使用 topojson.feature 将其转换为 GeoJSON 文件,实际上最终使用时还是 GeoJSON 的格式,但是在读取时速度会快很多。

d3.json("china.topojson", function(error, toporoot) {
    if (error) 
        return console.error(error);
    
    //输出china.topojson的对象
    console.log(toporoot);
    
    //将TopoJSON对象转换成GeoJSON,保存在georoot中
    var georoot = topojson.feature(toporoot,toporoot.objects.china);
    
    //输出GeoJSON对象
    console.log(georoot);

    //包含中国各省路径的分组元素
    var china = svg.append("g");
        
    //添加中国各种的路径元素
    var provinces = china.selectAll("path")
            .data( georoot.features )
            .enter()
            .append("path")
            .attr("class","province")
            .style("fill", "#ccc")
            .attr("d", path );

});

此外,南海诸岛的地图是不包含在地图文件里的。但是,中国的南海诸岛,一般只是显示在右下角,用一个方框框起来而已,不一定要做成GeoJSON格式。直接制作一个SVG格式的文件即可。

我制作了一个:southchinasea.svg

添加到代码里,形如:

d3.xml("southchinasea.svg", function(error, xmlDocument) {
    svg.html(function(d){
        return d3.select(this).html() + xmlDocument.getElementsByTagName("g")[0].outerHTML;
    });
    
    var gSouthSea = d3.select("#southsea");
    
    gSouthSea.attr("transform","translate(540,410)scale(0.5)")
        .attr("class","southsea");

});

为各省市添加颜色

假设现在有一组反应各省旅游业发展的数据,保存到文件 tourism.json 里:

{
    "name": "中国",
    "provinces":
    [
        {"name": "北京", "value": 14149    },
        {"name": "天津", "value": 2226.41},
        {"name": "河北", "value": 1544.94},
        {"name": "山西", "value": 3720.24},
                // 省略
    ]
}

读取此文件后,按照第一节的思路,创建一个颜色插值函数:

        //求最大值和最小值
        var maxvalue = d3.max(valuedata.provinces, function(d){ return d.value; });
        var minvalue = 0;

        //定义一个线性比例尺,将最小值和最大值之间的值映射到[0, 1]
        var linear = d3.scale.linear()
                        .domain([minvalue, maxvalue])
                        .range([0, 1]);

        //定义最小值和最大值对应的颜色
        var a = d3.rgb(0,255,255);  //浅蓝色
        var b = d3.rgb(0,0,255);    //蓝色
         
        //颜色插值函数
        var computeColor = d3.interpolate(a,b);

computeColor 是我们需要的函数。接下来,只需要修改各省份的填充色即可,为了方便,将读取到的数据都放到一个values数组里,令其索引号为各省的名称。

      //将读取到的数据存到数组values,令其索引号为各省的名称
        var values = [];
        for(var i=0; i<valuedata.provinces.length; i++){
            var name = valuedata.provinces[i].name;
            var value = valuedata.provinces[i].value;
            values[name] = value;
        }

        //设定各省份的填充色
        provinces.style("fill", function(d,i){
            var t = linear( values[d.properties.name] );
            var color = computeColor(t);
            return color.toString();
        });

这样,虽然把地图绘制了,填充色也按照值域对应了,但是还需要一个标志,来告诉用户什么颜色对应什么值。

添加颜色标志

在地图左下角添加一个渐变标志,告诉用户什么颜色对应什么值。

       //定义一个线性渐变
        var defs = svg.append("defs");

        var linearGradient = defs.append("linearGradient")
                                .attr("id","linearColor")
                                .attr("x1","0%")
                                .attr("y1","0%")
                                .attr("x2","100%")
                                .attr("y2","0%");

        var stop1 = linearGradient.append("stop")
                        .attr("offset","0%")
                        .style("stop-color",a.toString());

        var stop2 = linearGradient.append("stop")
                        .attr("offset","100%")
                        .style("stop-color",b.toString());

        //添加一个矩形,并应用线性渐变
        var colorRect = svg.append("rect")
                    .attr("x", 20)
                    .attr("y", 490)
                    .attr("width", 140)
                    .attr("height", 30)
                    .style("fill","url(#" + linearGradient.attr("id") + ")");

        //添加文字
        var minValueText = svg.append("text")
                    .attr("class","valueText")
                    .attr("x", 20)
                    .attr("y", 490)
                    .attr("dy", "-0.3em")
                    .text(function(){
                        return minvalue;
                    });

        var maxValueText = svg.append("text")
                    .attr("class","valueText")
                    .attr("x", 160)
                    .attr("y", 490)
                    .attr("dy", "-0.3em")
                    .text(function(){
                        return maxvalue;
                    });

结果

源代码

下载地址:range.zip

在线演示

委托名片直接发送邮件(需邮箱客户端)点击复制邮箱地址14721230383项目委托、技术支持,欢迎与我们联系。休息日7:00 ~ 11:0012:00 ~ 22:00工作日11:00 ~ 11:4016:00 ~ 22:00

Copyright 2014-2016, DecemberCafe, All Rights Reserved