April 14, 2014
Last Letter of First Names
A D3 recreation of David Taylor's visualization The meteoric rise of boys' names ending in 'n'.
The chart shows the number of children born with names ending in each letter. Adjust the slider to change the year. Toggle the checkboxes to show boys, girls, or both.
Data
The original dataset is the same used in the Compare Historically Popular Names visualization. Of course, this visualization doesn't require every name — just the last letter of each name and its aggregate count. The 28 MB names dataset can be whittled down to a mere 80 KB JSON file.
import csv
import json
def main():
output = {}
# Initialize output
for year in range(1880, 2013):
output[year] = {'F': {}, 'M': {}}
for letter in list(map(chr, range(97, 123))):
output[year]['F'][letter] = 0
output[year]['M'][letter] = 0
reader = csv.reader(open('names1880-2012.csv', 'r'))
reader.next()
for row in reader:
last_letter = row[0][-1]
gender = row[1]
count = int(row[2])
year = int(row[3])
output[year][gender][last_letter] += count
writer = open('last-letter.json', 'w')
json.dump(output, writer)
writer.close()
Visualization
var margin = {top: 10, right: 20, bottom: 40, left: 60},
width = 840 - margin.left - margin.right,
height = 470 - margin.top - margin.bottom,
lettersArray = "abcdefghijklmnopqrstuvwxyz".split("");
var x = d3.scale.ordinal()
.domain(lettersArray)
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var chart = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("last-letter.json", function(letters) {
y.domain(d3.extent(d3.entries(letters["1880"].M), function(d) { return d.value; }));
var bar = chart.selectAll(".bar")
.data(d3.entries(letters["1880"].M))
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.key) + ",0)"})
.append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxisGroup = chart.append("g")
.attr("class", "y axis")
.call(yAxis);
chart.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".5em")
.attr("transform", "rotate(-90)")
.text("# Names");
var input = d3.select("input[name='inyear']"),
output = d3.select("output"),
maleInput = d3.select("input[name='male']")
femaleInput = d3.select("input[name='female']");
input.on("input", function() { updateYear(input.node().value); });
maleInput.on("change", function() { updateYear(input.node().value); });
femaleInput.on("change", function() { updateYear(input.node().value); });
function updateYear(year) {
var data = {};
if (maleInput.property("checked") && femaleInput.property("checked")) {
for (key in letters[year].M) {
data[key] = letters[year].M[key] + letters[year].F[key];
}
}
else if (maleInput.property("checked")) data = letters[year].M;
else if (femaleInput.property("checked")) data = letters[year].F;
else for (key in lettersArray) data[key] = 0;
output.text(year);
y.domain(d3.extent(d3.entries(data), function(d) { return d.value; }));
yAxis.scale(y);
yAxisGroup.transition().duration(100).call(yAxis);
bar.data(d3.entries(data)).transition().duration(100).call(resize);
}
function resize(bar) {
bar .attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
}
var demo = setInterval(function() {
var year = incrementYear();
if (year >= 2012) {
clearInterval(demo);
d3.selectAll("input").attr("disabled", null);
}
input.node().value = year;
updateYear(year);
}, 100);
var incrementYear = (function() { var x = 1880; return function() { return x++; }})();
});