Skip to content

Commit

Permalink
Merge pull request #6381 from plotly/group-scattermode
Browse files Browse the repository at this point in the history
introduce group scatter attributes
  • Loading branch information
archmoj authored Dec 21, 2022
2 parents 984a691 + e98f3c6 commit 9014a11
Show file tree
Hide file tree
Showing 26 changed files with 381 additions and 37 deletions.
2 changes: 2 additions & 0 deletions draftlogs/6381_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Introduce group attributes for `scatter` trace i.e. `alignmentgroup`, `offsetgroup`, `scattermode` and `scattergap` [[#6381](https://github.com/plotly/plotly.js/pull/6381)],
this feature was anonymously sponsored: thank you to our sponsor!
3 changes: 3 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var BADNUM = require('../constants/numerical').BADNUM;

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

var animationAttrs = require('./animation_attributes');
var frameAttrs = require('./frame_attributes');
Expand Down Expand Up @@ -1566,6 +1567,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
'fx',
'supplyLayoutGlobalDefaults'
)(layoutIn, layoutOut, coerce);

Lib.coerce(layoutIn, layoutOut, scatterAttrs, 'scattermode');
};

function getComputedSize(attr) {
Expand Down
23 changes: 2 additions & 21 deletions src/traces/bar/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,27 +196,8 @@ module.exports = {

marker: marker,

offsetgroup: {
valType: 'string',
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',
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(' ')
},
offsetgroup: scatterAttrs.offsetgroup,
alignmentgroup: scatterAttrs.alignmentgroup,

selected: {
marker: {
Expand Down
17 changes: 14 additions & 3 deletions src/traces/bar/cross_trace_calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,14 @@ function setBarCenterAndWidth(pa, sieve) {

// store the actual bar width and position, for use by hover
var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth;
calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2;

if(calcBar.p === undefined) {
calcBar.p = calcBar[pLetter];
calcBar['orig_' + pLetter] = calcBar[pLetter];
}

var delta = (poffsetIsArray ? poffset[j] : poffset) + width / 2;
calcBar[pLetter] = calcBar.p + delta;
}
}
}
Expand Down Expand Up @@ -498,13 +505,17 @@ function setBaseAndTop(sa, sieve) {
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var fullTrace = calcTrace[0].trace;
var isScatter = fullTrace.type === 'scatter';
var isVertical = fullTrace.orientation === 'v';
var pts = [];
var tozero = false;

for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];
var base = bar.b;
var top = base + bar.s;
var base = isScatter ? 0 : bar.b;
var top = isScatter ? (
isVertical ? bar.y : bar.x
) : base + bar.s;

bar[sLetter] = top;
pts.push(top);
Expand Down
2 changes: 1 addition & 1 deletion src/traces/bar/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var Registry = require('../../registry');
var handleXYDefaults = require('../scatter/xy_defaults');
var handlePeriodDefaults = require('../scatter/period_defaults');
var handleStyleDefaults = require('./style_defaults');
var handleGroupingDefaults = require('./grouping_defaults');
var handleGroupingDefaults = require('../scatter/grouping_defaults');
var attributes = require('./attributes');

var coerceFont = Lib.coerceFont;
Expand Down
9 changes: 7 additions & 2 deletions src/traces/bar/sieve.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
module.exports = Sieve;

var distinctVals = require('../../lib').distinctVals;
var BADNUM = require('../../constants/numerical').BADNUM;

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

var axLetter = opts.posAxis._id.charAt(0);

var positions = [];
for(var i = 0; i < traces.length; i++) {
var trace = traces[i];
for(var j = 0; j < trace.length; j++) {
var bar = trace[j];
if(bar.p !== BADNUM) positions.push(bar.p);
var pos = bar.p;
if(pos === undefined) {
pos = bar[axLetter];
}
if(pos !== undefined) positions.push(pos);
}
if(trace[0] && trace[0].width1) {
width1 = Math.min(trace[0].width1, width1);
Expand Down
2 changes: 1 addition & 1 deletion src/traces/box/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var Lib = require('../../lib');
var Registry = require('../../registry');
var Color = require('../../components/color');
var handlePeriodDefaults = require('../scatter/period_defaults');
var handleGroupingDefaults = require('../bar/grouping_defaults');
var handleGroupingDefaults = require('../scatter/grouping_defaults');
var autoType = require('../../plots/cartesian/axis_autotype');
var attributes = require('./attributes');

Expand Down
2 changes: 1 addition & 1 deletion src/traces/funnel/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

var handleGroupingDefaults = require('../bar/grouping_defaults');
var handleGroupingDefaults = require('../scatter/grouping_defaults');
var handleText = require('../bar/defaults').handleText;
var handleXYDefaults = require('../scatter/xy_defaults');
var handlePeriodDefaults = require('../scatter/period_defaults');
Expand Down
2 changes: 1 addition & 1 deletion src/traces/histogram/cross_trace_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var Lib = require('../../lib');
var axisIds = require('../../plots/cartesian/axis_ids');

var traceIs = require('../../registry').traceIs;
var handleGroupingDefaults = require('../bar/grouping_defaults');
var handleGroupingDefaults = require('../scatter/grouping_defaults');

var nestedProperty = Lib.nestedProperty;
var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;
Expand Down
27 changes: 26 additions & 1 deletion src/traces/scatter/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,29 @@ module.exports = {
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),

offsetgroup: {
valType: 'string',
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',
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(' ')
},

stackgroup: {
valType: 'string',
dflt: '',
Expand All @@ -146,7 +169,9 @@ module.exports = {
values: ['v', 'h'],
editType: 'calc',
description: [
'Only relevant when `stackgroup` is used, and only the first',
'Only relevant in the following cases:',
'1. when `scattermode` is set to *group*.',
'2. when `stackgroup` is used, and only the first',
'`orientation` found in the `stackgroup` will be used - including',
'if `visible` is *legendonly* but not if it is `false`. Sets the',
'stacking direction. With *v* (*h*), the y (x) values of subsequent',
Expand Down
40 changes: 40 additions & 0 deletions src/traces/scatter/cross_trace_calc.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,53 @@
'use strict';

var calc = require('./calc');
var setGroupPositions = require('../bar/cross_trace_calc').setGroupPositions;

function groupCrossTraceCalc(gd, plotinfo) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;

var fullLayout = gd._fullLayout;
var fullTraces = gd._fullData;
var calcTraces = gd.calcdata;
var calcTracesHorz = [];
var calcTracesVert = [];

for(var i = 0; i < fullTraces.length; i++) {
var fullTrace = fullTraces[i];
if(
fullTrace.visible === true &&
fullTrace.type === 'scatter' &&
fullTrace.xaxis === xa._id &&
fullTrace.yaxis === ya._id
) {
if(fullTrace.orientation === 'h') {
calcTracesHorz.push(calcTraces[i]);
} else if(fullTrace.orientation === 'v') { // check for v since certain scatter traces may not have an orientation
calcTracesVert.push(calcTraces[i]);
}
}
}

var opts = {
mode: fullLayout.scattermode,
gap: fullLayout.scattergap
};

setGroupPositions(gd, xa, ya, calcTracesVert, opts);
setGroupPositions(gd, ya, xa, calcTracesHorz, opts);
}

/*
* Scatter stacking & normalization calculations
* runs per subplot, and can handle multiple stacking groups
*/

module.exports = function crossTraceCalc(gd, plotinfo) {
if(gd._fullLayout.scattermode === 'group') {
groupCrossTraceCalc(gd, plotinfo);
}

var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var subplot = xa._id + ya._id;
Expand Down
24 changes: 22 additions & 2 deletions src/traces/scatter/cross_trace_defaults.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
'use strict';

var Lib = require('../../lib');
var handleGroupingDefaults = require('./grouping_defaults');
var attributes = require('./attributes');

// remove opacity for any trace that has a fill or is filled to
module.exports = function crossTraceDefaults(fullData) {
for(var i = 0; i < fullData.length; i++) {
module.exports = function crossTraceDefaults(fullData, fullLayout) {
var traceIn, traceOut, i;

function coerce(attr) {
return Lib.coerce(traceOut._input, traceOut, attributes, attr);
}

if(fullLayout.scattermode === 'group') {
for(i = 0; i < fullData.length; i++) {
traceOut = fullData[i];

if(traceOut.type === 'scatter') {
traceIn = traceOut._input;
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
}
}
}

for(i = 0; i < fullData.length; i++) {
var tracei = fullData[i];
if(tracei.type !== 'scatter') continue;

Expand Down
6 changes: 6 additions & 0 deletions src/traces/scatter/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('yhoverformat');

var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce);
if(
layout.scattermode === 'group' &&
traceOut.orientation === undefined
) {
coerce('orientation', 'v');
}

var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ?
'lines+markers' : 'lines';
Expand Down
10 changes: 8 additions & 2 deletions src/traces/scatter/format_labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ module.exports = function formatLabels(cdi, trace, fullLayout) {
var xa = Axes.getFromTrace(mockGd, trace, 'x');
var ya = Axes.getFromTrace(mockGd, trace, 'y');

labels.xLabel = Axes.tickText(xa, xa.c2l(cdi.x), true).text;
labels.yLabel = Axes.tickText(ya, ya.c2l(cdi.y), true).text;
var x = cdi.orig_x;
if(x === undefined) x = cdi.x;

var y = cdi.orig_y;
if(y === undefined) y = cdi.y;

labels.xLabel = Axes.tickText(xa, xa.c2l(x), true).text;
labels.yLabel = Axes.tickText(ya, ya.c2l(y), true).text;

return labels;
};
File renamed without changes.
2 changes: 2 additions & 0 deletions src/traces/scatter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ module.exports = {
isBubble: subtypes.isBubble,

attributes: require('./attributes'),
layoutAttributes: require('./layout_attributes'),
supplyDefaults: require('./defaults'),
crossTraceDefaults: require('./cross_trace_defaults'),
supplyLayoutDefaults: require('./layout_defaults'),
calc: require('./calc').calc,
crossTraceCalc: require('./cross_trace_calc'),
arraysToCalcdata: require('./arrays_to_calcdata'),
Expand Down
30 changes: 30 additions & 0 deletions src/traces/scatter/layout_attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';


module.exports = {
scattermode: {
valType: 'enumerated',
values: ['group', 'overlay'],
dflt: 'overlay',
editType: 'calc',
description: [
'Determines how scatter points at the same location coordinate',
'are displayed on the graph.',
'With *group*, the scatter points are plotted next to one another',
'centered around the shared location.',
'With *overlay*, the scatter points are plotted over one another,',
'you might need to reduce *opacity* to see multiple scatter points.'
].join(' ')
},
scattergap: {
valType: 'number',
min: 0,
max: 1,
editType: 'calc',
description: [
'Sets the gap (in plot fraction) between scatter points of',
'adjacent location coordinates.',
'Defaults to `bargap`.'
].join(' ')
}
};
17 changes: 17 additions & 0 deletions src/traces/scatter/layout_defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

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

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

module.exports = function(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}

var groupBarmode = layoutOut.barmode === 'group';

if(layoutOut.scattermode === 'group') {
coerce('scattergap', groupBarmode ? layoutOut.bargap : 0.2);
}
};
2 changes: 1 addition & 1 deletion src/traces/waterfall/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

var handleGroupingDefaults = require('../bar/grouping_defaults');
var handleGroupingDefaults = require('../scatter/grouping_defaults');
var handleText = require('../bar/defaults').handleText;
var handleXYDefaults = require('../scatter/xy_defaults');
var handlePeriodDefaults = require('../scatter/period_defaults');
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/zz-grouped_scatter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9014a11

Please sign in to comment.