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 ; }
@@ -39,10 +41,24 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
39
41
xAxisIds = dragOptions . xaxes . map ( getAxId ) ,
40
42
yAxisIds = dragOptions . yaxes . map ( getAxId ) ,
41
43
allAxes = dragOptions . xaxes . concat ( dragOptions . yaxes ) ,
42
- pts ;
44
+ filterPoly , testPoly , mergedPolygons , currentPolygon ,
45
+ subtract = e . altKey ;
46
+
47
+
48
+ // take over selection polygons from prev mode, if any
49
+ if ( ( e . shiftKey || e . altKey ) && ( plotinfo . selection && plotinfo . selection . polygons ) && ! dragOptions . polygons ) {
50
+ dragOptions . polygons = plotinfo . selection . polygons ;
51
+ dragOptions . mergedPolygons = plotinfo . selection . mergedPolygons ;
52
+ }
53
+ // create new polygons, if shift mode
54
+ else if ( ( ! e . shiftKey && ! e . altKey ) || ( ( e . shiftKey || e . altKey ) && ! plotinfo . selection ) ) {
55
+ plotinfo . selection = { } ;
56
+ plotinfo . selection . polygons = dragOptions . polygons = [ ] ;
57
+ plotinfo . selection . mergedPolygons = dragOptions . mergedPolygons = [ ] ;
58
+ }
43
59
44
60
if ( mode === 'lasso' ) {
45
- pts = filteredPolygon ( [ [ x0 , y0 ] ] , constants . BENDPX ) ;
61
+ filterPoly = filteredPolygon ( [ [ x0 , y0 ] ] , constants . BENDPX ) ;
46
62
}
47
63
48
64
var outlines = zoomLayer . selectAll ( 'path.select-outline' ) . data ( [ 1 , 2 ] ) ;
@@ -129,20 +145,18 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
129
145
}
130
146
} ;
131
147
} else {
132
- fillRangeItems = function ( eventData , poly , pts ) {
148
+ fillRangeItems = function ( eventData , currentPolygon , filterPoly ) {
133
149
var dataPts = eventData . lassoPoints = { } ;
134
150
135
151
for ( i = 0 ; i < allAxes . length ; i ++ ) {
136
152
var ax = allAxes [ i ] ;
137
- dataPts [ ax . _id ] = pts . filtered . map ( axValue ( ax ) ) ;
153
+ dataPts [ ax . _id ] = filterPoly . filtered . map ( axValue ( ax ) ) ;
138
154
}
139
155
} ;
140
156
}
141
157
}
142
158
143
159
dragOptions . moveFn = function ( dx0 , dy0 ) {
144
- var poly ;
145
-
146
160
x1 = Math . max ( 0 , Math . min ( pw , dx0 + x0 ) ) ;
147
161
y1 = Math . max ( 0 , Math . min ( ph , dy0 + y0 ) ) ;
148
162
@@ -152,46 +166,79 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
152
166
if ( mode === 'select' ) {
153
167
if ( dy < Math . min ( dx * 0.6 , MINSELECT ) ) {
154
168
// horizontal motion: make a vertical box
155
- poly = polygonTester ( [ [ x0 , 0 ] , [ x0 , ph ] , [ x1 , ph ] , [ x1 , 0 ] ] ) ;
169
+ currentPolygon = [ [ x0 , 0 ] , [ x0 , ph ] , [ x1 , ph ] , [ x1 , 0 ] ] ;
170
+ currentPolygon . xmin = Math . min ( x0 , x1 ) ;
171
+ currentPolygon . xmax = Math . max ( x0 , x1 ) ;
172
+ currentPolygon . ymin = Math . min ( 0 , ph ) ;
173
+ currentPolygon . ymax = Math . max ( 0 , ph ) ;
156
174
// extras to guide users in keeping a straight selection
157
- corners . attr ( 'd' , 'M' + poly . xmin + ',' + ( y0 - MINSELECT ) +
175
+ corners . attr ( 'd' , 'M' + currentPolygon . xmin + ',' + ( y0 - MINSELECT ) +
158
176
'h-4v' + ( 2 * MINSELECT ) + 'h4Z' +
159
- 'M' + ( poly . xmax - 1 ) + ',' + ( y0 - MINSELECT ) +
177
+ 'M' + ( currentPolygon . xmax - 1 ) + ',' + ( y0 - MINSELECT ) +
160
178
'h4v' + ( 2 * MINSELECT ) + 'h-4Z' ) ;
161
179
162
180
}
163
181
else if ( dx < Math . min ( dy * 0.6 , MINSELECT ) ) {
164
182
// vertical motion: make a horizontal box
165
- poly = polygonTester ( [ [ 0 , y0 ] , [ 0 , y1 ] , [ pw , y1 ] , [ pw , y0 ] ] ) ;
166
- corners . attr ( 'd' , 'M' + ( x0 - MINSELECT ) + ',' + poly . ymin +
183
+ currentPolygon = [ [ 0 , y0 ] , [ 0 , y1 ] , [ pw , y1 ] , [ pw , y0 ] ] ;
184
+ currentPolygon . xmin = Math . min ( 0 , pw ) ;
185
+ currentPolygon . xmax = Math . max ( 0 , pw ) ;
186
+ currentPolygon . ymin = Math . min ( y0 , y1 ) ;
187
+ currentPolygon . ymax = Math . max ( y0 , y1 ) ;
188
+ corners . attr ( 'd' , 'M' + ( x0 - MINSELECT ) + ',' + currentPolygon . ymin +
167
189
'v-4h' + ( 2 * MINSELECT ) + 'v4Z' +
168
- 'M' + ( x0 - MINSELECT ) + ',' + ( poly . ymax - 1 ) +
190
+ 'M' + ( x0 - MINSELECT ) + ',' + ( currentPolygon . ymax - 1 ) +
169
191
'v4h' + ( 2 * MINSELECT ) + 'v-4Z' ) ;
170
192
}
171
193
else {
172
194
// diagonal motion
173
- poly = polygonTester ( [ [ x0 , y0 ] , [ x0 , y1 ] , [ x1 , y1 ] , [ x1 , y0 ] ] ) ;
195
+ currentPolygon = [ [ x0 , y0 ] , [ x0 , y1 ] , [ x1 , y1 ] , [ x1 , y0 ] ] ;
196
+ currentPolygon . xmin = Math . min ( x0 , x1 ) ;
197
+ currentPolygon . xmax = Math . max ( x0 , x1 ) ;
198
+ currentPolygon . ymin = Math . min ( y0 , y1 ) ;
199
+ currentPolygon . ymax = Math . max ( y0 , y1 ) ;
174
200
corners . attr ( 'd' , 'M0,0Z' ) ;
175
201
}
176
- outlines . attr ( 'd' , 'M' + poly . xmin + ',' + poly . ymin +
177
- 'H' + ( poly . xmax - 1 ) + 'V' + ( poly . ymax - 1 ) +
178
- 'H' + poly . xmin + 'Z' ) ;
179
202
}
180
203
else if ( mode === 'lasso' ) {
181
- pts . addPt ( [ x1 , y1 ] ) ;
182
- poly = polygonTester ( pts . filtered ) ;
183
- outlines . attr ( 'd' , 'M' + pts . filtered . join ( 'L' ) + 'Z' ) ;
204
+ filterPoly . addPt ( [ x1 , y1 ] ) ;
205
+ currentPolygon = filterPoly . filtered ;
184
206
}
185
207
208
+ // create outline & tester
209
+ if ( dragOptions . polygons && dragOptions . polygons . length ) {
210
+ mergedPolygons = mergePolygons ( dragOptions . mergedPolygons , currentPolygon , subtract ) ;
211
+ currentPolygon . subtract = subtract ;
212
+ testPoly = multipolygonTester ( dragOptions . polygons . concat ( [ currentPolygon ] ) ) ;
213
+ }
214
+ else {
215
+ mergedPolygons = [ currentPolygon ] ;
216
+ testPoly = polygonTester ( currentPolygon ) ;
217
+ }
218
+
219
+ // draw selection
220
+ var paths = [ ] ;
221
+ for ( i = 0 ; i < mergedPolygons . length ; i ++ ) {
222
+ var ppts = mergedPolygons [ i ] ;
223
+ paths . push ( ppts . join ( 'L' ) + 'L' + ppts [ 0 ] ) ;
224
+ }
225
+ outlines . attr ( 'd' , 'M' + paths . join ( 'M' ) + 'Z' ) ;
226
+
186
227
throttle . throttle (
187
228
throttleID ,
188
229
constants . SELECTDELAY ,
189
230
function ( ) {
190
231
selection = [ ] ;
232
+
233
+ var traceSelections = [ ] , traceSelection ;
191
234
for ( i = 0 ; i < searchTraces . length ; i ++ ) {
192
235
searchInfo = searchTraces [ i ] ;
236
+
237
+ traceSelection = searchInfo . selectPoints ( searchInfo , testPoly ) ;
238
+ traceSelections . push ( traceSelection ) ;
239
+
193
240
var thisSelection = fillSelectionItem (
194
- searchInfo . selectPoints ( searchInfo , poly ) , searchInfo
241
+ traceSelection , searchInfo
195
242
) ;
196
243
if ( selection . length ) {
197
244
for ( var j = 0 ; j < thisSelection . length ; j ++ ) {
@@ -202,14 +249,15 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
202
249
}
203
250
204
251
eventData = { points : selection } ;
205
- fillRangeItems ( eventData , poly , pts ) ;
252
+ fillRangeItems ( eventData , currentPolygon , filterPoly ) ;
206
253
dragOptions . gd . emit ( 'plotly_selecting' , eventData ) ;
207
254
}
208
255
) ;
209
256
} ;
210
257
211
258
dragOptions . doneFn = function ( dragged , numclicks ) {
212
259
corners . remove ( ) ;
260
+
213
261
throttle . done ( throttleID ) . then ( function ( ) {
214
262
throttle . clear ( throttleID ) ;
215
263
@@ -226,10 +274,46 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
226
274
else {
227
275
dragOptions . gd . emit ( 'plotly_selected' , eventData ) ;
228
276
}
277
+
278
+ if ( currentPolygon && dragOptions . polygons ) {
279
+ // save last polygons
280
+ currentPolygon . subtract = subtract ;
281
+ dragOptions . polygons . push ( currentPolygon ) ;
282
+
283
+ // we have to keep reference to arrays container
284
+ dragOptions . mergedPolygons . length = 0 ;
285
+ [ ] . push . apply ( dragOptions . mergedPolygons , mergedPolygons ) ;
286
+ }
229
287
} ) ;
230
288
} ;
231
289
} ;
232
290
291
+ function mergePolygons ( list , poly , subtract ) {
292
+ var res ;
293
+
294
+ if ( subtract ) {
295
+ res = polybool . difference ( {
296
+ regions : list ,
297
+ inverted : false
298
+ } , {
299
+ regions : [ poly ] ,
300
+ inverted : false
301
+ } ) ;
302
+
303
+ return res . regions ;
304
+ }
305
+
306
+ res = polybool . union ( {
307
+ regions : list ,
308
+ inverted : false
309
+ } , {
310
+ regions : [ poly ] ,
311
+ inverted : false
312
+ } ) ;
313
+
314
+ return res . regions ;
315
+ }
316
+
233
317
function fillSelectionItem ( selection , searchInfo ) {
234
318
if ( Array . isArray ( selection ) ) {
235
319
var trace = searchInfo . cd [ 0 ] . trace ;
0 commit comments