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

make offsetgroup work with barmode 'stacked' and 'relative' for bar traces #7009

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d3b5068
For bar traces make offsetgroup work with barmode 'stacked' and 'rela…
my-tien Jun 1, 2024
6ce9aa6
Fix handleGroupingDefaults calls outside of bar/defaults.js
my-tien Jun 3, 2024
7664219
Fix linter error on missing newline at EOF
my-tien Jun 3, 2024
ed7d541
Fix offset calculation and stacking for barpolar
my-tien Jun 4, 2024
24b4fb5
Fix angularaxis check in setOffsetAndWidth
my-tien Jun 5, 2024
c91f06c
consider modes of other traces as well (scattermode, funnelmode,... )
my-tien Jun 5, 2024
724c83f
Don't use poffset for sieve.put, use trace._offsetIndex instead.
my-tien Jun 5, 2024
315c888
Fix logic for adding a gap between bars
my-tien Jun 5, 2024
c6f5dbe
baseline image for zzz_bar_relative_offsetgroup
my-tien Jun 5, 2024
86478a4
Fix sieve for offsetgroups
my-tien Jun 6, 2024
2d9d0a9
Fix for failing baseline test waterfall_funnel_template_date
my-tien Jun 6, 2024
8caa69b
Pass missing barmode param for histogram
my-tien Jun 6, 2024
68fae33
Ensure offsetIndex and offsetgroup are not undefined
my-tien Jun 6, 2024
e8bd5db
Track alignmentgroups in fullLayout._alignmentOpts by trace type
my-tien Jun 7, 2024
ac93f5d
Make offsetgroup work for boxmode overlay as well
my-tien Jun 7, 2024
e43227c
Documentation for setOffsetAndWidth and fix for sieve documentations
my-tien Jun 7, 2024
14e92a3
Merge remote-tracking branch 'origin-plotly/master' into barmode_rela…
my-tien Jun 7, 2024
960f910
Merge remote-tracking branch 'origin-plotly/master' into barmode_rela…
my-tien Aug 28, 2024
f08f925
Revert "Make offsetgroup work for boxmode overlay as well"
my-tien Aug 28, 2024
d855f17
Revert "Track alignmentgroups in fullLayout._alignmentOpts by trace t…
my-tien Aug 28, 2024
b3ac3c9
Ignore offsetgroup and alignmentgroup for those trace types that don'…
my-tien Aug 28, 2024
db602ab
Update funnel test after adding support for alignmentgroup and offset…
my-tien Sep 2, 2024
9f3624d
Update bar test after adding support for alignmentgroup and offsetgro…
my-tien Sep 2, 2024
2059f80
Update baseline images for legendgroup_bar-stack and funnel_attrs
my-tien Sep 4, 2024
bc2ea29
Merge remote-tracking branch 'origin-plotly/master' into barmode_rela…
my-tien Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 69 additions & 74 deletions src/traces/bar/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function setGroupPositions(gd, pa, sa, calcTraces, opts) {

switch(opts.mode) {
case 'overlay':
setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts);
setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces, opts);
break;

case 'group':
Expand All @@ -94,7 +94,7 @@ function setGroupPositions(gd, pa, sa, calcTraces, opts) {
setGroupPositionsInGroupMode(gd, pa, sa, included, opts);
}
if(excluded.length) {
setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
setGroupPositionsInOverlayMode(gd, pa, sa, excluded, opts);
}
break;

Expand All @@ -119,7 +119,7 @@ function setGroupPositions(gd, pa, sa, calcTraces, opts) {
setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included, opts);
}
if(excluded.length) {
setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
setGroupPositionsInOverlayMode(gd, pa, sa, excluded, opts);
}
break;
}
Expand Down Expand Up @@ -217,7 +217,7 @@ function initBase(sa, calcTraces) {
}
}

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

// set bar offsets and widths, and update position axis
setOffsetAndWidth(pa, sieve, opts);
setOffsetAndWidth(gd, pa, sieve, opts);

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

// set bar offsets and widths, and update position axis
setOffsetAndWidthInGroupMode(gd, pa, sieve, opts);
setOffsetAndWidth(gd, pa, sieve, opts);

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

