diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js
index 8e0bf5003d3..eb36d1bf1c2 100644
--- a/src/plots/cartesian/axis_ids.js
+++ b/src/plots/cartesian/axis_ids.js
@@ -114,3 +114,13 @@ exports.idSort = function(id1, id2) {
if(letter1 !== letter2) return letter1 > letter2 ? 1 : -1;
return +(id1.substr(1) || 1) - +(id2.substr(1) || 1);
};
+
+exports.getAxisGroup = function getAxisGroup(fullLayout, axId) {
+ var matchGroups = fullLayout._axisMatchGroups;
+
+ for(var i = 0; i < matchGroups.length; i++) {
+ var group = matchGroups[i];
+ if(group[axId]) return 'g' + i;
+ }
+ return axId;
+};
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 9fa2616a69e..cde651366e8 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -394,6 +394,8 @@ plots.supplyDefaults = function(gd, opts) {
newFullLayout._scatterStackOpts = {};
// for the first scatter trace on each subplot (so it knows tonext->tozero)
newFullLayout._firstScatter = {};
+ // for grouped bar/box/violin trace to share config across traces
+ newFullLayout._alignmentOpts = {};
// for traces to request a default rangeslider on their x axes
// eg set `_requestRangeslider.x2 = true` for xaxis2
diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js
index ea0694f077c..5cf9003da59 100644
--- a/src/traces/bar/attributes.js
+++ b/src/traces/bar/attributes.js
@@ -174,6 +174,30 @@ module.exports = {
marker: marker,
+ offsetgroup: {
+ valType: 'string',
+ role: 'info',
+ dflt: '',
+ editType: 'calc',
+ description: [
+ 'Set several traces linked to the same position axis',
+ 'or matching axes to the same',
+ 'offsetgroup where bars of the same position coordinate will line up.'
+ ].join(' ')
+ },
+ alignmentgroup: {
+ valType: 'string',
+ role: 'info',
+ dflt: '',
+ editType: 'calc',
+ description: [
+ 'Set several traces linked to the same position axis',
+ 'or matching axes to the same',
+ 'alignmentgroup. This controls whether bars compute their positional',
+ 'range dependently or independently.'
+ ].join(' ')
+ },
+
selected: {
marker: {
opacity: scatterAttrs.selected.marker.opacity,
diff --git a/src/traces/bar/cross_trace_calc.js b/src/traces/bar/cross_trace_calc.js
index 2a0224dc35e..916e853eec5 100644
--- a/src/traces/bar/cross_trace_calc.js
+++ b/src/traces/bar/cross_trace_calc.js
@@ -14,6 +14,7 @@ var BADNUM = require('../../constants/numerical').BADNUM;
var Registry = require('../../registry');
var Axes = require('../../plots/cartesian/axes');
+var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
var Sieve = require('./sieve.js');
/*
@@ -279,26 +280,42 @@ function setOffsetAndWidthInGroupMode(gd, pa, sieve) {
var distinctPositions = sieve.distinctPositions;
var minDiff = sieve.minDiff;
var calcTraces = sieve.traces;
+ var nTraces = calcTraces.length;
// if there aren't any overlapping positions,
// let them have full width even if mode is group
var overlap = (positions.length !== distinctPositions.length);
-
- var nTraces = calcTraces.length;
var barGroupWidth = minDiff * (1 - bargap);
- var barWidthPlusGap = (overlap) ? barGroupWidth / nTraces : barGroupWidth;
- var barWidth = barWidthPlusGap * (1 - bargroupgap);
+
+ var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
+ var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
for(var i = 0; i < nTraces; i++) {
var calcTrace = calcTraces[i];
- var t = calcTrace[0].t;
+ var trace = calcTrace[0].trace;
- // computer bar group center and bar offset
- var offsetFromCenter = overlap ?
- ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
- -barWidth / 2;
+ var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
+ var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
- // store bar width and offset for this trace
+ var barWidthPlusGap;
+ if(nOffsetGroups) {
+ barWidthPlusGap = barGroupWidth / nOffsetGroups;
+ } else {
+ barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
+ }
+
+ var barWidth = barWidthPlusGap * (1 - bargroupgap);
+
+ 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;
+ }
+
+ var t = calcTrace[0].t;
t.barwidth = barWidth;
t.poffset = offsetFromCenter;
t.bargroupwidth = barGroupWidth;
diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js
index b32a14d5cf2..b6715411524 100644
--- a/src/traces/bar/defaults.js
+++ b/src/traces/bar/defaults.js
@@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var Lib = require('../../lib');
@@ -15,9 +14,10 @@ var Registry = require('../../registry');
var handleXYDefaults = require('../scatter/xy_defaults');
var handleStyleDefaults = require('../bar/style_defaults');
+var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
var attributes = require('./attributes');
-module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
+function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
@@ -78,4 +78,68 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
+}
+
+function handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce) {
+ var orientation = traceOut.orientation;
+ // N.B. grouping is done across all trace trace types that support it
+ var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis'];
+ var groupId = getAxisGroup(fullLayout, posAxId) + orientation;
+
+ var alignmentOpts = fullLayout._alignmentOpts || {};
+ var alignmentgroup = coerce('alignmentgroup');
+
+ var alignmentGroups = alignmentOpts[groupId];
+ if(!alignmentGroups) alignmentGroups = alignmentOpts[groupId] = {};
+
+ var alignmentGroupOpts = alignmentGroups[alignmentgroup];
+
+ if(alignmentGroupOpts) {
+ alignmentGroupOpts.traces.push(traceOut);
+ } else {
+ alignmentGroupOpts = alignmentGroups[alignmentgroup] = {
+ traces: [traceOut],
+ alignmentIndex: Object.keys(alignmentGroups).length,
+ offsetGroups: {}
+ };
+ }
+
+ var offsetgroup = coerce('offsetgroup');
+ var offsetGroups = alignmentGroupOpts.offsetGroups;
+ var offsetGroupOpts = offsetGroups[offsetgroup];
+
+ if(offsetgroup) {
+ if(!offsetGroupOpts) {
+ offsetGroupOpts = offsetGroups[offsetgroup] = {
+ offsetIndex: Object.keys(offsetGroups).length
+ };
+ }
+
+ traceOut._offsetIndex = offsetGroupOpts.offsetIndex;
+ }
+}
+
+function crossTraceDefaults(fullData, fullLayout) {
+ var traceIn, traceOut;
+
+ function coerce(attr) {
+ return Lib.coerce(traceOut._input, traceOut, attributes, attr);
+ }
+
+ for(var i = 0; i < fullData.length; i++) {
+ traceOut = fullData[i];
+
+ if(traceOut.type === 'bar') {
+ traceIn = traceOut._input;
+ if(fullLayout.barmode === 'group') {
+ handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
+ }
+ }
+ }
+}
+
+module.exports = {
+ supplyDefaults: supplyDefaults,
+ crossTraceDefaults: crossTraceDefaults,
+ handleGroupingDefaults: handleGroupingDefaults
};
diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js
index fa579dbf3fa..2da83f64c5b 100644
--- a/src/traces/bar/index.js
+++ b/src/traces/bar/index.js
@@ -6,14 +6,14 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var Bar = {};
Bar.attributes = require('./attributes');
Bar.layoutAttributes = require('./layout_attributes');
-Bar.supplyDefaults = require('./defaults');
+Bar.supplyDefaults = require('./defaults').supplyDefaults;
+Bar.crossTraceDefaults = require('./defaults').crossTraceDefaults;
Bar.supplyLayoutDefaults = require('./layout_defaults');
Bar.calc = require('./calc');
Bar.crossTraceCalc = require('./cross_trace_calc').crossTraceCalc;
diff --git a/src/traces/bar/layout_defaults.js b/src/traces/bar/layout_defaults.js
index 7abb2920dfa..1f688164dd0 100644
--- a/src/traces/bar/layout_defaults.js
+++ b/src/traces/bar/layout_defaults.js
@@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var Registry = require('../../registry');
@@ -15,7 +14,6 @@ var Lib = require('../../lib');
var layoutAttributes = require('./layout_attributes');
-
module.exports = function(layoutIn, layoutOut, fullData) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js
index 7bfd9bce3ce..93318226ee3 100644
--- a/src/traces/box/attributes.js
+++ b/src/traces/box/attributes.js
@@ -9,6 +9,7 @@
'use strict';
var scatterAttrs = require('../scatter/attributes');
+var barAttrs = require('../bar/attributes');
var colorAttrs = require('../../components/color/attributes');
var extendFlat = require('../../lib/extend').extendFlat;
@@ -250,6 +251,9 @@ module.exports = {
},
fillcolor: scatterAttrs.fillcolor,
+ offsetgroup: barAttrs.offsetgroup,
+ alignmentgroup: barAttrs.alignmentgroup,
+
selected: {
marker: scatterAttrs.selected.marker,
editType: 'style'
diff --git a/src/traces/box/cross_trace_calc.js b/src/traces/box/cross_trace_calc.js
index 8c5d4d2fc7e..566c47d368a 100644
--- a/src/traces/box/cross_trace_calc.js
+++ b/src/traces/box/cross_trace_calc.js
@@ -10,6 +10,7 @@
var Axes = require('../../plots/cartesian/axes');
var Lib = require('../../lib');
+var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
var orientations = ['v', 'h'];
@@ -51,9 +52,6 @@ function setPositionOffset(traceType, gd, boxList, posAxis) {
var axId = posAxis._id;
var axLetter = axId.charAt(0);
- // N.B. reused in violin
- var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';
-
var i, j, calcTrace;
var pointList = [];
var shownPts = 0;
@@ -76,8 +74,9 @@ function setPositionOffset(traceType, gd, boxList, posAxis) {
// check for forced minimum dtick
Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true);
- var num = fullLayout[numKey];
- var group = (fullLayout[traceType + 'mode'] === 'group' && num > 1);
+ var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';
+ var numTotal = fullLayout[numKey];
+ var group = fullLayout[traceType + 'mode'] === 'group' && numTotal > 1;
var groupFraction = 1 - fullLayout[traceType + 'gap'];
var groupGapFraction = 1 - fullLayout[traceType + 'groupgap'];
@@ -104,9 +103,23 @@ function setPositionOffset(traceType, gd, boxList, posAxis) {
bPos = 0;
} else {
dPos = dPos0;
- bdPos = dPos * groupFraction * groupGapFraction / (group ? num : 1);
- bPos = group ? 2 * dPos * (-0.5 + (t.num + 0.5) / num) * groupFraction : 0;
- wHover = dPos * (group ? groupFraction / num : 1);
+
+ if(group) {
+ var groupId = getAxisGroup(fullLayout, posAxis._id) + trace.orientation;
+ var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
+ var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
+ var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
+ var num = nOffsetGroups || numTotal;
+ var shift = nOffsetGroups ? trace._offsetIndex : t.num;
+
+ bdPos = dPos * groupFraction * groupGapFraction / num;
+ bPos = 2 * dPos * (-0.5 + (shift + 0.5) / num) * groupFraction;
+ wHover = dPos * groupFraction / num;
+ } else {
+ bdPos = dPos * groupFraction * groupGapFraction;
+ bPos = 0;
+ wHover = dPos;
+ }
}
t.dPos = dPos;
t.bPos = bPos;
diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js
index b3d6500e5d4..b8e315cd0fc 100644
--- a/src/traces/box/defaults.js
+++ b/src/traces/box/defaults.js
@@ -11,7 +11,7 @@
var Lib = require('../../lib');
var Registry = require('../../registry');
var Color = require('../../components/color');
-
+var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
var attributes = require('./attributes');
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
@@ -109,8 +109,30 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}
+function crossTraceDefaults(fullData, fullLayout) {
+ var traceIn, traceOut;
+
+ function coerce(attr) {
+ return Lib.coerce(traceOut._input, traceOut, attributes, attr);
+ }
+
+ for(var i = 0; i < fullData.length; i++) {
+ traceOut = fullData[i];
+ var traceType = traceOut.type;
+
+ if(traceType === 'box' || traceType === 'violin') {
+ traceIn = traceOut._input;
+ if(fullLayout[traceType + 'mode'] === 'group') {
+ handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
+ }
+ }
+ }
+}
+
module.exports = {
supplyDefaults: supplyDefaults,
+ crossTraceDefaults: crossTraceDefaults,
+
handleSampleDefaults: handleSampleDefaults,
handlePointsDefaults: handlePointsDefaults
};
diff --git a/src/traces/box/index.js b/src/traces/box/index.js
index e8ffe05cc8a..174a3d6bb36 100644
--- a/src/traces/box/index.js
+++ b/src/traces/box/index.js
@@ -13,6 +13,7 @@ var Box = {};
Box.attributes = require('./attributes');
Box.layoutAttributes = require('./layout_attributes');
Box.supplyDefaults = require('./defaults').supplyDefaults;
+Box.crossTraceDefaults = require('./defaults').crossTraceDefaults;
Box.supplyLayoutDefaults = require('./layout_defaults').supplyLayoutDefaults;
Box.calc = require('./calc');
Box.crossTraceCalc = require('./cross_trace_calc').crossTraceCalc;
diff --git a/src/traces/box/layout_defaults.js b/src/traces/box/layout_defaults.js
index 027ab407ff7..6c23d343a47 100644
--- a/src/traces/box/layout_defaults.js
+++ b/src/traces/box/layout_defaults.js
@@ -13,10 +13,13 @@ var Lib = require('../../lib');
var layoutAttributes = require('./layout_attributes');
function _supply(layoutIn, layoutOut, fullData, coerce, traceType) {
- var hasTraceType;
var category = traceType + 'Layout';
+ var hasTraceType = false;
+
for(var i = 0; i < fullData.length; i++) {
- if(Registry.traceIs(fullData[i], category)) {
+ var trace = fullData[i];
+
+ if(Registry.traceIs(trace, category)) {
hasTraceType = true;
break;
}
diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js
index 5391922a702..4433288d720 100644
--- a/src/traces/histogram/attributes.js
+++ b/src/traces/histogram/attributes.js
@@ -188,6 +188,9 @@ module.exports = {
marker: barAttrs.marker,
+ offsetgroup: barAttrs.offsetgroup,
+ alignmentgroup: barAttrs.alignmentgroup,
+
selected: barAttrs.selected,
unselected: barAttrs.unselected,
diff --git a/src/traces/histogram/cross_trace_defaults.js b/src/traces/histogram/cross_trace_defaults.js
index 4df1814fc71..0a7663cc43d 100644
--- a/src/traces/histogram/cross_trace_defaults.js
+++ b/src/traces/histogram/cross_trace_defaults.js
@@ -6,12 +6,13 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var Lib = require('../../lib');
var nestedProperty = Lib.nestedProperty;
+var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
+var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
var attributes = require('./attributes');
var BINATTRS = {
@@ -65,6 +66,8 @@ module.exports = function crossTraceDefaults(fullData, fullLayout) {
direction: binDirection
};
}
+
+ handleGroupingDefaults(traceOut._input, traceOut, fullLayout, coerce);
}
for(group in allBinOpts) {
@@ -111,13 +114,3 @@ module.exports = function crossTraceDefaults(fullData, fullLayout) {
}
}
};
-
-function getAxisGroup(fullLayout, axId) {
- var matchGroups = fullLayout._axisMatchGroups;
-
- for(var i = 0; i < matchGroups.length; i++) {
- var group = matchGroups[i];
- if(group[axId]) return 'g' + i;
- }
- return axId;
-}
diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js
index 747559f352a..b8e94518e72 100644
--- a/src/traces/histogram/defaults.js
+++ b/src/traces/histogram/defaults.js
@@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/
-
'use strict';
var Registry = require('../../registry');
diff --git a/src/traces/violin/attributes.js b/src/traces/violin/attributes.js
index ba71026aadf..1da220b2ab3 100644
--- a/src/traces/violin/attributes.js
+++ b/src/traces/violin/attributes.js
@@ -238,6 +238,9 @@ module.exports = {
].join(' ')
},
+ offsetgroup: boxAttrs.offsetgroup,
+ alignmentgroup: boxAttrs.alignmentgroup,
+
selected: boxAttrs.selected,
unselected: boxAttrs.unselected,
diff --git a/src/traces/violin/index.js b/src/traces/violin/index.js
index 9714268d519..92531e2cbf8 100644
--- a/src/traces/violin/index.js
+++ b/src/traces/violin/index.js
@@ -12,6 +12,7 @@ module.exports = {
attributes: require('./attributes'),
layoutAttributes: require('./layout_attributes'),
supplyDefaults: require('./defaults'),
+ crossTraceDefaults: require('../box/defaults').crossTraceDefaults,
supplyLayoutDefaults: require('./layout_defaults'),
calc: require('./calc'),
crossTraceCalc: require('./cross_trace_calc'),
diff --git a/test/image/baselines/bar-alignment-offset.png b/test/image/baselines/bar-alignment-offset.png
new file mode 100644
index 00000000000..3c004bb6918
Binary files /dev/null and b/test/image/baselines/bar-alignment-offset.png differ
diff --git a/test/image/baselines/bar-grouping-vs-defaults.png b/test/image/baselines/bar-grouping-vs-defaults.png
new file mode 100644
index 00000000000..6b5bb751dc1
Binary files /dev/null and b/test/image/baselines/bar-grouping-vs-defaults.png differ
diff --git a/test/image/baselines/bar-offsetgroups.png b/test/image/baselines/bar-offsetgroups.png
new file mode 100644
index 00000000000..b5f400efa4a
Binary files /dev/null and b/test/image/baselines/bar-offsetgroups.png differ
diff --git a/test/image/baselines/box-alignment-offset.png b/test/image/baselines/box-alignment-offset.png
new file mode 100644
index 00000000000..49531120be4
Binary files /dev/null and b/test/image/baselines/box-alignment-offset.png differ
diff --git a/test/image/baselines/groups-over-matching-axes.png b/test/image/baselines/groups-over-matching-axes.png
new file mode 100644
index 00000000000..635d61e7fac
Binary files /dev/null and b/test/image/baselines/groups-over-matching-axes.png differ
diff --git a/test/image/baselines/histogram-offsetgroups.png b/test/image/baselines/histogram-offsetgroups.png
new file mode 100644
index 00000000000..e2ecc685531
Binary files /dev/null and b/test/image/baselines/histogram-offsetgroups.png differ
diff --git a/test/image/baselines/violin-offsetgroups.png b/test/image/baselines/violin-offsetgroups.png
new file mode 100644
index 00000000000..b1507b55d78
Binary files /dev/null and b/test/image/baselines/violin-offsetgroups.png differ
diff --git a/test/image/mocks/bar-alignment-offset.json b/test/image/mocks/bar-alignment-offset.json
new file mode 100644
index 00000000000..a163a42fb91
--- /dev/null
+++ b/test/image/mocks/bar-alignment-offset.json
@@ -0,0 +1,547 @@
+{
+ "data": [
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x",
+ "yaxis": "y",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": true,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x",
+ "yaxis": "y",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": true,
+ "hoverinfo": "text",
+ "hovertext": "a2",
+ "xaxis": "x",
+ "yaxis": "y",
+ "alignmentgroup": "a",
+ "offsetgroup": 2,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x3",
+ "yaxis": "y3",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x3",
+ "yaxis": "y3",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "b1",
+ "xaxis": "x3",
+ "yaxis": "y3",
+ "alignmentgroup": "b",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x4",
+ "yaxis": "y5",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x4",
+ "yaxis": "y5",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a2",
+ "xaxis": "x4",
+ "yaxis": "y4",
+ "alignmentgroup": "a",
+ "offsetgroup": 2,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x5",
+ "yaxis": "y7",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x5",
+ "yaxis": "y7",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x5",
+ "yaxis": "y6",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x6",
+ "yaxis": "y9",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x6",
+ "yaxis": "y9",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ },
+ {
+ "type": "bar",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "b1",
+ "xaxis": "x6",
+ "yaxis": "y8",
+ "alignmentgroup": "b",
+ "offsetgroup": 1,
+ "x": [
+ 1
+ ],
+ "y": [
+ 1
+ ]
+ }
+ ],
+ "layout": {
+ "legend": {
+ "x": 1,
+ "xanchor": "right",
+ "y": 1,
+ "yanchor": "bottom",
+ "tracegroupgap": 0
+ },
+ "margin": {
+ "l": 25,
+ "r": 25,
+ "t": 60,
+ "b": 80
+ },
+ "title": {
+ "text": "Each zone refers to the relationship
between the blue bar and the opaque orange bar",
+ "x": 0,
+ "xref": "paper",
+ "y": 0.96,
+ "yref": "cont",
+ "font": {
+ "size": 20
+ }
+ },
+ "shapes": [
+ {
+ "type": "line",
+ "xref": "paper",
+ "yref": "paper",
+ "x0": 0.49,
+ "x1": 0.49,
+ "y0": -0.5,
+ "y1": 1
+ }
+ ],
+ "xaxis": {
+ "domain": [
+ 0,
+ 0.14666666666666667
+ ],
+ "anchor": "y",
+ "title": "=alignment
≠offset"
+ },
+ "yaxis": {
+ "domain": [
+ 0,
+ 1
+ ],
+ "anchor": "x"
+ },
+ "xaxis2": {
+ "domain": [
+ 0.18666666666666665,
+ 0.3133333333333333
+ ],
+ "anchor": "y2",
+ "title": "=alignment
=offset"
+ },
+ "yaxis2": {
+ "domain": [
+ 0,
+ 1
+ ],
+ "anchor": "x2"
+ },
+ "xaxis3": {
+ "domain": [
+ 0.35333333333333333,
+ 0.48
+ ],
+ "anchor": "y3",
+ "title": "≠alignment
=offset"
+ },
+ "yaxis3": {
+ "domain": [
+ 0,
+ 1
+ ],
+ "anchor": "x3"
+ },
+ "xaxis4": {
+ "domain": [
+ 0.52,
+ 0.6466666666666666
+ ],
+ "anchor": "y4",
+ "title": "=alignment
≠offset"
+ },
+ "yaxis4": {
+ "domain": [
+ 0,
+ 0.45
+ ],
+ "anchor": "x4"
+ },
+ "yaxis5": {
+ "domain": [
+ 0.55,
+ 1
+ ],
+ "anchor": "x4"
+ },
+ "xaxis5": {
+ "domain": [
+ 0.6866666666666666,
+ 0.8133333333333332
+ ],
+ "anchor": "y6",
+ "title": "=alignment
=offset"
+ },
+ "yaxis6": {
+ "domain": [
+ 0,
+ 0.45
+ ],
+ "anchor": "x5"
+ },
+ "yaxis7": {
+ "domain": [
+ 0.55,
+ 1
+ ],
+ "anchor": "x5"
+ },
+ "xaxis6": {
+ "domain": [
+ 0.8533333333333333,
+ 1
+ ],
+ "anchor": "y8",
+ "title": "≠alignment
=offset"
+ },
+ "yaxis8": {
+ "domain": [
+ 0,
+ 0.45
+ ],
+ "anchor": "x6"
+ },
+ "yaxis9": {
+ "domain": [
+ 0.55,
+ 1
+ ],
+ "anchor": "x6"
+ }
+ }
+}
diff --git a/test/image/mocks/bar-grouping-vs-defaults.json b/test/image/mocks/bar-grouping-vs-defaults.json
new file mode 100644
index 00000000000..1fe679360ba
--- /dev/null
+++ b/test/image/mocks/bar-grouping-vs-defaults.json
@@ -0,0 +1,61 @@
+{
+ "data": [
+ {
+ "type": "bar",
+ "y": [ 1, 2, 1 ],
+ "yaxis": "y2"
+ },
+ {
+ "type": "bar",
+ "y": [ 2, 1, 2 ]
+ },
+ {
+ "type": "bar",
+ "y": [ 1, 3, 0 ]
+ },
+ {
+ "type": "bar",
+ "y": [ 1, 2, 1 ],
+ "alignmentgroup": "top",
+ "hovertext": "alignmentgroup: top",
+ "xaxis": "x2",
+ "yaxis": "y2"
+ },
+ {
+ "type": "bar",
+ "y": [ 2, 1, 2 ],
+ "hovertext": "alignmentgroup: top
offsetgroup: 1",
+ "alignmentgroup": "bottom",
+ "offsetgroup": "1",
+ "xaxis": "x2"
+ },
+ {
+ "type": "bar",
+ "y": [ 1, 3, 0 ],
+ "hovertext": "alignmentgroup: top
offsetgroup: 2",
+ "alignmentgroup": "bottom",
+ "offsetgroup": "2",
+ "xaxis": "x2"
+ }
+ ],
+ "layout": {
+ "showlegend": false,
+ "grid": {
+ "rows": 2,
+ "columns": 2,
+ "roworder": "bottom to top"
+ },
+ "colorway": [ "blue", "orange", "green" ],
+ "margin": { "t": 20 },
+ "xaxis": {
+ "title": {
+ "text": "no alignmentgroup
no offsetgroup"
+ }
+ },
+ "xaxis2": {
+ "title": {
+ "text": "with alignmentgroup
with offsetgroup"
+ }
+ }
+ }
+}
diff --git a/test/image/mocks/bar-offsetgroups.json b/test/image/mocks/bar-offsetgroups.json
new file mode 100644
index 00000000000..c6281133827
--- /dev/null
+++ b/test/image/mocks/bar-offsetgroups.json
@@ -0,0 +1,88 @@
+{
+ "data": [
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "yaxis": "y2",
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "yaxis": "y2",
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1",
+ "xaxis": "x2"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2",
+ "xaxis": "x2"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "yaxis": "y2",
+ "offsetgroup": 3,
+ "hovertext": "offsetgroup: 3",
+ "xaxis": "x2"
+ },
+ {
+ "type": "bar",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "yaxis": "y2",
+ "offsetgroup": 4,
+ "hovertext": "offsetgroup: 4",
+ "xaxis": "x2"
+ }
+ ],
+ "layout": {
+ "showlegend": false,
+ "grid": {
+ "rows": 2,
+ "columns": 2
+ },
+ "title": {
+ "text": "Bar offset groups"
+ },
+ "xaxis": {
+ "title": {
+ "text": "two distinct offset groups"
+ }
+ },
+ "xaxis2": {
+ "title": {
+ "text": "four distinct offset groups"
+ }
+ }
+ }
+}
diff --git a/test/image/mocks/box-alignment-offset.json b/test/image/mocks/box-alignment-offset.json
new file mode 100644
index 00000000000..24fbf2cf5b0
--- /dev/null
+++ b/test/image/mocks/box-alignment-offset.json
@@ -0,0 +1,944 @@
+{
+ "data": [
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x",
+ "yaxis": "y",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": true,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x",
+ "yaxis": "y",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": true,
+ "hoverinfo": "text",
+ "hovertext": "a2",
+ "xaxis": "x",
+ "yaxis": "y",
+ "alignmentgroup": "a",
+ "offsetgroup": 2,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x3",
+ "yaxis": "y3",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x3",
+ "yaxis": "y3",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "b1",
+ "xaxis": "x3",
+ "yaxis": "y3",
+ "alignmentgroup": "b",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x4",
+ "yaxis": "y5",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x4",
+ "yaxis": "y5",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a2",
+ "xaxis": "x4",
+ "yaxis": "y4",
+ "alignmentgroup": "a",
+ "offsetgroup": 2,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x5",
+ "yaxis": "y7",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x5",
+ "yaxis": "y7",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x5",
+ "yaxis": "y6",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 0.6,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a0",
+ "xaxis": "x6",
+ "yaxis": "y9",
+ "alignmentgroup": "a",
+ "offsetgroup": 0,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "orange"
+ },
+ "opacity": 1,
+ "name": "B",
+ "legendgroup": "B",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "a1",
+ "xaxis": "x6",
+ "yaxis": "y9",
+ "alignmentgroup": "a",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ {
+ "type": "box",
+ "marker": {
+ "color": "blue"
+ },
+ "opacity": 0.7,
+ "name": "A",
+ "legendgroup": "A",
+ "showlegend": false,
+ "hoverinfo": "text",
+ "hovertext": "b1",
+ "xaxis": "x6",
+ "yaxis": "y8",
+ "alignmentgroup": "b",
+ "offsetgroup": 1,
+ "y": [
+ 0.2,
+ 0.2,
+ 0.6,
+ 1,
+ 0.5,
+ 0.4,
+ 0.2,
+ 0.7,
+ 0.9,
+ 0.1,
+ 0.5,
+ 0.3
+ ],
+ "x": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ ],
+ "layout": {
+ "legend": {
+ "x": 1,
+ "xanchor": "right",
+ "y": 1,
+ "yanchor": "bottom",
+ "tracegroupgap": 0
+ },
+ "margin": {
+ "l": 25,
+ "r": 25,
+ "t": 60,
+ "b": 80
+ },
+ "title": {
+ "text": "Each zone refers to the relationship
between the blue box and the opaque orange box",
+ "x": 0,
+ "xref": "paper",
+ "y": 0.96,
+ "yref": "cont",
+ "font": {
+ "size": 20
+ }
+ },
+ "shapes": [
+ {
+ "type": "line",
+ "xref": "paper",
+ "yref": "paper",
+ "x0": 0.49,
+ "x1": 0.49,
+ "y0": -0.5,
+ "y1": 1
+ }
+ ],
+ "xaxis": {
+ "domain": [
+ 0,
+ 0.14666666666666667
+ ],
+ "anchor": "y",
+ "title": "=alignment
≠offset"
+ },
+ "yaxis": {
+ "domain": [
+ 0,
+ 1
+ ],
+ "anchor": "x"
+ },
+ "xaxis2": {
+ "domain": [
+ 0.18666666666666665,
+ 0.3133333333333333
+ ],
+ "anchor": "y2",
+ "title": "=alignment
=offset"
+ },
+ "yaxis2": {
+ "domain": [
+ 0,
+ 1
+ ],
+ "anchor": "x2"
+ },
+ "xaxis3": {
+ "domain": [
+ 0.35333333333333333,
+ 0.48
+ ],
+ "anchor": "y3",
+ "title": "≠alignment
=offset"
+ },
+ "yaxis3": {
+ "domain": [
+ 0,
+ 1
+ ],
+ "anchor": "x3"
+ },
+ "xaxis4": {
+ "domain": [
+ 0.52,
+ 0.6466666666666666
+ ],
+ "anchor": "y4",
+ "title": "=alignment
≠offset"
+ },
+ "yaxis4": {
+ "domain": [
+ 0,
+ 0.45
+ ],
+ "anchor": "x4"
+ },
+ "yaxis5": {
+ "domain": [
+ 0.55,
+ 1
+ ],
+ "anchor": "x4"
+ },
+ "xaxis5": {
+ "domain": [
+ 0.6866666666666666,
+ 0.8133333333333332
+ ],
+ "anchor": "y6",
+ "title": "=alignment
=offset"
+ },
+ "yaxis6": {
+ "domain": [
+ 0,
+ 0.45
+ ],
+ "anchor": "x5"
+ },
+ "yaxis7": {
+ "domain": [
+ 0.55,
+ 1
+ ],
+ "anchor": "x5"
+ },
+ "xaxis6": {
+ "domain": [
+ 0.8533333333333333,
+ 1
+ ],
+ "anchor": "y8",
+ "title": "≠alignment
=offset"
+ },
+ "yaxis8": {
+ "domain": [
+ 0,
+ 0.45
+ ],
+ "anchor": "x6"
+ },
+ "yaxis9": {
+ "domain": [
+ 0.55,
+ 1
+ ],
+ "anchor": "x6"
+ },
+ "boxmode": "group"
+ }
+}
diff --git a/test/image/mocks/groups-over-matching-axes.json b/test/image/mocks/groups-over-matching-axes.json
new file mode 100644
index 00000000000..7a31a6ac370
--- /dev/null
+++ b/test/image/mocks/groups-over-matching-axes.json
@@ -0,0 +1,111 @@
+{
+ "data": [
+ {
+ "type": "violin",
+ "x": ["A", "B"],
+ "y": [1, 2],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1"
+ },
+ {
+ "type": "violin",
+ "x": ["A", "B"],
+ "y": [2, 3],
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2"
+ },
+ {
+ "type": "box",
+ "x": ["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"],
+ "y": [1, 2, 2, 2, 3, 1, 2, 2, 2, 3],
+ "yaxis": "y2",
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1"
+ },
+ {
+ "type": "box",
+ "x": ["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"],
+ "y": [1, 2, 2, 2, 3, 1, 2, 2, 2, 3],
+ "yaxis": "y2",
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2"
+ },
+ {
+ "type": "violin",
+ "x": ["A", "B"],
+ "y": [1, 2],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1",
+ "xaxis": "x2"
+ },
+ {
+ "type": "violin",
+ "x": ["A", "B"],
+ "y": [2, 3],
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2",
+ "xaxis": "x2"
+ },
+ {
+ "type": "box",
+ "x": ["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"],
+ "y": [1, 2, 2, 2, 3, 1, 2, 2, 2, 3],
+ "yaxis": "y2",
+ "offsetgroup": 3,
+ "hovertext": "offsetgroup: 3",
+ "xaxis": "x2"
+ },
+ {
+ "type": "box",
+ "x": ["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"],
+ "y": [1, 2, 2, 2, 3, 1, 2, 2, 2, 3],
+ "yaxis": "y2",
+ "offsetgroup": 4,
+ "hovertext": "offsetgroup: 4",
+ "xaxis": "x2"
+ },
+ {
+ "type": "bar",
+ "name": "some bar",
+ "marker": {"color": "black"},
+ "x": ["B"],
+ "y": [2],
+ "offsetgroup": 3,
+ "hovertext": "offsetgroup: 3",
+ "xaxis": "x2"
+ },
+ {
+ "type": "bar",
+ "name": "some other bar",
+ "marker": {"color": "black"},
+ "x": ["A"],
+ "y": [0.5],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1",
+ "yaxis": "y2"
+ }
+ ],
+ "layout": {
+ "boxmode": "group",
+ "violinmode": "group",
+ "showlegend": false,
+ "grid": {
+ "rows": 2,
+ "columns": 2
+ },
+ "title": {
+ "text": "4 offsetgroups in total across matching x-axes"
+ },
+ "xaxis": {
+ "title": {
+ "text": "offsetgroup 1-2-1-2"
+ }
+ },
+ "xaxis2": {
+ "title": {
+ "text": "offsetgroup 1-2-3-4"
+ },
+ "matches": "x"
+ }
+ }
+}
diff --git a/test/image/mocks/histogram-offsetgroups.json b/test/image/mocks/histogram-offsetgroups.json
new file mode 100644
index 00000000000..42cb0d4f567
--- /dev/null
+++ b/test/image/mocks/histogram-offsetgroups.json
@@ -0,0 +1,80 @@
+{
+ "data": [
+ {
+ "type": "histogram",
+ "y": [ 1, 2, 3, 4 ],
+ "offsetgroup": 1,
+ "text": "offsetgroup: 1"
+ },
+ {
+ "type": "histogram",
+ "y": [ 2, 3, 1, 5 ],
+ "offsetgroup": 2,
+ "text": "offsetgroup: 2"
+ },
+ {
+ "type": "histogram",
+ "y": [ 1, 2, 3, 4 ],
+ "yaxis": "y2",
+ "offsetgroup": 1,
+ "text": "offsetgroup: 1"
+ },
+ {
+ "type": "histogram",
+ "y": [ 2, 3, 1, 5 ],
+ "yaxis": "y2",
+ "offsetgroup": 2,
+ "text": "offsetgroup: 2"
+ },
+ {
+ "type": "histogram",
+ "y": [ 1, 2, 3, 4 ],
+ "offsetgroup": 1,
+ "xaxis": "x2",
+ "text": "offsetgroup: 1"
+ },
+ {
+ "type": "histogram",
+ "y": [ 2, 3, 1, 5 ],
+ "offsetgroup": 2,
+ "xaxis": "x2",
+ "text": "offsetgroup: 2"
+ },
+ {
+ "type": "histogram",
+ "y": [ 1, 2, 3, 4 ],
+ "yaxis": "y2",
+ "offsetgroup": 3,
+ "xaxis": "x2",
+ "text": "offsetgroup: 3"
+ },
+ {
+ "type": "histogram",
+ "y": [ 2, 3, 1, 5 ],
+ "yaxis": "y2",
+ "offsetgroup": 4,
+ "xaxis": "x2",
+ "text": "offsetgroup: 4"
+ }
+ ],
+ "layout": {
+ "showlegend": false,
+ "grid": {
+ "rows": 2,
+ "columns": 2
+ },
+ "title": {
+ "text": "Histogram offset groups"
+ },
+ "yaxis": {
+ "title": {
+ "text": "two distinct
offset groups"
+ }
+ },
+ "yaxis2": {
+ "title": {
+ "text": "four distinct
offset groups"
+ }
+ }
+ }
+}
diff --git a/test/image/mocks/violin-offsetgroups.json b/test/image/mocks/violin-offsetgroups.json
new file mode 100644
index 00000000000..4aaac563003
--- /dev/null
+++ b/test/image/mocks/violin-offsetgroups.json
@@ -0,0 +1,89 @@
+{
+ "data": [
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "yaxis": "y2",
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "yaxis": "y2",
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "offsetgroup": 1,
+ "hovertext": "offsetgroup: 1",
+ "xaxis": "x2"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "offsetgroup": 2,
+ "hovertext": "offsetgroup: 2",
+ "xaxis": "x2"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 1, 2, 3, 4 ],
+ "yaxis": "y2",
+ "offsetgroup": 3,
+ "hovertext": "offsetgroup: 3",
+ "xaxis": "x2"
+ },
+ {
+ "type": "violin",
+ "x": [ "A", "B", "C", "D" ],
+ "y": [ 2, 3, 1, 5 ],
+ "yaxis": "y2",
+ "offsetgroup": 4,
+ "hovertext": "offsetgroup: 4",
+ "xaxis": "x2"
+ }
+ ],
+ "layout": {
+ "violinmode": "group",
+ "showlegend": false,
+ "grid": {
+ "rows": 2,
+ "columns": 2
+ },
+ "title": {
+ "text": "Violin offset groups"
+ },
+ "xaxis": {
+ "title": {
+ "text": "two distinct offset groups"
+ }
+ },
+ "xaxis2": {
+ "title": {
+ "text": "four distinct offset groups"
+ }
+ }
+ }
+}
diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js
index e79ea3627a5..5a0934734be 100644
--- a/test/jasmine/tests/bar_test.js
+++ b/test/jasmine/tests/bar_test.js
@@ -210,6 +210,22 @@ describe('Bar.supplyDefaults', function() {
expect(traceOut.xcalendar).toBe('coptic');
expect(traceOut.ycalendar).toBe('ethiopian');
});
+
+ it('should not include alignementgroup/offsetgroup when barmode is not *group*', function() {
+ var gd = {
+ data: [{type: 'bar', y: [1], alignmentgroup: 'a', offsetgroup: '1'}],
+ layout: {barmode: 'group'}
+ };
+
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].alignmentgroup).toBe('a', 'alignementgroup');
+ expect(gd._fullData[0].offsetgroup).toBe('1', 'offsetgroup');
+
+ gd.layout.barmode = 'stack';
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].alignmentgroup).toBe(undefined, 'alignementgroup');
+ expect(gd._fullData[0].offsetgroup).toBe(undefined, 'offsetgroup');
+ });
});
describe('bar calc / crossTraceCalc (formerly known as setPositions)', function() {
diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js
index 694735de757..cd655fc47d7 100644
--- a/test/jasmine/tests/box_test.js
+++ b/test/jasmine/tests/box_test.js
@@ -8,6 +8,7 @@ var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
var mouseEvent = require('../assets/mouse_event');
+var supplyAllDefaults = require('../assets/supply_defaults');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
@@ -150,6 +151,22 @@ describe('Test boxes supplyDefaults', function() {
expect(traceOut.marker).toBeDefined();
expect(traceOut.text).toBeDefined();
});
+
+ it('should not include alignementgroup/offsetgroup when boxmode is not *group*', function() {
+ var gd = {
+ data: [{type: 'box', y: [1], alignmentgroup: 'a', offsetgroup: '1'}],
+ layout: {boxmode: 'group'}
+ };
+
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].alignmentgroup).toBe('a', 'alignementgroup');
+ expect(gd._fullData[0].offsetgroup).toBe('1', 'offsetgroup');
+
+ gd.layout.boxmode = 'overlay';
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].alignmentgroup).toBe(undefined, 'alignementgroup');
+ expect(gd._fullData[0].offsetgroup).toBe(undefined, 'offsetgroup');
+ });
});
describe('Test box hover:', function() {
diff --git a/test/jasmine/tests/violin_test.js b/test/jasmine/tests/violin_test.js
index 9cca890e9c7..930e9c1dbbc 100644
--- a/test/jasmine/tests/violin_test.js
+++ b/test/jasmine/tests/violin_test.js
@@ -159,6 +159,22 @@ describe('Test violin defaults', function() {
expect(traceOut.scalemode).toBe('width');
expect(traceOut.scalegroup).toBe('');
});
+
+ it('should not include alignementgroup/offsetgroup when violinmode is not *group*', function() {
+ var gd = {
+ data: [{type: 'violin', y: [1], alignmentgroup: 'a', offsetgroup: '1'}],
+ layout: {violinmode: 'group'}
+ };
+
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].alignmentgroup).toBe('a', 'alignementgroup');
+ expect(gd._fullData[0].offsetgroup).toBe('1', 'offsetgroup');
+
+ gd.layout.violinmode = 'overlay';
+ supplyAllDefaults(gd);
+ expect(gd._fullData[0].alignmentgroup).toBe(undefined, 'alignementgroup');
+ expect(gd._fullData[0].offsetgroup).toBe(undefined, 'offsetgroup');
+ });
});
describe('Test violin calc:', function() {