Skip to content

Commit 069a6f0

Browse files
authored
Merge pull request #2442 from plotly/scattergl-visible-fix
scattergl visible restyle fix
2 parents fa85e21 + 4776832 commit 069a6f0

File tree

3 files changed

+125
-110
lines changed

3 files changed

+125
-110
lines changed

src/traces/scattergl/attributes.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ var attrs = module.exports = overrideAll({
8383
marker: scatterAttrs.unselected.marker
8484
},
8585

86-
opacity: extendFlat({}, plotAttrs.opacity, {
87-
editType: 'calc'
88-
}),
86+
opacity: plotAttrs.opacity
8987

9088
}, 'calc', 'nested');
9189

src/traces/scattergl/index.js

+76-107
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,9 @@ function sceneOptions(gd, subplot, trace, positions) {
141141
var fullLayout = gd._fullLayout;
142142
var count = positions.length / 2;
143143
var markerOpts = trace.marker;
144-
var xaxis = AxisIDs.getFromId(gd, trace.xaxis);
145-
var yaxis = AxisIDs.getFromId(gd, trace.yaxis);
146-
var ptrX = 0;
147-
var ptrY = 0;
148144
var i;
149145

150-
var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill;
146+
var hasLines, hasErrorX, hasErrorY, hasMarkers, hasFill;
151147

152148
if(trace.visible !== true) {
153149
hasLines = false;
@@ -159,7 +155,6 @@ function sceneOptions(gd, subplot, trace, positions) {
159155
hasLines = subTypes.hasLines(trace) && positions.length > 1;
160156
hasErrorX = trace.error_x && trace.error_x.visible === true;
161157
hasErrorY = trace.error_y && trace.error_y.visible === true;
162-
hasError = hasErrorX || hasErrorY;
163158
hasMarkers = subTypes.hasMarkers(trace);
164159
hasFill = !!trace.fill && trace.fill !== 'none';
165160
}
@@ -169,67 +164,16 @@ function sceneOptions(gd, subplot, trace, positions) {
169164
var selectedOptions, unselectedOptions;
170165
var linePositions;
171166

172-
// get error values
173-
var errorVals = hasError ?
174-
Registry.getComponentMethod('errorbars', 'calcFromTrace')(trace, fullLayout) :
175-
null;
176-
177-
if(hasErrorX) {
178-
errorXOptions = {};
179-
errorXOptions.positions = positions;
180-
var errorsX = new Float64Array(4 * count);
181-
182-
if(xaxis.type === 'log') {
183-
for(i = 0; i < count; ++i) {
184-
errorsX[ptrX++] = positions[i * 2] - xaxis.d2l(errorVals[i].xs) || 0;
185-
errorsX[ptrX++] = xaxis.d2l(errorVals[i].xh) - positions[i * 2] || 0;
186-
errorsX[ptrX++] = 0;
187-
errorsX[ptrX++] = 0;
188-
}
189-
} else {
190-
for(i = 0; i < count; ++i) {
191-
errorsX[ptrX++] = positions[i * 2] - errorVals[i].xs || 0;
192-
errorsX[ptrX++] = errorVals[i].xh - positions[i * 2] || 0;
193-
errorsX[ptrX++] = 0;
194-
errorsX[ptrX++] = 0;
195-
}
196-
}
167+
if(hasErrorX || hasErrorY) {
168+
var calcFromTrace = Registry.getComponentMethod('errorbars', 'calcFromTrace');
169+
var errorVals = calcFromTrace(trace, fullLayout);
197170

198-
if(trace.error_x.copy_ystyle) {
199-
trace.error_x = trace.error_y;
171+
if(hasErrorX) {
172+
errorXOptions = makeErrorOptions('x', trace.error_x, errorVals);
200173
}
201-
202-
errorXOptions.errors = errorsX;
203-
errorXOptions.capSize = trace.error_x.width * 2;
204-
errorXOptions.lineWidth = trace.error_x.thickness;
205-
errorXOptions.color = trace.error_x.color;
206-
}
207-
208-
if(hasErrorY) {
209-
errorYOptions = {};
210-
errorYOptions.positions = positions;
211-
var errorsY = new Float64Array(4 * count);
212-
213-
if(yaxis.type === 'log') {
214-
for(i = 0; i < count; ++i) {
215-
errorsY[ptrY++] = 0;
216-
errorsY[ptrY++] = 0;
217-
errorsY[ptrY++] = positions[i * 2 + 1] - yaxis.d2l(errorVals[i].ys) || 0;
218-
errorsY[ptrY++] = yaxis.d2l(errorVals[i].yh) - positions[i * 2 + 1] || 0;
219-
}
220-
} else {
221-
for(i = 0; i < count; ++i) {
222-
errorsY[ptrY++] = 0;
223-
errorsY[ptrY++] = 0;
224-
errorsY[ptrY++] = positions[i * 2 + 1] - errorVals[i].ys || 0;
225-
errorsY[ptrY++] = errorVals[i].yh - positions[i * 2 + 1] || 0;
226-
}
174+
if(hasErrorY) {
175+
errorYOptions = makeErrorOptions('y', trace.error_y, errorVals);
227176
}
228-
229-
errorYOptions.errors = errorsY;
230-
errorYOptions.capSize = trace.error_y.width * 2;
231-
errorYOptions.lineWidth = trace.error_y.thickness;
232-
errorYOptions.color = trace.error_y.color;
233177
}
234178

235179
if(hasLines) {
@@ -330,6 +274,33 @@ function sceneOptions(gd, subplot, trace, positions) {
330274
markerOptions.positions = positions;
331275
}
332276

277+
function makeErrorOptions(axLetter, errorOpts, vals) {
278+
var options = {};
279+
options.positions = positions;
280+
281+
var ax = AxisIDs.getFromId(gd, trace[axLetter + 'axis']);
282+
var errors = options.errors = new Float64Array(4 * count);
283+
var pOffset = {x: 0, y: 1}[axLetter];
284+
var eOffset = {x: [0, 1, 2, 3], y: [2, 3, 0, 1]}[axLetter];
285+
286+
for(var i = 0, p = 0; i < count; i++, p += 4) {
287+
errors[p + eOffset[0]] = positions[i * 2 + pOffset] - ax.d2l(vals[i][axLetter + 's']) || 0;
288+
errors[p + eOffset[1]] = ax.d2l(vals[i][axLetter + 'h']) - positions[i * 2 + pOffset] || 0;
289+
errors[p + eOffset[2]] = 0;
290+
errors[p + eOffset[3]] = 0;
291+
}
292+
293+
if(errorOpts.copy_ystyle) {
294+
errorOpts = trace.error_y;
295+
}
296+
297+
options.capSize = errorOpts.width * 2;
298+
options.lineWidth = errorOpts.thickness;
299+
options.color = errorOpts.color;
300+
301+
return options;
302+
}
303+
333304
function makeSelectedOptions(selected, markerOpts) {
334305
var options = {};
335306

@@ -486,39 +457,42 @@ function sceneUpdate(gd, subplot) {
486457
var scene = subplot._scene;
487458
var fullLayout = gd._fullLayout;
488459

460+
var reset = {
461+
// number of traces in subplot, since scene:subplot → 1:1
462+
count: 0,
463+
// whether scene requires init hook in plot call (dirty plot call)
464+
dirty: true,
465+
// last used options
466+
lineOptions: [],
467+
fillOptions: [],
468+
markerOptions: [],
469+
selectedOptions: [],
470+
unselectedOptions: [],
471+
errorXOptions: [],
472+
errorYOptions: []
473+
};
474+
475+
var first = {
476+
selectBatch: null,
477+
unselectBatch: null,
478+
// regl- component stubs, initialized in dirty plot call
479+
fill2d: false,
480+
scatter2d: false,
481+
error2d: false,
482+
line2d: false,
483+
select2d: null
484+
};
485+
489486
if(!subplot._scene) {
490-
scene = subplot._scene = {
491-
// number of traces in subplot, since scene:subplot → 1:1
492-
count: 0,
493-
494-
// whether scene requires init hook in plot call (dirty plot call)
495-
dirty: true,
496-
497-
// last used options
498-
lineOptions: [],
499-
fillOptions: [],
500-
markerOptions: [],
501-
selectedOptions: [],
502-
unselectedOptions: [],
503-
errorXOptions: [],
504-
errorYOptions: [],
505-
selectBatch: null,
506-
unselectBatch: null,
507-
508-
// regl- component stubs, initialized in dirty plot call
509-
fill2d: false,
510-
scatter2d: false,
511-
error2d: false,
512-
line2d: false,
513-
select2d: null
514-
};
487+
scene = subplot._scene = Lib.extendFlat({}, reset, first);
515488

516-
// apply new option to all regl components
489+
// apply new option to all regl components (used on drag)
517490
scene.update = function update(opt) {
518-
var opts = Array(scene.count);
491+
var opts = new Array(scene.count);
519492
for(var i = 0; i < scene.count; i++) {
520493
opts[i] = opt;
521494
}
495+
522496
if(scene.fill2d) scene.fill2d.update(opts);
523497
if(scene.scatter2d) scene.scatter2d.update(opts);
524498
if(scene.line2d) scene.line2d.update(opts);
@@ -532,21 +506,24 @@ function sceneUpdate(gd, subplot) {
532506
scene.draw = function draw() {
533507
var i;
534508
for(i = 0; i < scene.count; i++) {
535-
if(scene.fill2d) scene.fill2d.draw(i);
509+
if(scene.fill2d && scene.fillOptions[i]) {
510+
// must do all fills first
511+
scene.fill2d.draw(i);
512+
}
536513
}
537514
for(i = 0; i < scene.count; i++) {
538-
if(scene.line2d) {
515+
if(scene.line2d && scene.lineOptions[i]) {
539516
scene.line2d.draw(i);
540517
}
541-
if(scene.error2d) {
518+
if(scene.error2d && scene.errorXOptions[i]) {
542519
scene.error2d.draw(i);
520+
}
521+
if(scene.error2d && scene.errorYOptions[i]) {
543522
scene.error2d.draw(i + scene.count);
544523
}
545-
if(scene.scatter2d) {
524+
if(scene.scatter2d && scene.markerOptions[i] && (!scene.selectBatch || !scene.selectBatch[i])) {
546525
// traces in no-selection mode
547-
if(!scene.selectBatch || !scene.selectBatch[i]) {
548-
scene.scatter2d.draw(i);
549-
}
526+
scene.scatter2d.draw(i);
550527
}
551528
}
552529

@@ -639,15 +616,7 @@ function sceneUpdate(gd, subplot) {
639616

640617
// In case if we have scene from the last calc - reset data
641618
if(!scene.dirty) {
642-
scene.dirty = true;
643-
scene.count = 0;
644-
scene.lineOptions = [];
645-
scene.fillOptions = [];
646-
scene.markerOptions = [];
647-
scene.selectedOptions = [];
648-
scene.unselectedOptions = [];
649-
scene.errorXOptions = [];
650-
scene.errorYOptions = [];
619+
Lib.extendFlat(scene, reset);
651620
}
652621

653622
return scene;

test/jasmine/tests/gl2d_plot_interact_test.js

+48
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,16 @@ describe('@gl Test gl2d plots', function() {
282282
gd.on('plotly_relayout', relayoutCallback);
283283
})
284284
.then(function() {
285+
var scene = gd._fullLayout._plots.xy._scene;
286+
spyOn(scene, 'draw');
287+
285288
// Drag scene along the X axis
286289
return mouseTo([200, 200], [220, 200]);
287290
})
288291
.then(function() {
292+
var scene = gd._fullLayout._plots.xy._scene;
293+
expect(scene.draw).toHaveBeenCalledTimes(3);
294+
289295
expect(gd.layout.xaxis.autorange).toBe(false);
290296
expect(gd.layout.yaxis.autorange).toBe(false);
291297
expect(gd.layout.xaxis.range).toBeCloseToArray(newX, precision);
@@ -379,6 +385,48 @@ describe('@gl Test gl2d plots', function() {
379385
.then(done);
380386
});
381387

388+
it('should be able to toggle trace with different modes', function(done) {
389+
Plotly.newPlot(gd, [{
390+
// a trace with all regl2d objects
391+
type: 'scattergl',
392+
y: [1, 2, 1],
393+
error_x: {value: 10},
394+
error_y: {value: 10},
395+
fill: 'tozeroy'
396+
}, {
397+
type: 'scattergl',
398+
mode: 'markers',
399+
y: [0, 1, -1]
400+
}])
401+
.then(function() {
402+
var scene = gd._fullLayout._plots.xy._scene;
403+
spyOn(scene.fill2d, 'draw');
404+
spyOn(scene.line2d, 'draw');
405+
spyOn(scene.error2d, 'draw');
406+
spyOn(scene.scatter2d, 'draw');
407+
408+
return Plotly.restyle(gd, 'visible', 'legendonly', [0]);
409+
})
410+
.then(function() {
411+
var scene = gd._fullLayout._plots.xy._scene;
412+
expect(scene.fill2d.draw).toHaveBeenCalledTimes(0);
413+
expect(scene.line2d.draw).toHaveBeenCalledTimes(0);
414+
expect(scene.error2d.draw).toHaveBeenCalledTimes(0);
415+
expect(scene.scatter2d.draw).toHaveBeenCalledTimes(1);
416+
417+
return Plotly.restyle(gd, 'visible', true, [0]);
418+
})
419+
.then(function() {
420+
var scene = gd._fullLayout._plots.xy._scene;
421+
expect(scene.fill2d.draw).toHaveBeenCalledTimes(1);
422+
expect(scene.line2d.draw).toHaveBeenCalledTimes(1);
423+
expect(scene.error2d.draw).toHaveBeenCalledTimes(2, 'twice for x AND y');
424+
expect(scene.scatter2d.draw).toHaveBeenCalledTimes(3, 'both traces have markers');
425+
})
426+
.catch(fail)
427+
.then(done);
428+
});
429+
382430
it('@noCI should display selection of big number of regular points', function(done) {
383431
// generate large number of points
384432
var x = [], y = [], n = 2e2, N = n * n;

0 commit comments

Comments
 (0)