Skip to content

Commit

Permalink
Rewrite stack layout for flexibility.
Browse files Browse the repository at this point in the history
The stack layout can now operate on arbitrary data structures, rather than
requiring a specific input format. To do this, you can now specify functions to
access values per series, along with x- and y-coordinates.

One tricky thing is that the stack layout needs to be able to write the stacked
values back into the data; this is accomplished using the customizable `out`
function. This function takes three arguments: the current datum (d), the offset
value (y0), and the y value. The y value is unchanged unless the "expand" offset
is used.

This commit also allows you to specify arbitrary functions for order and offset
computation, in addition to the built-in methods supported previously.
  • Loading branch information
mbostock committed May 12, 2011
1 parent 25a3a50 commit 3018f87
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 111 deletions.
167 changes: 113 additions & 54 deletions d3.layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -636,49 +636,104 @@ d3.layout.pie = function() {
return pie;
};
// data is two-dimensional array of x,y; we populate y0
// TODO perhaps make the `x`, `y` and `y0` structure customizable
d3.layout.stack = function() {
var order = "default",
offset = "zero";
var values = Object,
order = d3_layout_stackOrders["default"],
offset = d3_layout_stackOffsets["zero"],
out = d3_layout_stackOut,
x = d3_layout_stackX,
y = d3_layout_stackY;

function stack(data, index) {

// Convert series to canonical two-dimensional representation.
var series = data.map(function(d, i) {
return values.call(stack, d, i);
});

function stack(data) {
var n = data.length,
m = data[0].length,
i,
j,
y0;
// Convert each series to canonical [[x,y]] representation.
var points = series.map(function(d, i) {
return d.map(function(v, i) {
return [x.call(stack, v, i), y.call(stack, v, i)];
});
});

// compute the order of series
var index = d3_layout_stackOrders[order](data);
// Compute the order of series, and permute them.
var orders = order.call(stack, points, index);
series = d3.permute(series, orders);
points = d3.permute(points, orders);

// set y0 on the baseline
d3_layout_stackOffsets[offset](data, index);
// Compute the baseline
var offsets = offset.call(stack, points, index);

// propagate offset to other series
// And propagate it to other series.
var n = series.length,
m = series[0].length,
i,
j,
o;
for (j = 0; j < m; ++j) {
for (i = 1, y0 = data[index[0]][j].y0; i < n; ++i) {
data[index[i]][j].y0 = y0 += data[index[i - 1]][j].y;
out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
for (i = 1; i < n; ++i) {
out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
}
}

return data;
}

stack.values = function(x) {
if (!arguments.length) return values;
values = x;
return stack;
};

stack.order = function(x) {
if (!arguments.length) return order;
order = x;
order = typeof x === "function" ? x : d3_layout_stackOrders[x];
return stack;
};

stack.offset = function(x) {
if (!arguments.length) return offset;
offset = x;
offset = typeof x === "function" ? x : d3_layout_stackOffsets[x];
return stack;
};

stack.x = function(z) {
if (!arguments.length) return x;
x = z;
return stack;
};

stack.y = function(z) {
if (!arguments.length) return y;
y = z;
return stack;
};

stack.out = function(z) {
if (!arguments.length) return out;
out = z;
return stack;
};

return stack;
}

function d3_layout_stackX(d) {
return d.x;
}

function d3_layout_stackY(d) {
return d.y;
}

function d3_layout_stackOut(d, y0, y) {
d.y0 = y0;
d.y = y;
}

var d3_layout_stackOrders = {

"inside-out": function(data) {
Expand All @@ -692,7 +747,7 @@ var d3_layout_stackOrders = {
bottom = 0,
tops = [],
bottoms = [];
for (i = 0; i < n; i++) {
for (i = 0; i < n; ++i) {
j = index[i];
if (top < bottom) {
top += sums[j];
Expand All @@ -717,101 +772,105 @@ var d3_layout_stackOrders = {

var d3_layout_stackOffsets = {

"silhouette": function(data, index) {
"silhouette": function(data) {
var n = data.length,
m = data[0].length,
sums = [],
max = 0,
i,
j,
o;
o,
y0 = [];
for (j = 0; j < m; ++j) {
for (i = 0, o = 0; i < n; i++) o += data[i][j].y;
for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
if (o > max) max = o;
sums.push(o);
}
for (j = 0, i = index[0]; j < m; ++j) {
data[i][j].y0 = (max - sums[j]) / 2;
for (j = 0; j < m; ++j) {
y0[j] = (max - sums[j]) / 2;
}
return y0;
},

"wiggle": function(data, index) {
"wiggle": function(data) {
var n = data.length,
x = data[0],
m = x.length,
max = 0,
i,
j,
k,
ii,
ik,
i0 = index[0],
s1,
s2,
s3,
dx,
o,
o0;
data[i0][0].y0 = o = o0 = 0;
o0,
y0 = [];
y0[0] = o = o0 = 0;
for (j = 1; j < m; ++j) {
for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j].y;
for (i = 0, s2 = 0, dx = x[j].x - x[j - 1].x; i < n; ++i) {
for (k = 0, ii = index[i], s3 = (data[ii][j].y - data[ii][j - 1].y) / (2 * dx); k < i; ++k) {
s3 += (data[ik = index[k]][j].y - data[ik][j - 1].y) / dx;
for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
}
s2 += s3 * data[ii][j].y;
s2 += s3 * data[i][j][1];
}
data[i0][j].y0 = o -= s1 ? s2 / s1 * dx : 0;
y0[j] = o -= s1 ? s2 / s1 * dx : 0;
if (o < o0) o0 = o;
}
for (j = 0; j < m; ++j) data[i0][j].y0 -= o0;
for (j = 0; j < m; ++j) y0[j] -= o0;
return y0;
},

"expand": function(data, index) {
"expand": function(data) {
var n = data.length,
m = data[0].length,
k = 1 / n,
i,
j,
o;
o,
y0 = [];
for (j = 0; j < m; ++j) {
for (i = 0, o = 0; i < n; i++) o += data[i][j].y;
if (o) for (i = 0; i < n; i++) data[i][j].y /= o;
else for (i = 0; i < n; i++) data[i][j].y = k;
for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
if (o) for (i = 0; i < n; i++) data[i][j][1] /= o;
else for (i = 0; i < n; i++) data[i][j][1] = k;
}
for (i = index[0], j = 0; j < m; ++j) data[i][j].y0 = 0;
for (j = 0; j < m; ++j) y0[j] = 0;
return y0;
},

"zero": function(data, index) {
var j = 0,
"zero": function(data) {
var j = -1,
m = data[0].length,
i0 = index[0];
for (; j < m; ++j) data[i0][j].y0 = 0;
y0 = [];
while (++j < m) y0[j] = 0;
return y0;
}

};

function d3_layout_stackReduceSum(d) {
return d.reduce(d3_layout_stackSum, 0);
}

function d3_layout_stackMaxIndex(array) {
var i = 1,
j = 0,
v = array[0].y,
v = array[0][1],
k,
n = array.length;
for (; i < n; ++i) {
if ((k = array[i].y) > v) {
if ((k = array[i][1]) > v) {
j = i;
v = k;
}
}
return j;
}

function d3_layout_stackReduceSum(d) {
return d.reduce(d3_layout_stackSum, 0);
}

function d3_layout_stackSum(p, d) {
return p + d.y;
return p + d[1];
}
d3.layout.hierarchy = function() {
var sort = d3_layout_hierarchySort,
Expand Down
Loading

1 comment on commit 3018f87

@jamagara
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there, could you maybe update the streamgraph example to demonstrate the use of customized data structures?

Please sign in to comment.