Skip to content

Commit f0e508b

Browse files
authored
Merge pull request #7009 from my-tien/barmode_relative_offsetgroup
make offsetgroup work with barmode 'stacked' and 'relative' for bar traces
2 parents ef721c4 + 5ad372b commit f0e508b

17 files changed

+161
-107
lines changed

draftlogs/7009_change.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Make offsetgroup work with barmode 'stacked' and 'relative' for bar traces [[#7009](https://github.com/plotly/plotly.js/pull/7009)]

src/traces/bar/cross_trace_calc.js

+69-74
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function setGroupPositions(gd, pa, sa, calcTraces, opts) {
7575

7676
switch(opts.mode) {
7777
case 'overlay':
78-
setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts);
78+
setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces, opts);
7979
break;
8080

8181
case 'group':
@@ -94,7 +94,7 @@ function setGroupPositions(gd, pa, sa, calcTraces, opts) {
9494
setGroupPositionsInGroupMode(gd, pa, sa, included, opts);
9595
}
9696
if(excluded.length) {
97-
setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
97+
setGroupPositionsInOverlayMode(gd, pa, sa, excluded, opts);
9898
}
9999
break;
100100

@@ -119,7 +119,7 @@ function setGroupPositions(gd, pa, sa, calcTraces, opts) {
119119
setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included, opts);
120120
}
121121
if(excluded.length) {
122-
setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
122+
setGroupPositionsInOverlayMode(gd, pa, sa, excluded, opts);
123123
}
124124
break;
125125
}
@@ -217,7 +217,7 @@ function initBase(sa, calcTraces) {
217217
}
218218
}
219219

220-
function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) {
220+
function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces, opts) {
221221
// update position axis and set bar offsets and widths
222222
for(var i = 0; i < calcTraces.length; i++) {
223223
var calcTrace = calcTraces[i];
@@ -229,7 +229,7 @@ function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) {
229229
});
230230

231231
// set bar offsets and widths, and update position axis
232-
setOffsetAndWidth(pa, sieve, opts);
232+
setOffsetAndWidth(gd, pa, sieve, opts);
233233

234234
// set bar bases and sizes, and update size axis
235235
//
@@ -253,7 +253,7 @@ function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces, opts) {
253253
});
254254

255255
// set bar offsets and widths, and update position axis
256-
setOffsetAndWidthInGroupMode(gd, pa, sieve, opts);
256+
setOffsetAndWidth(gd, pa, sieve, opts);
257257

258258
// relative-stack bars within the same trace that would otherwise
259259
// be hidden
@@ -276,20 +276,20 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) {
276276
});
277277

278278
// set bar offsets and widths, and update position axis
279-
setOffsetAndWidth(pa, sieve, opts);
279+
setOffsetAndWidth(gd, pa, sieve, opts);
280280

281281
// set bar bases and sizes, and update size axis
282282
stackBars(sa, sieve, opts);
283283

284284
// flag the outmost bar (for text display purposes)
285285
for(var i = 0; i < calcTraces.length; i++) {
286286
var calcTrace = calcTraces[i];
287-
287+
var offsetIndex = calcTrace[0].t.offsetindex;
288288
for(var j = 0; j < calcTrace.length; j++) {
289289
var bar = calcTrace[j];
290290

291291
if(bar.s !== BADNUM) {
292-
var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s));
292+
var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, offsetIndex, bar.s));
293293
if(isOutmostBar) bar._outmost = true;
294294
}
295295
}
@@ -300,43 +300,19 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) {
300300
if(opts.norm) normalizeBars(sa, sieve, opts);
301301
}
302302