// set bar offsets and widths, and update position axis
setOffsetAndWidth(pa, sieve, opts);
setOffsetAndWidth(gd, pa, sieve, opts);

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

// flag the outmost bar (for text display purposes)
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];

var offsetIndex = calcTrace[0].t.offsetindex;
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];

if(bar.s !== BADNUM) {
var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s));
var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, offsetIndex, bar.s));
if(isOutmostBar) bar._outmost = true;
}
}
Expand All @@ -300,43 +300,19 @@ function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) {
if(opts.norm) normalizeBars(sa, sieve, opts);
}

function setOffsetAndWidth(pa, sieve, opts) {
var minDiff = sieve.minDiff;
var calcTraces = sieve.traces;

// set bar offsets and widths
var barGroupWidth = minDiff * (1 - opts.gap);
var barWidthPlusGap = barGroupWidth;
var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));

// computer bar group center and bar offset
var offsetFromCenter = -barWidth / 2;

for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var t = calcTrace[0].t;

// store bar width and offset for this trace
t.barwidth = barWidth;
t.poffset = offsetFromCenter;
t.bargroupwidth = barGroupWidth;
t.bardelta = minDiff;
}

// stack bars that only differ by rounding
sieve.binWidth = calcTraces[0][0].t.barwidth / 100;

// if defined, apply trace offset and width
applyAttributes(sieve);

// store the bar center in each calcdata item
setBarCenterAndWidth(pa, sieve);

// update position axes
updatePositionAxis(pa, sieve);
}

function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
/**
* Mode group: Traces should be offsetted to other traces at the same position if they have a
* different offsetgroup or if no offsetgroups are specified.
* If there are no other traces at the same position, the trace will not be offsetted and it
* can occupy the whole width.
* If two traces share an offsetgroup, they should overlap.
* Mode overlay/stack/relative: Traces should be offseted to other traces at the same position if
* they have a different offsetgroup.
* If two traces share an offsetgroup or if no offsetgroups are specified, they should instead
* overlap/stack.
* Angular axes (for barpolar type) don't support group offsets.
*/
function setOffsetAndWidth(gd, pa, sieve, opts) {
var fullLayout = gd._fullLayout;
var positions = sieve.positions;
var distinctPositions = sieve.distinctPositions;
Expand All @@ -347,38 +323,48 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
// if there aren't any overlapping positions,
// let them have full width even if mode is group
var overlap = (positions.length !== distinctPositions.length);
var barGroupWidth = minDiff * (1 - opts.gap);

var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
var barGroupWidth = minDiff * (1 - opts.gap);
var barWidthPlusGap;
var barWidth;
var offsetFromCenter;
var alignmentGroups;
if(pa._id === 'angularaxis') {
barWidthPlusGap = barGroupWidth;
barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
offsetFromCenter = -barWidth / 2;
} else { // collect groups and calculate values in loop below
var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
}

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

var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;

var barWidthPlusGap;
if(nOffsetGroups) {
barWidthPlusGap = barGroupWidth / nOffsetGroups;
} else {
barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
}
if(nOffsetGroups) {
barWidthPlusGap = barGroupWidth / nOffsetGroups;
} else {
barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
}

var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));

