|
/* Remember: Use four-space indents. |
|
* However, use two-space indents for operations that change the selection. |
|
* For more information, see: |
|
* https://bost.ocks.org/mike/d3/workshop/#35 |
|
*/ |
|
var svg = d3.select("svg"), |
|
margin = {top: 20, right: 90, bottom: 40, left: 40}, |
|
width = +svg.attr("width") - margin.left - margin.right, |
|
height = +svg.attr("height") - margin.top - margin.bottom; |
|
|
|
var curves = [ |
|
"curveBasis", "curveCardinal", "curveCatmullRom", "curveLinear", |
|
"curveStepAfter", |
|
] |
|
|
|
var g = svg.append("g") |
|
.attr("transform", translate(margin.left, margin.top)); |
|
|
|
d3.csv("index_data.csv", row, function (error, index_data) { |
|
if (error) throw error; |
|
|
|
d3.select("select") |
|
.on("change", update) |
|
.selectAll("option") |
|
.data(curves) |
|
.enter().append("option") |
|
.text(function (d) { return d; }); |
|
|
|
var x = d3.scaleTime() |
|
.domain(d3.extent(index_data, function (d) { return d.date; })) |
|
.range([0, width]); |
|
|
|
var y = d3.scaleLinear() |
|
.domain([0, 10]) |
|
.range([height, 0]); |
|
|
|
var colour = d3.scaleOrdinal(d3.schemeCategory10); |
|
|
|
var nest = d3.nest() |
|
.key(function (d) { return d.country; }) |
|
.sortValues(function (a, b) { return d3.ascending(a.date, b.date); }) |
|
.entries(index_data); |
|
|
|
var line = d3.line() |
|
.x(function (d) { return x(d.date); }) |
|
.y(function (d) { return y(d.score); }); |
|
|
|
g.append("g") |
|
.attr("class", "paths"); |
|
|
|
g.append("g") |
|
.attr("class", "annotations"); |
|
|
|
g.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", translate(0, height)) |
|
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%b"))); |
|
|
|
g.append("g") |
|
.attr("class", "y axis") |
|
.call(d3.axisLeft(y)); |
|
|
|
update(); |
|
|
|
function update() { |
|
line.curve(d3[this.value ? this.value : "curveBasis"]); |
|
|
|
var path = d3.select(".paths").selectAll(".path") |
|
.data(nest, function (d) { return d.key; }); |
|
|
|
path.enter().append("path") |
|
.attr("class", "path") |
|
.attr("d", function (d) { return line(d.values); }) |
|
.attr("fill", "none") |
|
.attr("stroke", function (d) { return colour(d.key); }) |
|
.attr("stroke-width", 2) |
|
.merge(path) |
|
.each(transition); |
|
|
|
var annotation = d3.select(".annotations").selectAll(".annotation") |
|
.data(nest, function (d) { return d.key; }); |
|
|
|
annotation.enter().append("text") |
|
.attr("class", "annotation") |
|
.attr("x", function (d) { return x(d.values[d.values.length - 1].date); }) |
|
.attr("y", function (d) { return y(d.values[d.values.length - 1].score); }) |
|
.attr("dx", 3) |
|
.attr("dy", 3) |
|
.attr("font-family", "Helvetica") |
|
.attr("font-size", 12) |
|
.attr("fill", function (d) { return colour(d.key); }) |
|
.text(function (d) { return d.key; }); |
|
|
|
function transition(d) { |
|
d3.select(this).transition() |
|
.duration(1000) |
|
.attrTween("d", pathTween(line(d.values), 4)); |
|
} |
|
} |
|
}); |
|
|
|
function pathTween(d1, precision) { |
|
// An interpolator factory: a function that returns a function. |
|
// d1: The target path (a string containing a series of path descriptions). |
|
// precision: The precision to sample the source and target paths. |
|
return function () { |
|
var path0 = this, |
|
path1 = path0.cloneNode(), |
|
n0 = path0.getTotalLength(), |
|
n1 = (path1.setAttribute("d", d1), path1).getTotalLength(); |
|
|
|
// Uniformly sample either the source or target path, whichever is longer. |
|
var distances = [0], |
|
i = 0, |
|
dt = precision / Math.max(n0, n1); |
|
|
|
while ((i += dt) < 1) { |
|
distances.push(i); |
|
} |
|
|
|
distances.push(1); |
|
|
|
// Get an interpolator for each pair of points. |
|
var points = distances.map(function (distance) { |
|
var p0 = path0.getPointAtLength(distance * n0), |
|
p1 = path1.getPointAtLength(distance * n1); |
|
return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]); |
|
}); |
|
|
|
// Here, `p` is the current interpolator. |
|
// We join the interpolated points using the `Lineto` path command. |
|
return function(t) { |
|
return t < 1 ? "M" + points.map(function (p) { return p(t); }).join("L") : d1; |
|
} |
|
} |
|
} |
|
|
|
function row(d) { |
|
var tokens = d.date.split("-"), |
|
year = +tokens[0], |
|
quarter = +tokens[1].replace("Q", ""), |
|
month = ((quarter - 1) * 3) + 1; |
|
d.date = new Date(year, month); |
|
d.score = +d.score; |
|
return d; |
|
} |
|
|
|
function translate(x, y) { |
|
return "translate(" + x + "," + y + ")"; |
|
} |