Skip to content

Commit 3e26089

Browse files
committed
Introduce change of trace selection interface [1852]
- Reason: new approach is needed to support proper interplay of polygon and click select. See #1852 for a discussion. - New approach separates the two concerns 'what was selected' and 'set selected state of data points', that were previously handled by `selectPoints`, into multiple functions. - Also the polygons now longer implicitly carry selection state in cartesian/select.js but are only visual cues anymore. - Introduce a new Lib function for calculating the set difference of two arrays.
1 parent 943f7bf commit 3e26089

File tree

5 files changed

+106
-80
lines changed

5 files changed

+106
-80
lines changed

src/lib/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ lib.variance = statsModule.variance;
7272
lib.stdev = statsModule.stdev;
7373
lib.interp = statsModule.interp;
7474

75+
var setOpsModule = require('./set_operations');
76+
lib.difference = setOpsModule.difference;
77+
7578
var matrixModule = require('./matrix');
7679
lib.init2dArray = matrixModule.init2dArray;
7780
lib.transposeRagged = matrixModule.transposeRagged;

src/lib/set_operations.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
12+
/*
13+
* Computes the set difference of two arrays.
14+
*
15+
* Returns all elements of a that are not in b.
16+
*/
17+
function difference(a, b) {
18+
return a.filter(function(e) {
19+
return b.indexOf(e) < 0;
20+
});
21+
}
22+
23+
module.exports = {
24+
difference: difference
25+
};

src/plots/cartesian/select.js

+21-8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var Registry = require('../../registry');
1515
var Color = require('../../components/color');
1616
var Fx = require('../../components/fx');
1717

18+
var difference = require('../../lib/set_operations').difference;
1819
var polygon = require('../../lib/polygon');
1920
var throttle = require('../../lib/throttle');
2021
var makeEventData = require('../../components/fx/helpers').makeEventData;
@@ -51,6 +52,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
5152
var subtract = e.altKey;
5253

5354
var filterPoly, testPoly, mergedPolygons, currentPolygon;
55+
var pointsInPolygon = [];
5456
var i, cd, trace, searchInfo, eventData;
5557