var offsetFromCenter;
if(nOffsetGroups) {
offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
} else {
offsetFromCenter = overlap ?
((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
-barWidth / 2;
if(nOffsetGroups) {
offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
} else {
offsetFromCenter = overlap ?
((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
-barWidth / 2;
}
}

var t = calcTrace[0].t;
t.barwidth = barWidth;
t.offsetindex = trace._offsetIndex || 0;
t.poffset = offsetFromCenter;
t.bargroupwidth = barGroupWidth;
t.bardelta = minDiff;
Expand All @@ -394,7 +380,11 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
setBarCenterAndWidth(pa, sieve);

// update position axes
updatePositionAxis(pa, sieve, overlap);
if(pa._id === 'angularaxis') {
updatePositionAxis(pa, sieve);
} else {
updatePositionAxis(pa, sieve, overlap);
}
}

function applyAttributes(sieve) {
Expand Down Expand Up @@ -592,18 +582,20 @@ function stackBars(sa, sieve, opts) {
var isFunnel;
var i, j;
var bar;
var offsetIndex;

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

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

if(bar.s !== BADNUM) {
// create base of funnels
sieve.put(bar.p, -0.5 * bar.s);
sieve.put(bar.p, offsetIndex, -0.5 * bar.s);
}
}
}
Expand All @@ -615,6 +607,8 @@ function stackBars(sa, sieve, opts) {

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

offsetIndex = fullTrace.type === 'barpolar' ? 0 : calcTrace[0].t.offsetindex;

var pts = [];

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

var base = sieve.put(bar.p, value);

var base = sieve.put(bar.p, offsetIndex, value);
var top = base + value;

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

for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];

var offsetIndex = calcTrace[0].t.offsetindex;
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];

if(bar.s !== BADNUM) {
sieve.put(bar.p, bar.b + bar.s);
sieve.put(bar.p, offsetIndex, bar.b + bar.s);
}
}
}
Expand All @@ -680,6 +673,7 @@ function unhideBarsWithinTrace(sieve, pa) {
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var fullTrace = calcTrace[0].trace;
var offsetIndex = calcTrace[0].t.offsetindex;

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

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

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

for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var offsetIndex = calcTrace[0].t.offsetindex;
var fullTrace = calcTrace[0].trace;
var pts = [];
var tozero = false;
Expand All @@ -735,7 +730,7 @@ function normalizeBars(sa, sieve, opts) {
var bar = calcTrace[j];

if(bar.s !== BADNUM) {
var scale = Math.abs(sTop / sieve.get(bar.p, bar.s));
var scale = Math.abs(sTop / sieve.get(bar.p, offsetIndex, bar.s));
bar.b *= scale;
bar.s *= scale;

Expand Down
4 changes: 1 addition & 3 deletions src/traces/bar/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ function crossTraceDefaults(fullData, fullLayout) {
traceOut.marker.cornerradius = validateCornerradius(r);
}

if(fullLayout.barmode === 'group') {
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
}
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce, fullLayout.barmode);
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/traces/bar/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = function(layoutIn, layoutOut, fullData) {
var usedSubplots = {};

var mode = coerce('barmode');
var isGroup = mode === 'group';

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

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

if(trace.visible && trace.type === 'histogram') {
Expand Down
15 changes: 9 additions & 6 deletions src/traces/bar/sieve.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ function Sieve(traces, opts) {
*
* @method
* @param {number} position
* @param {number} group
* @param {number} value
* @returns {number} Previous bin value
*/
Sieve.prototype.put = function put(position, value) {
var label = this.getLabel(position, value);
Sieve.prototype.put = function put(position, group, value) {
var label = this.getLabel(position, group, value);
var oldValue = this.bins[label] || 0;

this.bins[label] = oldValue + value;
Expand All @@ -83,12 +84,13 @@ Sieve.prototype.put = function put(position, value) {
*
* @method
* @param {number} position Position of datum
* @param {number} group
* @param {number} [value] Value of datum
* (required if this.sepNegVal is true)
* @returns {number} Current bin value
*/
Sieve.prototype.get = function get(position, value) {
var label = this.getLabel(position, value);
Sieve.prototype.get = function get(position, group, value) {
var label = this.getLabel(position, group, value);
return this.bins[label] || 0;
};

Expand All @@ -97,16 +99,17 @@ Sieve.prototype.get = function get(position, value) {
*
* @method
* @param {number} position Position of datum
* @param {number} group
* @param {number} [value] Value of datum
* (required if this.sepNegVal is true)
* @returns {string} Bin label
* (prefixed with a 'v' if value is negative and this.sepNegVal is
* true; otherwise prefixed with '^')
*/
Sieve.prototype.getLabel = function getLabel(position, value) {
Sieve.prototype.getLabel = function getLabel(position, group, value) {
var prefix = (value < 0 && this.sepNegVal) ? 'v' : '^';
var label = (this.overlapNoMerge) ?
position :
Math.round(position / this.binWidth);
return prefix + label;
return prefix + label + 'g' + group;
};
Loading