@@ -206,137 +206,156 @@ function getInterval(d, y) {
206206 return out ;
207207}
208208
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+
209343function attachDragBehavior ( selection ) {
210344 // There's some fiddling with pointer cursor styling so that the cursor preserves its shape while dragging a brush
211345 // even if the cursor strays from the interacting bar, which is bound to happen as bars are thin and the user
212346 // will inevitably leave the hotspot strip. In this regard, it does something similar to what the D3 brush would do.
213347 selection
214348 . on ( 'mousemove' , function ( d ) {
215349 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 ) ;
226351 } )
227352 . on ( 'mouseleave' , function ( d ) {
228353 if ( ! d . parent . inBrushDrag ) clearCursor ( ) ;
229354 } )
230355 . 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 ) ; } )
340359 ) ;
341360}
342361
0 commit comments