diff --git a/src/traces/scatter/cross_trace_calc.js b/src/traces/scatter/cross_trace_calc.js index 64db8eaad7d..de58d961514 100644 --- a/src/traces/scatter/cross_trace_calc.js +++ b/src/traces/scatter/cross_trace_calc.js @@ -30,48 +30,6 @@ module.exports = function crossTraceCalc(gd, plotinfo) { var groupOpts, interpolate, groupnorm, posAttr, valAttr; var hasAnyBlanks; - function insertBlank(calcTrace, index, position, traceIndex) { - hasAnyBlanks[traceIndex] = true; - var newEntry = { - i: null, - gap: true, - s: 0 - }; - newEntry[posAttr] = position; - calcTrace.splice(index, 0, newEntry); - // Even if we're not interpolating, if one trace has multiple - // values at the same position and this trace only has one value there, - // we just duplicate that one value rather than insert a zero. - // We also make it look like a real point - because it's ambiguous which - // one really is the real one! - if(index && position === calcTrace[index - 1][posAttr]) { - var prevEntry = calcTrace[index - 1]; - newEntry.s = prevEntry.s; - // TODO is it going to cause any problems to have multiple - // calcdata points with the same index? - newEntry.i = prevEntry.i; - newEntry.gap = prevEntry.gap; - } - else if(interpolate) { - newEntry.s = getInterp(calcTrace, index, position); - } - if(!index) { - // t and trace need to stay on the first cd entry - cd[0].t = cd[1].t; - cd[0].trace = cd[1].trace; - delete cd[1].t; - delete cd[1].trace; - } - } - - function getInterp(calcTrace, index, position) { - var pt0 = calcTrace[index - 1]; - var pt1 = calcTrace[index + 1]; - if(!pt1) return pt0.s; - if(!pt0) return pt1.s; - return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]); - } - for(var stackGroup in subplotStackOpts) { groupOpts = subplotStackOpts[stackGroup]; var indices = groupOpts.traceIndices; @@ -111,20 +69,20 @@ module.exports = function crossTraceCalc(gd, plotinfo) { posj = cd[j][posAttr]; for(; posj > allPositions[k] && k < allPositions.length; k++) { // the current trace is missing a position from some previous trace(s) - insertBlank(cd, j, allPositions[k], i); + insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr); j++; } if(posj !== allPositions[k]) { // previous trace(s) are missing a position from the current trace for(i2 = 0; i2 < i; i2++) { - insertBlank(calcTraces[indices[i2]], k, posj, i2); + insertBlank(calcTraces[indices[i2]], k, posj, i2, hasAnyBlanks, interpolate, posAttr); } allPositions.splice(k, 0, posj); } k++; } for(; k < allPositions.length; k++) { - insertBlank(cd, j, allPositions[k], i); + insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr); j++; } } @@ -179,3 +137,45 @@ module.exports = function crossTraceCalc(gd, plotinfo) { } } }; + +function insertBlank(calcTrace, index, position, traceIndex, hasAnyBlanks, interpolate, posAttr) { + hasAnyBlanks[traceIndex] = true; + var newEntry = { + i: null, + gap: true, + s: 0 + }; + newEntry[posAttr] = position; + calcTrace.splice(index, 0, newEntry); + // Even if we're not interpolating, if one trace has multiple + // values at the same position and this trace only has one value there, + // we just duplicate that one value rather than insert a zero. + // We also make it look like a real point - because it's ambiguous which + // one really is the real one! + if(index && position === calcTrace[index - 1][posAttr]) { + var prevEntry = calcTrace[index - 1]; + newEntry.s = prevEntry.s; + // TODO is it going to cause any problems to have multiple + // calcdata points with the same index? + newEntry.i = prevEntry.i; + newEntry.gap = prevEntry.gap; + } + else if(interpolate) { + newEntry.s = getInterp(calcTrace, index, position, posAttr); + } + if(!index) { + // t and trace need to stay on the first cd entry + calcTrace[0].t = calcTrace[1].t; + calcTrace[0].trace = calcTrace[1].trace; + delete calcTrace[1].t; + delete calcTrace[1].trace; + } +} + +function getInterp(calcTrace, index, position, posAttr) { + var pt0 = calcTrace[index - 1]; + var pt1 = calcTrace[index + 1]; + if(!pt1) return pt0.s; + if(!pt0) return pt1.s; + return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]); +} diff --git a/test/image/baselines/stacked_area.png b/test/image/baselines/stacked_area.png index 2f508049fbc..e81b7207353 100644 Binary files a/test/image/baselines/stacked_area.png and b/test/image/baselines/stacked_area.png differ diff --git a/test/image/mocks/stacked_area.json b/test/image/mocks/stacked_area.json index 0c2aac3076b..8cfcae2838e 100644 --- a/test/image/mocks/stacked_area.json +++ b/test/image/mocks/stacked_area.json @@ -1,54 +1,54 @@ { "data": [ { - "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b" + "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m" }, { - "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m" + "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b" }, { "x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t" }, { - "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b", + "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m", + "showlegend": false, "xaxis": "x2", "yaxis": "y2" + }, { + "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b", "showlegend": false, "xaxis": "x2", "yaxis": "y2", "stackgaps": "interpolate" - }, { - "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m", - "showlegend": false, "xaxis": "x2", "yaxis": "y2" }, { "x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t", "showlegend": false, "xaxis": "x2", "yaxis": "y2" }, { - "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "b", "name": "bottom", "legendgroup": "b", + "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "b", "name": "bottom", "legendgroup": "m", + "showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers" + }, { + "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "b", "name": "middle", "legendgroup": "b", "showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers", "groupnorm": "fraction" - }, { - "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "b", "name": "middle", "legendgroup": "m", - "showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers" }, { "x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "b", "name": "top", "legendgroup": "t", "showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers" }, { - "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b", - "showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers" - }, { - "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m", + "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m", "showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers", "stackgaps": "interpolate", "groupnorm": "fraction" + }, { + "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b", + "showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers" }, { "x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t", "showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers" }, { - "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b", + "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m", "showlegend": false, "xaxis": "x5", "yaxis": "y5" }, { - "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m", + "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b", "showlegend": false, "xaxis": "x5", "yaxis": "y5" }, { "x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t", @@ -57,12 +57,12 @@ }, { - "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b", + "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m", + "showlegend": false, "xaxis": "x6", "yaxis": "y6" + }, { + "x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b", "showlegend": false, "xaxis": "x6", "yaxis": "y6", "groupnorm": "percent" - }, { - "x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m", - "showlegend": false, "xaxis": "x6", "yaxis": "y6" }, { "x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t", "showlegend": false, "xaxis": "x6", "yaxis": "y6", diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index ff049278ffb..cca7961e1c3 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -1106,6 +1106,11 @@ describe('stacked area', function() { .toBeCloseToArray(ranges[axId], 0.1, msg + ' - ' + axId); } } + + var bottoms = [0, 3, 6, 9, 12, 15]; + var middles = [1, 4, 7, 10, 13, 16]; + var midsAndBottoms = bottoms.concat(middles); + Plotly.newPlot(gd, Lib.extendDeep({}, mock)) .then(function() { // initial ranges, as in the baseline image @@ -1120,7 +1125,7 @@ describe('stacked area', function() { y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26] }, 'base case'); - return Plotly.restyle(gd, 'visible', 'legendonly', [0, 3, 6, 9, 12, 15]); + return Plotly.restyle(gd, 'visible', 'legendonly', middles); }) .then(function() { var xr = [2, 6]; @@ -1128,9 +1133,9 @@ describe('stacked area', function() { x: xr, x2: xr, x3: xr, x4: xr, x5: xr, x6: xr, y: [0, 4.21], y2: [0, 5.26], y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26] - }, 'bottom trace legendonly'); + }, 'middle trace legendonly'); - return Plotly.restyle(gd, 'visible', false, [0, 3, 6, 9, 12, 15]); + return Plotly.restyle(gd, 'visible', false, middles); }) .then(function() { var xr = [2, 6]; @@ -1140,12 +1145,11 @@ describe('stacked area', function() { // which we kept when it was visible: 'legendonly' y: [0, 4.21], y2: [0, 4.21], y3: [0, 4.32], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 5.26] - }, 'bottom trace visible: false'); + }, 'middle trace visible: false'); // put the bottom traces back to legendonly so they still contribute // config attributes, and hide the middles too - return Plotly.restyle(gd, 'visible', 'legendonly', - [0, 3, 6, 9, 12, 15, 1, 4, 7, 10, 13, 16]); + return Plotly.restyle(gd, 'visible', 'legendonly', midsAndBottoms); }) .then(function() { var xr = [3, 5]; @@ -1155,7 +1159,7 @@ describe('stacked area', function() { y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26] }, 'only top trace showing'); - return Plotly.restyle(gd, 'visible', true, [0, 3, 6, 9, 12, 15]); + return Plotly.restyle(gd, 'visible', true, middles); }) .then(function() { var xr = [1, 7]; @@ -1163,12 +1167,12 @@ describe('stacked area', function() { x: xr, x2: xr, x3: xr, x4: xr, x5: xr, x6: xr, y: [0, 7.37], y2: [0, 7.37], y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26] - }, 'top and bottom showing'); + }, 'top and middle showing'); - return Plotly.restyle(gd, {x: null, y: null}, [0, 3, 6, 9, 12, 15]); + return Plotly.restyle(gd, {x: null, y: null}, middles); }) .then(function() { - return Plotly.restyle(gd, 'visible', true, [1, 4, 7, 10, 13, 16]); + return Plotly.restyle(gd, 'visible', true, bottoms); }) .then(function() { var xr = [2, 6]; @@ -1178,7 +1182,7 @@ describe('stacked area', function() { x: xr, x2: xr, x3: xr, x4: xr, x5: xr, x6: xr, y: [0, 4.21], y2: [0, 4.21], y3: [0, 4.32], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 5.26] - }, 'bottom trace *implicit* visible: false'); + }, 'middle trace *implicit* visible: false'); }) .catch(failTest) .then(done);