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