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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce group scatter attributes #6381

Merged
merged 6 commits into from Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/plots/plots.js
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
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
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
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
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
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
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
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
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
@@ -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
@@ -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
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
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;
};
2 changes: 2 additions & 0 deletions src/traces/scatter/index.js
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
@@ -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
@@ -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
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
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
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.