|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>Income distribution in Canada</title> |
|
<style> |
|
.axis .domain { |
|
display: none; |
|
} |
|
|
|
.grid .line { |
|
fill: none; |
|
stroke: white; |
|
stroke-width: 1px; |
|
stroke-dasharray: 1, 5; |
|
} |
|
.grid .line.major { |
|
stroke-dasharray: 0; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<svg width="1280" height="500"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
var svg = d3.select("svg"), |
|
margin = {top: 20, right: 20, bottom: 50, left: 40}, |
|
width = +svg.attr("width") - margin.left - margin.right, |
|
height = +svg.attr("height") - margin.top - margin.bottom, |
|
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
var incomeBuckets = [ |
|
"under-5_000", "5_000-10_000", "10_000-15_000", "15_000-20_000", |
|
"20_000-25_000", "25_000-30_000", "30_000-35_000", "35_000-40_000", |
|
"40_000-45_000", "45_000-50_000", "50_000-55_000", "55_000-60_000", |
|
"60_000-70_000", "70_000-80_000", "80_000-90_000", "90_000-100_000", |
|
"100_000-150_000", "150_000-250_000", "over-250_000", |
|
], |
|
incomeBucketLabels = [ |
|
"Under $5,000", "$5,000 to $9,999", |
|
"$10,000 to $14,999", "$15,000 to $19,999", |
|
"$20,000 to $24,999", "$25,000 to $29,999", |
|
"$30,000 to $34,999", "$35,000 to $39,999", |
|
"$40,000 to $44,999", "$45,000 to $49,999", |
|
"$50,000 to $54,999", "$55,000 to $59,999", |
|
"$60,000 to $69,999", |
|
"$70,000 to $79,999", |
|
"$80,000 to $89,999", |
|
"$90,000 to $99,999", |
|
"$100,000 to $149,999", |
|
"$150,000 to $249,999", |
|
"$250,000 and over", |
|
]; |
|
|
|
var firstLevelX = d3.scaleBand() |
|
.rangeRound([0, width]) |
|
.paddingInner(0.1); |
|
|
|
var secondLevelX = d3.scaleBand() |
|
.padding(0.05); |
|
|
|
var y = d3.scaleLinear() |
|
.rangeRound([height, 0]); |
|
|
|
var color = d3.scaleOrdinal(d3.schemeCategory20); // 20 just looks prettier than 10 |
|
|
|
d3.csv("cndtbl-summary.csv", function(error, data) { |
|
if (error) { |
|
throw error; |
|
} |
|
//console.log(data); |
|
|
|
var totalData = data.filter(function(item) { |
|
return item.classification.trim() === "CANADA TOTAL"; |
|
}); |
|
//console.log(totalData); |
|
|
|
// Configure domains for scales. |
|
firstLevelX.domain(incomeBuckets); |
|
var years = totalData.map(function(item) { return item.year; }).sort(); |
|
secondLevelX |
|
.domain(years) |
|
.rangeRound([0, firstLevelX.bandwidth()]); |
|
var yMax = d3.max(totalData, function(item) { |
|
return d3.max(incomeBuckets, function(bucket) { |
|
return parseInt(item[bucket]); |
|
}); |
|
}); |
|
// Add 0.2 million to have some head space at the top. |
|
y.domain([0, yMax + 200000]); |
|
|
|
// Transform data: first level is income buckets, second level is years. |
|
var processedData = {}; |
|
incomeBuckets.forEach(function(bucket) { |
|
processedData[bucket] = { |
|
bucket: bucket, |
|
yearlyData: [], |
|
}; |
|
}); |
|
totalData.forEach(function(yearData) { |
|
var year = yearData.year; |
|
incomeBuckets.forEach(function(bucket) { |
|
processedData[bucket].yearlyData.push({ |
|
year: year, |
|
value: parseInt(yearData[bucket]), |
|
}); |
|
}); |
|
}); |
|
//console.log(processedData); |
|
|
|
// Plot data. |
|
g.append("g") |
|
.selectAll("g") |
|
.data(Object.values(processedData)) |
|
.enter().append("g") |
|
.attr("transform", function(d) { return "translate(" + firstLevelX(d.bucket) + ",0)"; }) |
|
.selectAll("rect") |
|
.data(function(d) { return d.yearlyData; }) |
|
.enter().append("rect") |
|
.attr("x", function(d) { return secondLevelX(d.year); }) |
|
.attr("y", function(d) { return y(d.value); }) |
|
.attr("width", secondLevelX.bandwidth()) |
|
.attr("height", function(d) { return height - y(d.value); }) |
|
.attr("fill", function(d) { return color(d.year); }); |
|
|
|
// Draw text at different height because labels for consecutive bands overlap. |
|
function customXAxis(g) { |
|
// Based on Axis Styling https://bl.ocks.org/mbostock/3371592 |
|
var xAxis = d3.axisBottom(firstLevelX.copy().domain(incomeBucketLabels)); |
|
g.call(xAxis); |
|
g.selectAll(".tick:nth-child(odd) text").attr("dy", 20); |
|
} |
|
g.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(customXAxis); |
|
|
|
g.append("g") |
|
.attr("class", "axis") |
|
.call(d3.axisLeft(y).ticks(null, "s")) |
|
.append("text") |
|
.attr("x", 2) |
|
.attr("y", y(y.ticks().pop()) + 0.5) |
|
.attr("dy", "0.32em") |
|
.attr("fill", "#000") |
|
.attr("font-weight", "bold") |
|
.attr("text-anchor", "start") |
|
.text("Population with income"); |
|
|
|
var yGridValues = y.ticks().filter(function(d) { return (0 < d) && (d <= yMax); }); |
|
var gridData = yGridValues.map(function(yValue) { |
|
return [[0, yValue], [width, yValue]]; |
|
}); |
|
g.append("g") |
|
.attr("class", "grid") |
|
.selectAll("path") |
|
.data(gridData) |
|
.enter().append("path") |
|
.attr("class", "line") |
|
.attr("d", d3.line().y(function(d) { return y(d[1]); })) |
|
.classed("major", function(d) { return (d[0][1] % 500000) === 0; }); |
|
|
|
var legend = g.append("g") |
|
.attr("font-family", "sans-serif") |
|
.attr("font-size", 10) |
|
.attr("text-anchor", "end") |
|
.selectAll("g") |
|
.data(years) |
|
.enter().append("g") |
|
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); |
|
|
|
legend.append("rect") |
|
.attr("x", width - 19) |
|
.attr("width", 19) |
|
.attr("height", 19) |
|
.attr("fill", color); |
|
|
|
legend.append("text") |
|
.attr("x", width - 24) |
|
.attr("y", 9.5) |
|
.attr("dy", "0.32em") |
|
.text(function(d) { return d + " tax year"; }); |
|
}); |
|
</script> |
|
</body> |
|
</html> |