Skip to content

Commit 0af88a7

Browse files
authored
Merge pull request #2311 from plotly/unify-selection
Refactor scattergl selection
2 parents 84c064b + 0ad735c commit 0af88a7

File tree

4 files changed

+136
-56
lines changed

4 files changed

+136
-56
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"3d-view": "^2.0.0",
5858
"@plotly/d3-sankey": "^0.5.0",
5959
"alpha-shape": "^1.0.0",
60+
"array-range": "^1.0.1",
6061
"bubleify": "^1.0.0",
6162
"canvas-fit": "^1.5.0",
6263
"color-normalize": "^1.0.3",
@@ -97,7 +98,7 @@
9798
"regl": "^1.3.1",
9899
"regl-error2d": "^2.0.3",
99100
"regl-line2d": "^2.1.2",
100-
"regl-scatter2d": "^2.1.12",
101+
"regl-scatter2d": "^2.1.13",
101102
"right-now": "^1.0.0",
102103
"robust-orientation": "^1.1.3",
103104
"sane-topojson": "^2.0.0",

src/traces/scattergl/attributes.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
'use strict';
1010

11+
var plotAttrs = require('../../plots/attributes');
1112
var scatterAttrs = require('../scatter/attributes');
12-
var colorAttributes = require('../../components/colorscale/color_attributes');
13+
var colorAttrs = require('../../components/colorscale/color_attributes');
1314

