Skip to content

Stacks

Examples · Stacking converts lengths into contiguous position intervals. For example, a bar chart of monthly sales might be broken down into a multi-series bar chart by category, stacking bars vertically and applying a categorical color encoding. Stacked charts can show overall value and per-category value simultaneously; however, it is typically harder to compare across categories as only the bottom layer of the stack is aligned. So, chose the stack order carefully, and consider a streamgraph. (See also grouped charts.)

Like the pie generator, the stack generator does not produce a shape directly. Instead it computes positions which you can then pass to an area generator or use directly, say to position bars.

stack()

Source · Constructs a new stack generator with the default settings. See stack for usage.

stack(data, ...arguments)

Source · Generates a stack for the given array of data and returns an array representing each series. Any additional arguments are arbitrary; they are propagated to accessors along with the this object.

For example, consider this tidy table of monthly fruit sales:

datefruitsales
1/2015apples3840
1/2015bananas1920
1/2015cherries960
1/2015durians400
2/2015apples1600
2/2015bananas1440
2/2015cherries960
2/2015durians400
3/2015apples640
3/2015bananas960
3/2015cherries640
3/2015durians400
4/2015apples320
4/2015bananas480
4/2015cherries640
4/2015durians400

This could be represented in JavaScript as an array of objects, perhaps parsed from CSV:

js
const data = [
  {date: new Date("2015-01-01"), fruit: "apples", sales: 3840},
  {date: new Date("2015-01-01"), fruit: "bananas", sales: 1920},
  {date: new Date("2015-01-01"), fruit: "cherries", sales: 960},
  {date: new Date("2015-01-01"), fruit: "durians", sales: 400},
  {date: new Date("2015-02-01"), fruit: "apples", sales: 1600},
  {date: new Date("2015-02-01"), fruit: "bananas", sales: 1440},
  {date: new Date("2015-02-01"), fruit: "cherries", sales: 960},
  {date: new Date("2015-02-01"), fruit: "durians", sales: 400},
  {date: new Date("2015-03-01"), fruit: "apples", sales: 640},
  {date: new Date("2015-03-01"), fruit: "bananas", sales: 960},
  {date: new Date("2015-03-01"), fruit: "cherries", sales: 640},
  {date: new Date("2015-03-01"), fruit: "durians", sales: 400},
  {date: new Date("2015-04-01"), fruit: "apples", sales: 320},
  {date: new Date("2015-04-01"), fruit: "bananas", sales: 480},
  {date: new Date("2015-04-01"), fruit: "cherries", sales: 640},
  {date: new Date("2015-04-01"), fruit: "durians", sales: 400}
];

To compute the stacked series (a series, or layer, for each fruit; and a stack, or column, for each date), we can index the data by date and then fruit, compute the distinct fruit names across the data set, and lastly get the sales value for each date and fruit.

js
const series = d3.stack()
    .keys(d3.union(data.map(d => d.fruit))) // apples, bananas, cherries, …
    .value(([, group], key) => group.get(key).sales)
  (d3.index(data, d => d.date, d => d.fruit));

TIP

See union and index from d3-array.

The resulting array has one element per series. Each series has one point per month, and each point has a lower and upper value defining the baseline and topline:

js
[
  [[   0, 3840], [   0, 1600], [   0,  640], [   0,  320]], // apples
  [[3840, 5760], [1600, 3040], [ 640, 1600], [ 320,  800]], // bananas
  [[5760, 6720], [3040, 4000], [1600, 2240], [ 800, 1440]], // cherries
  [[6720, 7120], [4000, 4400], [2240, 2640], [1440, 1840]]  // durians
]

Each series in then typically passed to an area generator to render an area chart, or used to construct rectangles for a bar chart.

js
svg.append("g")
  .selectAll("g")
  .data(series)
  .join("g")
    .attr("fill", d => color(d.key))
  .selectAll("rect")
  .data(D => D)
  .join("rect")
    .attr("x", d => x(d.data[0]))
    .attr("y", d => y(d[1]))
    .attr("height", d => y(d[0]) - y(d[1]))
    .attr("width", x.bandwidth());

The series are determined by the keys accessor; each series i in the returned array corresponds to the ith key. Each series is an array of points, where each point j corresponds to the jth element in the input data. Lastly, each point is represented as an array [y0, y1] where y0 is the lower value (baseline) and y1 is the upper value (topline); the difference between y0 and y1 corresponds to the computed value for this point. The key for each series is available as series.key, and the index as series.index. The input data element for each point is available as point.data.

stack.keys(keys)

Source · If keys is specified, sets the keys accessor to the specified function or array and returns this stack generator.

js
const stack = d3.stack().keys(["apples", "bananas", "cherries", "durians"]);

If keys is not specified, returns the current keys accessor.

