Area Layer Graph

126 views
Skip to first unread message

Namit Setia

unread,
Jan 14, 2013, 12:17:02 PM1/14/13
to d3...@googlegroups.com
Hi,

I'm trying to understand the area layer graph example: http://bl.ocks.org/3020685

Had a couple of questions:

1) Where does d.values get created in the following snippet?  Is nest.entries creating it? (I didn't see it in the output to nest.entries)
var stack = d3.layout.stack()
    .offset("zero")
    .values(function(d) { return d.values; })
    .x(function(d) { return d.date; })
    .y(function(d) { return d.value; });
2) Does nest.entries() and stack() make assumptions about the data?  For instance, that for every x coordinate I have a datapoint designated for each layer (even if that value is 0)


Namit Setia

unread,
Jan 14, 2013, 12:43:27 PM1/14/13
to d3...@googlegroups.com

Sorry, it looks like my post got cut off.

2) Any assumption about the data -- like, do I need to have the same set of x points defined for every series?

I ask because I'm trying to implement this and my keep getting errors in the path that is generated to draw the area graph with a bunch of NaN's
Message has been deleted

Nikhil Sonnad

unread,
Jan 15, 2013, 2:19:22 AM1/15/13
to d3...@googlegroups.com
Answering #1: As I understand it, function(d) { d.values; } is using d3.layout.stack().values to retrieve and map data from your dataset array. This will take the values of your dataset array and map them to the layers that will be used for your stack layout. From the API docs: "[d3.layout.stack] accessor is a function which is invoked on each input layer passed to stack, equivalent to calling layers.map(accessor) before computing the stack layout." I'm not sure but I don't think for a stack layout it can be written any way other than d.values.

I'm not sure about #2, but if you post your code I might be able to help a bit further.

Shripad K

unread,
Jan 15, 2013, 3:20:44 AM1/15/13
to d3...@googlegroups.com
Its quite simple.

Data passed into nest.entries has a format of [{date: 'mm/dd/yy', key: 'GroupX', value: 37}, ..... ]

nest.entries just groups the data by "key". Example: [{key: "GroupX", values: [{date: 'mm/dd/yy', key: 'GroupX', value: 37}, {date: 'mm/dd/yy', key: 'GroupX', value: 32}....]}, .... ]

Then the result of nest.entries is passed to the "stack" function instantiated via d3.layout.stack(). The stack function calculates the baseline (y0) depending on the kind of offset you have chosen (one of "wiggle", "silhouette", "expand" or "zero") and copies the "value" of the particular value in the nested data to "y". By default the offset is "zero", so y0 begins with 0 as its value for the base layer. The layers above it will have the value of "y" (of bottom layer) as the y0 value (this is how the stacking is done -> y of the bottom layer becomes y0 of the layer above it).

Example: [{key: "GroupX", values: [{date: 'mm/dd/yy', key: 'GroupX', value: 37, y: 37, y0: 0}, {date: 'mm/dd/yy', key: 'GroupX', value: 32, y: 32, y0: 0}....]}, {key: "GroupY", values: [{date: 'mm/dd/yy', key: 'GroupY', value: 4, y: 4y0: 37}, {date: 'mm/dd/yy', key: 'GroupY', value: 32, y: 5y0: 32} ].

To verify my answer you can log the data object to the console. You'll understand much better that way.

How does stack compute the values for y and y0? Magic? Nope! You have defined it here:
        stack.values(function(d) { return d.values; });
        stack.x(function(d) { return d.date; });
        stack.y(function(d) { return d.value; });

y0 is calculated when you call stack(nest.entries(data)).

And the d3.svg.area generator takes care of constructing the path for generating the layers:
        area.x(function(d) { return x(d.date); })
        area.y0(function(d) { return y(d.y0); });
        area.y1(function(d) { return y(d.y0 + d.y); });

Always think of these generators as corresponding to a single layer/path and subsequently as points within these layers (x, y0 and y). So the above function generates a layer with points mapped to x-axis via function area.x and the baseline defined by function area.y0 and the height defined by function area.y1 (which is y(d.y0 + d.y)).

Then the rest is really easy to grasp. Just pass the domain extents for x and y. Do the awesome d3 data-join stuff with svg.selectAll('.layer').data(layers) and append 'path' (layer) with its 'd' attribute being 'area(d.values)' ( remember that you are now passing "layers" object and not your original "data" object. So the "values" here is computed via "stack(nest.entries(data))" )

Hope this helps.

Namit Setia

unread,
Jan 16, 2013, 3:24:56 PM1/16/13
to d3...@googlegroups.com
Thanks!  That was an awesome reply.  They should add this to the stacked area graph example.

Chris Viau

unread,
Jan 16, 2013, 4:30:13 PM1/16/13
to d3...@googlegroups.com
You should turn these explanation into a blog post ;)

Shripad K

unread,
Jan 17, 2013, 4:04:50 AM1/17/13
to d3...@googlegroups.com
Yes I have been wanting to write articles on D3. Will make time this week and starting publishing some :)

Shripad K

unread,
Jan 17, 2013, 5:37:09 AM1/17/13
to d3...@googlegroups.com
Also try to avoid passing data with keys "x" and "y". The "y" key will be overwritten by the stack function (as the stack uses keys "y" and "y0" as defaults). So to circumvent this, use the stack.out function and define custom calculated keys. Something i learned after spending a day figuring out why my stacked graph was rendering incorrectly on redraw (think dynamic updates, window resize etc). I think this should be made more explicit in the docs with a "Warning" perhaps. The fix for anyone wanting to pass data with keys "x" and "y":

stack.out(function(d, y0, y) {
  d.y0_generated = y0;
  d.y_generated = y;
});

Make sure all occurrences of y0 and y are replaced with y0_generated and y_generated in your snippets.
Reply all
Reply to author
Forward
0 new messages