【d3.js实践教程02】基于中国地图的高考一本录取率排行

学习d3.js(以下都简称d3)也有一段时间了,运行d3做了几个项目。我发现中文的d3教程很少,国外资料多但要求有一定的英文阅读能力(推荐网址:http://bl.ocks.org/mbostock),于是就萌发了写一个d3实际运用系列文章的想法,现在开始付之行动。在系列中,我会用d3+html5 canvas实现一些实际效果(如统计结果展示,地图数据展示等),希望可以跟大家共同学习交流。

代码我公布在git.cschina.com上,大家可以clone到本地运行,地址是:http://git.oschina.net/0604hx/d3lesson

运行环境是java 7+,tomcat 7.0.47+(以后会用到websocket,所以需要javaee7 跟 tomcat 7+的支持),IDE 是IntelliJ IDEA 13, 项目的视图使用了freemarker。

这一章讲的是在中国地图上展示2013年大陆各省份高考一本录取率的排行。

准备数据

首先需要有录取率的相关数据,我从网上复制出来了一份统计数据:

2013年一本录取率排名
1	 天津	 6.3	 1.5447	 24.52%
2	 北京	 7.27	 1.7686	 24.33%
3	 上海	 5.3	 1.2	 22.64%
4	 青海	 3.6733	 0.6837	 18.61%
5	 山东	 50.9	 9.351	 18.37%
6	 宁夏	 5.87	 1.001	 17.05%
7	 吉林	 15.5	 2.2435	 14.47%
8	 福建	 25.5	 3.6186	 14.19%
9	 贵州	 24.78	 3.4369	 13.87%
10	 浙江	 31.3	 4.1887	 13.38%
11	 陕西	 36.65	 4.8422	 13.21%
12	 新疆	 15.87	 2.05	 12.92%
13	 云南	 23.6	 3.0179	 12.79%
14	 海南	 5.6	 0.6396	 11.42%
15	 内蒙古	 19.3	 2.163	 11.21%
16	 甘肃	 28.3	 2.9598	 10.46%
17	 安徽	 51.1	 5.1692	 10.12%
18	 江苏	 45.1	 4.5085	 10.00%
19	 湖南	  37.3	 3.5789	 9.59%
20	 黑龙江	 20.8	 1.9931	 9.58%
21	 重庆	 23.5	 2.195	 9.34%
22	 江西	 27.43	 2.4891	 9.07%
23	 河北	 44.98	 4.0602	 9.03%
24	 湖北	 43.8	 3.5923	 8.20%
25	 广西	 29.8	 2.3	 7.72%
26	 河南	 71.63	 4.8655	 6.79%
27	 广东	 72.7	 4.3092	 5.93%
28	 山西	 35.8	 2.1091	 5.89%
29	 辽宁	 25.4	 1.4583	 5.74%
30	 四川	 54	 	 2.849   5.28%
31	 西藏	 1.89	 0.0904	 4.78%

这个文件可以在d3lesson中找到

SouthEast

对于有规则的txt数据(如一行一个对象),我写了一个转换工具,可以转成json(json格式在网页中使用较为方便),具体可见:org.nerve.d3lesson.common.tools.impl.TxtToJSONImporter 这个类。

如何实现?

1. 画出中国地图

整个地图是用svg的path绘制,那么需要有相应的数据。中国地图的json数据在 /web/data/china.json 中,我们可以用d3的json()方法加载这个json,然后绘制出地图。

加载方法:

d3.json("{json路径}", function(data){
	//这里是回调函数,如果加载成功,data就是json对象
	//执行drawChina方法绘制地图
	
});


绘制函数(这里使用过的是墨卡托投影, projection 的调整我暂时没弄透彻,反正是对着屏幕调到满意的位置就好了,如果有朋友知道欢迎解答,万分感谢!)

在绘制过程中,给每个省份对应的path加一个唯一的id,方便以后调用(如修改颜色就是通过id获取path来完成)

// Project from latlng to pixel coords
//使用墨卡托投影
var projection = d3.geo.mercator()
        .scale(width/2)                                   //对地图进行缩放
        .translate([width / 2, height / 2])                 //将地图平移到屏幕中间
        .rotate([-110, 0])
        .center([0, 37.5])                                  //设置中心点,调整到屏幕中心
;

// Draw geojson to svg path using the projection
var path = d3.geo.path().projection(projection);

//画出中国地图
function drawChina(ds){
    if(!chinaG)
        chinaG = container.append("g");
    chinaG.selectAll("path")
            .data(ds.features)
            .enter()
            .insert("path")
            .attr("id", function(d){
                return d.id;
            })
            .attr("fill", "#000000")
            .attr("d", path)
            .attr('stroke',setting.strokeColor)
            .attr('stroke-width','0.7px')
    ;
}

看看dom中都创建了什么?

SouthEast

2. 根据排行对省份进行颜色分配

接着,就要根据排序规则对省份上色了。先看看统计数据是怎么样的(就是第一步中转换过来的json数据):

{
    "_title": "2013年一本录取率排名",
    "datas": [
        {
            "enter": 1.5447,
            "id": "TIANJIN",
            "index": 1,
            "province": "天津",
            "rate": "24.52%",
            "total": 6.3
        },
        {
            "enter": 1.7686,
            "id": "BEIJING",
            "index": 2,
            "province": "北京",
            "rate": "24.33%",
            "total": 7.27
        },
        {
            "enter": 1.2,
            "id": "SHANGHAI",
            "index": 3,
            "province": "上海",
            "rate": "22.64%",
            "total": 5.3
        },
        //.....
        //剩下的就不列出来了

同样的,我们用d3.json() 方法加载这些数据,然后排序其中的datas数组。

这里要说一下过度颜色,我是这样定义的:

 //创建过度颜色,注意上一步的排序是从大到小,那么颜色应该是从深到浅
        var rateColors = d3.scale.linear()
                .domain([1, 340])
                .range([d3.rgb(20, 120, 140),d3.rgb(180, 230, 255)]);

那么可以这样得到一个颜色值: rateColors(index); 传进去的index应该是 1 到 340 之间(当然你传更大或更小的也可以),那么就得到d3.rgb(20, 120, 140),d3.rgb(180, 230, 255) 之间相对应的一个颜色。 如index=1 就得到 d3.rgb(20, 120, 140), index = 340 就得到d3.rgb(180, 230, 255), index=170 就得到两个端点颜色的中间颜色。

最后就是对数据排序,然后更新对应省份的颜色了:

/**
 * 根据录取率排序
 */
function sortByRate(){
    //首先我们需要对数据进行录取率从大到小的排序
    //因为rate 是 xx.xx% 的格式,所以在对比前需要进行parseFloat 的操作
    var data = gkData.datas.sort(function(d1,d2){
        return parseFloat(d2.rate) - parseFloat(d1.rate);
    });

    //创建过度颜色,注意上一步的排序是从大到小,那么颜色应该是从深到浅
    var rateColors = d3.scale.linear()
                .domain([1, 340])
                .range([d3.rgb(130, 140, 20),d3.rgb(255, 255, 180)]);
    /*
    遍历上一步得到是数组
    forEach 参数中的 d 就是遍历到的某个数据, i 就是该对象的下标序号,从0开始
    */
    data.forEach(function(d,i){
        d.sort = i+1;
        //通过d.id 来获取中国地图上对应的省份,因为地图中的省份块是根据省份拼音命名的
        d3.select("#"+ d.id)
                .transition()
                .duration(duration)
                .delay(10*i)
                .attr("fill", rateColors((i+1)*10))
        ;
    });

    buildTip(data);
    showOnTable(data);
}

/**
 * 根据参加高考人数排序
 */
function sortByTotal(){
    //首先我们需要对数据进行录取率从大到小的排序
    //因为rate 是 xx.xx% 的格式,所以在对比前需要进行parseFloat 的操作
    var data = gkData.datas.sort(function(d1,d2){
        return d2.total - d1.total;
    });

    //创建过度颜色,注意上一步的排序是从大到小,那么颜色应该是从深到浅
    var rateColors = d3.scale.linear()
            .domain([1, 340])
            .range([d3.rgb(20, 120, 140),d3.rgb(180, 230, 255)]);
//                .range([d3.rgb(30, 40, 160),d3.rgb(180, 160, 255)]);
    /*
    遍历上一步得到是数组
    forEach 参数中的 d 就是遍历到的某个数据, i 就是该对象的下标序号,从0开始
    */
    data.forEach(function(d,i){
        d.sort = i+1;
        //通过d.id 来获取中国地图上对应的省份,因为地图中的省份块是根据省份拼音命名的
        d3.select("#"+ d.id)
                .transition()
                .duration(duration)
                .delay(10*i)
                .attr("fill", rateColors((i+1)*10))
        ;
    });

    buildTip(data);
    showOnTable(data);
}

3. 创建提示

鼠标移动到省份上,可以显示具体的信息(这个功能是很实用的!客户绝对是需要的)
首先,先定义好用来显示提示的div元素
<!--div提示框-->
<div id="tooltip" class="hidden box">
    <p>
        <strong class="dataHolder" name="province"></strong>
        排名:<span class="dataHolder" name="sort"></span>
    </p>
    <div>
        高考人数:<span class="dataHolder" name="total"></span>万 
        录取率:<span class="dataHolder" name="rate"></span>
    </div>
</div>
然后用d3填充数据

/**
 * 创建提示条
 * 提示的创建大致有3种方式
 * 1: 给svg元素里面增加一个title元素,
 *     var t = d3.select(id).append("title").text("我是提示条");
 *      这种方法效果不大理想,而且提示单调
 *
 * 2: 给需要提示的元素添加mouseover, mouseout 事件,当鼠标在该元素上移动时,就显示提示条(动态创建的svg元素),如:
 *      var t = d3.select(id);
 *      t.on('mouseover',function(){
 *          //创建提示条
            svg.append("text")
              .attr("id", "tooltip")
              .attr("x", d3.event.x)
              .attr("y", d3.event.y)
              .attr("text-anchor", "middle")
              .attr("font-family", "sans-serif")
              .attr("font-size", "11px")
              .attr("font-weight", "bold")
              .attr("fill", "black")
              .text("我是svg的提示条");
            })
 *      });
 *
 * 3: 类似方法2,但是提示条不是svg元素,而是普通的html元素(如div),动态修改提示框里面的内容跟提示框的x,y坐标
 *      达到提示的效果,总体来说这个方法较好,较为灵活,而且可以使用css3,同时不用担心提示框超出svg范围的问题
 *
 *      所以,在教程中,都是使用这个方法
 */
function buildTip(data){
    var t = "#tooltip";
    chinaG.selectAll("path")
            .data(data, function(d){
                return d.id;
            })
            .on("mouseover",function(d){
                d3.select(t)
                        .style("left", d3.event.x + "px")
                        .style("top", d3.event.y + "px")
                        .classed("hidden", false)
                        .selectAll(".dataHolder")[0]
                        .forEach(function(h){
                            h = d3.select(h);
                            h.html(d[h.attr('name')]);
                        })
                ;
                d3.select(this)
                        .attr("opacity", 0.8);
            })
            .on("mouseout",function(){
                d3.select(t).classed("hidden", true);
                d3.select(this)
                        .attr("opacity", 1);
            })
    ;
}


详细的代码请到:http://git.oschina.net/0604hx/d3lesson

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

集成显卡

码字不易,需要您的鼓励😄

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值