js
stack.keys() // () => ["apples", "bananas", "cherries", "durians"]

The keys accessor defaults to the empty array. A series (layer) is generated for each key. Keys are typically strings, but they may be arbitrary values; see InternMap. The series’ key is passed to the value accessor, along with each data point, to compute the point’s value.

stack.value(value)

Source · If value is specified, sets the value accessor to the specified function or number and returns this stack generator.

js
const stack = d3.stack().value((d, key) => d[key]);

If value is not specified, returns the current value accessor.

js
stack.value() // (d, key) => d[key]

The value accessor defaults to:

js
function value(d, key) {
  return d[key];
}

CAUTION

The default value accessor assumes that the input data is an array of objects exposing named properties with numeric values. This is a “wide” rather than “tidy” representation of data and is no longer recommended. See stack for an example using tidy data.

stack.order(order)

Source · If order is specified, sets the order accessor to the specified function or array and returns this stack generator.

js
const stack = d3.stack().order(d3.stackOrderNone);

If order is a function, it is passed the generated series array and must return an array of numeric indexes representing the stack order. For example, to use reverse key order:

js
const stack = d3.stack().order(series => d3.range(series.length).reverse());

The stack order is computed prior to the offset; thus, the lower value for all points is zero at the time the order is computed. The index attribute for each series is also not set until after the order is computed.

If order is not specified, returns the current order accessor.

js
stack.order() // d3.stackOrderNone

The order accessor defaults to stackOrderNone; this uses the order given by the key accessor. See stack orders for the built-in orders.

stack.offset(offset)

Source · If offset is specified, sets the offset accessor to the specified function and returns this stack generator.

js
const stack = d3.stack().offset(d3.stackOffsetExpand);

The offset function is passed the generated series array and the order index array; it is then responsible for updating the lower and upper values in the series array. See the built-in offsets for a reference implementation.

If offset is not specified, returns the current offset acccesor.

js
stack.offset() // d3.stackOffsetExpand

The offset accessor defaults to stackOffsetNone; this uses a zero baseline. See stack offsets for the built-in offsets.

Stack orders

Stack orders are typically not used directly, but are instead passed to stack.order.

stackOrderAppearance(series)

js
const stack = d3.stack().order(d3.stackOrderAppearance);

Source · Returns a series order such that the earliest series (according to the maximum value) is at the bottom.

stackOrderAscending(series)

js
const stack = d3.stack().order(d3.stackOrderAscending);

Source · Returns a series order such that the smallest series (according to the sum of values) is at the bottom.

stackOrderDescending(series)

js
const stack = d3.stack().order(d3.stackOrderDescending);

Source · Returns a series order such that the largest series (according to the sum of values) is at the bottom.

stackOrderInsideOut(series)

js
const stack = d3.stack().order(d3.stackOrderInsideOut);

Source · Returns a series order such that the earliest series (according to the maximum value) are on the inside and the later series are on the outside. This order is recommended for streamgraphs in conjunction with the wiggle offset. See Stacked Graphs — Geometry & Aesthetics by Byron & Wattenberg for more information.

stackOrderNone(series)

js
const stack = d3.stack().order(d3.stackOrderNone);

Source · Returns the given series order [0, 1, … n - 1] where n is the number of elements in series. Thus, the stack order is given by the key accessor.

stackOrderReverse(series)

js
const stack = d3.stack().order(d3.stackOrderReverse);

Source · Returns the reverse of the given series order [n - 1, n - 2, … 0] where n is the number of elements in series. Thus, the stack order is given by the reverse of the key accessor.

Stack offsets

Stack offsets are typically not used directly, but are instead passed to stack.offset.

stackOffsetExpand(series, order)

js
const stack = d3.stack().offset(d3.stackOffsetExpand);

Source · Applies a zero baseline and normalizes the values for each point such that the topline is always one.

stackOffsetDiverging(series, order)

js
const stack = d3.stack().offset(d3.stackOffsetDiverging);

Source · Positive values are stacked above zero, negative values are stacked below zero, and zero values are stacked at zero.

stackOffsetNone(series, order)

js
const stack = d3.stack().offset(d3.stackOffsetNone);

Source · Applies a zero baseline.

stackOffsetSilhouette(series, order)

js
const stack = d3.stack().offset(d3.stackOffsetSilhouette);

Source · Shifts the baseline down such that the center of the streamgraph is always at zero.

stackOffsetWiggle(series, order)

js
const stack = d3.stack().offset(d3.stackOffsetWiggle);

Source · Shifts the baseline so as to minimize the weighted wiggle of layers. This offset is recommended for streamgraphs in conjunction with the inside-out order. See Stacked Graphs — Geometry & Aesthetics by Bryon & Wattenberg for more information.