@@ -206,137 +206,156 @@ function getInterval(d, y) {
206
206
return out ;
207
207
}
208
208
209
+ function dragstart ( lThis , d ) {
210
+ d3 . event . sourceEvent . stopPropagation ( ) ;
211
+ var y = d . height - d3 . mouse ( lThis ) [ 1 ] - 2 * c . verticalPadding ;
212
+ var unitLocation = d . unitToPaddedPx . invert ( y ) ;
213
+ var b = d . brush ;
214
+ var interval = getInterval ( d , y ) ;
215
+ var unitRange = interval . interval ;
216
+ var s = b . svgBrush ;
217
+ s . wasDragged = false ; // we start assuming there won't be a drag - useful for reset
218
+ s . grabbingBar = interval . region === 'ns' ;
219
+ if ( s . grabbingBar ) {
220
+ var pixelRange = unitRange . map ( d . unitToPaddedPx ) ;
221
+ s . grabPoint = y - pixelRange [ 0 ] - c . verticalPadding ;
222
+ s . barLength = pixelRange [ 1 ] - pixelRange [ 0 ] ;
223
+ }
224
+ s . clickableOrdinalRange = interval . clickableOrdinalRange ;
225
+ s . stayingIntervals = ( d . multiselect && b . filterSpecified ) ? b . filter . getConsolidated ( ) : [ ] ;
226
+ if ( unitRange ) {
227
+ s . stayingIntervals = s . stayingIntervals . filter ( function ( int2 ) {
228
+ return int2 [ 0 ] !== unitRange [ 0 ] && int2 [ 1 ] !== unitRange [ 1 ] ;
229
+ } ) ;
230
+ }
231
+ s . startExtent = interval . region ? unitRange [ interval . region === 's' ? 1 : 0 ] : unitLocation ;
232
+ d . parent . inBrushDrag = true ;
233
+ s . brushStartCallback ( ) ;
234
+ }
235
+
236
+ function drag ( lThis , d ) {
237
+ d3 . event . sourceEvent . stopPropagation ( ) ;
238
+ var y = d . height - d3 . mouse ( lThis ) [ 1 ] - 2 * c . verticalPadding ;
239
+ var s = d . brush . svgBrush ;
240
+ s . wasDragged = true ;
241
+ s . _dragging = true ;
242
+
243
+ if ( s . grabbingBar ) { // moving the bar
244
+ s . newExtent = [ y - s . grabPoint , y + s . barLength - s . grabPoint ] . map ( d . unitToPaddedPx . invert ) ;
245
+ } else { // south/north drag or new bar creation
246
+ s . newExtent = [ s . startExtent , d . unitToPaddedPx . invert ( y ) ] . sort ( sortAsc ) ;
247
+ }
248
+
249
+ d . brush . filterSpecified = true ;
250
+ s . extent = s . stayingIntervals . concat ( [ s . newExtent ] ) ;
251
+ s . brushCallback ( d ) ;
252
+ renderHighlight ( lThis . parentNode ) ;
253
+ }
254
+
255
+ function dragend ( lThis , d ) {
256
+ var brush = d . brush ;
257
+ var filter = brush . filter ;
258
+ var s = brush . svgBrush ;
259
+
260
+ if ( ! s . _dragging ) { // i.e. click
261
+ // mock zero drag
262
+ mousemove ( lThis , d ) ;
263
+ drag ( lThis , d ) ;
264
+ // remember it is a click not a drag
265
+ d . brush . svgBrush . wasDragged = false ;
266
+ }
267
+ s . _dragging = false ;
268
+
269
+ var e = d3 . event ;
270
+ e . sourceEvent . stopPropagation ( ) ;
271
+ var grabbingBar = s . grabbingBar ;
272
+ s . grabbingBar = false ;
273
+ s . grabLocation = undefined ;
274
+ d . parent . inBrushDrag = false ;
275
+ clearCursor ( ) ; // instead of clearing, a nicer thing would be to set it according to current location
276
+ if ( ! s . wasDragged ) { // a click+release on the same spot (ie. w/o dragging) means a bar or full reset
277
+ s . wasDragged = undefined ; // logic-wise unneeded, just shows `wasDragged` has no longer a meaning
278
+ if ( s . clickableOrdinalRange ) {
279
+ if ( brush . filterSpecified && d . multiselect ) {
280
+ s . extent . push ( s . clickableOrdinalRange ) ;
281
+ } else {
282
+ s . extent = [ s . clickableOrdinalRange ] ;
283
+ brush . filterSpecified = true ;
284
+ }
285
+ } else if ( grabbingBar ) {
286
+ s . extent = s . stayingIntervals ;
287
+ if ( s . extent . length === 0 ) {
288
+ brushClear ( brush ) ;
289
+ }
290
+ } else {
291
+ brushClear ( brush ) ;
292
+ }
293
+ s . brushCallback ( d ) ;
294
+ renderHighlight ( lThis . parentNode ) ;
295
+ s . brushEndCallback ( brush . filterSpecified ? filter . getConsolidated ( ) : [ ] ) ;
296
+ return ; // no need to fuse intervals or snap to ordinals, so we can bail early
297
+ }
298
+
299
+ var mergeIntervals = function ( ) {
300
+ // Key piece of logic: once the button is released, possibly overlapping intervals will be fused:
301
+ // Here it's done immediately on click release while on ordinal snap transition it's done at the end
302
+ filter . set ( filter . getConsolidated ( ) ) ;
303
+ } ;
304
+
305
+ if ( d . ordinal ) {
306
+ var a = d . unitTickvals ;
307
+ if ( a [ a . length - 1 ] < a [ 0 ] ) a . reverse ( ) ;
308
+ s . newExtent = [
309
+ ordinalScaleSnap ( 0 , a , s . newExtent [ 0 ] , s . stayingIntervals ) ,
310
+ ordinalScaleSnap ( 1 , a , s . newExtent [ 1 ] , s . stayingIntervals )
311
+ ] ;
312
+ var hasNewExtent = s . newExtent [ 1 ] > s . newExtent [ 0 ] ;
313
+ s . extent = s . stayingIntervals . concat ( hasNewExtent ? [ s . newExtent ] : [ ] ) ;
314
+ if ( ! s . extent . length ) {
315
+ brushClear ( brush ) ;
316
+ }
317
+ s . brushCallback ( d ) ;
318
+ if ( hasNewExtent ) {
319
+ // merging intervals post the snap tween
320
+ renderHighlight ( lThis . parentNode , mergeIntervals ) ;
321
+ } else {
322
+ // if no new interval, don't animate, just redraw the highlight immediately
323
+ mergeIntervals ( ) ;
324
+ renderHighlight ( lThis . parentNode ) ;
325
+ }
326
+ } else {
327
+ mergeIntervals ( ) ; // merging intervals immediately
328
+ }
329
+ s . brushEndCallback ( brush . filterSpecified ? filter . getConsolidated ( ) : [ ] ) ;
330
+ }
331
+
332
+ function mousemove ( lThis , d ) {
333
+ var y = d . height - d3 . mouse ( lThis ) [ 1 ] - 2 * c . verticalPadding ;
334
+ var interval = getInterval ( d , y ) ;
335
+
336
+ var cursor = 'crosshair' ;
337
+ if ( interval . clickableOrdinalRange ) cursor = 'pointer' ;
338
+ else if ( interval . region ) cursor = interval . region + '-resize' ;
339
+ d3 . select ( document . body )
340
+ . style ( 'cursor' , cursor ) ;
341
+ }
342
+
209
343
function attachDragBehavior ( selection ) {
210
344
// There's some fiddling with pointer cursor styling so that the cursor preserves its shape while dragging a brush
211
345
// even if the cursor strays from the interacting bar, which is bound to happen as bars are thin and the user
212
346
// will inevitably leave the hotspot strip. In this regard, it does something similar to what the D3 brush would do.
213
347
selection
214
348
. on ( 'mousemove' , function ( d ) {
215
349
d3 . event . preventDefault ( ) ;
216
- if ( ! d . parent . inBrushDrag ) {
217
- var y = d . height - d3 . mouse ( this ) [ 1 ] - 2 * c . verticalPadding ;
218
- var interval = getInterval ( d , y ) ;
219
-
220
- var cursor = 'crosshair' ;
221
- if ( interval . clickableOrdinalRange ) cursor = 'pointer' ;
222
- else if ( interval . region ) cursor = interval . region + '-resize' ;
223
- d3 . select ( document . body )
224
- . style ( 'cursor' , cursor ) ;
225
- }
350
+ if ( ! d . parent . inBrushDrag ) mousemove ( this , d ) ;
226
351
} )
227
352
. on ( 'mouseleave' , function ( d ) {
228
353
if ( ! d . parent . inBrushDrag ) clearCursor ( ) ;
229
354
} )
230
355
. call ( d3 . behavior . drag ( )
231
- . on ( 'dragstart' , function ( d ) {
232
- d3 . event . sourceEvent . stopPropagation ( ) ;
233
- var y = d . height - d3 . mouse ( this ) [ 1 ] - 2 * c . verticalPadding ;
234
- var unitLocation = d . unitToPaddedPx . invert ( y ) ;
235
- var b = d . brush ;
236
- var interval = getInterval ( d , y ) ;
237
- var unitRange = interval . interval ;
238
- var s = b . svgBrush ;
239
- s . wasDragged = false ; // we start assuming there won't be a drag - useful for reset
240
- s . grabbingBar = interval . region === 'ns' ;
241
- if ( s . grabbingBar ) {
242
- var pixelRange = unitRange . map ( d . unitToPaddedPx ) ;
243
- s . grabPoint = y - pixelRange [ 0 ] - c . verticalPadding ;
244
- s . barLength = pixelRange [ 1 ] - pixelRange [ 0 ] ;
245
- }
246
- s . clickableOrdinalRange = interval . clickableOrdinalRange ;
247
- s . stayingIntervals = ( d . multiselect && b . filterSpecified ) ? b . filter . getConsolidated ( ) : [ ] ;
248
- if ( unitRange ) {
249
- s . stayingIntervals = s . stayingIntervals . filter ( function ( int2 ) {
250
- return int2 [ 0 ] !== unitRange [ 0 ] && int2 [ 1 ] !== unitRange [ 1 ] ;
251
- } ) ;
252
- }
253
- s . startExtent = interval . region ? unitRange [ interval . region === 's' ? 1 : 0 ] : unitLocation ;
254
- d . parent . inBrushDrag = true ;
255
- s . brushStartCallback ( ) ;
256
- } )
257
- . on ( 'drag' , function ( d ) {
258
- d3 . event . sourceEvent . stopPropagation ( ) ;
259
- var y = d . height - d3 . mouse ( this ) [ 1 ] - 2 * c . verticalPadding ;
260
- var s = d . brush . svgBrush ;
261
- s . wasDragged = true ;
262
-
263
- if ( s . grabbingBar ) { // moving the bar
264
- s . newExtent = [ y - s . grabPoint , y + s . barLength - s . grabPoint ] . map ( d . unitToPaddedPx . invert ) ;
265
- } else { // south/north drag or new bar creation
266
- s . newExtent = [ s . startExtent , d . unitToPaddedPx . invert ( y ) ] . sort ( sortAsc ) ;
267
- }
268
-
269
- d . brush . filterSpecified = true ;
270
- s . extent = s . stayingIntervals . concat ( [ s . newExtent ] ) ;
271
- s . brushCallback ( d ) ;
272
- renderHighlight ( this . parentNode ) ;
273
- } )
274
- . on ( 'dragend' , function ( d ) {
275
- var e = d3 . event ;
276
- e . sourceEvent . stopPropagation ( ) ;
277
- var brush = d . brush ;
278
- var filter = brush . filter ;
279
- var s = brush . svgBrush ;
280
- var grabbingBar = s . grabbingBar ;
281
- s . grabbingBar = false ;
282
- s . grabLocation = undefined ;
283
- d . parent . inBrushDrag = false ;
284
- clearCursor ( ) ; // instead of clearing, a nicer thing would be to set it according to current location
285
- if ( ! s . wasDragged ) { // a click+release on the same spot (ie. w/o dragging) means a bar or full reset
286
- s . wasDragged = undefined ; // logic-wise unneeded, just shows `wasDragged` has no longer a meaning
287
- if ( s . clickableOrdinalRange ) {
288
- if ( brush . filterSpecified && d . multiselect ) {
289
- s . extent . push ( s . clickableOrdinalRange ) ;
290
- } else {
291
- s . extent = [ s . clickableOrdinalRange ] ;
292
- brush . filterSpecified = true ;
293
- }
294
- } else if ( grabbingBar ) {
295
- s . extent = s . stayingIntervals ;
296
- if ( s . extent . length === 0 ) {
297
- brushClear ( brush ) ;
298
- }
299
- } else {
300
- brushClear ( brush ) ;
301
- }
302
- s . brushCallback ( d ) ;
303
- renderHighlight ( this . parentNode ) ;
304
- s . brushEndCallback ( brush . filterSpecified ? filter . getConsolidated ( ) : [ ] ) ;
305
- return ; // no need to fuse intervals or snap to ordinals, so we can bail early
306
- }
307
-
308
- var mergeIntervals = function ( ) {
309
- // Key piece of logic: once the button is released, possibly overlapping intervals will be fused:
310
- // Here it's done immediately on click release while on ordinal snap transition it's done at the end
311
- filter . set ( filter . getConsolidated ( ) ) ;
312
- } ;
313
-
314
- if ( d . ordinal ) {
315
- var a = d . unitTickvals ;
316
- if ( a [ a . length - 1 ] < a [ 0 ] ) a . reverse ( ) ;
317
- s . newExtent = [
318
- ordinalScaleSnap ( 0 , a , s . newExtent [ 0 ] , s . stayingIntervals ) ,
319
- ordinalScaleSnap ( 1 , a , s . newExtent [ 1 ] , s . stayingIntervals )
320
- ] ;
321
- var hasNewExtent = s . newExtent [ 1 ] > s . newExtent [ 0 ] ;
322
- s . extent = s . stayingIntervals . concat ( hasNewExtent ? [ s . newExtent ] : [ ] ) ;
323
- if ( ! s . extent . length ) {
324
- brushClear ( brush ) ;
325
- }
326
- s . brushCallback ( d ) ;
327
- if ( hasNewExtent ) {
328
- // merging intervals post the snap tween
329
- renderHighlight ( this . parentNode , mergeIntervals ) ;
330
- } else {
331
- // if no new interval, don't animate, just redraw the highlight immediately
332
- mergeIntervals ( ) ;
333
- renderHighlight ( this . parentNode ) ;
334
- }
335
- } else {
336
- mergeIntervals ( ) ; // merging intervals immediately
337
- }
338
- s . brushEndCallback ( brush . filterSpecified ? filter . getConsolidated ( ) : [ ] ) ;
339
- } )
356
+ . on ( 'dragstart' , function ( d ) { dragstart ( this , d ) ; } )
357
+ . on ( 'drag' , function ( d ) { drag ( this , d ) ; } )
358
+ . on ( 'dragend' , function ( d ) { dragend ( this , d ) ; } )
340
359
) ;
341
360
}
342
361
0 commit comments