D3.js中Population Pyramid詳解
阿新 • • 發佈:2019-02-12
// 定義相關尺寸
// 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); });
}
});