|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
body { |
|
width: 960px; |
|
margin: auto; |
|
position: relative; |
|
} |
|
|
|
svg { |
|
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif; |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.axis--y path { |
|
display: none; |
|
} |
|
|
|
.cities { |
|
fill: none; |
|
stroke: #aaa; |
|
stroke-linejoin: round; |
|
stroke-linecap: round; |
|
stroke-width: 1.5px; |
|
} |
|
|
|
.city--hover { |
|
stroke: #000; |
|
} |
|
|
|
.focus text { |
|
text-anchor: middle; |
|
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff; |
|
} |
|
|
|
.voronoi path { |
|
fill: none; |
|
pointer-events: all; |
|
} |
|
|
|
.voronoi--show path { |
|
stroke: red; |
|
stroke-opacity: .2; |
|
} |
|
|
|
#form { |
|
position: absolute; |
|
top: 20px; |
|
right: 30px; |
|
} |
|
|
|
</style> |
|
<label id="form" for="show-voronoi"> |
|
Show Voronoi |
|
<input type="checkbox" id="show-voronoi" disabled> |
|
</label> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<script> |
|
|
|
var normalizer; |
|
|
|
var months, |
|
monthFormat = d3.time.format("%Y-%m"); |
|
|
|
var margin = {top: 20, right: 30, bottom: 30, left: 40}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var x = d3.time.scale() |
|
.range([0, width]); |
|
|
|
var y = d3.scale.linear() |
|
.range([height, 0]); |
|
|
|
var voronoi = d3.geom.voronoi() |
|
.x(function(d) { return x(d.date); }) |
|
.y(function(d) { return y(d.value); }) |
|
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]); |
|
|
|
var line = d3.svg.line() |
|
.x(function(d) { return x(d.date); }) |
|
.y(function(d) { return y(d.value); }); |
|
|
|
var svg = d3.select("body").append("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.tsv("unemployment.tsv", type, function(error, cities) { |
|
x.domain(d3.extent(months)); |
|
y.domain([0, d3.max(cities, function(c) { return d3.max(c.values, function(d) { return d.value; }); })]).nice(); |
|
|
|
svg.append("g") |
|
.attr("class", "axis axis--x") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(d3.svg.axis() |
|
.scale(x) |
|
.orient("bottom")); |
|
|
|
svg.append("g") |
|
.attr("class", "axis axis--y") |
|
.call(d3.svg.axis() |
|
.scale(y) |
|
.orient("left") |
|
.ticks(10, "%")) |
|
.append("text") |
|
.attr("x", 4) |
|
.attr("dy", ".32em") |
|
.style("font-weight", "bold") |
|
.text("Unemployment Rate"); |
|
|
|
svg.append("g") |
|
.attr("class", "cities") |
|
.selectAll("path") |
|
.data(cities) |
|
.enter().append("path") |
|
.each(function(d) { console.log(d.values); }) |
|
.attr("d", function(d) { d.line = this; return line(d.values); }); |
|
|
|
var focus = svg.append("g") |
|
.attr("transform", "translate(-100,-100)") |
|
.attr("class", "focus"); |
|
|
|
focus.append("circle") |
|
.attr("r", 3.5); |
|
|
|
focus.append("text") |
|
.attr("y", -10); |
|
|
|
var voronoiGroup = svg.append("g") |
|
.attr("class", "voronoi"); |
|
|
|
voronoiGroup.selectAll("path") |
|
.data(voronoi(d3.nest() |
|
.key(function(d) { return x(d.date) + "," + y(d.value); }) |
|
.rollup(function(v) { return v[0]; }) |
|
.entries(d3.merge(cities.map(function(d) { return d.values; }))) |
|
.map(function(d) { return d.values; }))) |
|
.enter().append("path") |
|
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) |
|
.datum(function(d) { return d.point; }) |
|
.on("mouseover", mouseover) |
|
.on("mouseout", mouseout) |
|
.on("click", click); |
|
|
|
d3.select("#show-voronoi") |
|
.property("disabled", false) |
|
.on("change", function() { voronoiGroup.classed("voronoi--show", this.checked); }); |
|
|
|
function mouseover(d) { |
|
d3.select(d.city.line).classed("city--hover", true); |
|
d.city.line.parentNode.appendChild(d.city.line); |
|
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")"); |
|
focus.select("text").text(d.city.name); |
|
} |
|
|
|
function mouseout(d) { |
|
d3.select(d.city.line).classed("city--hover", false); |
|
focus.attr("transform", "translate(-100,-100)"); |
|
} |
|
|
|
function click(d) { |
|
normalizer = d.city.values; |
|
|
|
y.domain([0,2]); |
|
|
|
d3.select(".axis--y") |
|
.call(d3.svg.axis() |
|
.scale(y) |
|
.orient("left") |
|
.ticks(10, "%")); |
|
|
|
d3.select(".axis--y text") |
|
.text("Unemployment rate as percentage of " + d.name); |
|
|
|
var normalLine = d3.svg.line() |
|
.x(function(d) { return x(d.date); }) |
|
.y(function(d, i) { return y(d.value / normalizer[i].value); }); |
|
|
|
d3.selectAll("g.cities path") |
|
.transition() |
|
.duration(5000) |
|
.each(function(d) { console.log(d.values); }) |
|
.attr("d", function(d) { return normalLine(d.values); }); |
|
} |
|
}); |
|
|
|
function type(d, i) { |
|
if (!i) months = Object.keys(d).map(monthFormat.parse).filter(Number); |
|
var city = { |
|
name: d.name.replace(/ (msa|necta div|met necta|met div)$/i, ""), |
|
values: null |
|
}; |
|
city.values = months.map(function(m) { |
|
return { |
|
city: city, |
|
date: m, |
|
value: d[monthFormat(m)] / 100 |
|
}; |
|
}); |
|
return city; |
|
} |
|
|
|
</script> |