1
0
mirror of https://github.com/d3/d3.git synced 2025-12-08 19:46:24 +00:00
d3/docs/d3-shape/stack.md
Mike Bostock 6d6c6792f1
vitepress docs (#3654)
* checkpoint vitepress docs

* edits

* edits

* hero drop shadow

* d3-array edits

* resolve d3

* split d3-array

* move d3-array stuff around

* d3-array is collapsed: true

* italicize parameter names

* searching edits

* update dependencies

* d3-array edits

* array edits

* array edits

* array edits

* array edits

* array edits

* move files

* array edits

* array edits

* array edits

* getting started edits

* modules page

* array edits

* more structure

* live example

* dsv edits

* fetch edits

* dsv edits

* random edits

* time format edits

* time edits

* time edits

* modules edits

* color edits

* color edits

* interpolate edits

* scale-chromatic edits

* selection edits

* break up d3-interpolate

* scale edits

* time scale edits

* scale edits

* scale edits

* band edits

* band edits

* more descriptive titles

* band and point edits

* sequential edits

* diverging edits

* quantize edits

* quantile edits

* threshold edits

* doc edits

* fix titles

* sequential edits

* axis edits

* axis edits

* axis edits

* shape edits

* shape edits

* dark mode chart

* dark mode chart

* curve edits

* interpolate edits

* line edits

* link edits

* radial edits

* pie edits

* symbol edits

* stack edits

* stack examples

* path edits

* polygon edits

* quadtree edits

* random examples

* ease edits

* ease edits

* ease edits

* timer edits

* delaunay edits

* quadtree find example

* voronoi edits

* dispatch edits

* contour edits

* chord edits

* chord edits

* fix find highlight

* quadtree animation

* transition edits

* transition edits

* transition edits

* zoom edits

* drag edits

* brush edits

* force edits

* voronoi neighbors example

* hierarchy edits

* api edits

* community edits

* getting started edits

* geo edits

* Add short "D3 in React" section (#3659)

* Add short "D3 in React" section

I know you removed the TODO but I was already trying to fill it in! I think just making the distinction of modules that touch the DOM and those that don't was super clarifying for me personally when I figured that out. And I always forget the most basic ref pattern (and still might've messed it up here). I don't think we should get into updating or interactivity or whatever, but I think just this much goes a long way toward demystifying (and showing just the most basic best practices).

* forgot i made data generic, rm reference to normal distribution

* useEffect cleans up after itself

Co-authored-by: Mike Bostock <mbostock@gmail.com>

* Update getting-started.md

---------

Co-authored-by: Mike Bostock <mbostock@gmail.com>

* build fixes

* index edits

---------

Co-authored-by: Toph Tucker <tophtucker@gmail.com>
2023-06-07 21:30:47 -04:00

16 KiB
Raw Blame History

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:

date fruit sales
1/2015 apples 3840
1/2015 bananas 1920
1/2015 cherries 960
1/2015 durians 400
2/2015 apples 1600
2/2015 bananas 1440
2/2015 cherries 960
2/2015 durians 400
3/2015 apples 640
3/2015 bananas 960
3/2015 cherries 640
3/2015 durians 400
4/2015 apples 320
4/2015 bananas 480
4/2015 cherries 640
4/2015 durians 400

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

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.

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:

[
  [[   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.

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.

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

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

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 points value.

stack.value(value)

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

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

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

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

The value accessor defaults to:

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

:::warning 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.

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:

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.

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.

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.

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)

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)

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)

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)

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)

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)

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)

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)

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)

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

Source · Applies a zero baseline.

stackOffsetSilhouette(series, order)

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)

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.