Skip to content

Commit 29f94b6

Browse files
authored
Merge pull request #3810 from plotly/scattergl-selection-fixes
scattergl selection fixes
2 parents 862f1f8 + f5b2902 commit 29f94b6

File tree

8 files changed

+367
-249
lines changed

8 files changed

+367
-249
lines changed

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
"dependencies": {
5959
"@plotly/d3-sankey": "0.7.2",
6060
"alpha-shape": "^1.0.0",
61-
"array-range": "^1.0.1",
6261
"canvas-fit": "^1.5.0",
6362
"color-normalize": "^1.3.0",
6463
"convex-hull": "^1.0.3",

src/traces/scattergl/index.js

+64-72
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ var createScatter = require('regl-scatter2d');
1212
var createLine = require('regl-line2d');
1313
var createError = require('regl-error2d');
1414
var cluster = require('point-cluster');
15-
var arrayRange = require('array-range');
1615
var Text = require('gl-text');
1716

1817
var Registry = require('../../registry');
@@ -116,7 +115,6 @@ function calc(gd, trace) {
116115
opts.marker.snap = stash.tree || TOO_MANY_POINTS;
117116
}
118117

119-
// save scene opts batch
120118
scene.lineOptions.push(opts.line);
121119
scene.errorXOptions.push(opts.errorX);
122120
scene.errorYOptions.push(opts.errorY);
@@ -127,8 +125,9 @@ function calc(gd, trace) {
127125
scene.textOptions.push(opts.text);
128126
scene.textSelectedOptions.push(opts.textSel);
129127
scene.textUnselectedOptions.push(opts.textUnsel);
128+
scene.selectBatch.push([]);
129+
scene.unselectBatch.push([]);
130130

131-
// stash scene ref
132131
stash._scene = scene;
133132
stash.index = scene.count;
134133
stash.x = x;
@@ -146,7 +145,6 @@ function expandForErrorBars(trace, ax, opts) {
146145
extremes.max = extremes.max.concat(errExt.max);
147146
}
148147

149-
// create scene options
150148
function sceneOptions(gd, subplot, trace, positions, x, y) {
151149
var opts = convert.style(gd, trace);
152150

@@ -193,13 +191,12 @@ function sceneOptions(gd, subplot, trace, positions, x, y) {
193191
return opts;
194192
}
195193

196-
197194
// make sure scene exists on subplot, return it
198195
function sceneUpdate(gd, subplot) {
199196
var scene = subplot._scene;
200197

201198
var resetOpts = {
202-
// number of traces in subplot, since scene:subplot 1:1
199+
// number of traces in subplot, since scene:subplot -> 1:1
203200
count: 0,
204201
// whether scene requires init hook in plot call (dirty plot call)
205202
dirty: true,
@@ -213,19 +210,20 @@ function sceneUpdate(gd, subplot) {
213210
errorYOptions: [],
214211
textOptions: [],
215212
textSelectedOptions: [],
216-
textUnselectedOptions: []
213+
textUnselectedOptions: [],
214+
// selection batches
215+
selectBatch: [],
216+
unselectBatch: []
217217
};
218218

219+
// regl- component stubs, initialized in dirty plot call
219220
var initOpts = {
220-
selectBatch: null,
221-
unselectBatch: null,
222-
// regl- component stubs, initialized in dirty plot call
223221
fill2d: false,
224222
scatter2d: false,
225223
error2d: false,
226224
line2d: false,
227225
glText: false,
228-
select2d: null
226+
select2d: false
229227
};
230228

231229
if(!subplot._scene) {
@@ -276,17 +274,22 @@ function sceneUpdate(gd, subplot) {
276274
if(scene.errorXOptions[i]) error2d.draw(i);
277275
if(scene.errorYOptions[i]) error2d.draw(i + count);
278276
}
279-
if(scatter2d && scene.markerOptions[i] && (!selectBatch || !selectBatch[i])) {
280-
scatter2d.draw(i);
277+
if(scatter2d && scene.markerOptions[i]) {
278+
if(unselectBatch[i].length) {
279+
var arg = Lib.repeat([], scene.count);
280+
arg[i] = unselectBatch[i];
281+
scatter2d.draw(arg);
282+
} else if(!selectBatch[i].length) {
283+
scatter2d.draw(i);
284+
}
281285
}
282286
if(glText[i] && scene.textOptions[i]) {
283287
glText[i].render();
284288
}
285289
}
286290

287-
if(scatter2d && select2d && selectBatch) {
291+
if(select2d) {
288292
select2d.draw(selectBatch);
289-
scatter2d.draw(unselectBatch);
290293
}
291294

292295
scene.dirty = false;
@@ -325,7 +328,7 @@ function sceneUpdate(gd, subplot) {
325328
};
326329
}
327330

328-
// In case if we have scene from the last calc - reset data
331+
// in case if we have scene from the last calc - reset data
329332
if(!scene.dirty) {
330333
Lib.extendFlat(scene, resetOpts);
331334
}
@@ -363,6 +366,7 @@ function plot(gd, subplot, cdata) {
363366
return;
364367
}
365368

369+
var count = scene.count;
366370
var regl = fullLayout._glcanvas.data()[0].regl;
367371

368372
// that is needed for fills
@@ -383,28 +387,28 @@ function plot(gd, subplot, cdata) {
383387
scene.fill2d = createLine(regl);
384388
}
385389
if(scene.glText === true) {
386-
scene.glText = new Array(scene.count);
387-
for(i = 0; i < scene.count; i++) {
390+
scene.glText = new Array(count);
391+
for(i = 0; i < count; i++) {
388392
scene.glText[i] = new Text(regl);
389393
}
390394
}
391395

392396
// update main marker options
393397
if(scene.glText) {
394-
if(scene.count > scene.glText.length) {
398+
if(count > scene.glText.length) {
395399
// add gl text marker
396-
var textsToAdd = scene.count - scene.glText.length;
400+
var textsToAdd = count - scene.glText.length;
397401
for(i = 0; i < textsToAdd; i++) {
398402
scene.glText.push(new Text(regl));
399403
}
400-
} else if(scene.count < scene.glText.length) {
404+
} else if(count < scene.glText.length) {
401405
// remove gl text marker
402-
var textsToRemove = scene.glText.length - scene.count;
403-
var removedTexts = scene.glText.splice(scene.count, textsToRemove);
406+
var textsToRemove = scene.glText.length - count;
407+
var removedTexts = scene.glText.splice(count, textsToRemove);
404408
removedTexts.forEach(function(text) { text.destroy(); });
405409
}
406410

407-
for(i = 0; i < scene.count; i++) {
411+
for(i = 0; i < count; i++) {
408412
scene.glText[i].update(scene.textOptions[i]);
409413
}
410414
}
@@ -437,7 +441,7 @@ function plot(gd, subplot, cdata) {
437441
}
438442

439443
// fill requires linked traces, so we generate it's positions here
440-
scene.fillOrder = Lib.repeat(null, scene.count);
444+
scene.fillOrder = Lib.repeat(null, count);
441445
if(scene.fill2d) {
442446
scene.fillOptions = scene.fillOptions.map(function(fillOptions, i) {
443447
var cdscatter = cdata[i];
@@ -556,13 +560,11 @@ function plot(gd, subplot, cdata) {
556560
}
557561

558562
// form batch arrays, and check for selected points
559-
scene.selectBatch = null;
560-
scene.unselectBatch = null;
561563
var dragmode = fullLayout.dragmode;
562564
var selectMode = dragmode === 'lasso' || dragmode === 'select';
563565
var clickSelectEnabled = fullLayout.clickmode.indexOf('select') > -1;
564566

565-
for(i = 0; i < cdata.length; i++) {
567+
for(i = 0; i < count; i++) {
566568
var cd0 = cdata[i][0];
567569
var trace = cd0.trace;
568570
var stash = cd0.t;
@@ -574,11 +576,6 @@ function plot(gd, subplot, cdata) {
574576
if(trace.selectedpoints || selectMode || clickSelectEnabled) {
575577
if(!selectMode) selectMode = true;
576578

577-
if(!scene.selectBatch) {
578-
scene.selectBatch = [];
579-
scene.unselectBatch = [];
580-
}
581-
582579
// regenerate scene batch, if traces number changed during selection
583580
if(trace.selectedpoints) {
584581
var selPts = scene.selectBatch[index] = Lib.selIndices2selPoints(trace);
@@ -610,21 +607,24 @@ function plot(gd, subplot, cdata) {
610607
}
611608
}
612609

613-
614610
if(selectMode) {
615-
// create select2d
611+
// create scatter instance by cloning scatter2d
616612
if(!scene.select2d) {
617-
// create scatter instance by cloning scatter2d
618613
scene.select2d = createScatter(fullLayout._glcanvas.data()[1].regl);
619614
}
620615

621-
if(scene.scatter2d && scene.selectBatch && scene.selectBatch.length) {
622-
// update only traces with selection
623-
scene.scatter2d.update(scene.markerUnselectedOptions.map(function(opts, i) {
624-
return scene.selectBatch[i] ? opts : null;
625-
}));
616+
// use unselected styles on 'context' canvas
617+
if(scene.scatter2d) {
618+
var unselOpts = new Array(count);
619+
for(i = 0; i < count; i++) {
620+
unselOpts[i] = scene.selectBatch[i].length || scene.unselectBatch[i].length ?
621+
scene.markerUnselectedOptions[i] :
622+
{};
623+
}
624+
scene.scatter2d.update(unselOpts);
626625
}
627626

627+
// use selected style on 'focus' canvas
628628
if(scene.select2d) {
629629
scene.select2d.update(scene.markerOptions);
630630
scene.select2d.update(scene.markerSelectedOptions);
@@ -639,9 +639,9 @@ function plot(gd, subplot, cdata) {
639639
});
640640
}
641641
} else {
642+
// reset 'context' scatter2d opts to base opts,
643+
// thus unsetting markerUnselectedOptions from selection
642644
if(scene.scatter2d) {
643-
// reset scatter2d opts to base opts,
644-
// thus unsetting markerUnselectedOptions from selection
645645
scene.scatter2d.update(scene.markerOptions);
646646
}
647647
}
@@ -680,7 +680,6 @@ function plot(gd, subplot, cdata) {
680680
}
681681
}
682682

683-
684683
function hoverPoints(pointData, xval, yval, hovermode) {
685684
var cd = pointData.cd;
686685
var stash = cd[0].t;
@@ -758,7 +757,6 @@ function hoverPoints(pointData, xval, yval, hovermode) {
758757
return [pointData];
759758
}
760759

761-
762760
function calcHover(pointData, x, y, trace) {
763761
var xa = pointData.xa;
764762
var ya = pointData.ya;
@@ -861,7 +859,6 @@ function calcHover(pointData, x, y, trace) {
861859
return pointData;
862860
}
863861

864-
865862
function selectPoints(searchInfo, selectionTester) {
866863
var cd = searchInfo.cd;
867864
var selection = [];
@@ -871,23 +868,23 @@ function selectPoints(searchInfo, selectionTester) {
871868
var x = stash.x;
872869
var y = stash.y;
873870
var scene = stash._scene;
871+
var index = stash.index;
874872

875873
if(!scene) return selection;
876874

877875
var hasText = subTypes.hasText(trace);
878876
var hasMarkers = subTypes.hasMarkers(trace);
879877
var hasOnlyLines = !hasMarkers && !hasText;
878+
880879
if(trace.visible !== true || hasOnlyLines) return selection;
881880

881+
var els = [];
882+
var unels = [];
883+
882884
// degenerate polygon does not enable selection
883885
// filter out points by visible scatter ones
884-
var els = null;
885-
var unels = null;
886-
// FIXME: clearing selection does not work here
887-
var i;
888886
if(selectionTester !== false && !selectionTester.degenerate) {
889-
els = [], unels = [];
890-
for(i = 0; i < len; i++) {
887+
for(var i = 0; i < len; i++) {
891888
if(selectionTester.contains([stash.xpx[i], stash.ypx[i]], false, i, searchInfo)) {
892889
els.push(i);
893890
selection.push({
@@ -899,32 +896,27 @@ function selectPoints(searchInfo, selectionTester) {
899896
unels.push(i);
900897
}
901898
}
902-
} else {
903-
unels = arrayRange(len);
904899
}
905900

906-
// make sure selectBatch is created
907-
if(!scene.selectBatch) {
908-
scene.selectBatch = [];
909-
scene.unselectBatch = [];
910-
}
901+
if(hasMarkers) {
902+
var scatter2d = scene.scatter2d;
911903

912-
if(!scene.selectBatch[stash.index]) {
913-
// enter every trace select mode
914-
for(i = 0; i < scene.count; i++) {
915-
scene.selectBatch[i] = [];
916-
scene.unselectBatch[i] = [];
917-
}
918-
// we should turn scatter2d into unselected once we have any points selected
919-
if(hasMarkers) {
920-
scene.scatter2d.update(scene.markerUnselectedOptions);
904+
if(!els.length && !unels.length) {
905+
// reset to base styles when clearing
906+
var baseOpts = new Array(scene.count);
907+
baseOpts[index] = scene.markerOptions[index];
908+
scatter2d.update.apply(scatter2d, baseOpts);
909+
} else if(!scene.selectBatch[index].length && !scene.unselectBatch[index].length) {
910+
// set unselected styles on 'context' canvas (if not done already)
911+
var unselOpts = new Array(scene.count);
912+
unselOpts[index] = scene.markerUnselectedOptions[index];
913+
scatter2d.update.apply(scatter2d, unselOpts);
921914
}
922915
}
923916

924-
scene.selectBatch[stash.index] = els;
925-
scene.unselectBatch[stash.index] = unels;
917+
scene.selectBatch[index] = els;
918+
scene.unselectBatch[index] = unels;
926919

927-
// update text options
928920
if(hasText) {
929921
styleTextSelection(cd);
930922
}
@@ -946,7 +938,7 @@ function styleTextSelection(cd) {
946938
var opts = Lib.extendFlat({}, baseOpts);
947939
var i, j;
948940

949-
if(els && unels) {
941+
if(els.length || unels.length) {
950942
var stc = selOpts.color;
951943
var utc = unselOpts.color;
952944
var base = baseOpts.color;

src/traces/scatterpolargl/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ function plot(gd, subplot, cdata) {
155155
scene.textOptions.push(opts.text);
156156
scene.textSelectedOptions.push(opts.textSel);
157157
scene.textUnselectedOptions.push(opts.textUnsel);
158+
scene.selectBatch.push([]);
159+
scene.unselectBatch.push([]);
158160

159161
stash.x = x;
160162
stash.y = y;

0 commit comments

Comments
 (0)