Skip to content

Commit 9014a11

Browse files
authored
Merge pull request #6381 from plotly/group-scattermode
introduce group scatter attributes
2 parents 984a691 + e98f3c6 commit 9014a11

26 files changed

+381
-37
lines changed

draftlogs/6381_add.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Introduce group attributes for `scatter` trace i.e. `alignmentgroup`, `offsetgroup`, `scattermode` and `scattergap` [[#6381](https://github.com/plotly/plotly.js/pull/6381)],
2+
this feature was anonymously sponsored: thank you to our sponsor!

src/plots/plots.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var BADNUM = require('../constants/numerical').BADNUM;
1414

1515
var axisIDs = require('./cartesian/axis_ids');
1616
var clearOutline = require('../components/shapes/handle_outline').clearOutline;
17+
var scatterAttrs = require('../traces/scatter/layout_attributes');
1718

1819
var animationAttrs = require('./animation_attributes');
1920
var frameAttrs = require('./frame_attributes');
@@ -1566,6 +1567,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
15661567
'fx',
15671568
'supplyLayoutGlobalDefaults'
15681569
)(layoutIn, layoutOut, coerce);
1570+
1571+
Lib.coerce(layoutIn, layoutOut, scatterAttrs, 'scattermode');
15691572
};
15701573

