Skip to content

Commit df5f58e

Browse files
committed
Merge pull request #517 from keeganmccallum/relative-barmode
Bar: Add relative barmode for stacking negative values below axis
2 parents 106e52c + f14e4b6 commit df5f58e

7 files changed

+106
-9
lines changed

src/traces/bar/layout_attributes.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
module.exports = {
1313
barmode: {
1414
valType: 'enumerated',
15-
values: ['stack', 'group', 'overlay'],
15+
values: ['stack', 'group', 'overlay', 'relative'],
1616
dflt: 'group',
1717
role: 'info',
1818
description: [
1919
'Determines how bars at the same location coordinate',
2020
'are displayed on the graph.',
2121
'With *stack*, the bars are stacked on top of one another',
22+
'With *relative*, the bars are stacked on top of one another,',
23+
'with negative values below the axis, positive values above',
2224
'With *group*, the bars are plotted next to one another',
2325
'centered around the shared location.',
2426
'With *overlay*, the bars are plotted over one another,',

src/traces/bar/set_positions.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,11 @@ module.exports = function setPositions(gd, plotinfo) {
116116
else barposition(bl);
117117

118118
var stack = (fullLayout.barmode === 'stack'),
119+
relative = (fullLayout.barmode ==='relative'),
119120
norm = fullLayout.barnorm;
120121

121122
// bar size range and stacking calculation
122-
if(stack || norm) {
123+
if(stack || relative || norm) {
123124
// for stacked bars, we need to evaluate every step in every
124125
// stack, because negative bars mean the extremes could be
125126
// anywhere
@@ -142,13 +143,15 @@ module.exports = function setPositions(gd, plotinfo) {
142143
ti = gd.calcdata[bl[i]];
143144
for(j = 0; j < ti.length; j++) {
144145
sv = Math.round(ti[j].p / sumround);
146+
// store the negative sum value for p at the same key, with sign flipped
147+
if(relative && ti[j].s < 0) sv = -sv;
145148
var previousSum = sums[sv] || 0;
146-
if(stack) ti[j].b = previousSum;
149+
if(stack || relative) ti[j].b = previousSum;
147150
barEnd = ti[j].b + ti[j].s;
148151
sums[sv] = previousSum + ti[j].s;
149152

150153
// store the bar top in each calcdata item
151-
if(stack) {
154+
if(stack || relative) {
152155
ti[j][sLetter] = barEnd;
153156
if(!norm && isNumeric(sa.c2l(barEnd))) {
154157
sMax = Math.max(sMax,barEnd);
@@ -161,13 +164,18 @@ module.exports = function setPositions(gd, plotinfo) {
161164
if(norm) {
162165
padded = false;
163166
var top = norm==='fraction' ? 1 : 100,
167+
relAndNegative = false,
164168
tiny = top/1e9; // in case of rounding error in sum
165169
sMin = 0;
166170
sMax = stack ? top : 0;
167171
for(i = 0; i < bl.length; i++) { // trace index
168172
ti = gd.calcdata[bl[i]];
169173
for(j = 0; j < ti.length; j++) {
170-
scale = top / sums[Math.round(ti[j].p/sumround)];
174+
relAndNegative = relative && ti[j].s < 0;
175+
sv = Math.round(ti[j].p / sumround);
176+
if(relAndNegative) sv = -sv; // locate negative sum amount for this p val
177+
scale = top / sums[sv];
178+
if(relAndNegative) scale *= -1; // preserve sign if negative
171179
ti[j].b *= scale;
172180
ti[j].s *= scale;
173181
barEnd = ti[j].b + ti[j].s;
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"data":[
3+
{
4+
"name":"Col1",
5+
"y":["-1","2","3","4","5"],
6+
"x":["1","2","3","4","5"],
7+
"type":"bar"
8+
},
9+
{
10+
"name":"Col2",
11+
"y":["2","3","4","-3","2"],
12+
"x":["1","2","3","4","5"],
13+
"type":"bar"
14+
},
15+
{
16+
"name":"Col3",
17+
"y":["5","4","3","-2","1"],
18+
"x":["1","2","3","4","5"],
19+
"type":"bar"
20+
},
21+
{
22+
"name":"Col4",
23+
"y":["-3","0","1","0","-3"],
24+
"x":["1","2","3","4","5"],
25+
"type":"bar"
26+
}
27+
],
28+
"layout":{
29+
"height":400,
30+
"width":400,
31+
"barmode":"relative"
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"data":[
3+
{
4+
"name":"Col1",
5+
"y":["-1","2","3","4","5"],
6+
"x":["1","2","3","4","5"],
7+
"type":"bar"
8+
},
9+
{
10+
"name":"Col2",
11+
"y":["2","3","4","-3","2"],
12+
"x":["1","2","3","4","5"],
13+
"type":"bar"
14+
},
15+
{
16+
"name":"Col3",
17+
"y":["5","4","3","-2","1"],
18+
"x":["1","2","3","4","5"],
19+
"type":"bar"
20+
},
21+
{
22+
"name":"Col4",
23+
"y":["-3","0","1","0","-3"],
24+
"x":["1","2","3","4","5"],
25+
"type":"bar"
26+
}
27+
],
28+
"layout":{
29+
"height":400,
30+
"width":400,
31+
"barmode":"relative",
32+
"barnorm":"percent"
33+
}
34+
}

test/image/mocks/bar_stackto100_negative.json

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
{
22
"data":[
3-
{"name":"Col1","y":["1","2","3","4","5"],"x":["1","2","3","4","5"],"type":"bar","uid":"aeb9ea"},
4-
{"name":"Col2","y":["2","3","4","3","2"],"x":["1","2","3","4","5"],"type":"bar","uid":"2f201d"},
5-
{"name":"Col3","y":["5","4","3","2","1"],"x":["1","2","3","4","5"],"type":"bar","uid":"aef0bf"},
6-
{"name":"Col4","y":["-1","0","1","0","-1"],"x":["1","2","3","4","5"],"type":"bar","uid":"330b4d"}
3+
{
4+
"name":"Col1",
5+
"y":["1","2","3","4","5"],
6+
"x":["1","2","3","4","5"],
7+
"type":"bar"
8+
},
9+
{
10+
"name":"Col2",
11+
"y":["2","3","4","3","2"],
12+
"x":["1","2","3","4","5"],
13+
"type":"bar"
14+
},
15+
{
16+
"name":"Col3",
17+
"y":["5","4","3","2","1"],
18+
"x":["1","2","3","4","5"],
19+
"type":"bar"
20+
},
21+
{
22+
"name":"Col4",
23+
"y":["-1","0","1","0","-1"],
24+
"x":["1","2","3","4","5"],
25+
"type":"bar"
26+
}
727
],
828
"layout":{
929
"height":400,

0 commit comments

Comments
 (0)