@trinary
@wordcrank
D3 lives in the browser, so having a good browser worflow is important
The simplest possible approach, just link everything as file URLs, e.g. file:///home/user/project/file.js
Use Python's built-in SimpleHTTPServer to serve static files
If you have Python and a shell, this gets you a web server:
python -m SimpleHTTPServer
Live D3 coding environment
Use the yeoman node module to generate a static site.
There are tons of options:
Find something that works well with your editor, browser, and allows quick reloading of your site.
Stacked area chart, adapted from Scott Murray's D3 book:
Clone it, use python to serve it, or just open index.html up and follow along.
http://chimera.labs.oreilly.com/books/1230000000345/ch11.html#_stack_layout
D3's layouts produce a function that modifies or adds properties to your data so that it is convenient to display in a particular way.
The stack layout calculates a a 'baseline' for each element in a stack, based on the values before it.
If your stack is along the y axis (as ours is), this results in a new y0 attribute on our data.
D3's stack layout expects an array of arrays.
The points for each color go into an array together.
All of these arrays (housed in an array) make our dataset.
This confused me initially when applying it to stacked bars.
To provide the data that D3 expects:
If I have apples, bananas, and grapes types, and a value for each type for 5 time periods:
var dataset = [ [ // apples { y: 5 }, { y: 4 }, { y: 2 }, { y: 7 }, { y: 23 } ], [ // bananas { y: 10 }, { y: 12 }, { y: 19 }, { y: 23 }, { y: 17 } ], [ // grapes { y: 22 }, { y: 28 }, { y: 32 }, { y: 35 }, { y: 43 } ] ];
Take note, 3 series of 5 items each, not the other way around.
First, create our stack function: (API Docs)
var stack = d3.layout.stack();
And then apply it to our data:
stack(dataset);
The stack function we got back from D3 will add baseline values for our stacks, using D3's defaults.
Our data has been annotated with y0 values representing the baseline for each part of the stack.
data => [ // Apples. Bottom of the stack, so all baselines are 0! [{y: 5, y0: 0}, {y: 4, y0: 0} ... ], // Bananas. Second on the stack, so all // baselines are the corresponding apple value. [{y: 10, y0: 5}, {y: 12, y0: 4} ... ], // Grapes. Top of the stack, so all baselines // are the sum of apple and orange values. [{y: 22, y0: 15}, {y: 28, y0: 16} ... ], ]
If the first apples value is 5, the first bananas baseline should be 5.
A two level binding is required.
First, we bind a group element to each series
Then, inside each group, we bind rectangles to each data value
We use y0 as our baseline, and y as our height, with a linear scale applied.
var groups = svg.selectAll("g").data(dataset) .enter() .append("g") .attr("transform","translate(" + [margin.left, margin.top] + ")") .style("fill", function(d, i) { return colors(i); });
var rects = groups.selectAll("rect") // Creating a new sub-selection inside groups .data(function(d) { return d; }) // Our data is the value of the data bound to each group. .enter() .append("rect") .attr("x", function(d, i) { return xScale(i); }) .attr("y", function(d) { return yScale(d.y0);}) .attr("height", function(d) { return yScale(d.y); }) .attr("width", xScale.rangeBand());
http://chimera.labs.oreilly.com/books/1230000000345/ch11.html#_stack_layout
The original example uses SVG's default y axis, from the top.
var yScale = d3.scale.linear() .domain([0, max]) .range([0, height]);
The output of this scale starts at 0 (the top of the SVG).
Instead, let's change the output of the y scale to start at the height and go to 0:
var invertedYScale = d3.scale.linear() .domain([0,max]) .range([height, 0]);
But how does this affect our y and height attributes?
Before:
var rects = groups.selectAll("rect") .data(function(d) { return d; }) .enter() .append("rect") .attr("x", function(d, i) { return xScale(i); }) .attr("y", function(d) { return yScale(d.y0); }) .attr("height", function(d) { return yScale(d.y); }) .attr("width", xScale.rangeBand());
After:
var rects = groups.selectAll("rect") .data(function(d) { return d; }) .enter() .append("rect") .attr("x", function(d, i) { return xScale(i); }) .attr("y", function(d) { return invertedYScale(d.y0 + d.y); }) .attr("height", function(d) { return height - invertedYScale(d.y); }) .attr("width", xScale.rangeBand());
[ { name: "apples", values: [{y: 5}, {y:4}...etc]}, { name: "bananas", values: [{y: 5}, {y:4}...etc]}, ]
Fix the d3.layout.stack() call, data() bindings, scales, legend generation, etc.