D3 stacked layout: x and y accessor functions behave differently

1,097 views
Skip to first unread message

Robin Linacre

unread,
Mar 3, 2013, 7:34:26 AM3/3/13
to d3...@googlegroups.com
Hi All,

I am experimenting the d3's stacked output.  I am confused about the difference between stack.x([accessor]) and stack.y([accessor]).  These do not seem to behave in the same way, and I'd like to know why.

In the following I assume the user has not specified an stack.out([setter]) setter.

The stack.y([accessor]) function

When you call d3.layout.stack(myData), the stacked layout writes a y and a y0 value to each data point.  If a y or y0 values already exist, these are overwritten.  

By default, the stacked layout will leave the y value unchanged.  However, if you specify an 'accessor function', then you can manipulate how D3 'reads in' y values.

For example, the following code makes d3 output a numeric y value based on a 'count' property of the datapoints, which needs converting to a number:

var stack = d3.layout.stack();

stack = stack.y(function(d){
    return parseFloat(d.count)+1000;
});

Note that if no accessor function is specified, and there is no 'incoming' y value, then the y value is undefined, and y0 comes out as 0 or NaN.

The stack.x([accessor]) function

When you call d3.layout.stack(myData), as far as I can tell, d3 does not make any modifications to the x property of each data point.

If x already exists, it is left unchanged.  If it doesn't exist, it is not created.

This is true even if I specify an accessor function.  For instance if I do this:

stack = stack.x(function(d){
   return parseFloat(d.x)*2;
});

I expected D3 to double any 'incoming' x values.  However, it does not.  Therefore, I'm confused:  what does the stack.x() do?

Further investigation into the source code shows that D3 does apply the stack.x() function, doubling my x values.  However, it then doesn't seem to use these doubled x values anywhere.

The source code can be modified to 'activate' the stack.x([accessor])  function, making it behave as I expect it to.  This would be done as follows

for (j = 0; j < m; ++j) {
//Line 4430: added the final argument, which corresponds to the x value in points[]
out.call(stack, series[0][j], o = offsets[j], points[0][j][1],points[0][j][0]);
for (i = 1; i < n; ++i) {
//added the final argument, which corresponds to the x value in points[]
out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1],points[i][j][0]);
}
}


// Line 4475 of d3.js
function d3_layout_stackOut(d, y0, y, x) { //Added an x argument
d.y0 = y0;
d.y = y;
// Added assignment of x argument
d.x=x;
}

I think I must be misunderstanding something about what D3 does with x values.  Help is very much appreciated.  I would love it if my above alterations were a genuine bug fix but I suspect I have misunderstood the purpose of the stack.x() function.

Thanks very much!

Robin


p.s. there is a similar question on Stackoverflow, but I don't really understand the answer:

Robin Linacre

unread,
Mar 6, 2013, 3:27:05 PM3/6/13
to d3...@googlegroups.com
Hi All,

No thoughts at all on this one?

Thanks in advance for any help,

Robin

Robin Linacre

unread,
Mar 14, 2013, 3:38:04 PM3/14/13
to d3...@googlegroups.com
One last bump just in case someone can help...

Thanks,

Robin

Dave Boyce

unread,
Mar 14, 2013, 5:00:37 PM3/14/13
to d3...@googlegroups.com

There's nothing wrong with stack's x and y, it just takes a while to work out
how to use stack.

Imagine a vertically stacked bar chart. You need three variables to define this:
- the x axis values;
- the y axis values;
- the stacking category values, ie, how the stacks are grouped.

The x() and y() accessor functions tell stack where to get the x and y values.
If your values are actually the 'x' and 'y' variables in your data objects, then
you don't need to do anything. If they're not, and you don't define the
accessor functions, then everything breaks and you get undefines and NaNs
everywhere.

Your data has to be arranged as an array of arrays. Remember the vertically
stacked bar chart. The first array contains the values in the first row. The second
array is the second row, and so on. You split the data up using the
(grouping) stacking values. Each array has to be the same length, and the ordering
also has to be the same. Each array contains one value for each of your
possible x axis values.

All stack really does is generate y and y0 values. The first row gets all the y0 values
set to zero. The second row, the y0 values are zero plus the appropriate y value
in the first row, and so on.

Stack never writes to x, and it has no need to.

Using the vertical stacked bar chart example, y0 defines the base of each rectangle,
and y is the height.

You can also use stack to do horizontal stacking, you just need to set the x and y
accessor values the other way round, and remember that y0 actually means x0 ;)

Typically you use stack something like this:

var mylayers = stackValues.map(function(f) {
return data.filter(function(d) { return d.stackvalue == f; });
});

var mystack = d3.layout.stack()
.x(function(d) { return d.xvalue; })
.y(function(d) { return d.yvalue; })
.stack(mylayers);

And draw the shapes approximately like:

svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr('x', xscale)
.attr('y', function(d) { return yscale(d.y0-d.y); })
.attr('width', mywidth)
.attr('height', function(d) { return yscale(d.y); });

Hope this helps.

- Dave
> --
> You received this message because you are subscribed to the Google Groups "d3-js" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>


Dave Boyce

da...@iconomical.com
http://www.iconomical.com/



Robin Linacre

unread,
Mar 15, 2013, 5:44:44 PM3/15/13
to d3...@googlegroups.com
Hi Dave.  Thanks very much for your detailed response.  

I think I understand why the stacked function doesn't really need the 'x' values - it's only purpose is to calculate the y and y0 values from some input json.  If in the raw data, the y variable is not called 'y', we use the y accessor.

However, I'm still very puzzled by the presence of the x accessor, which on the face of it looks the same as the y accessor, but doesn't seem to do anything at all.

I've prepared two examples to demonstrate.  In the following two examples, the stack function produces precisely the same layers (if you look into the layers object, they are the same in both examples).  One uses the x accessor, the other doesn't.  So it seems the x accessor doesn't do anything at all.  Note that in both examples the y accessor is essential for it to work.


Example 1:  Without x accessor http://tributary.io/inlet/5173294
Example 2:  With x accessor http://tributary.io/inlet/5173292

I feel like I must be missing something really stupid!

Robin
Reply all
Reply to author
Forward
0 new messages