303-
function setOffsetAndWidth(pa, sieve, opts) {
304-
var minDiff = sieve.minDiff;
305-
var calcTraces = sieve.traces;
306-
307-
// set bar offsets and widths
308-
var barGroupWidth = minDiff * (1 - opts.gap);
309-
var barWidthPlusGap = barGroupWidth;
310-
var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
311-
312-
// computer bar group center and bar offset
313-
var offsetFromCenter = -barWidth / 2;
314-
315-
for(var i = 0; i < calcTraces.length; i++) {
316-
var calcTrace = calcTraces[i];
317-
var t = calcTrace[0].t;
318-
319-
// store bar width and offset for this trace
320-
t.barwidth = barWidth;
321-
t.poffset = offsetFromCenter;
322-
t.bargroupwidth = barGroupWidth;
323-
t.bardelta = minDiff;
324-
}
325-
326-
// stack bars that only differ by rounding
327-
sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
328-
329-
// if defined, apply trace offset and width
330-
applyAttributes(sieve);
331-
332-
// store the bar center in each calcdata item
333-
setBarCenterAndWidth(pa, sieve);
334-
335-
// update position axes
336-
updatePositionAxis(pa, sieve);
337-
}
338-
339-
function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
303+
/**
304+
* Mode group: Traces should be offsetted to other traces at the same position if they have a
305+
* different offsetgroup or if no offsetgroups are specified.
306+
* If there are no other traces at the same position, the trace will not be offsetted and it
307+
* can occupy the whole width.
308+
* If two traces share an offsetgroup, they should overlap.
309+
* Mode overlay/stack/relative: Traces should be offseted to other traces at the same position if
310+
* they have a different offsetgroup.
311+
* If two traces share an offsetgroup or if no offsetgroups are specified, they should instead
312+
* overlap/stack.
313+
* Angular axes (for barpolar type) don't support group offsets.
314+
*/
315+
function setOffsetAndWidth(gd, pa, sieve, opts) {
340316
var fullLayout = gd._fullLayout;
341317
var positions = sieve.positions;
342318
var distinctPositions = sieve.distinctPositions;
@@ -347,38 +323,48 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
347323
// if there aren't any overlapping positions,
348324
// let them have full width even if mode is group
349325
var overlap = (positions.length !== distinctPositions.length);
350-
var barGroupWidth = minDiff * (1 - opts.gap);
351326

352-
var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
353-
var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
327+
var barGroupWidth = minDiff * (1 - opts.gap);
328+
var barWidthPlusGap;
329+
var barWidth;
330+
var offsetFromCenter;
331+
var alignmentGroups;
332+
if(pa._id === 'angularaxis') {
333+
barWidthPlusGap = barGroupWidth;
334+
barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
335+
offsetFromCenter = -barWidth / 2;
336+
} else { // collect groups and calculate values in loop below
337+
var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
338+
alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
339+
}
354340

355341
for(var i = 0; i < nTraces; i++) {
356342
var calcTrace = calcTraces[i];
357343
var trace = calcTrace[0].trace;
344+
if(pa._id !== 'angularaxis') {
345+
var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
346+
var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
358347

359-
var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
360-
var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
361-
362-
var barWidthPlusGap;
363-
if(nOffsetGroups) {
364-
barWidthPlusGap = barGroupWidth / nOffsetGroups;
365-
} else {
366-
barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
367-
}
348+
if(nOffsetGroups) {
349+
barWidthPlusGap = barGroupWidth / nOffsetGroups;
350+
} else {
351+
barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
352+
}
368353

369-
var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
354+
barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
370355

371-
var offsetFromCenter;
372-
if(nOffsetGroups) {
373-
offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
374-
} else {
375-
offsetFromCenter = overlap ?
376-
((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
377-
-barWidth / 2;
356+
if(nOffsetGroups) {
357+
offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
358+
} else {
359+
offsetFromCenter = overlap ?
360+
((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
361+
-barWidth / 2;
362+
}
378363
}
379364

380365
var t = calcTrace[0].t;
381366
t.barwidth = barWidth;
367+
t.offsetindex = trace._offsetIndex || 0;
382368
t.poffset = offsetFromCenter;
383369
t.bargroupwidth = barGroupWidth;
384370
t.bardelta = minDiff;
@@ -394,7 +380,11 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
394380
setBarCenterAndWidth(pa, sieve);
395381

396382
// update position axes
397-
updatePositionAxis(pa, sieve, overlap);
383+
if(pa._id === 'angularaxis') {
384+
updatePositionAxis(pa, sieve);
385+
} else {
386+
updatePositionAxis(pa, sieve, overlap);
387+
}
398388
}
399389

400390
function applyAttributes(sieve) {
@@ -592,18 +582,20 @@ function stackBars(sa, sieve, opts) {
592582
var isFunnel;
593583
var i, j;
594584
var bar;
585+
var offsetIndex;
595586

596587
for(i = 0; i < calcTraces.length; i++) {
597588
calcTrace = calcTraces[i];
598589
fullTrace = calcTrace[0].trace;
599590

600591
if(fullTrace.type === 'funnel') {
592+
offsetIndex = calcTrace[0].t.offsetindex;
601593
for(j = 0; j < calcTrace.length; j++) {
602594
bar = calcTrace[j];
603595

604596
if(bar.s !== BADNUM) {
605597
// create base of funnels
606-
sieve.put(bar.p, -0.5 * bar.s);
598+
sieve.put(bar.p, offsetIndex, -0.5 * bar.s);
607599
}
608600
}
609601
}
@@ -615,6 +607,8 @@ function stackBars(sa, sieve, opts) {
615607

616608
isFunnel = (fullTrace.type === 'funnel');
617609

610+
offsetIndex = fullTrace.type === 'barpolar' ? 0 : calcTrace[0].t.offsetindex;
611+
618612
var pts = [];
619613

620614
for(j = 0; j < calcTrace.length; j++) {
@@ -629,8 +623,7 @@ function stackBars(sa, sieve, opts) {
629623
value = bar.s + bar.b;
630624
}
631625

632-
var base = sieve.put(bar.p, value);
633-
626+
var base = sieve.put(bar.p, offsetIndex, value);
634627
var top = base + value;
635628

636629
// store the bar base and top in each calcdata item
@@ -663,12 +656,12 @@ function sieveBars(sieve) {
663656

664657
for(var i = 0; i < calcTraces.length; i++) {
665658
var calcTrace = calcTraces[i];
666-
659+
var offsetIndex = calcTrace[0].t.offsetindex;
667660
for(var j = 0; j < calcTrace.length; j++) {
668661
var bar = calcTrace[j];
669662

670663
if(bar.s !== BADNUM) {
671-
sieve.put(bar.p, bar.b + bar.s);
664+
sieve.put(bar.p, offsetIndex, bar.b + bar.s);
672665
}
673666
}
674667
}
@@ -680,6 +673,7 @@ function unhideBarsWithinTrace(sieve, pa) {
680673
for(var i = 0; i < calcTraces.length; i++) {
681674
var calcTrace = calcTraces[i];
682675
var fullTrace = calcTrace[0].trace;
676+
var offsetIndex = calcTrace[0].t.offsetindex;
683677

684678
if(fullTrace.base === undefined) {
685679
var inTraceSieve = new Sieve([calcTrace], {
@@ -693,7 +687,7 @@ function unhideBarsWithinTrace(sieve, pa) {
693687

694688
if(bar.p !== BADNUM) {
695689
// stack current bar and get previous sum
696-
var base = inTraceSieve.put(bar.p, bar.b + bar.s);
690+
var base = inTraceSieve.put(bar.p, offsetIndex, bar.b + bar.s);
697691

698692
// if previous sum if non-zero, this means:
699693
// multiple bars have same starting point are potentially hidden,
@@ -726,6 +720,7 @@ function normalizeBars(sa, sieve, opts) {
726720

727721
for(var i = 0; i < calcTraces.length; i++) {
728722
var calcTrace = calcTraces[i];
723+
var offsetIndex = calcTrace[0].t.offsetindex;
729724
var fullTrace = calcTrace[0].trace;
730725
var pts = [];
731726
var tozero = false;
@@ -735,7 +730,7 @@ function normalizeBars(sa, sieve, opts) {
735730
var bar = calcTrace[j];
736731

737732
if(bar.s !== BADNUM) {
738-
var scale = Math.abs(sTop / sieve.get(bar.p, bar.s));
733+
var scale = Math.abs(sTop / sieve.get(bar.p, offsetIndex, bar.s));
739734
bar.b *= scale;
740735
bar.s *= scale;
741736

src/traces/bar/defaults.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,7 @@ function crossTraceDefaults(fullData, fullLayout) {
8080
traceOut.marker.cornerradius = validateCornerradius(r);
8181
}
8282

83-
if(fullLayout.barmode === 'group') {
84-
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
85-
}
83+
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce, fullLayout.barmode);
8684
}
8785
}
8886
}

src/traces/bar/layout_defaults.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = function(layoutIn, layoutOut, fullData) {
1919
var usedSubplots = {};
2020

2121
var mode = coerce('barmode');
22+
var isGroup = mode === 'group';
2223

2324
for(var i = 0; i < fullData.length; i++) {
2425
var trace = fullData[i];
@@ -27,10 +28,17 @@ module.exports = function(layoutIn, layoutOut, fullData) {
2728

2829
// if we have at least 2 grouped bar traces on the same subplot,
2930
// we should default to a gap anyway, even if the data is histograms
30-
if(mode === 'group') {
31-
var subploti = trace.xaxis + trace.yaxis;
31+
var subploti = trace.xaxis + trace.yaxis;
32+
if(isGroup) {
33+
// with barmode group, bars are grouped next to each other when sharing the same axes
3234
if(usedSubplots[subploti]) gappedAnyway = true;
3335
usedSubplots[subploti] = true;
36+
} else {
37+
// with other barmodes bars are grouped next to each other when sharing the same axes
38+
// and using different offsetgroups
39+
subploti += trace._input.offsetgroup;
40+
if(usedSubplots.length > 0 && !usedSubplots[subploti]) gappedAnyway = true;
41+
usedSubplots[subploti] = true;
3442
}
3543

3644
if(trace.visible && trace.type === 'histogram') {

src/traces/bar/sieve.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ function Sieve(traces, opts) {
6666
*
6767
* @method
6868
* @param {number} position
69+
* @param {number} group
6970
* @param {number} value
7071
* @returns {number} Previous bin value
7172
*/
72-
Sieve.prototype.put = function put(position, value) {
73-
var label = this.getLabel(position, value);
73+
Sieve.prototype.put = function put(position, group, value) {
74+
var label = this.getLabel(position, group, value);
7475
var oldValue = this.bins[label] || 0;
7576

7677
this.bins[label] = oldValue + value;
@@ -83,12 +84,13 @@ Sieve.prototype.put = function put(position, value) {
8384
*
8485
* @method
8586
* @param {number} position Position of datum
87+
* @param {number} group
8688
* @param {number} [value] Value of datum
8789
* (required if this.sepNegVal is true)
8890
* @returns {number} Current bin value
8991
*/
90-
Sieve.prototype.get = function get(position, value) {
91-
var label = this.getLabel(position, value);
92+
Sieve.prototype.get = function get(position, group, value) {
93+
var label = this.getLabel(position, group, value);
9294
return this.bins[label] || 0;
9395
};
9496

@@ -97,16 +99,17 @@ Sieve.prototype.get = function get(position, value) {
9799
*
98100
* @method
99101
* @param {number} position Position of datum
102+
* @param {number} group
100103
* @param {number} [value] Value of datum
101104
* (required if this.sepNegVal is true)
102105
* @returns {string} Bin label
103106
* (prefixed with a 'v' if value is negative and this.sepNegVal is
104107
* true; otherwise prefixed with '^')
105108
*/
106-
Sieve.prototype.getLabel = function getLabel(position, value) {
109+
Sieve.prototype.getLabel = function getLabel(position, group, value) {
107110
var prefix = (value < 0 && this.sepNegVal) ? 'v' : '^';
108111
var label = (this.overlapNoMerge) ?
109112
position :
110113
Math.round(position / this.binWidth);
111-
return prefix + label;
114+
return prefix + label + 'g' + group;
112115
};

0 commit comments

Comments
 (0)