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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement alignmentgroup and offsetgroup #3529

Merged
merged 5 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
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
22 changes: 22 additions & 0 deletions src/traces/bar/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,28 @@ module.exports = {

marker: marker,

offsetgroup: {
valType: 'string',
role: 'info',
dflt: '',
editType: 'calc',
description: [
'Set several traces linked to the same position axis 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 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
36 changes: 26 additions & 10 deletions src/traces/bar/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,26 +279,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 = 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
52 changes: 50 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 @@ -17,7 +16,7 @@ var handleXYDefaults = require('../scatter/xy_defaults');
var handleStyleDefaults = require('../bar/style_defaults');
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 @@ -77,5 +76,54 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});

handleGroupingDefaults(traceIn, traceOut, layout, coerce);

Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}

function handleGroupingDefaults(traceIn, traceOut, layout, coerce) {
var orientation = traceOut.orientation;
// TODO make this work across matching axes too?!?
// TODO should this work per trace-type?
// one set for bar/histogram another for box/violin?
// or just one set for all trace trace types?
Copy link
Contributor Author

@etpinard etpinard Feb 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anyone here have a strong opinion about this?

Should alignmentgroup and offsetgroup work across trace types? Or should we match bar and histogram and box and violin groupings separately?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like making it work across trace types would be the most flexible - you can always just give different values to different trace types, which I guess is what the default would be. It's also probably what I'd expect as a user. It's an awfully weird use case, so not worth a huge effort, but if we omit it now and wanted to add it later it would be a breaking change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bars above for counts (say) and boxes below for distributions? Seems not that weird to me :) kinda useful actually!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah good point. That would magically work if you have the same traces in each case, and in the same order... but break those assumptions (which could happen for good reasons) and you're back to manual alignment if we don't implement this cross-type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e746b08

var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis'];
var groupId = posAxId + orientation;

var alignmentOpts = layout._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;
}
}

module.exports = {
supplyDefaults: supplyDefaults,
handleGroupingDefaults: handleGroupingDefaults
};
3 changes: 1 addition & 2 deletions src/traces/bar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
* 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.supplyLayoutDefaults = require('./layout_defaults');
Bar.calc = require('./calc');
Bar.crossTraceCalc = require('./cross_trace_calc').crossTraceCalc;
Expand Down
21 changes: 17 additions & 4 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,8 +14,9 @@ var Lib = require('../../lib');

var layoutAttributes = require('./layout_attributes');


module.exports = function(layoutIn, layoutOut, fullData) {
var i, trace;

function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
Expand All @@ -25,9 +25,10 @@ module.exports = function(layoutIn, layoutOut, fullData) {
var shouldBeGapless = false;
var gappedAnyway = false;
var usedSubplots = {};
var tracesWithGroupAttrs = [];

for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
for(i = 0; i < fullData.length; i++) {
trace = fullData[i];
if(Registry.traceIs(trace, 'bar') && trace.visible) hasBars = true;
else continue;

Expand All @@ -44,6 +45,10 @@ module.exports = function(layoutIn, layoutOut, fullData) {
trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']);
if(pa.type !== 'category') shouldBeGapless = true;
}

if(trace.alignmentgroup || trace.offsetgroup) {
tracesWithGroupAttrs.push(trace);
}
}

if(!hasBars) return;
Expand All @@ -53,4 +58,12 @@ module.exports = function(layoutIn, layoutOut, fullData) {

coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2);
coerce('bargroupgap');

if(mode !== 'group') {
for(i = 0; i < tracesWithGroupAttrs.length; i++) {
trace = tracesWithGroupAttrs[i];
delete trace.alignmentgroup;
delete trace.offsetgroup;
}
}
};
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
28 changes: 20 additions & 8 deletions src/traces/box/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,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 +73,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 +102,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 = 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
3 changes: 2 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 All @@ -34,6 +34,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
if(notched) coerce('notchwidth');

handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
handleGroupingDefaults(traceIn, traceOut, layout, coerce);
}

function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
Expand Down
27 changes: 22 additions & 5 deletions src/traces/box/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,36 @@ var Lib = require('../../lib');
var layoutAttributes = require('./layout_attributes');

function _supply(layoutIn, layoutOut, fullData, coerce, traceType) {
var hasTraceType;
var i, trace;

var category = traceType + 'Layout';
for(var i = 0; i < fullData.length; i++) {
if(Registry.traceIs(fullData[i], category)) {
var hasTraceType = false;
var tracesWithGroupAttrs = [];

for(i = 0; i < fullData.length; i++) {
trace = fullData[i];

if(Registry.traceIs(trace, category)) {
hasTraceType = true;
break;

if(trace.alignmentgroup || trace.offsetgroup) {
tracesWithGroupAttrs.push(trace);
}
}
}
if(!hasTraceType) return;

coerce(traceType + 'mode');
var mode = coerce(traceType + 'mode');
coerce(traceType + 'gap');
coerce(traceType + 'groupgap');

if(mode !== 'group') {
for(i = 0; i < tracesWithGroupAttrs.length; i++) {
trace = tracesWithGroupAttrs[i];
delete trace.alignmentgroup;
delete trace.offsetgroup;
}
}
}

function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
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
4 changes: 3 additions & 1 deletion src/traces/histogram/defaults.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 Registry = require('../../registry');
var Lib = require('../../lib');
var Color = require('../../components/color');

var handleStyleDefaults = require('../bar/style_defaults');
var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
var attributes = require('./attributes');

module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
Expand Down Expand Up @@ -70,4 +70,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});

handleGroupingDefaults(traceIn, traceOut, layout, coerce);
};
Loading