|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
/* The "lighter" font-weight just makes the Helvetica thin and stylish */ |
|
body { |
|
font: 14px Helvetica; |
|
font-weight: lighter; |
|
} |
|
|
|
/* The svg lines here are the tick marks, and the svg paths are the long horizontal and vertical |
|
lines that run parallel to the axes and have a little nub on the end. D3 axes include both by |
|
default. Also, by default, SVG shapes come with a fill but not a stroke. You want to set the |
|
fill to "none" here because otherwise the paths will be filled in (they aren't one dimensional |
|
because of the nub, so they actually have an inner area that can be filled). You need to |
|
specificy a stroke color, otherwise there won't be a stroke at all (in other words, the paths |
|
and lines won't show up). Setting the shape-rendering to crispEdges turns off anti-aliasing, so |
|
the lines and paths will be black pixels surrounded by white pixels (instead of a gradation of |
|
grey). CrispEdges are great for straight, horiztonal and vertical lines, but not for diagonal |
|
lines or curves */ |
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
/* This turns off the path for the y-axis. This is just a personal preference. */ |
|
.y.axis path { |
|
display: none; |
|
} |
|
|
|
/* This bit is referring to everything with the class "line," which is different from above, |
|
where we applied style rules to svg line objects with the class "axis." As you'll see below, the |
|
object with the class, "line" is the line of the line chart itself. Again, you need to add a |
|
stroke color. I've changed the width to 2 pixels from the default 1 pixel. Also, notice that the |
|
shape-rendering is not set to crispEdges because this is a curve. */ |
|
.line { |
|
fill: none; |
|
stroke: darkmagenta; |
|
stroke-width: 2px; |
|
} |
|
|
|
</style> |
|
<body> |
|
<!-- You always want to put your script (or your reference to it) inside the body of your HTML. --> |
|
<script src="http://d3js.org/d3.v3.js"></script> |
|
<script> |
|
|
|
// Set up your margins the Bostock way! Bostock always creates a JavaScript object called margin |
|
// with the properties top, right, bottom, and left (pretty self-explanatory). He then defines |
|
// the variables width and the height based on the dimensions inside of those margins. The 960px |
|
// and 500px here are the overall width and height, respectively — this is the standard size of |
|
// a block. These variables will be used to define the size of the SVG element (as you'll see |
|
// below). |
|
var margin = {top: 20, right: 20, bottom: 30, left: 50}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
// This is a helper function that turns a string representing a date into an actual date object |
|
// that JavaScript can understand. The "%d/%m/%Y" bit tells D3 how your raw date data is |
|
// formatted. %m is the month (as a number), %d is the day of the month, and %Y is the four- |
|
// digit year. The D3 API reference has a list of more of these so-called directives. |
|
var parseDate = d3.time.format("%m/%d/%Y").parse; |
|
|
|
// Create a time scale for the horiztonal or x-axis. Time scales take date objects as inputs and |
|
// map them linearly to an output range. Here the output range is set to go from 0 to width. |
|
// Remember that the width variable includes everything inside of the left and right margins. |
|
// That means the x-axis will span that entire distance and but up against both of the margins. |
|
// Because the input (domain) depends on the data, it is defined below, inside of the d3.tsv() |
|
// function, where the data is called in. |
|
var x = d3.time.scale() |
|
.range([0, width]); |
|
|
|
// Create a linear scale for the y-axis. This takes regular old numbers and maps them linearly |
|
// to an output range. Note that the range goes from height to 0, not the other way around. This |
|
// is because, on the web, the upper left-hand corner of the page is 0,0 and as you move down, y |
|
// increases. In charts, however, you want y to increase as you move up the page. So the range |
|
// has to be reversed. |
|
var y = d3.scale.linear() |
|
.range([height, 0]); |
|
|
|
// xAxis and yAxis are super handy axis generators. They depend on their respective scales are |
|
// will be used below to automatically create default axes. Orient tells the axis where to put |
|
// the labels relative to the tick marks — bottom means the labels go below the tick marks and |
|
// left means they go to the left. |
|
var xAxis = d3.svg.axis() |
|
.scale(x) |
|
.orient("bottom"); |
|
|
|
var yAxis = d3.svg.axis() |
|
.scale(y) |
|
.orient("left"); |
|
|
|
// This is a helper function for creating the life expectancy curve. In general, the curves that |
|
// you make in D3 will be SVG paths. Paths are pretty complex. Their shape is described by an |
|
// attribute called d — a string written in a code that is like a mini-language unto itself. |
|
// This helper function takes x and y inputs and then translates them into a string to set d |
|
// equal to. The x input here is the year, and the y input is the life expectancy. You have to |
|
// use an anonymous function to access each of them. |
|
var line = d3.svg.line() |
|
.x(function(d) { return x(d.year); }) |
|
.y(function(d) { return y(d.lifeExpectancy); }); |
|
|
|
// Here is where the margin convention above really comes into play. What Bostock does is he |
|
// creates an SVG element that is the size of the entire block - 960 pixels by 500 pixels ( |
|
// notice that he adds the margins back to the width and height variables). Then he appends an |
|
// SVG group and translates it over to the left by a distance equal to the left margin and down |
|
// by a distance equal to the top margin. The variable svg actually refers to the group, not to |
|
// the parent svg element. Everything will be based around that group. One thing this does is |
|
// change the frame of reference. Now, 0,0 is in the upper left-hand corner inside of the |
|
// margins, instead of outside of them. Conveniently, however, if svg objects spill over into |
|
// the margins, they will still be displayed because it is still within the bounds of the |
|
// granddaddy SVG element. In fact, the axes will be placed in the margins. |
|
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 + ")"); |
|
|
|
// This function pulls in the data. Data in D3 is loaded asynchronously, which means the rest of |
|
// the page is loaded first, just in case the data takes a while to load. As a result, |
|
// everything that your code needs to do that involves data has to happen within this d3.tsv() |
|
// function. Also, in case you were wondering, tsv is a file format and it stands for tab- |
|
// separated values. You can see in the data.tsv file below that the values are indeed separated |
|
// by tabs. |
|
d3.tsv("data.tsv", function(error, data) { |
|
// Note that when D3 pulls in a tsv file, it creates an array of objects. Each object has |
|
// property names that correspond to the headers in the file - in this case "year" and |
|
// "lifeExpectancy." The array is assigned to a variable, called data in this case. |
|
|
|
// This self-contained loop iterates through each object in the data array and uses our helper |
|
// function above to parse the raw data strings into actual JavaScript date objects. It also |
|
// ensures that the life expectancy values are numbers and not strings. |
|
data.forEach(function(d) { |
|
d.year = parseDate(d.year); |
|
d.lifeExpectancy = +d.lifeExpectancy; |
|
}); |
|
|
|
// The domain of the x scale is defined using the extent of the year data. That function d3. |
|
// extent() returns an array of length 2, where the first entry is the minimum value and the |
|
// second entry is the maximum value. |
|
x.domain(d3.extent(data, function(d) { return d.year; })); |
|
|
|
// For the y domain, instead of using the extent, I'm setting the lower bound at a nice, even |
|
// 50, and then computing the maximum for the upper bound. |
|
y.domain([50, d3.max(data, function(d) { return d.lifeExpectancy; })]); |
|
|
|
// Append the x axis, and assign two classes - "x" and "axis." Note that all you have to do is |
|
// append a group, then you can use the call operator to call the x-axis generator and it will |
|
// take care of the rest (by putting objects inside of that group). The default for the x-axis |
|
// is to put it at the top, so you have to use transform to move the group to the bottom. |
|
svg.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(xAxis); |
|
|
|
// Create y axis and add label. When you append a text label to the axis, the point of |
|
// reference is the axis group itself. |
|
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("years"); |
|
|
|
// Create the path for your curve. In this case, we can use .datum() instead of .data(). The |
|
// difference is subtle. Both bind data to objects (or more precisely to selections). But when |
|
// .data() binds data to a selection, it computes a data-join, which, among other things, |
|
// allows you to easily create a new SVG object for every data point in your array. .datum() |
|
// does not do this - it only binds data. But it works in this case, because you only need a |
|
// single path for your line. Every data point in your data set, instead of getting turned |
|
// into a separate line, will be translated into SVG path-speak by the helper function above, ( |
|
// called line). This SVG path speak is used to set the "d" attribute for the path and |
|
// generate the curve. |
|
svg.append("path") |
|
.datum(data) |
|
.attr("class", "line") |
|
.attr("d", line); |
|
}); |
|
|
|
</script> |