Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stacked Bar Chart with both positive and negative groups #1207

Open
IgorShch opened this issue Sep 22, 2016 · 8 comments
Open

Stacked Bar Chart with both positive and negative groups #1207

IgorShch opened this issue Sep 22, 2016 · 8 comments
Milestone

Comments

@IgorShch
Copy link

Hi,

Is it possible to correctly display a stacked bar chart when you have to stack both negative and positive groups?

The problem is well described here https://groups.google.com/forum/#!topic/dc-js-user-group/CjGLKECU1II and the author provides a solution, but for 1.3. I wonder if anyone has done the same for 2.0? or if anyone could point me into right direction?

@gordonwoodhull
Copy link
Contributor

I don't know of a current solution for bar charts, but #1068 presents something similar for row charts (which don't have stacking, but if you only have one positive and one negative stack it's the same thing).

@gordonwoodhull gordonwoodhull added this to the v2.1 milestone Sep 25, 2016
@gordonwoodhull
Copy link
Contributor

Apparently d3.stack supports this through stack.offset:

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

Haven't tried this with stackMixin.stackLayout – anyone can help out by giving this a shot and reporting back here.

@gordonwoodhull
Copy link
Contributor

Or rather, it's part of d3v4 now. d3/d3-shape#96

@gordonwoodhull gordonwoodhull modified the milestones: v3.0, v2.1 Mar 11, 2017
@Sahab-Shahab
Copy link

Sahab-Shahab commented Apr 19, 2017

Hi everybody,
I have tried this issue with stackMixin.stackLayout. Its quite easy and straightforward:
.stackLayout(d3.layout.stack().out( function out(d, y0, y) { if(y > 0) { d.y0 = maxV; d.y = y; maxV += y; } else { d.y0 = minV; d.y = y; minV += y; } })
while maxV, minV are some variables defined outside.
The problem is only that the out() is called thrice for each dimension * group/stack. In the 2nd and 3rd passes, the value of minV and maxV must be reset. I used a tricky way to solve it. Write this code if you have 1stack+1group:
.stackLayout(d3.layout.stack().out( function out(d, y0, y) { if(iterate == 2) { minV = maxV = iterate = 0; } iterate++; if(y > 0) { d.y0 = maxV; d.y = y; maxV += y; } else { d.y0 = minV; d.y = y; minV += y; } })
again, iterate is a value defined outside. For more stacks just change "2" value in if(iterate == 2).
I know it's like a hack or maybe rediculous to solve the problem. If someone found a better way please let me know.

@Sahab-Shahab
Copy link

I found a better way, instead of "iterate" use variable x and check if the d.x is a new value of a repeated:
.stackLayout(d3.layout.stack().out( function out(d, y0, y) { if(x != d.x) { x = d.x; minY = maxY = 0; } if(y > 0) { d.y0 = maxY; maxY += y; } else { d.y0 = minY; minY += y; } d.y = y; } ))
In this manner, we don't need to know the number of Stacks.

@frankisans
Copy link

Hi @gordonwoodhull

I tested a bar graph with stackOffsetDiverging, calling stackLayout.

chart.stackLayout(d3.stack().offset(d3.stackOffsetDiverging));

To make it work it is necessary to make some adjustments:

  • change yAxisMin and yAxisMax to correctly calculate the "Y" scale using y0 for min and y1 for max, regardless of the y value (positive or negative)
  • define a renderlet listener to modify the value of the y attribute for the bars, using the value of y1 instead of the sum of y + y0 that is used in the function renderBars.
chart.on('renderlet.internal', (chart) => chart.selectAll(".bar").attr('y', (d) => chart.y()(d.y1)));
chart.yAxisMin = () => {
    const flattenStacks = Array.prototype.concat.apply([], this._dcChart.data().map((layer) => layer.domainValues));
    return dc.utils.subtract(min(flattenStacks, (p) => p.y0), this._dcChart.yAxisPadding());
};
chart.yAxisMax = () => {
    const flattenStacks = Array.prototype.concat.apply([], this._dcChart.data().map((layer) => layer.domainValues));
    return dc.utils.add(max(flattenStacks, (p) => p.y1), this._dcChart.yAxisPadding());
};

These modifications do not affect the behavior of the chart with the default stack.

If a renderlet is used to modify the chart, it is important to first define the listener that modifies the y attribute, so that it is executed first and the subsequent listeners have the correct value.

In my case for example I define a renderlet to add some labels in each bar and I must execute it with the correct y, so I use a postRender for my custom labels:

image

I have not yet been able to verify why, but I have seen that if I hide all the stacks except the one with negative values, the height of the bars is not calculated correctly, it is zero.

I have used this example as a reference: https://bl.ocks.org/mbostock/b5935342c6d21928111928401e2c8608

@gordonwoodhull
Copy link
Contributor

Thanks @frankisans, that's helpful info. TBH I am kind of surprised that it works as well as it does, given that the stack mixin was never designed to work with the new stack layouts.

I take it you are saying that these changes could be pulled in without breaking the original stack functionality. That would be nice! If so, maybe someone could create a PR with tests.

@jyotisingh144
Copy link

Thanks @frankisans, that's helpful info. TBH I am kind of surprised that it works as well as it does, given that the stack mixin was never designed to work with the new stack layouts.

I take it you are saying that these changes could be pulled in without breaking the original stack functionality. That would be nice! If so, maybe someone could create a PR with tests.

Do you have any angular code for this on stackblitz?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants