Skip to content

Commit ed39d82

Browse files
authored
Merge pull request #3017 from plotly/stacked-area-missing-data
Fix #3013 - broken stacked area gap insertion case
2 parents dd3e950 + bd34fe8 commit ed39d82

File tree

4 files changed

+80
-76
lines changed

4 files changed

+80
-76
lines changed

Diff for: src/traces/scatter/cross_trace_calc.js

+45-45
Original file line numberDiff line numberDiff line change
@@ -30,48 +30,6 @@ module.exports = function crossTraceCalc(gd, plotinfo) {
3030
var groupOpts, interpolate, groupnorm, posAttr, valAttr;
3131
var hasAnyBlanks;
3232

33-
function insertBlank(calcTrace, index, position, traceIndex) {
34-
hasAnyBlanks[traceIndex] = true;
35-
var newEntry = {
36-
i: null,
37-
gap: true,
38-
s: 0
39-
};
40-
newEntry[posAttr] = position;
41-
calcTrace.splice(index, 0, newEntry);
42-
// Even if we're not interpolating, if one trace has multiple
43-
// values at the same position and this trace only has one value there,
44-
// we just duplicate that one value rather than insert a zero.
45-
// We also make it look like a real point - because it's ambiguous which
46-
// one really is the real one!
47-
if(index && position === calcTrace[index - 1][posAttr]) {
48-
var prevEntry = calcTrace[index - 1];
49-
newEntry.s = prevEntry.s;
50-
// TODO is it going to cause any problems to have multiple
51-
// calcdata points with the same index?
52-
newEntry.i = prevEntry.i;
53-
newEntry.gap = prevEntry.gap;
54-
}
55-
else if(interpolate) {
56-
newEntry.s = getInterp(calcTrace, index, position);
57-
}
58-
if(!index) {
59-
// t and trace need to stay on the first cd entry
60-
cd[0].t = cd[1].t;
61-
cd[0].trace = cd[1].trace;
62-
delete cd[1].t;
63-
delete cd[1].trace;
64-
}
65-
}
66-
67-
function getInterp(calcTrace, index, position) {
68-
var pt0 = calcTrace[index - 1];
69-
var pt1 = calcTrace[index + 1];
70-
if(!pt1) return pt0.s;
71-
if(!pt0) return pt1.s;
72-
return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]);
73-
}
74-
7533
for(var stackGroup in subplotStackOpts) {
7634
groupOpts = subplotStackOpts[stackGroup];
7735
var indices = groupOpts.traceIndices;
@@ -111,20 +69,20 @@ module.exports = function crossTraceCalc(gd, plotinfo) {
11169
posj = cd[j][posAttr];
11270
for(; posj > allPositions[k] && k < allPositions.length; k++) {
11371
// the current trace is missing a position from some previous trace(s)
114-
insertBlank(cd, j, allPositions[k], i);
72+
insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
11573
j++;
11674
}
11775
if(posj !== allPositions[k]) {
11876
// previous trace(s) are missing a position from the current trace
11977
for(i2 = 0; i2 < i; i2++) {
120-
insertBlank(calcTraces[indices[i2]], k, posj, i2);
78+
insertBlank(calcTraces[indices[i2]], k, posj, i2, hasAnyBlanks, interpolate, posAttr);
12179
}
12280
allPositions.splice(k, 0, posj);
12381
}
12482
k++;
12583
}
12684
for(; k < allPositions.length; k++) {
127-
insertBlank(cd, j, allPositions[k], i);
85+
insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
12886
j++;
12987
}
13088
}
@@ -179,3 +137,45 @@ module.exports = function crossTraceCalc(gd, plotinfo) {
179137
}
180138
}
181139
};
140+
141+
function insertBlank(calcTrace, index, position, traceIndex, hasAnyBlanks, interpolate, posAttr) {
142+
hasAnyBlanks[traceIndex] = true;
143+
var newEntry = {
144+
i: null,
145+
gap: true,
146+
s: 0
147+
};
148+
newEntry[posAttr] = position;
149+
calcTrace.splice(index, 0, newEntry);
150+
// Even if we're not interpolating, if one trace has multiple
151+
// values at the same position and this trace only has one value there,
152+
// we just duplicate that one value rather than insert a zero.
153+
// We also make it look like a real point - because it's ambiguous which
154+
// one really is the real one!
155+
if(index && position === calcTrace[index - 1][posAttr]) {
156+
var prevEntry = calcTrace[index - 1];
157+
newEntry.s = prevEntry.s;
158+
// TODO is it going to cause any problems to have multiple
159+
// calcdata points with the same index?
160+
newEntry.i = prevEntry.i;
161+
newEntry.gap = prevEntry.gap;
162+
}
163+
else if(interpolate) {
164+
newEntry.s = getInterp(calcTrace, index, position, posAttr);
165+
}
166+
if(!index) {
167+
// t and trace need to stay on the first cd entry
168+
calcTrace[0].t = calcTrace[1].t;
169+
calcTrace[0].trace = calcTrace[1].trace;
170+
delete calcTrace[1].t;
171+
delete calcTrace[1].trace;
172+
}
173+
}
174+
175+
function getInterp(calcTrace, index, position, posAttr) {
176+
var pt0 = calcTrace[index - 1];
177+
var pt1 = calcTrace[index + 1];
178+
if(!pt1) return pt0.s;
179+
if(!pt0) return pt1.s;
180+
return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]);
181+
}

Diff for: test/image/baselines/stacked_area.png

-2.55 KB
Loading

Diff for: test/image/mocks/stacked_area.json

+20-20
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,54 @@
11
{
22
"data": [
33
{
4-
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b"
4+
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m"
55
}, {
6-
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m"
6+
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b"
77
}, {
88
"x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t"
99
},
1010

1111
{
12-
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b",
12+
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m",
13+
"showlegend": false, "xaxis": "x2", "yaxis": "y2"
14+
}, {
15+
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b",
1316
"showlegend": false, "xaxis": "x2", "yaxis": "y2",
1417
"stackgaps": "interpolate"
15-
}, {
16-
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m",
17-
"showlegend": false, "xaxis": "x2", "yaxis": "y2"
1818
}, {
1919
"x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t",
2020
"showlegend": false, "xaxis": "x2", "yaxis": "y2"
2121
},
2222

2323
{
24-
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "b", "name": "bottom", "legendgroup": "b",
24+
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "b", "name": "bottom", "legendgroup": "m",
25+
"showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers"
26+
}, {
27+
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "b", "name": "middle", "legendgroup": "b",
2528
"showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers",
2629
"groupnorm": "fraction"
27-
}, {
28-
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "b", "name": "middle", "legendgroup": "m",
29-
"showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers"
3030
}, {
3131
"x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "b", "name": "top", "legendgroup": "t",
3232
"showlegend": false, "xaxis": "x3", "yaxis": "y3", "mode": "lines+markers"
3333
},
3434

3535
{
36-
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b",
37-
"showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers"
38-
}, {
39-
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m",
36+
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m",
4037
"showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers",
4138
"stackgaps": "interpolate", "groupnorm": "fraction"
39+
}, {
40+
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b",
41+
"showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers"
4242
}, {
4343
"x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t",
4444
"showlegend": false, "xaxis": "x4", "yaxis": "y4", "mode": "lines+markers"
4545
},
4646

4747
{
48-
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b",
48+
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m",
4949
"showlegend": false, "xaxis": "x5", "yaxis": "y5"
5050
}, {
51-
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m",
51+
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b",
5252
"showlegend": false, "xaxis": "x5", "yaxis": "y5"
5353
}, {
5454
"x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t",
@@ -57,12 +57,12 @@
5757
},
5858

5959
{
60-
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "bottom", "legendgroup": "b",
60+
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "bottom", "legendgroup": "m",
61+
"showlegend": false, "xaxis": "x6", "yaxis": "y6"
62+
}, {
63+
"x": [1, 3, 5, 7], "y": [2, 6, 4, 5], "line": {"color": "red"}, "stackgroup": "a", "name": "middle", "legendgroup": "b",
6164
"showlegend": false, "xaxis": "x6", "yaxis": "y6",
6265
"groupnorm": "percent"
63-
}, {
64-
"x": [2, 5, 6], "y": [1, 3, 4], "line": {"color": "green"}, "stackgroup": "a", "name": "middle", "legendgroup": "m",
65-
"showlegend": false, "xaxis": "x6", "yaxis": "y6"
6666
}, {
6767
"x": [3, 4 ,5], "y": [1, 2, 1], "line": {"color": "blue"}, "stackgroup": "a", "name": "top", "legendgroup": "t",
6868
"showlegend": false, "xaxis": "x6", "yaxis": "y6",

Diff for: test/jasmine/tests/scatter_test.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,11 @@ describe('stacked area', function() {
11061106
.toBeCloseToArray(ranges[axId], 0.1, msg + ' - ' + axId);
11071107
}
11081108
}
1109+
1110+
var bottoms = [0, 3, 6, 9, 12, 15];
1111+
var middles = [1, 4, 7, 10, 13, 16];
1112+
var midsAndBottoms = bottoms.concat(middles);
1113+
11091114
Plotly.newPlot(gd, Lib.extendDeep({}, mock))
11101115
.then(function() {
11111116
// initial ranges, as in the baseline image
@@ -1120,17 +1125,17 @@ describe('stacked area', function() {
11201125
y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26]
11211126
}, 'base case');
11221127

1123-
return Plotly.restyle(gd, 'visible', 'legendonly', [0, 3, 6, 9, 12, 15]);
1128+
return Plotly.restyle(gd, 'visible', 'legendonly', middles);
11241129
})
11251130
.then(function() {
11261131
var xr = [2, 6];
11271132
checkRanges({
11281133
x: xr, x2: xr, x3: xr, x4: xr, x5: xr, x6: xr,
11291134
y: [0, 4.21], y2: [0, 5.26],
11301135
y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26]
1131-
}, 'bottom trace legendonly');
1136+
}, 'middle trace legendonly');
11321137

1133-
return Plotly.restyle(gd, 'visible', false, [0, 3, 6, 9, 12, 15]);
1138+
return Plotly.restyle(gd, 'visible', false, middles);
11341139
})
11351140
.then(function() {
11361141
var xr = [2, 6];
@@ -1140,12 +1145,11 @@ describe('stacked area', function() {
11401145
// which we kept when it was visible: 'legendonly'
11411146
y: [0, 4.21], y2: [0, 4.21],
11421147
y3: [0, 4.32], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 5.26]
1143-
}, 'bottom trace visible: false');
1148+
}, 'middle trace visible: false');
11441149

11451150
// put the bottom traces back to legendonly so they still contribute
11461151
// config attributes, and hide the middles too
1147-
return Plotly.restyle(gd, 'visible', 'legendonly',
1148-
[0, 3, 6, 9, 12, 15, 1, 4, 7, 10, 13, 16]);
1152+
return Plotly.restyle(gd, 'visible', 'legendonly', midsAndBottoms);
11491153
})
11501154
.then(function() {
11511155
var xr = [3, 5];
@@ -1155,20 +1159,20 @@ describe('stacked area', function() {
11551159
y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26]
11561160
}, 'only top trace showing');
11571161

1158-
return Plotly.restyle(gd, 'visible', true, [0, 3, 6, 9, 12, 15]);
1162+
return Plotly.restyle(gd, 'visible', true, middles);
11591163
})
11601164
.then(function() {
11611165
var xr = [1, 7];
11621166
checkRanges({
11631167
x: xr, x2: xr, x3: xr, x4: xr, x5: xr, x6: xr,
11641168
y: [0, 7.37], y2: [0, 7.37],
11651169
y3: [0, 1.08], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 105.26]
1166-
}, 'top and bottom showing');
1170+
}, 'top and middle showing');
11671171

1168-
return Plotly.restyle(gd, {x: null, y: null}, [0, 3, 6, 9, 12, 15]);
1172+
return Plotly.restyle(gd, {x: null, y: null}, middles);
11691173
})
11701174
.then(function() {
1171-
return Plotly.restyle(gd, 'visible', true, [1, 4, 7, 10, 13, 16]);
1175+
return Plotly.restyle(gd, 'visible', true, bottoms);
11721176
})
11731177
.then(function() {
11741178
var xr = [2, 6];
@@ -1178,7 +1182,7 @@ describe('stacked area', function() {
11781182
x: xr, x2: xr, x3: xr, x4: xr, x5: xr, x6: xr,
11791183
y: [0, 4.21], y2: [0, 4.21],
11801184
y3: [0, 4.32], y4: [0, 1.08], y5: [0, 105.26], y6: [0, 5.26]
1181-
}, 'bottom trace *implicit* visible: false');
1185+
}, 'middle trace *implicit* visible: false');
11821186
})
11831187
.catch(failTest)
11841188
.then(done);

0 commit comments

Comments
 (0)