d3 / d3-shape

Graphical primitives for visualization, such as lines and areas.
https://d3js.org/d3-shape
ISC License
2.48k stars 310 forks source link

Use Stack layout with positive and negative series #95

Closed PAK90 closed 7 years ago

PAK90 commented 7 years ago

There's already an issue for this for d3 v3 but I felt this requires a new question. I have N series (where N is an even number), and half of N series represent something being removed from a cumulative total while the other half represent something being added. Currently the stack layout works fine for adding all of these as positive stacks, but I'd really like to show the 'removed' half of the series as stacks below the X axis. With the upgrade to d3 v4 there's no example I could find to do this (and my SO question has gone unanswered for a while) so I'm hoping for a suggestion here.

mbostock commented 7 years ago

The first strategy that comes to mind is that you use d3.stack twice: once for the positive values and once on the negative values. Before stacking the negative values, negate them so that d3.stack again operates on positive; then after stacking and before rendering, negate the values again so that they descend below the axis rather than rise above it.

A more elegant strategy is to take advantage of the stack operator being extensible: you can pass in whatever function you want as stack.offset. One strategy would be to stack using absolute values, but then shift each column down so that the negative values are below the axis and positive values are above the axis. This is sometimes called a diverging stacked bar chart.

Here’s my example:

https://bl.ocks.org/mbostock/b5935342c6d21928111928401e2c8608

If you have a question about D3’s behavior and want to discuss it with other users, also consider the d3-js Google Group or joining the d3-js Slack.

Thank you! 🤗

nschwan94 commented 7 years ago

I'd like to add a note that I get two different outputs using your stackOffsetDiverging function in this example (https://bl.ocks.org/mbostock/b5935342c6d21928111928401e2c8608) and the d3.stackOffsetDiverging preset. The results using the method in the example are the results I expected.

mbostock commented 7 years ago

d3.stackOffsetDiverging hasn’t been released yet; it’s only in master. Are you sure you’re not accidentally passing null to stack.offset?

nschwan94 commented 7 years ago

That would be a noob mistake on my part. My apologies, and incredible amount of gratitude to you otherwise!

mbostock commented 7 years ago

No worries. You reminded me I need to release it, so I just published 1.1.

nschwan94 commented 7 years ago

I got the most recent release, and I was attempting to use d3.stackOffsetDiverging with d3.stackOrderInsideOut. Since one of the series had both positive and negative values that were being summed to find the max and min series,

This was my output. image

So I modified the 'sum' method in the source code of d3.stackOrderInsideOut to use the absolute values of the series, and voila my output was what I wanted.

ORIGINAL:

export function sum(series) {
  var s = 0, i = -1, n = series.length, v;
  while (++i < n) if (v = +series[i][1]) s += v;
  return s;
}

REVISED:

export function sum(series) {
  var s = 0, i = -1, n = series.length, v;
  while (++i < n) if (v = +series[i][1]) {
    s += Math.abs(v);
  }
  return s;
}

image

I'm posting this principally just to document my experience, but maybe it offers an improvement or useful extension.

mbostock commented 7 years ago

@nschwan94 This would make a reasonable pull request if you want to submit it and add a corresponding test. Thank you!