15711574
function getComputedSize(attr) {

src/traces/bar/attributes.js

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -196,27 +196,8 @@ module.exports = {
196196

197197
marker: marker,
198198

199-
offsetgroup: {
200-
valType: 'string',
201-
dflt: '',
202-
editType: 'calc',
203-
description: [
204-
'Set several traces linked to the same position axis',
205-
'or matching axes to the same',
206-
'offsetgroup where bars of the same position coordinate will line up.'
207-
].join(' ')
208-
},
209-
alignmentgroup: {
210-
valType: 'string',
211-
dflt: '',
212-
editType: 'calc',
213-
description: [
214-
'Set several traces linked to the same position axis',
215-
'or matching axes to the same',
216-
'alignmentgroup. This controls whether bars compute their positional',
217-
'range dependently or independently.'
218-
].join(' ')
219-
},
199+
offsetgroup: scatterAttrs.offsetgroup,
200+
alignmentgroup: scatterAttrs.alignmentgroup,
220201

221202
selected: {
222203
marker: {

src/traces/bar/cross_trace_calc.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,14 @@ function setBarCenterAndWidth(pa, sieve) {
441441

442442
// store the actual bar width and position, for use by hover
443443
var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth;
444-
calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2;
444+
445+
if(calcBar.p === undefined) {
446+
calcBar.p = calcBar[pLetter];
447+
calcBar['orig_' + pLetter] = calcBar[pLetter];
448+
}
449+
450+
var delta = (poffsetIsArray ? poffset[j] : poffset) + width / 2;
451+
calcBar[pLetter] = calcBar.p + delta;
445452
}
446453
}
447454
}
@@ -498,13 +505,17 @@ function setBaseAndTop(sa, sieve) {
498505
for(var i = 0; i < calcTraces.length; i++) {
499506
var calcTrace = calcTraces[i];
500507
var fullTrace = calcTrace[0].trace;
508+
var isScatter = fullTrace.type === 'scatter';
509+
var isVertical = fullTrace.orientation === 'v';
501510
var pts = [];
502511
var tozero = false;
503512

504513
for(var j = 0; j < calcTrace.length; j++) {
505514
var bar = calcTrace[j];
506-
var base = bar.b;
507-
var top = base + bar.s;
515+
var base = isScatter ? 0 : bar.b;
516+
var top = isScatter ? (
517+
isVertical ? bar.y : bar.x
518+
) : base + bar.s;
508519

509520
bar[sLetter] = top;
510521
pts.push(top);

src/traces/bar/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var Registry = require('../../registry');
77
var handleXYDefaults = require('../scatter/xy_defaults');
88
var handlePeriodDefaults = require('../scatter/period_defaults');
99
var handleStyleDefaults = require('./style_defaults');
10-
var handleGroupingDefaults = require('./grouping_defaults');
10+
var handleGroupingDefaults = require('../scatter/grouping_defaults');
1111
var attributes = require('./attributes');
1212

1313
var coerceFont = Lib.coerceFont;

src/traces/bar/sieve.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module.exports = Sieve;
44

55
var distinctVals = require('../../lib').distinctVals;
6-
var BADNUM = require('../../constants/numerical').BADNUM;
76

87
/**
98
* Helper class to sieve data from traces into bins
@@ -27,12 +26,18 @@ function Sieve(traces, opts) {
2726
// for single-bin histograms - see histogram/calc
2827
var width1 = Infinity;
2928

29+
var axLetter = opts.posAxis._id.charAt(0);
30+
3031
var positions = [];
3132
for(var i = 0; i < traces.length; i++) {
3233
var trace = traces[i];
3334
for(var j = 0; j < trace.length; j++) {
3435
var bar = trace[j];
35-
if(bar.p !== BADNUM) positions.push(bar.p);
36+
var pos = bar.p;
37+
if(pos === undefined) {
38+
pos = bar[axLetter];
39+
}
40+
if(pos !== undefined) positions.push(pos);
3641
}
3742
if(trace[0] && trace[0].width1) {
3843
width1 = Math.min(trace[0].width1, width1);

src/traces/box/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var Lib = require('../../lib');
44
var Registry = require('../../registry');
55
var Color = require('../../components/color');
66
var handlePeriodDefaults = require('../scatter/period_defaults');
7-
var handleGroupingDefaults = require('../bar/grouping_defaults');
7+
var handleGroupingDefaults = require('../scatter/grouping_defaults');
88
var autoType = require('../../plots/cartesian/axis_autotype');
99
var attributes = require('./attributes');
1010

src/traces/funnel/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
var Lib = require('../../lib');
44

5-
var handleGroupingDefaults = require('../bar/grouping_defaults');
5+
var handleGroupingDefaults = require('../scatter/grouping_defaults');
66
var handleText = require('../bar/defaults').handleText;
77
var handleXYDefaults = require('../scatter/xy_defaults');
88
var handlePeriodDefaults = require('../scatter/period_defaults');

src/traces/histogram/cross_trace_defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var Lib = require('../../lib');
44
var axisIds = require('../../plots/cartesian/axis_ids');
55

66
var traceIs = require('../../registry').traceIs;
7-
var handleGroupingDefaults = require('../bar/grouping_defaults');
7+
var handleGroupingDefaults = require('../scatter/grouping_defaults');
88

99
var nestedProperty = Lib.nestedProperty;
1010
var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;

src/traces/scatter/attributes.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,29 @@ module.exports = {
123123
xhoverformat: axisHoverFormat('x'),
124124
yhoverformat: axisHoverFormat('y'),
125125

126+
offsetgroup: {
127+
valType: 'string',
128+
dflt: '',
129+
editType: 'calc',
130+
description: [
131+
'Set several traces linked to the same position axis',
132+
'or matching axes to the same',
133+
'offsetgroup where bars of the same position coordinate will line up.'
134+
].join(' ')
135+
},
136+
137+
alignmentgroup: {
138+
valType: 'string',
139+
dflt: '',
140+
editType: 'calc',
141+
description: [
142+
'Set several traces linked to the same position axis',
143+
'or matching axes to the same',
144+
'alignmentgroup. This controls whether bars compute their positional',
145+
'range dependently or independently.'
146+
].join(' ')
147+
},
148+
126149
stackgroup: {
127150
valType: 'string',
128151
dflt: '',
@@ -146,7 +169,9 @@ module.exports = {
146169
values: ['v', 'h'],
147170
editType: 'calc',
148171
description: [
149-
'Only relevant when `stackgroup` is used, and only the first',
172+
'Only relevant in the following cases:',
173+
'1. when `scattermode` is set to *group*.',
174+
'2. when `stackgroup` is used, and only the first',
150175
'`orientation` found in the `stackgroup` will be used - including',
151176
'if `visible` is *legendonly* but not if it is `false`. Sets the',
152177
'stacking direction. With *v* (*h*), the y (x) values of subsequent',

src/traces/scatter/cross_trace_calc.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,53 @@
11
'use strict';
22

33
var calc = require('./calc');
4+
var setGroupPositions = require('../bar/cross_trace_calc').setGroupPositions;
5+
6+
function groupCrossTraceCalc(gd, plotinfo) {
7+
var xa = plotinfo.xaxis;
8+
var ya = plotinfo.yaxis;
9+
10+
var fullLayout = gd._fullLayout;
11+
var fullTraces = gd._fullData;
12+
var calcTraces = gd.calcdata;
13+
var calcTracesHorz = [];
14+
var calcTracesVert = [];
15+
16+
for(var i = 0; i < fullTraces.length; i++) {
17+
var fullTrace = fullTraces[i];
18+
if(
19+
fullTrace.visible === true &&
20+
fullTrace.type === 'scatter' &&
21+
fullTrace.xaxis === xa._id &&
22+
fullTrace.yaxis === ya._id
23+
) {
24+
if(fullTrace.orientation === 'h') {
25+
calcTracesHorz.push(calcTraces[i]);
26+
} else if(fullTrace.orientation === 'v') { // check for v since certain scatter traces may not have an orientation
27+
calcTracesVert.push(calcTraces[i]);
28+
}
29+
}
30+
}
31+
32+
var opts = {
33+
mode: fullLayout.scattermode,
34+
gap: fullLayout.scattergap
35+
};
36+
37+
setGroupPositions(gd, xa, ya, calcTracesVert, opts);
38+
setGroupPositions(gd, ya, xa, calcTracesHorz, opts);
39+
}
440

541
/*
642
* Scatter stacking & normalization calculations
743
* runs per subplot, and can handle multiple stacking groups
844
*/
945

1046
module.exports = function crossTraceCalc(gd, plotinfo) {
47+
if(gd._fullLayout.scattermode === 'group') {
48+
groupCrossTraceCalc(gd, plotinfo);
49+
}
50+
1151
var xa = plotinfo.xaxis;
1252
var ya = plotinfo.yaxis;
1353
var subplot = xa._id + ya._id;

src/traces/scatter/cross_trace_defaults.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
'use strict';
22

3+
var Lib = require('../../lib');
4+
var handleGroupingDefaults = require('./grouping_defaults');
5+
var attributes = require('./attributes');
36

47
// remove opacity for any trace that has a fill or is filled to
5-
module.exports = function crossTraceDefaults(fullData) {
6-
for(var i = 0; i < fullData.length; i++) {
8+
module.exports = function crossTraceDefaults(fullData, fullLayout) {
9+
var traceIn, traceOut, i;
10+
11+
function coerce(attr) {
12+
return Lib.coerce(traceOut._input, traceOut, attributes, attr);
13+
}
14+
15+
if(fullLayout.scattermode === 'group') {
16+
for(i = 0; i < fullData.length; i++) {
17+
traceOut = fullData[i];
18+
19+
if(traceOut.type === 'scatter') {
20+
traceIn = traceOut._input;
21+
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
22+
}
23+
}
24+
}
25+
26+
for(i = 0; i < fullData.length; i++) {
727
var tracei = fullData[i];
828
if(tracei.type !== 'scatter') continue;
929

src/traces/scatter/defaults.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3131
coerce('yhoverformat');
3232

3333
var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce);
34+
if(
35+
layout.scattermode === 'group' &&
36+
traceOut.orientation === undefined
37+
) {
38+
coerce('orientation', 'v');
39+
}
3440

3541
var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ?
3642
'lines+markers' : 'lines';

src/traces/scatter/format_labels.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ module.exports = function formatLabels(cdi, trace, fullLayout) {
99
var xa = Axes.getFromTrace(mockGd, trace, 'x');
1010
var ya = Axes.getFromTrace(mockGd, trace, 'y');
1111

12-
labels.xLabel = Axes.tickText(xa, xa.c2l(cdi.x), true).text;
13-
labels.yLabel = Axes.tickText(ya, ya.c2l(cdi.y), true).text;
12+
var x = cdi.orig_x;
13+
if(x === undefined) x = cdi.x;
14+
15+
var y = cdi.orig_y;
16+
if(y === undefined) y = cdi.y;
17+
18+
labels.xLabel = Axes.tickText(xa, xa.c2l(x), true).text;
19+
labels.yLabel = Axes.tickText(ya, ya.c2l(y), true).text;
1420

1521
return labels;
1622
};

src/traces/scatter/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ module.exports = {
99
isBubble: subtypes.isBubble,
1010

1111
attributes: require('./attributes'),
12+
layoutAttributes: require('./layout_attributes'),
1213
supplyDefaults: require('./defaults'),
1314
crossTraceDefaults: require('./cross_trace_defaults'),
15+
supplyLayoutDefaults: require('./layout_defaults'),
1416
calc: require('./calc').calc,
1517
crossTraceCalc: require('./cross_trace_calc'),
1618
arraysToCalcdata: require('./arrays_to_calcdata'),
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
4+
module.exports = {
5+
scattermode: {
6+
valType: 'enumerated',
7+
values: ['group', 'overlay'],
8+
dflt: 'overlay',
9+
editType: 'calc',
10+
description: [
11+
'Determines how scatter points at the same location coordinate',
12+
'are displayed on the graph.',
13+
'With *group*, the scatter points are plotted next to one another',
14+
'centered around the shared location.',
15+
'With *overlay*, the scatter points are plotted over one another,',
16+
'you might need to reduce *opacity* to see multiple scatter points.'
17+
].join(' ')
18+
},
19+
scattergap: {
20+
valType: 'number',
21+
min: 0,
22+
max: 1,
23+
editType: 'calc',
24+
description: [
25+
'Sets the gap (in plot fraction) between scatter points of',
26+
'adjacent location coordinates.',
27+
'Defaults to `bargap`.'
28+
].join(' ')
29+
}
30+
};

src/traces/scatter/layout_defaults.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
var Lib = require('../../lib');
4+
5+
var layoutAttributes = require('./layout_attributes');
6+
7+
module.exports = function(layoutIn, layoutOut) {
8+
function coerce(attr, dflt) {
9+
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
10+
}
11+
12+
var groupBarmode = layoutOut.barmode === 'group';
13+
14+
if(layoutOut.scattermode === 'group') {
15+
coerce('scattergap', groupBarmode ? layoutOut.bargap : 0.2);
16+
}
17+
};

src/traces/waterfall/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
var Lib = require('../../lib');
44

5-
var handleGroupingDefaults = require('../bar/grouping_defaults');
5+
var handleGroupingDefaults = require('../scatter/grouping_defaults');
66
var handleText = require('../bar/defaults').handleText;
77
var handleXYDefaults = require('../scatter/xy_defaults');
88
var handlePeriodDefaults = require('../scatter/period_defaults');
Loading
21.2 KB
Loading
Loading

0 commit comments

Comments
 (0)