Skip to content

Commit

Permalink
Merge pull request #3529 from plotly/offset-alignment-groups
Browse files Browse the repository at this point in the history
Implement alignmentgroup and offsetgroup
  • Loading branch information
etpinard authored Feb 19, 2019
2 parents 25fa0c2 + e746b08 commit 399d03b
Show file tree
Hide file tree
Showing 34 changed files with 2,165 additions and 39 deletions.
10 changes: 10 additions & 0 deletions src/plots/cartesian/axis_ids.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
2 changes: 2 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/traces/bar/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 27 additions & 10 deletions src/traces/bar/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

/*
Expand Down Expand Up @@ -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;
Expand Down
68 changes: 66 additions & 2 deletions src/traces/bar/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var Lib = require('../../lib');
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
};
4 changes: 2 additions & 2 deletions src/traces/bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 0 additions & 2 deletions src/traces/bar/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var Registry = require('../../registry');
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/traces/box/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -250,6 +251,9 @@ module.exports = {
},
fillcolor: scatterAttrs.fillcolor,

offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup,

selected: {
marker: scatterAttrs.selected.marker,
editType: 'style'
Expand Down
29 changes: 21 additions & 8 deletions src/traces/box/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down Expand Up @@ -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;
Expand All @@ -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'];

Expand All @@ -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;
Expand Down
24 changes: 23 additions & 1 deletion src/traces/box/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
};
1 change: 1 addition & 0 deletions src/traces/box/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/traces/box/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions src/traces/histogram/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ module.exports = {

marker: barAttrs.marker,

offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup,

selected: barAttrs.selected,
unselected: barAttrs.unselected,

Expand Down
Loading

0 comments on commit 399d03b

Please sign in to comment.