5658
var selectingOnSameSubplot = (
@@ -283,7 +285,15 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
283285
for(i = 0; i < searchTraces.length; i++) {
284286
searchInfo = searchTraces[i];
285287

286-
traceSelection = searchInfo._module.selectPoints(searchInfo, testPoly, shouldRetainSelection(e));
288+
var currentPolygonTester = polygonTester(currentPolygon);
289+
var pointIds = searchInfo._module.getPointsIn(searchInfo, currentPolygonTester);
290+
traceSelection = searchInfo._module.selectPoints(searchInfo, pointIds);
291+
var pointsNoLongerSelected = difference(pointsInPolygon, pointIds);
292+
293+
searchInfo._module.deselectPoints(searchInfo, pointsNoLongerSelected);
294+
pointsInPolygon = pointIds;
295+
296+
// traceSelection = searchInfo._module.selectPoints(searchInfo, testPoly, shouldRetainSelection(e));
287297
traceSelections.push(traceSelection);
288298

289299
thisSelection = fillSelectionItem(traceSelection, searchInfo);
@@ -313,7 +323,8 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
313323
if(numClicks === 2) {
314324
for(i = 0; i < searchTraces.length; i++) {
315325
searchInfo = searchTraces[i];
316-
searchInfo._module.selectPoints(searchInfo, false);
326+
// searchInfo._module.selectPoints(searchInfo, false);
327+
searchInfo._module.clearSelection(searchInfo);
317328
}
318329

319330
updateSelectedState(gd, searchTraces);
@@ -371,23 +382,25 @@ function selectOnClick(gd, numClicks, evt) {
371382
if(selectPreconditionsMet) {
372383
var trace = calcData[0].trace,
373384
hoverDatum = hoverData[0],
374-
module = trace._module;
385+
module = trace._module,
386+
searchInfo = _createSearchInfo(module, calcData, hoverDatum.xaxis, hoverDatum.yaxis);
375387

376388
// Execute selection by delegating to respective module
377389
var retainSelection = shouldRetainSelection(evt),
378390
pointSelected = isPointSelected(trace, hoverDatum.pointNumber),
379391
onePointSelectedOnly = isOnePointSelectedOnly(trace);
380392

393+
if(!retainSelection) {
394+
module.clearSelection(searchInfo);
395+
}
396+
381397
var shouldDeselectPoint = (pointSelected && onePointSelectedOnly) ||
382398
(pointSelected && !onePointSelectedOnly && retainSelection);
383-
384399
var newTraceSelection = shouldDeselectPoint ?
385-
module.deselectPoint(calcData, hoverDatum, retainSelection) :
386-
module.selectPoint(calcData, hoverDatum, retainSelection);
400+
module.deselectPoints(searchInfo, [hoverDatum.pointNumber]) :
401+
module.selectPoints(searchInfo, [hoverDatum.pointNumber]);
387402

388403
// Update selection state
389-
var searchInfo =
390-
_createSearchInfo(module, calcData, hoverDatum.xaxis, hoverDatum.yaxis);
391404
var selection = fillSelectionItem(newTraceSelection, searchInfo);
392405
var eventData = {points: selection};
393406

src/traces/scatter/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ Scatter.colorbar = require('./marker_colorbar');
2727
Scatter.style = require('./style').style;
2828
Scatter.styleOnSelect = require('./style').styleOnSelect;
2929
Scatter.hoverPoints = require('./hover');
30+
Scatter.getPointsIn = require('./select').getPointsIn;
3031
Scatter.selectPoints = require('./select').selectPoints;
31-
Scatter.selectPoint = require('./select').selectPoint;
32-
Scatter.deselectPoint = require('./select').deselectPoint;
32+
Scatter.deselectPoints = require('./select').deselectPoints;
33+
Scatter.clearSelection = require('./select').clearSelection;
3334
Scatter.animatable = true;
3435

3536
Scatter.moduleType = 'trace';

src/traces/scatter/select.js

+54-70
Original file line numberDiff line numberDiff line change
@@ -11,86 +11,33 @@
1111

1212
var subtypes = require('./subtypes');
1313

14-
function selectPoints(searchInfo, polygon, retainOtherSelectModesState) {
15-
var cd = searchInfo.cd,
16-
xa = searchInfo.xaxis,
17-
ya = searchInfo.yaxis,
18-
selection = [],
19-
trace = cd[0].trace,
20-
i,
21-
di,
22-
x,
23-
y;
24-
25-
var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace));
26-
if(hasOnlyLines) return [];
27-
28-
if(polygon === false) { // clear selection
29-
_clearSelection(cd);
30-
}
31-
else {
32-
for(i = 0; i < cd.length; i++) {
33-
di = cd[i];
34-
x = xa.c2p(di.x);
35-
y = ya.c2p(di.y);
36-
37-
if(polygon.contains([x, y])) {
38-
selection.push(_newSelectionItem(i, xa.c2d(di.x), ya.c2d(di.y)));
39-
di.selected = 1;
40-
di.selectedByPolygon = true;
41-
} else {
42-
if(retainOtherSelectModesState && !di.selectedByPolygon && di.selected === 1) {
43-
continue;
44-
}
45-
di.selected = 0;
46-
delete di.selectedByPolygon;
47-
}
48-
}
49-
}
50-
51-
return selection;
52-
}
53-
54-
function selectPoint(calcData, hoverDataItem, retain) {
55-
return _togglePointSelectedState(calcData, hoverDataItem, true, retain);
56-
}
57-
58-
function deselectPoint(calcData, hoverDataItem, retain) {
59-
return _togglePointSelectedState(calcData, hoverDataItem, false, retain);
60-
}
61-
62-
function _togglePointSelectedState(calcData, hoverDataItem, selected, retain) {
14+
function _togglePointSelectedState(searchInfo, pointIds, selected) {
6315
var selection = [];
64-
var selectedPointNumber = hoverDataItem.pointNumber;
65-
var cdItem = calcData[selectedPointNumber];
6616

67-
if(!retain) _clearSelection(calcData);
17+
var calcData = searchInfo.cd,
18+
xAxis = searchInfo.xaxis,
19+
yAxis = searchInfo.yaxis;
6820

69-
if(selected) {
70-
cdItem.selected = 1;
71-
} else {
72-
cdItem.selected = 0;
21+
// TODO use foreach?!
22+
// Mutate state
23+
for(var j = 0; j < pointIds.length; j++) {
24+
var pointId = pointIds[j];
25+
calcData[pointId].selected = selected ? 1 : 0;
7326
}
7427

28+
// Compute selection array from internal state
7529
for(var i = 0; i < calcData.length; i++) {
76-
cdItem = calcData[i];
77-
if(cdItem.selected === 1) {
30+
if(calcData[i].selected === 1) {
7831
selection.push(_newSelectionItem(
7932
i,
80-
hoverDataItem.xaxis.c2d(cdItem.x),
81-
hoverDataItem.yaxis.c2d(cdItem.y)));
33+
xAxis.c2d(calcData[i].x),
34+
yAxis.c2d(calcData[i].y)));
8235
}
8336
}
8437

8538
return selection;
8639
}
8740

88-
function _clearSelection(calcData) {
89-
for(var i = 0; i < calcData.length; i++) {
90-
calcData[i].selected = 0;
91-
}
92-
}
93-
9441
// TODO May be needed in other trace types as well, so may centralize somewhere
9542
function _newSelectionItem(pointNumber, xInData, yInData) {
9643
return {
@@ -100,8 +47,45 @@ function _newSelectionItem(pointNumber, xInData, yInData) {
10047
};
10148
}
10249

103-
module.exports = {
104-
selectPoints: selectPoints,
105-
selectPoint: selectPoint,
106-
deselectPoint: deselectPoint
50+
function _clearSelection(calcData) {
51+
for(var i = 0; i < calcData.length; i++) {
52+
calcData[i].selected = 0;
53+
}
54+
}
55+
56+
exports.getPointsIn = function(searchInfo, polygon) {
57+
var pointsIn = [];
58+
59+
var calcData = searchInfo.cd,
60+
trace = calcData[0].trace,
61+
xAxis = searchInfo.xaxis,
62+
yAxis = searchInfo.yaxis,
63+
i,
64+
x, y;
65+
66+
var hasOnlyLines = !subtypes.hasMarkers(trace) && !subtypes.hasText(trace);
67+
if(hasOnlyLines) return [];
68+
69+
for(i = 0; i < calcData.length; i++) {
70+
x = xAxis.c2p(calcData[i].x);
71+
y = yAxis.c2p(calcData[i].y);
72+
73+
if(polygon.contains([x, y])) {
74+
pointsIn.push(i);
75+
}
76+
}
77+
78+
return pointsIn;
79+
};
80+
81+
exports.selectPoints = function(searchInfo, pointIds) {
82+
return _togglePointSelectedState(searchInfo, pointIds, true);
83+
};
84+
85+
exports.deselectPoints = function(searchInfo, pointIds) {
86+
return _togglePointSelectedState(searchInfo, pointIds, false);
87+
};
88+
89+
exports.clearSelection = function(searchInfo) {
90+
_clearSelection(searchInfo.cd);
10791
};

0 commit comments

Comments
 (0)