D3.js中Population Pyramid详解

来源:互联网 发布:网络燃爆了什么意思 编辑:程序博客网 时间:2024/05/17 12:20

Population Pyramid

聊一聊人口金字塔图。

人口金字塔是按人口年龄和性别表示人口分布的特种塔状条形图,是形象地表示某一人口的年龄和性别构成的图形。——百度百科

一般的人口金字塔图如下图所示:

例如上图表示,2011年利比亚男女不同年龄阶段的比例分布情况。
而本篇要讲的Population Pyramid图,将男女人口数据画在了坐标轴的同一边,通过柱状图的覆盖来看不同年龄阶段的男女比例分布情况,如下图所示:

图中用粉色来标识女性的数据、蓝色标识男性的数据,数据重叠部分,由于粉色和蓝色重叠而呈现出紫色,例如70岁的人群当中,女性的比例比男性的多;而20岁的人群当中,男性的比例比女性的多。

接下来详细解释D3.js是如何实现这张人口金字塔图的。

index.html——源码

<!DOCTYPE html><meta charset="utf-8"><style>svg {  font: 10px sans-serif;}.y.axis path {  display: none;}.y.axis line {  stroke: #fff;  stroke-opacity: .2;  shape-rendering: crispEdges;}.y.axis .zero line {  stroke: #000;  stroke-opacity: 1;}.title {  font: 300 78px Helvetica Neue;  fill: #666;}.birthyear,.age {  text-anchor: middle;}.birthyear {  fill: #fff;}rect {  fill-opacity: .6;  fill: #e377c2;}rect:first-child {  fill: #1f77b4;}</style><body><script src="//d3js.org/d3.v3.min.js"></script><script>// 定义相关尺寸// margin定义svg画图的上 、右、下、左的外边距var margin = {top: 20, right: 40, bottom: 30, left: 20},     // 计算宽度    width = 960 - margin.left - margin.right,    // 计算高度    height = 500 - margin.top - margin.bottom,    // 计算柱状条的宽度,其中19由于分了19个年龄段    barWidth = Math.floor(width / 19) - 1;// 为x轴定义线性比例尺,值域range的定义可以看出,x轴的刻度尺都会位于柱状图的底部中间位置var x = d3.scale.linear()    .range([barWidth / 2, width - barWidth / 2]);// 为y轴定义线性比例尺,值域为height到0var y = d3.scale.linear()    .range([height, 0]);// 定义y坐标轴var yAxis = d3.svg.axis()    // 设置y轴的比例尺    .scale(y)    // y轴坐标刻度文字在右侧    .orient("right")    // 这里设置为“-width”,个人理解为,y轴刻度线本应该在轴的右边,设置为负数,刻度线绘制在y轴的左边    // 而且刻度线的长度为图形的宽度,表现在图上就是那些横穿柱状条的白色线,看不见白色线的部分是因为    // 图背景和刻度线都是白色    .tickSize(-width)    // 设置y轴刻度的格式    .tickFormat(function(d) { return Math.round(d / 1e6) + "M"; });// An SVG element with a bottom-right origin.// 定义svg画布var svg = d3.select("body").append("svg")    // 设置svg画布的宽度    .attr("width", width + margin.left + margin.right)    // 设置svg画布的高度    .attr("height", height + margin.top + margin.bottom)  .append("g")    // 定位svg画布    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");// A sliding container to hold the bars by birthyear.// 定义表示 出生年 的元素var birthyears = svg.append("g")    .attr("class", "birthyears");// A label for the current year.// 绘制当年的年份文字,即图中左上角的 2000字样var title = svg.append("text")    .attr("class", "title")    .attr("dy", ".71em")    .text(2000);// 处理数据d3.csv("population.csv", function(error, data) {  // Convert strings to numbers.  // 将csv数据文件中的pepole,yaer,age字段的值转换成数字类型  data.forEach(function(d) {    d.people = +d.people;    d.year = +d.year;    d.age = +d.age;  });  // Compute the extent of the data set in age and years.  // 计算年龄和年份数据集的范围  // 获取最大年龄  var age1 = d3.max(data, function(d) { return d.age; }),      // 获取最小年份      year0 = d3.min(data, function(d) { return d.year; }),      // 获取最大年份      year1 = d3.max(data, function(d) { return d.year; }),      // 设置year为最大年份      year = year1;  // Update the scale domains.  // 上面在定义x,y的比例尺时没有设置“定义域”,此处开始设置  // 设置x比例尺的定义域,可以看出,x轴表示年龄的变化  x.domain([year1 - age1, year1]);  // 设置y比例尺的定义域,可以看出,y轴表示人口数量的变化  y.domain([0, d3.max(data, function(d) { return d.people; })]);  // Produce a map from year and birthyear to [male, female].  // d3.nest()函数用来将数据分组为任意层次结构  // d3.nest().key(fun)用来对每数据以fun函数返回的键值来进行分组,此处以year来进行分组  // 后,返回的是以year作为键的不同的数组;再以year-age作为键值进行第二次分组;  // rollup()函数将用返回的值d.people来替换key所对应的值  // d3.nest().map()返回最终的分组后的层次结构的数据  // 可以通过在浏览器中调试状态下看到最终返回的data数组是以年份进行第一层分组,每个年份下又以  // d.year -d.age进行了第二层的分组,第二层分组对应的数据为rollup中指定的d.people。  data = d3.nest()      .key(function(d) { return d.year; })      .key(function(d) { return d.year - d.age; })      .rollup(function(v) { return v.map(function(d) { return d.people; }); })      .map(data);  // Add an axis to show the population values.  // 绘制y轴  svg.append("g")      .attr("class", "y axis")      // 将y轴定位到画布右侧      .attr("transform", "translate(" + width + ",0)")      // 对该g元素执行yAxis定义的操作      .call(yAxis)    .selectAll("g")    // 筛选出 value为空的    .filter(function(value) { return !value; })    // 将筛选出的value为空的元素,为期添加zero样式类      .classed("zero", true);  // Add labeled rects for each birthyear (so that no enter or exit is required).  // 为表示出生年份的元素绑定数据,定义年份步长为5年  var birthyear = birthyears.selectAll(".birthyear")      .data(d3.range(year0 - age1, year1 + 1, 5))    .enter().append("g")      .attr("class", "birthyear")      // 定位年份的位置,通过上面定义的x()比例尺函数来计算      .attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)"; });  // 绘制柱状条  birthyear.selectAll("rect")      // 获取2000这一年里,出生年份为birthyear的分组      .data(function(birthyear) { return data[year][birthyear] || [0, 0]; })    .enter().append("rect")      .attr("x", -barWidth / 2)      .attr("width", barWidth)      // 设置y位置通过y比例尺来计算      .attr("y", y)      // 设置柱状条的高度      .attr("height", function(value) { return height - y(value); });  // Add labels to show birthyear.  // 添加出生年份文字  birthyear.append("text")      .attr("y", height - 4)      .text(function(birthyear) { return birthyear; });  // Add labels to show age (separate; not animated).  // 添加年龄文字  svg.selectAll(".age")      // 为年龄文字绑定数据,年龄步长为5      .data(d3.range(0, age1 + 1, 5))    .enter().append("text")      .attr("class", "age")      .attr("x", function(age) { return x(year - age); })      .attr("y", height + 4)      .attr("dy", ".71em")      .text(function(age) { return age; });  // Allow the arrow keys to change the displayed year.  // 通过方向键“←”和“→”来查滑动年份窗口,查看更多年份的人口分布情况  // 用focus()方法可把键盘焦点给予当前窗口  window.focus();  //为方向键“←”和“→”操作绑定动作  d3.select(window).on("keydown", function() {    switch (d3.event.keyCode) {      // 若为向左←,则将当前年份倒退10年      case 37: year = Math.max(year0, year - 10); break;      // 若为向右→,则将当前年份向前推进10年      case 39: year = Math.min(year1, year + 10); break;    }    // 对图进行更新    update();  });  // 定义更改年份窗口后,对图进行更新的操作  function update() {    // 若更改年份窗口后,data中无当前年份的数据,则不进行任何操作,直接返回    if (!(year in data)) return;    // 托更改年份窗口后,data中有当前年份数据,则首先更新左上角显示的年份    title.text(year);    // 更新出生年份,此处定义更新过渡动画    birthyears.transition()        // 动作持续750毫秒        .duration(750)        // 定义更新动作        .attr("transform", "translate(" + (x(year1) - x(year)) + ",0)");    // 更新柱状条    birthyear.selectAll("rect")        // 绑定新的年份窗口数据        .data(function(birthyear) { return data[year][birthyear] || [0, 0]; })        // 定义过渡动画      .transition()        .duration(750)        .attr("y", y)        .attr("height", function(value) { return height - y(value); });  }});</script>

至此,人口金字塔图的实现解释完毕。实现此人口金字塔图的重点:
一是通过d3.nest()数据处理方法,对官网给出的population.csv中的数据进行分组处理。
二是x,y坐标轴的刻度的计算方法。
今天尽管阳光明媚,但是空气冷凉,适合坐在室内窗边安安静静地就很美好。