|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<head> |
|
<link type="text/css" rel="stylesheet" href="style.css"/> |
|
<script src="http://d3js.org/d3.v3.js"></script> |
|
</head> |
|
<body> |
|
<script> |
|
// Dimensions of the visualization |
|
var width = 960; |
|
var height = 500; |
|
|
|
// Format for parsing dates |
|
var parseDate = d3.time.format("%Y%m%d").parse; |
|
|
|
// Means of getting a colour for the catagory |
|
var color = d3.scale.category10(); |
|
|
|
// Determine the scales and render the axis |
|
var x = d3.time.scale().range([0, width]); |
|
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"); |
|
|
|
// Great the SVG element |
|
var svg = d3.select("body") |
|
.append("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.append("g"); |
|
|
|
// Load up the data |
|
d3.tsv("line.json", function(error, data) { |
|
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; })); |
|
|
|
// For each item in the data, take the string representation of the date |
|
// and parse it to an actual date. |
|
data.forEach(function(d) { |
|
d.date = parseDate(d.date); |
|
}); |
|
|
|
// Loads the cities |
|
var cities = color.domain().map(function(name) { |
|
return { |
|
name: name, |
|
values: data.map(function(d) { |
|
return {date: d.date, temperature: +d[name]}; |
|
}) |
|
}; |
|
}); |
|
|
|
// Select all Cities |
|
var city = svg.selectAll(".city") |
|
.data(cities) |
|
.enter().append("g") |
|
.attr("class", "city"); |
|
|
|
// Set the x and y domains |
|
x.domain(d3.extent(data, function(d) { return d.date; })); |
|
y.domain([ |
|
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }), |
|
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); }) |
|
]); |
|
|
|
// Set the caption for the y-axis |
|
svg.append("g").attr("class", "y axis").call(yAxis).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", ".71em").style("text-anchor", "end").text("Temperature (ºF)"); |
|
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis); |
|
|
|
// Append the series names on the x-axis |
|
city.append("text") |
|
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; }) |
|
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; }) |
|
.attr("x", 3) |
|
.attr("dy", ".35em") |
|
.attr("fill", "white") |
|
.text(function(d) { return d.name; }); |
|
|
|
/* PATH / AREA Creation |
|
* ==================== |
|
* |
|
* This is where things get more interesting, there is a custom line generator in use |
|
* and a custom area function being applied using a bi-variate area graph. |
|
*/ |
|
|
|
// This is the date, at which all data following it will be considered a prediction |
|
// After this point we want to have a dashed line and an area of growning uncertainity. |
|
var predictDate = new Date("June 1, 2012 00:00:00"); |
|
|
|
// This is the function used to make a path for general line. This is solid originally |
|
// and then switches to a dashed line beyond the prediction date. There aren't any path |
|
// generators within D3 that do this so we have to do it at a very raw level. |
|
function mkPath(d) { |
|
var prev = -1; |
|
var lastDate = null; |
|
|
|
// s will be the string to represent the path, using the SVG definition. We're |
|
// therefore building up this path at the rawest level. See http://www.w3.org/TR/SVG/paths.html |
|
var s = ""; |
|
|
|
// Go through all the values that we have |
|
for(var i = 0; i < d.values.length; i++) { |
|
// Skip anything without a defined date |
|
if(d.values[i].date) { |
|
// If the date is beyond our threshold then we want a dashed line |
|
if(d.values[i].date > predictDate) { |
|
var xp = d3.scale.linear().range([d.values[i-1].date, d.values[i].date]); |
|
var yp = d3.scale.linear().range([d.values[i-1].temperature, d.values[i].temperature]); |
|
|
|
// Create little segments of a line, alternating between a fill and a non-filled section |
|
for(var j = 0.1; j < 1; j = j + 0.2) { |
|
s = s + "M" + x(xp(j)) + " " + y(yp(j)); |
|
s = s + " " + x(xp(j+0.1)) + " " + y(yp(j+0.1)); |
|
} |
|
} else { |
|
// Just create a normal path |
|
if(prev == -1) { s = s + "M"; } |
|
s = s + " " + x(d.values[i].date) + " " + y(d.values[i].temperature); |
|
} |
|
} |
|
prev = d.values[i].date; |
|
} |
|
return s; |
|
} |
|
|
|
// Create the line chart using the custom path generator |
|
city.append("path") |
|
.attr("class", "line") |
|
.attr("d", mkPath) |
|
.style("stroke", function(d) { return color(d.name); }); |
|
|
|
// This is the function to create an area, where we wish |
|
// to just track the line to start with and then build up and |
|
// increasing band as time progresses beyond the threshold date. |
|
var area = d3.svg.area() |
|
.x(function(d) { return x(d.date); }) |
|
.y0(function(d) { |
|
|
|
// Take the current value and work out a difference between the date and the |
|
// threshold date. Add this to the value, and do the reverse for the bottom y value. |
|
if(d.date > predictDate && d.temperature) { |
|
return y(d.temperature + ((d.date - predictDate) / 1000 / 60 / 60 / 24 / 10)) |
|
} |
|
return 0; |
|
}) |
|
.y1(function(d) { |
|
if(d.date > predictDate && d.temperature) { |
|
return y(d.temperature - ((d.date - predictDate) / 1000 / 60 / 60 / 24 / 10)) |
|
} |
|
return 0; |
|
}) |
|
|
|
// Create the area chart |
|
city.append("path") |
|
.attr("class", "area") |
|
.attr("d", function(d) { return area(d.values); }) |
|
.style("fill-opacity", 0.2) |
|
.style("fill", function(d) { return color(d.name); }); |
|
}); |
|
</script> |