1415
var DASHES = require('../../constants/gl2d_dashes');
1516
var extendFlat = require('../../lib/extend').extendFlat;
@@ -56,7 +57,7 @@ var attrs = module.exports = overrideAll({
5657
description: 'Sets the style of the lines.'
5758
}
5859
},
59-
marker: extendFlat({}, colorAttributes('marker'), {
60+
marker: extendFlat({}, colorAttrs('marker'), {
6061
symbol: scatterMarkerAttrs.symbol,
6162
size: scatterMarkerAttrs.size,
6263
sizeref: scatterMarkerAttrs.sizeref,
@@ -65,7 +66,7 @@ var attrs = module.exports = overrideAll({
6566
opacity: scatterMarkerAttrs.opacity,
6667
showscale: scatterMarkerAttrs.showscale,
6768
colorbar: scatterMarkerAttrs.colorbar,
68-
line: extendFlat({}, colorAttributes('marker.line'), {
69+
line: extendFlat({}, colorAttrs('marker.line'), {
6970
width: scatterMarkerLineAttrs.width
7071
})
7172
}),
@@ -82,6 +83,10 @@ var attrs = module.exports = overrideAll({
8283
marker: scatterAttrs.unselected.marker
8384
},
8485

86+
opacity: extendFlat({}, plotAttrs.opacity, {
87+
editType: 'calc'
88+
}),
89+
8590
error_y: scatterAttrs.error_y,
8691
error_x: scatterAttrs.error_x
8792
}, 'calc', 'nested');

src/traces/scattergl/index.js

+53-51
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var createError = require('regl-error2d');
2828
var rgba = require('color-normalize');
2929
var svgSdf = require('svg-path-sdf');
3030
var createRegl = require('regl');
31+
var arrayRange = require('array-range');
3132
var fillHoverText = require('../scatter/fill_hover_text');
3233
var isNumeric = require('fast-isnumeric');
3334

@@ -122,7 +123,6 @@ function calc(container, trace) {
122123
}
123124
}
124125

125-
126126
calcColorscales(trace);
127127

128128
var options = sceneOptions(container, subplot, trace, positions);
@@ -396,7 +396,9 @@ function sceneOptions(container, subplot, trace, positions) {
396396
function makeSelectedOptions(selected, markerOpts) {
397397
var options = {};
398398

399-
if(selected.marker.symbol) {
399+
if(!selected) return options;
400+
401+
if(selected.marker && selected.marker.symbol) {
400402
options = makeMarkerOptions(extend({}, markerOpts, selected.marker));
401403
}
402404

@@ -599,12 +601,15 @@ function sceneUpdate(container, subplot) {
599601
scene.error2d.draw(i);
600602
scene.error2d.draw(i + scene.count);
601603
}
602-
if(scene.scatter2d && !scene.selectBatch) {
603-
scene.scatter2d.draw(i);
604+
if(scene.scatter2d) {
605+
// traces in no-selection mode
606+
if(!scene.selectBatch || !scene.selectBatch[i]) {
607+
scene.scatter2d.draw(i);
608+
}
604609
}
605610
}
606611

607-
// persistent selection draw
612+
// draw traces in selection mode
608613
if(scene.select2d && scene.selectBatch) {
609614
scene.select2d.draw(scene.selectBatch);
610615
scene.scatter2d.draw(scene.unselectBatch);
@@ -740,8 +745,8 @@ function plot(container, subplot, cdata) {
740745
if(!cdata.length) return;
741746

742747
var layout = container._fullLayout;
743-
var stash = cdata[0][0].t;
744-
var scene = stash.scene;
748+
var scene = cdata[0][0].t.scene;
749+
var dragmode = layout.dragmode;
745750

746751
// we may have more subplots than initialized data due to Axes.getSubplots method
747752
if(!scene) return;
@@ -782,6 +787,7 @@ function plot(container, subplot, cdata) {
782787
scene.fill2d = createLine(regl);
783788
}
784789

790+
// update main marker options
785791
if(scene.line2d) {
786792
scene.line2d.update(scene.lineOptions);
787793
}
@@ -790,13 +796,7 @@ function plot(container, subplot, cdata) {
790796
scene.error2d.update(errorBatch);
791797
}
792798
if(scene.scatter2d) {
793-
if(!scene.selectBatch) {
794-
scene.scatter2d.update(scene.markerOptions);
795-
}
796-
else {
797-
scene.scatter2d.update(scene.unselectedOptions);
798-
scene.select2d.update(scene.selectedOptions);
799-
}
799+
scene.scatter2d.update(scene.markerOptions);
800800
}
801801
// fill requires linked traces, so we generate it's positions here
802802
if(scene.fill2d) {
@@ -888,21 +888,15 @@ function plot(container, subplot, cdata) {
888888
}
889889
}
890890

891-
// make sure selection layer is initialized if we require selection
892-
var dragmode = layout.dragmode;
893-
894-
if(dragmode === 'lasso' || dragmode === 'select') {
895-
if(scene.select2d && scene.selectBatch) {
896-
scene.scatter2d.update(scene.unselectedOptions);
897-
}
898-
}
891+
var selectMode = dragmode === 'lasso' || dragmode === 'select';
899892

900893
// provide viewport and range
901894
var vpRange = cdata.map(function(cdscatter) {
902895
if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return;
903896
var cd = cdscatter[0];
904897
var trace = cd.trace;
905898
var stash = cd.t;
899+
var id = stash.index;
906900
var x = stash.rawx,
907901
y = stash.rawy;
908902

@@ -924,30 +918,16 @@ function plot(container, subplot, cdata) {
924918
(height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h
925919
];
926920

927-
if(trace.selectedpoints || dragmode === 'lasso' || dragmode === 'select') {
928-
// create select2d
929-
if(!scene.select2d && scene.scatter2d) {
930-
var selectRegl = layout._glcanvas.data()[1].regl;
921+
if(trace.selectedpoints || selectMode) {
922+
if(!selectMode) selectMode = true;
931923

932-
// create scatter instance by cloning scatter2d
933-
scene.select2d = createScatter(selectRegl, {clone: scene.scatter2d});
934-
scene.select2d.update(scene.selectedOptions);
924+
if(!scene.selectBatch) scene.selectBatch = [];
925+
if(!scene.unselectBatch) scene.unselectBatch = [];
935926

936-
// create selection style once we have something selected
937-
if(trace.selectedpoints && !scene.selectBatch) {
938-
scene.selectBatch = Array(scene.count);
939-
scene.unselectBatch = Array(scene.count);
940-
scene.scatter2d.update(scene.unselectedOptions);
941-
}
942-
}
943-
else {
944-
// update selection positions, since they may have changed by panning or alike
945-
scene.select2d.update(scene.selectedOptions);
946-
}
927+
// regenerate scene batch, if traces number changed during selection
928+
if(trace.selectedpoints) {
929+
scene.selectBatch[id] = trace.selectedpoints;
947930

948-
// form unselected batch
949-
if(trace.selectedpoints && !scene.unselectBatch[stash.index]) {
950-
scene.selectBatch[stash.index] = trace.selectedpoints;
951931
var selPts = trace.selectedpoints;
952932
var selDict = {};
953933
for(i = 0; i < selPts.length; i++) {
@@ -957,7 +937,7 @@ function plot(container, subplot, cdata) {
957937
for(i = 0; i < stash.count; i++) {
958938
if(!selDict[i]) unselPts.push(i);
959939
}
960-
scene.unselectBatch[stash.index] = unselPts;
940+
scene.unselectBatch[id] = unselPts;
961941
}
962942

963943
// precalculate px coords since we are not going to pan during select
@@ -979,6 +959,21 @@ function plot(container, subplot, cdata) {
979959
} : null;
980960
});
981961

962+
if(selectMode) {
963+
// create select2d
964+
if(!scene.select2d) {
965+
// create scatter instance by cloning scatter2d
966+
scene.select2d = createScatter(layout._glcanvas.data()[1].regl, {clone: scene.scatter2d});
967+
}
968+
969+
// update only traces with selection
970+
scene.scatter2d.update(scene.unselectedOptions.map(function(opts, i) {
971+
return scene.selectBatch[i] ? opts : null;
972+
}));
973+
scene.select2d.update(scene.markerOptions);
974+
scene.select2d.update(scene.selectedOptions);
975+
}
976+
982977
// uploat viewport/range data to GPU
983978
if(scene.fill2d) {
984979
scene.fill2d.update(vpRange);
@@ -1193,18 +1188,25 @@ function selectPoints(searchInfo, polygon) {
11931188
}
11941189
}
11951190
else {
1196-
unels = Array(stash.count);
1197-
for(i = 0; i < stash.count; i++) {
1198-
unels[i] = i;
1199-
}
1191+
unels = arrayRange(stash.count);
12001192
}
12011193

1202-
// create selection style once we have something selected
1194+
// make sure selectBatch is created
12031195
if(!scene.selectBatch) {
1204-
scene.selectBatch = Array(scene.count);
1205-
scene.unselectBatch = Array(scene.count);
1196+
scene.selectBatch = [];
1197+
scene.unselectBatch = [];
1198+
}
1199+
1200+
if(!scene.selectBatch[stash.index]) {
1201+
// enter every trace select mode
1202+
for(i = 0; i < scene.count; i++) {
1203+
scene.selectBatch[i] = [];
1204+
scene.unselectBatch[i] = [];
1205+
}
1206+
// we should turn scatter2d into unselected once we have any points selected
12061207
scene.scatter2d.update(scene.unselectedOptions);
12071208
}
1209+
12081210
scene.selectBatch[stash.index] = els;
12091211
scene.unselectBatch[stash.index] = unels;
12101212

test/jasmine/tests/gl2d_plot_interact_test.js

+73-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var Plotly = require('@lib/index');
44
var Plots = require('@src/plots/plots');
55
var Lib = require('@src/lib');
66
var Drawing = require('@src/components/drawing');
7+
var ScatterGl = require('@src/traces/scattergl');
78

89
var createGraphDiv = require('../assets/create_graph_div');
910
var destroyGraphDiv = require('../assets/destroy_graph_div');
@@ -14,6 +15,7 @@ var selectButton = require('../assets/modebar_button');
1415
var delay = require('../assets/delay');
1516
var readPixel = require('../assets/read_pixel');
1617

18+
1719
function countCanvases() {
1820
return d3.selectAll('canvas').size();
1921
}
@@ -406,7 +408,6 @@ describe('Test gl2d plots', function() {
406408
.then(done);
407409
});
408410

409-
410411
it('@noCI should display selection of big number of miscellaneous points', function(done) {
411412
var colorList = [
412413
'#006385', '#F06E75', '#90ed7d', '#f7a35c', '#8085e9',
@@ -645,4 +646,75 @@ describe('Test gl2d plots', function() {
645646
.catch(fail)
646647
.then(done);
647648
});
649+
650+
it('should restyle opacity', function(done) {
651+
// #2299
652+
spyOn(ScatterGl, 'calc');
653+
654+
var dat = [{
655+
'x': [1, 2, 3],
656+
'y': [1, 2, 3],
657+
'type': 'scattergl',
658+
'mode': 'markers'
659+
}];
660+
661+
Plotly.plot(gd, dat, {width: 500, height: 500})
662+
.then(function() {
663+
expect(ScatterGl.calc).toHaveBeenCalledTimes(1);
664+
665+
return Plotly.restyle(gd, {'opacity': 0.1});
666+
})
667+
.then(function() {
668+
expect(ScatterGl.calc).toHaveBeenCalledTimes(2);
669+
})
670+
.catch(fail)
671+
.then(done);
672+
});
673+
674+
it('should update selected points', function(done) {
675+
// #2298
676+
var dat = [{
677+
'x': [1],
678+
'y': [1],
679+
'type': 'scattergl',
680+
'mode': 'markers',
681+
'selectedpoints': [0]
682+
}];
683+
684+
Plotly.plot(gd, dat, {
685+
width: 500,
686+
height: 500,
687+
dragmode: 'select'
688+
})
689+
.then(function() {
690+
var scene = gd._fullLayout._plots.xy._scene;
691+
692+
expect(scene.count).toBe(1);
693+
expect(scene.selectBatch).toEqual([[0]]);
694+
expect(scene.unselectBatch).toEqual([[]]);
695+
spyOn(scene.scatter2d, 'draw');
696+
697+
var trace = {
698+
x: [2],
699+
y: [1],
700+
type: 'scattergl',
701+
mode: 'markers',
702+
marker: {color: 'red'}
703+
};
704+
705+
return Plotly.addTraces(gd, trace);
706+
})
707+
.then(function() {
708+
var scene = gd._fullLayout._plots.xy._scene;
709+
710+
expect(scene.count).toBe(2);
711+
expect(scene.selectBatch).toBeDefined();
712+
expect(scene.unselectBatch).toBeDefined();
713+
expect(scene.markerOptions.length).toBe(2);
714+
expect(scene.markerOptions[1].color).toEqual(new Uint8Array([255, 0, 0, 255]));
715+
expect(scene.scatter2d.draw).toHaveBeenCalled();
716+
})
717+
.catch(fail)
718+
.then(done);
719+
});
648720
});

0 commit comments

Comments
 (0)