Skip to content

Commit cbd9f07

Browse files
committed
Refactor selection testing namings and structure [1852]
- Renaming because since click-to-select, selections not only can be defined by polygons, but also by point numbers. Up until now code was centered around polygons being the sole means to define a selection. Renamed identifiers to be more general. - Extract `multitester` to new module `lib/select.js` because `multitester` is no longer to been seen in the sole context of polygons.
1 parent 23024b2 commit cbd9f07

File tree

12 files changed

+178
-81
lines changed

12 files changed

+178
-81
lines changed

src/lib/polygon.js

+12-15
Original file line numberDiff line numberDiff line change
@@ -174,34 +174,31 @@ polygon.tester = function tester(ptsIn) {
174174
};
175175
};
176176

177+
// TODO Somewhat redundant to multiTester in 'lib/select.js'
177178
/**
178179
* Test multiple polygons
179180
*/
180181
polygon.multitester = function multitester(list) {
181182
var testers = [],
182-
xmin = list[0].contains ? 0 : list[0][0][0],
183+
xmin = list[0][0][0],
183184
xmax = xmin,
184-
ymin = list[0].contains ? 0 : list[0][0][1],
185+
ymin = list[0][0][1],
185186
ymax = ymin;
186187

187188
for(var i = 0; i < list.length; i++) {
188-
if(list[i].contains) {
189-
testers.push(list[i]);
190-
} else {
191-
var tester = polygon.tester(list[i]);
192-
tester.subtract = list[i].subtract;
193-
testers.push(tester);
194-
xmin = Math.min(xmin, tester.xmin);
195-
xmax = Math.max(xmax, tester.xmax);
196-
ymin = Math.min(ymin, tester.ymin);
197-
ymax = Math.max(ymax, tester.ymax);
198-
}
189+
var tester = polygon.tester(list[i]);
190+
tester.subtract = list[i].subtract;
191+
testers.push(tester);
192+
xmin = Math.min(xmin, tester.xmin);
193+
xmax = Math.max(xmax, tester.xmax);
194+
ymin = Math.min(ymin, tester.ymin);
195+
ymax = Math.max(ymax, tester.ymax);
199196
}
200197

201-
function contains(pt, arg, pointNumber, searchInfo) {
198+
function contains(pt, arg) {
202199
var yes = false;
203200
for(var i = 0; i < testers.length; i++) {
204-
if(testers[i].contains(pt, arg, pointNumber, searchInfo)) {
201+
if(testers[i].contains(pt, arg)) {
205202
// if contained by subtract polygon - exclude the point
206203
yes = testers[i].subtract === false;
207204
}

src/lib/select.js

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
10+
'use strict';
11+
12+
var polygon = require('./polygon');
13+
14+
var select = module.exports = {};
15+
16+
/**
17+
* Constructs a new point selection definition.
18+
*
19+
* @param pointNumber the point number of the point as in `data`
20+
* @param searchInfo identifies the trace the point is contained in
21+
* @param subtract true if the selection definition should mark a deselection
22+
* @return {{pointNumber: Number, searchInfo: Object, subtract: boolean}}
23+
*/
24+
select.pointSelectionDef = function(pointNumber, searchInfo, subtract) {
25+
return {
26+
pointNumber: pointNumber,
27+
searchInfo: searchInfo,
28+
subtract: subtract
29+
};
30+
};
31+
32+
function isPointSelectionDef(o) {
33+
return 'pointNumber' in o && 'searchInfo' in o;
34+
}
35+
36+
function pointNumberTester(pointSelectionDef) {
37+
return {
38+
xmin: 0,
39+
xmax: 0,
40+
ymin: 0,
41+
ymax: 0,
42+
pts: [],
43+
contains: function(pt, omitFirstEdge, pointNumber, searchInfo) {
44+
return searchInfo.cd[0].trace._expandedIndex === pointSelectionDef.searchInfo.cd[0].trace._expandedIndex &&
45+
pointNumber === pointSelectionDef.pointNumber;
46+
},
47+
isRect: false,
48+
degenerate: false,
49+
subtract: pointSelectionDef.subtract
50+
};
51+
}
52+
53+
/**
54+
* Wraps multiple selection testers.
55+
*
56+
* @param list an array of selection testers
57+
*
58+
* @return a selection tester object with a contains function
59+
* that can be called to evaluate a point against all wrapped
60+
* selection testers that were passed in list.
61+
*/
62+
select.multiTester = function multiTester(list) {
63+
var testers = [];
64+
var xmin = isPointSelectionDef(list[0]) ? 0 : list[0][0][0];
65+
var xmax = xmin;
66+
var ymin = isPointSelectionDef(list[0]) ? 0 : list[0][0][1];
67+
var ymax = ymin;
68+
69+
for(var i = 0; i < list.length; i++) {
70+
if(isPointSelectionDef(list[i])) {
71+
testers.push(pointNumberTester(list[i]));
72+
} else {
73+
var tester = polygon.tester(list[i]);
74+
tester.subtract = list[i].subtract;
75+
testers.push(tester);
76+
xmin = Math.min(xmin, tester.xmin);
77+
xmax = Math.max(xmax, tester.xmax);
78+
ymin = Math.min(ymin, tester.ymin);
79+
ymax = Math.max(ymax, tester.ymax);
80+
}
81+
}
82+
83+
// TODO Consider making signature of contains more lean
84+
/**
85+
* Tests if the given point is within this tester.
86+
*
87+
* @param pt an object having an `x` and a `y` property defining the location
88+
* of the point
89+
* @param arg parameter to pass additional arguments down to wrapped testers
90+
* @param pointNumber the point number of the point
91+
* @param searchInfo identifies the trace the point is contained in
92+
* @return {boolean}
93+
*/
94+
function contains(pt, arg, pointNumber, searchInfo) {
95+
var yes = false;
96+
for(var i = 0; i < testers.length; i++) {
97+
if(testers[i].contains(pt, arg, pointNumber, searchInfo)) {
98+
// if contained by subtract tester - exclude the point
99+
yes = testers[i].subtract === false;
100+
}
101+
}
102+
103+
return yes;
104+
}
105+
106+
return {
107+
xmin: xmin,
108+
xmax: xmax,
109+
ymin: ymin,
110+
ymax: ymax,
111+
pts: [],
112+
contains: contains,
113+
isRect: false,
114+
degenerate: false
115+
};
116+
};

src/plots/cartesian/select.js

+23-39
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var Color = require('../../components/color');
1616
var Fx = require('../../components/fx');
1717

1818
var polygon = require('../../lib/polygon');
19+
var select = require('../../lib/select');
1920
var throttle = require('../../lib/throttle');
2021
var makeEventData = require('../../components/fx/helpers').makeEventData;
2122
var getFromId = require('./axis_ids').getFromId;
@@ -26,7 +27,8 @@ var MINSELECT = constants.MINSELECT;
2627

2728
var filteredPolygon = polygon.filter;
2829
var polygonTester = polygon.tester;
29-
var multipolygonTester = polygon.multitester;
30+
var pointSelectionDef = select.pointSelectionDef;
31+
var multiTester = select.multiTester;
3032

3133
function getAxId(ax) { return ax._id; }
3234

@@ -50,7 +52,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
5052
var allAxes = dragOptions.xaxes.concat(dragOptions.yaxes);
5153
var subtract = e.altKey;
5254

53-
var filterPoly, testPoly, mergedPolygons, currentPolygon;
55+
var filterPoly, selectionTester, mergedPolygons, currentPolygon;
5456
var i, searchInfo, eventData;
5557

5658
coerceSelectionsCache(e, gd, dragOptions);
@@ -185,14 +187,14 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
185187
}
186188

187189
// create outline & tester
188-
if(dragOptions.polygons && dragOptions.polygons.length) {
190+
if(dragOptions.selectionDefs && dragOptions.selectionDefs.length) {
189191
mergedPolygons = mergePolygons(dragOptions.mergedPolygons, currentPolygon, subtract);
190192
currentPolygon.subtract = subtract;
191-
testPoly = multipolygonTester(dragOptions.polygons.concat([currentPolygon]));
193+
selectionTester = multiTester(dragOptions.selectionDefs.concat([currentPolygon]));
192194
}
193195
else {
194196
mergedPolygons = [currentPolygon];
195-
testPoly = polygonTester(currentPolygon);
197+
selectionTester = polygonTester(currentPolygon);
196198
}
197199

198200
// draw selection
@@ -209,7 +211,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
209211
for(i = 0; i < searchTraces.length; i++) {
210212
searchInfo = searchTraces[i];
211213

212-
traceSelection = searchInfo._module.selectPoints(searchInfo, testPoly);
214+
traceSelection = searchInfo._module.selectPoints(searchInfo, selectionTester);
213215
traceSelections.push(traceSelection);
214216

215217
thisSelection = fillSelectionItem(traceSelection, searchInfo);
@@ -276,10 +278,10 @@ function prepSelect(e, startX, startY, dragOptions, mode) {
276278
throttle.clear(throttleID);
277279
dragOptions.gd.emit('plotly_selected', eventData);
278280

279-
if(currentPolygon && dragOptions.polygons) {
281+
if(currentPolygon && dragOptions.selectionDefs) {
280282
// save last polygons
281283
currentPolygon.subtract = subtract;
282-
dragOptions.polygons.push(currentPolygon);
284+
dragOptions.selectionDefs.push(currentPolygon);
283285

284286
// we have to keep reference to arrays container
285287
dragOptions.mergedPolygons.length = 0;
@@ -296,8 +298,8 @@ function selectOnClick(evt, gd, xAxes, yAxes, subplot, dragOptions, polygonOutli
296298
var selection = [];
297299
var searchTraces;
298300
var searchInfo;
299-
var currentPolygon;
300-
var testPoly;
301+
var currentSelectionDef;
302+
var selectionTester;
301303
var traceSelection;
302304
var thisTracesSelection;
303305
var pointOrBinSelected;
@@ -338,13 +340,13 @@ function selectOnClick(evt, gd, xAxes, yAxes, subplot, dragOptions, polygonOutli
338340
(pointOrBinSelected !== undefined ?
339341
pointOrBinSelected :
340342
isPointOrBinSelected(clickedPtInfo));
341-
currentPolygon = createPtNumTester(clickedPtInfo.pointNumber, clickedPtInfo.searchInfo, subtract);
343+
currentSelectionDef = pointSelectionDef(clickedPtInfo.pointNumber, clickedPtInfo.searchInfo, subtract);
342344

343-
var concatenatedPolygons = dragOptions.polygons.concat([currentPolygon]);
344-
testPoly = multipolygonTester(concatenatedPolygons);
345+
var allSelectionDefs = dragOptions.selectionDefs.concat([currentSelectionDef]);
346+
selectionTester = multiTester(allSelectionDefs);
345347

346348
for(i = 0; i < searchTraces.length; i++) {
347-
traceSelection = searchTraces[i]._module.selectPoints(searchTraces[i], testPoly);
349+
traceSelection = searchTraces[i]._module.selectPoints(searchTraces[i], selectionTester);
348350
thisTracesSelection = fillSelectionItem(traceSelection, searchTraces[i]);
349351

350352
if(selection.length) {
@@ -358,8 +360,8 @@ function selectOnClick(evt, gd, xAxes, yAxes, subplot, dragOptions, polygonOutli
358360
eventData = {points: selection};
359361
updateSelectedState(gd, searchTraces, eventData);
360362

361-
if(currentPolygon && dragOptions) {
362-
dragOptions.polygons.push(currentPolygon);
363+
if(currentSelectionDef && dragOptions) {
364+
dragOptions.selectionDefs.push(currentSelectionDef);
363365
}
364366

365367
if(polygonOutlines) drawSelection(dragOptions.mergedPolygons, polygonOutlines);
@@ -384,11 +386,11 @@ function coerceSelectionsCache(evt, gd, dragOptions) {
384386
if(
385387
selectingOnSameSubplot &&
386388
(evt.shiftKey || evt.altKey) &&
387-
(plotinfo.selection && plotinfo.selection.polygons) &&
388-
!dragOptions.polygons
389+
(plotinfo.selection && plotinfo.selection.selectionDefs) &&
390+
!dragOptions.selectionDefs
389391
) {
390-
// take over selection polygons from prev mode, if any
391-
dragOptions.polygons = plotinfo.selection.polygons;
392+
// take over selection definitions from prev mode, if any
393+
dragOptions.selectionDefs = plotinfo.selection.selectionDefs;
392394
dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons;
393395
} else if(
394396
(!evt.shiftKey && !evt.altKey) ||
@@ -408,7 +410,7 @@ function clearSelectionsCache(dragOptions) {
408410
var plotinfo = dragOptions.plotinfo;
409411

410412
plotinfo.selection = {};
411-
plotinfo.selection.polygons = dragOptions.polygons = [];
413+
plotinfo.selection.selectionDefs = dragOptions.selectionDefs = [];
412414
plotinfo.selection.mergedPolygons = dragOptions.mergedPolygons = [];
413415
}
414416

@@ -515,24 +517,6 @@ function extractClickedPtInfo(hoverData, searchTraces) {
515517
};
516518
}
517519

518-
function createPtNumTester(wantedPointNumber, wantedSearchInfo, subtract) {
519-
return {
520-
xmin: 0,
521-
xmax: 0,
522-
ymin: 0,
523-
ymax: 0,
524-
pts: [],
525-
// TODO Consider making signature of contains more lean
526-
contains: function(pt, omitFirstEdge, pointNumber, searchInfo) {
527-
return searchInfo.cd[0].trace._expandedIndex === wantedSearchInfo.cd[0].trace._expandedIndex &&
528-
pointNumber === wantedPointNumber;
529-
},
530-
isRect: false,
531-
degenerate: false,
532-
subtract: subtract
533-
};
534-
}
535-
536520
function isPointOrBinSelected(clickedPtInfo) {
537521
var trace = clickedPtInfo.searchInfo.cd[0].trace;
538522
var ptNum = clickedPtInfo.pointNumber;

src/traces/bar/select.js

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

99
'use strict';
1010

11-
module.exports = function selectPoints(searchInfo, polygon) {
11+
module.exports = function selectPoints(searchInfo, selectionTester) {
1212
var cd = searchInfo.cd;
1313
var xa = searchInfo.xaxis;
1414
var ya = searchInfo.yaxis;
1515
var selection = [];
1616
var i;
1717

18-
if(polygon === false) {
18+
if(selectionTester === false) {
1919
// clear selection
2020
for(i = 0; i < cd.length; i++) {
2121
cd[i].selected = 0;
@@ -24,7 +24,7 @@ module.exports = function selectPoints(searchInfo, polygon) {
2424
for(i = 0; i < cd.length; i++) {
2525
var di = cd[i];
2626

27-
if(polygon.contains(di.ct, false, i, searchInfo)) {
27+
if(selectionTester.contains(di.ct, false, i, searchInfo)) {
2828
selection.push({
2929
pointNumber: i,
3030
x: xa.c2d(di.x),

src/traces/box/select.js

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

99
'use strict';
1010

11-
module.exports = function selectPoints(searchInfo, polygon) {
11+
module.exports = function selectPoints(searchInfo, selectionTester) {
1212
var cd = searchInfo.cd;
1313
var xa = searchInfo.xaxis;
1414
var ya = searchInfo.yaxis;
1515
var selection = [];
1616
var i, j;
1717

18-
if(polygon === false) {
18+
if(selectionTester === false) {
1919
for(i = 0; i < cd.length; i++) {
2020
for(j = 0; j < (cd[i].pts || []).length; j++) {
2121
// clear selection
@@ -29,7 +29,7 @@ module.exports = function selectPoints(searchInfo, polygon) {
2929
var x = xa.c2p(pt.x);
3030
var y = ya.c2p(pt.y);
3131

32-
if(polygon.contains([x, y], null, pt.i, searchInfo)) {
32+
if(selectionTester.contains([x, y], null, pt.i, searchInfo)) {
3333
selection.push({
3434
pointNumber: pt.i,
3535
x: xa.c2d(pt.x),

src/traces/choropleth/select.js

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

99
'use strict';
1010

11-
module.exports = function selectPoints(searchInfo, polygon) {
11+
module.exports = function selectPoints(searchInfo, selectionTester) {
1212
var cd = searchInfo.cd;
1313
var xa = searchInfo.xaxis;
1414
var ya = searchInfo.yaxis;
1515
var selection = [];
1616

1717
var i, di, ct, x, y;
1818

19-
if(polygon === false) {
19+
if(selectionTester === false) {
2020
for(i = 0; i < cd.length; i++) {
2121
cd[i].selected = 0;
2222
}
@@ -30,7 +30,7 @@ module.exports = function selectPoints(searchInfo, polygon) {
3030
x = xa.c2p(ct);
3131
y = ya.c2p(ct);
3232

33-
if(polygon.contains([x, y], null, i, searchInfo)) {
33+
if(selectionTester.contains([x, y], null, i, searchInfo)) {
3434
selection.push({
3535
pointNumber: i,
3636
lon: ct[0],

0 commit comments

Comments
 (0)