9
9
10
10
'use strict' ;
11
11
12
+ var polybool = require ( 'polybooljs' ) ;
12
13
var polygon = require ( '../../lib/polygon' ) ;
13
14
var throttle = require ( '../../lib/throttle' ) ;
14
15
var color = require ( '../../components/color' ) ;
@@ -19,6 +20,7 @@ var constants = require('./constants');
19
20
20
21
var filteredPolygon = polygon . filter ;
21
22
var polygonTester = polygon . tester ;
23
+ var multipolygonTester = polygon . multitester ;
22
24
var MINSELECT = constants . MINSELECT ;
23
25
24
26
function getAxId ( ax ) { return ax . _id ; }
@@ -41,10 +43,24 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
41
43
xAxisIds = dragOptions . xaxes . map ( getAxId ) ,
42
44
yAxisIds = dragOptions . yaxes . map ( getAxId ) ,
43
45
allAxes = dragOptions . xaxes . concat ( dragOptions . yaxes ) ,
44
- pts ;
46
+ filterPoly , testPoly , mergedPolygons , currentPolygon ,
47
+ subtract = e . altKey ;
48
+
49
+
50
+ // take over selection polygons from prev mode, if any
51
+ if ( ( e . shiftKey || e . altKey ) && ( plotinfo . selection && plotinfo . selection . polygons ) && ! dragOptions . polygons ) {
52
+ dragOptions . polygons = plotinfo . selection . polygons ;
53
+ dragOptions . mergedPolygons = plotinfo . selection . mergedPolygons ;
54
+ }
55
+ // create new polygons, if shift mode
56
+ else if ( ( ! e . shiftKey && ! e . altKey ) || ( ( e . shiftKey || e . altKey ) && ! plotinfo . selection ) ) {
57
+ plotinfo . selection = { } ;
58
+ plotinfo . selection . polygons = dragOptions . polygons = [ ] ;
59
+ plotinfo . selection . mergedPolygons = dragOptions . mergedPolygons = [ ] ;
60
+ }
45
61
46
62
if ( mode === 'lasso' ) {
47
- pts = filteredPolygon ( [ [ x0 , y0 ] ] , constants . BENDPX ) ;
63
+ filterPoly = filteredPolygon ( [ [ x0 , y0 ] ] , constants . BENDPX ) ;
48
64
}
49
65
50
66
var outlines = zoomLayer . selectAll ( 'path.select-outline' ) . data ( [ 1 , 2 ] ) ;
@@ -132,20 +148,18 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
132
148
}
133
149
} ;
134
150
} else {
135
- fillRangeItems = function ( eventData , poly , pts ) {
151
+ fillRangeItems = function ( eventData , currentPolygon , filterPoly ) {
136
152
var dataPts = eventData . lassoPoints = { } ;
137
153
138
154
for ( i = 0 ; i < allAxes . length ; i ++ ) {
139
155
var ax = allAxes [ i ] ;
140
- dataPts [ ax . _id ] = pts . filtered . map ( axValue ( ax ) ) ;
156
+ dataPts [ ax . _id ] = filterPoly . filtered . map ( axValue ( ax ) ) ;
141
157
}
142
158
} ;
143
159
}
144
160
}
145
161
146
162
dragOptions . moveFn = function ( dx0 , dy0 ) {
147
- var poly ;
148
-
149
163
x1 = Math . max ( 0 , Math . min ( pw , dx0 + x0 ) ) ;
150
164
y1 = Math . max ( 0 , Math . min ( ph , dy0 + y0 ) ) ;
151
165
@@ -155,46 +169,79 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
155
169
if ( mode === 'select' ) {
156
170
if ( dy < Math . min ( dx * 0.6 , MINSELECT ) ) {
157
171
// horizontal motion: make a vertical box
158
- poly = polygonTester ( [ [ x0 , 0 ] , [ x0 , ph ] , [ x1 , ph ] , [ x1 , 0 ] ] ) ;
172
+ currentPolygon = [ [ x0 , 0 ] , [ x0 , ph ] , [ x1 , ph ] , [ x1 , 0 ] ] ;
173
+ currentPolygon . xmin = Math . min ( x0 , x1 ) ;
174
+ currentPolygon . xmax = Math . max ( x0 , x1 ) ;
175
+ currentPolygon . ymin = Math . min ( 0 , ph ) ;
176
+ currentPolygon . ymax = Math . max ( 0 , ph ) ;
159
177
// extras to guide users in keeping a straight selection
160
- corners . attr ( 'd' , 'M' + poly . xmin + ',' + ( y0 - MINSELECT ) +
178
+ corners . attr ( 'd' , 'M' + currentPolygon . xmin + ',' + ( y0 - MINSELECT ) +
161
179
'h-4v' + ( 2 * MINSELECT ) + 'h4Z' +
162
- 'M' + ( poly . xmax - 1 ) + ',' + ( y0 - MINSELECT ) +
180
+ 'M' + ( currentPolygon . xmax - 1 ) + ',' + ( y0 - MINSELECT ) +
163
181
'h4v' + ( 2 * MINSELECT ) + 'h-4Z' ) ;
164
182
165
183
}
166
184
else if ( dx < Math . min ( dy * 0.6 , MINSELECT ) ) {
167
185
// vertical motion: make a horizontal box
168
- poly = polygonTester ( [ [ 0 , y0 ] , [ 0 , y1 ] , [ pw , y1 ] , [ pw , y0 ] ] ) ;
169
- corners . attr ( 'd' , 'M' + ( x0 - MINSELECT ) + ',' + poly . ymin +
186
+ currentPolygon = [ [ 0 , y0 ] , [ 0 , y1 ] , [ pw , y1 ] , [ pw , y0 ] ] ;
187
+ currentPolygon . xmin = Math . min ( 0 , pw ) ;
188
+ currentPolygon . xmax = Math . max ( 0 , pw ) ;
189
+ currentPolygon . ymin = Math . min ( y0 , y1 ) ;
190
+ currentPolygon . ymax = Math . max ( y0 , y1 ) ;
191
+ corners . attr ( 'd' , 'M' + ( x0 - MINSELECT ) + ',' + currentPolygon . ymin +
170
192
'v-4h' + ( 2 * MINSELECT ) + 'v4Z' +
171
- 'M' + ( x0 - MINSELECT ) + ',' + ( poly . ymax - 1 ) +
193
+ 'M' + ( x0 - MINSELECT ) + ',' + ( currentPolygon . ymax - 1 ) +
172
194
'v4h' + ( 2 * MINSELECT ) + 'v-4Z' ) ;
173
195
}
174
196
else {
175
197
// diagonal motion
176
- poly = polygonTester ( [ [ x0 , y0 ] , [ x0 , y1 ] , [ x1 , y1 ] , [ x1 , y0 ] ] ) ;
198
+ currentPolygon = [ [ x0 , y0 ] , [ x0 , y1 ] , [ x1 , y1 ] , [ x1 , y0 ] ] ;
199
+ currentPolygon . xmin = Math . min ( x0 , x1 ) ;
200
+ currentPolygon . xmax = Math . max ( x0 , x1 ) ;
201
+ currentPolygon . ymin = Math . min ( y0 , y1 ) ;
202
+ currentPolygon . ymax = Math . max ( y0 , y1 ) ;
177
203
corners . attr ( 'd' , 'M0,0Z' ) ;
178
204
}
179
- outlines . attr ( 'd' , 'M' + poly . xmin + ',' + poly . ymin +
180
- 'H' + ( poly . xmax - 1 ) + 'V' + ( poly . ymax - 1 ) +
181
- 'H' + poly . xmin + 'Z' ) ;
182
205
}
183
206
else if ( mode === 'lasso' ) {
184
- pts . addPt ( [ x1 , y1 ] ) ;
185
- poly = polygonTester ( pts . filtered ) ;
186
- outlines . attr ( 'd' , 'M' + pts . filtered . join ( 'L' ) + 'Z' ) ;
207
+ filterPoly . addPt ( [ x1 , y1 ] ) ;
208
+ currentPolygon = filterPoly . filtered ;
187
209
}
188
210
211
+ // create outline & tester
212
+ if ( dragOptions . polygons && dragOptions . polygons . length ) {
213
+ mergedPolygons = mergePolygons ( dragOptions . mergedPolygons , currentPolygon , subtract ) ;
214
+ currentPolygon . subtract = subtract ;
215
+ testPoly = multipolygonTester ( dragOptions . polygons . concat ( [ currentPolygon ] ) ) ;
216
+ }
217
+ else {
218
+ mergedPolygons = [ currentPolygon ] ;
219
+ testPoly = polygonTester ( currentPolygon ) ;
220
+ }
221
+
222
+ // draw selection
223
+ var paths = [ ] ;
224
+ for ( i = 0 ; i < mergedPolygons . length ; i ++ ) {
225
+ var ppts = mergedPolygons [ i ] ;
226
+ paths . push ( ppts . join ( 'L' ) + 'L' + ppts [ 0 ] ) ;
227
+ }
228
+ outlines . attr ( 'd' , 'M' + paths . join ( 'M' ) + 'Z' ) ;
229
+
189
230
throttle . throttle (
190
231
throttleID ,
191
232
constants . SELECTDELAY ,
192
233
function ( ) {
193
234
selection = [ ] ;
235
+
236
+ var traceSelections = [ ] , traceSelection ;
194
237
for ( i = 0 ; i < searchTraces . length ; i ++ ) {
195
238
searchInfo = searchTraces [ i ] ;
239
+
240
+ traceSelection = searchInfo . selectPoints ( searchInfo , testPoly ) ;
241
+ traceSelections . push ( traceSelection ) ;
242
+
196
243
var thisSelection = fillSelectionItem (
197
- searchInfo . selectPoints ( searchInfo , poly ) , searchInfo
244
+ traceSelection , searchInfo
198
245
) ;
199
246
if ( selection . length ) {
200
247
for ( var j = 0 ; j < thisSelection . length ; j ++ ) {
@@ -206,14 +253,15 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
206
253
207
254
eventData = { points : selection } ;
208
255
updateSelectedState ( gd , searchTraces , eventData ) ;
209
- fillRangeItems ( eventData , poly , pts ) ;
256
+ fillRangeItems ( eventData , currentPolygon , filterPoly ) ;
210
257
dragOptions . gd . emit ( 'plotly_selecting' , eventData ) ;
211
258
}
212
259
) ;
213
260
} ;
214
261
215
262
dragOptions . doneFn = function ( dragged , numclicks ) {
216
263
corners . remove ( ) ;
264
+
217
265
throttle . done ( throttleID ) . then ( function ( ) {
218
266
throttle . clear ( throttleID ) ;
219
267
@@ -231,6 +279,16 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
231
279
else {
232
280
dragOptions . gd . emit ( 'plotly_selected' , eventData ) ;
233
281
}
282
+
283
+ if ( currentPolygon && dragOptions . polygons ) {
284
+ // save last polygons
285
+ currentPolygon . subtract = subtract ;
286
+ dragOptions . polygons . push ( currentPolygon ) ;
287
+
288
+ // we have to keep reference to arrays container
289
+ dragOptions . mergedPolygons . length = 0 ;
290
+ [ ] . push . apply ( dragOptions . mergedPolygons , mergedPolygons ) ;
291
+ }
234
292
} ) ;
235
293
} ;
236
294
} ;
@@ -269,6 +327,32 @@ function updateSelectedState(gd, searchTraces, eventData) {
269
327
}
270
328
}
271
329
330
+ function mergePolygons ( list , poly , subtract ) {
331
+ var res ;
332
+
333
+ if ( subtract ) {
334
+ res = polybool . difference ( {
335
+ regions : list ,
336
+ inverted : false
337
+ } , {
338
+ regions : [ poly ] ,
339
+ inverted : false
340
+ } ) ;
341
+
342
+ return res . regions ;
343
+ }
344
+
345
+ res = polybool . union ( {
346
+ regions : list ,
347
+ inverted : false
348
+ } , {
349
+ regions : [ poly ] ,
350
+ inverted : false
351
+ } ) ;
352
+
353
+ return res . regions ;
354
+ }
355
+
272
356
function fillSelectionItem ( selection , searchInfo ) {
273
357
if ( Array . isArray ( selection ) ) {
274
358
var trace = searchInfo . cd [ 0 ] . trace ;
0 commit comments