D3.js中Population Pyramid详解

Posted wan353694124

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了D3.js中Population Pyramid详解相关的知识,希望对你有一定的参考价值。

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到0
var 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坐标轴的刻度的计算方法。
今天尽管阳光明媚,但是空气冷凉,适合坐在室内窗边安安静静地就很美好。

以上是关于D3.js中Population Pyramid详解的主要内容,如果未能解决你的问题,请参考以下文章

D3.js的v5版本入门教程(第十二章)—— D3.js中各种精美的图形

Javascript / D3.js - 绘制大型数据集 - 提高 d3.js 绘制的 svg 图表中的缩放和平移速度

D3.js学习中总结的那些金句

D3.Js图表excel

在 d3.js 中制作弧线

D3.js 与 React.js