@@ -23,6 +23,8 @@ var repeat = gup.repeat;
2323var unwrap = gup . unwrap ;
2424var interpolateNumber = require ( 'd3-interpolate' ) . interpolateNumber ;
2525
26+ var Registry = require ( '../../registry' ) ;
27+
2628// view models
2729
2830function sankeyModel ( layout , d , traceIndex ) {
@@ -67,13 +69,17 @@ function sankeyModel(layout, d, traceIndex) {
6769 Lib . warn ( 'node.pad was reduced to ' , sankey . nodePadding ( ) , ' to fit within the figure.' ) ;
6870 }
6971
72+ // Counters for nested loops
73+ var i , j , k ;
74+
7075 // Create transient nodes for animations
7176 for ( var nodePointNumber in calcData . _groupLookup ) {
7277 var groupIndex = parseInt ( calcData . _groupLookup [ nodePointNumber ] ) ;
7378
7479 // Find node representing groupIndex
7580 var groupingNode ;
76- for ( var i = 0 ; i < graph . nodes . length ; i ++ ) {
81+
82+ for ( i = 0 ; i < graph . nodes . length ; i ++ ) {
7783 if ( graph . nodes [ i ] . pointNumber === groupIndex ) {
7884 groupingNode = graph . nodes [ i ] ;
7985 break ;
@@ -98,7 +104,6 @@ function sankeyModel(layout, d, traceIndex) {
98104 }
99105
100106 function computeLinkConcentrations ( ) {
101- var i , j , k ;
102107 for ( i = 0 ; i < graph . nodes . length ; i ++ ) {
103108 var node = graph . nodes [ i ] ;
104109 // Links connecting the same two nodes are part of a flow
@@ -163,6 +168,93 @@ function sankeyModel(layout, d, traceIndex) {
163168 }
164169 computeLinkConcentrations ( ) ;
165170
171+ // Push any overlapping nodes down.
172+ function resolveCollisionsTopToBottom ( columns ) {
173+ columns . forEach ( function ( nodes ) {
174+ var node ;
175+ var dy ;
176+ var y = 0 ;
177+ var n = nodes . length ;
178+ var i ;
179+ nodes . sort ( function ( a , b ) {
180+ return a . y0 - b . y0 ;
181+ } ) ;
182+ for ( i = 0 ; i < n ; ++ i ) {
183+ node = nodes [ i ] ;
184+ if ( node . y0 >= y ) {
185+ // No overlap
186+ } else {
187+ dy = ( y - node . y0 ) ;
188+ if ( dy > 1e-6 ) node . y0 += dy , node . y1 += dy ;
189+ }
190+ y = node . y1 + nodePad ;
191+ }
192+ } ) ;
193+ }
194+
195+ // Group nodes into columns based on their x position
196+ function snapToColumns ( nodes ) {
197+ // Sort nodes by x position
198+ var orderedNodes = nodes . map ( function ( n , i ) {
199+ return {
200+ x0 : n . x0 ,
201+ index : i
202+ } ;
203+ } )
204+ . sort ( function ( a , b ) {
205+ return a . x0 - b . x0 ;
206+ } ) ;
207+
208+ var columns = [ ] ;
209+ var colNumber = - 1 ;
210+ var colX ; // Position of column
211+ var lastX = - Infinity ; // Position of last node
212+ var dx ;
213+ for ( i = 0 ; i < orderedNodes . length ; i ++ ) {
214+ var node = nodes [ orderedNodes [ i ] . index ] ;
215+ // If the node does not overlap with the last one
216+ if ( node . x0 > lastX + nodeThickness ) {
217+ // Start a new column
218+ colNumber += 1 ;
219+ colX = node . x0 ;
220+ }
221+ lastX = node . x0 ;
222+
223+ // Add node to its associated column
224+ if ( ! columns [ colNumber ] ) columns [ colNumber ] = [ ] ;
225+ columns [ colNumber ] . push ( node ) ;
226+
227+ // Change node's x position to align it with its column
228+ dx = colX - node . x0 ;
229+ node . x0 += dx , node . x1 += dx ;
230+
231+ }
232+ return columns ;
233+ }
234+
235+ // Force node position
236+ if ( trace . node . x . length && trace . node . y . length ) {
237+ for ( i = 0 ; i < Math . min ( trace . node . x . length , trace . node . y . length , graph . nodes . length ) ; i ++ ) {
238+ if ( trace . node . x [ i ] && trace . node . y [ i ] ) {
239+ var pos = [ trace . node . x [ i ] * width , trace . node . y [ i ] * height ] ;
240+ graph . nodes [ i ] . x0 = pos [ 0 ] - nodeThickness / 2 ;
241+ graph . nodes [ i ] . x1 = pos [ 0 ] + nodeThickness / 2 ;
242+
243+ var nodeHeight = graph . nodes [ i ] . y1 - graph . nodes [ i ] . y0 ;
244+ graph . nodes [ i ] . y0 = pos [ 1 ] - nodeHeight / 2 ;
245+ graph . nodes [ i ] . y1 = pos [ 1 ] + nodeHeight / 2 ;
246+ }
247+ }
248+ if ( trace . arrangement === 'snap' ) {
249+ nodes = graph . nodes ;
250+ var columns = snapToColumns ( nodes ) ;
251+ resolveCollisionsTopToBottom ( columns ) ;
252+ }
253+ // Update links
254+ sankey . update ( graph ) ;
255+ }
256+
257+
166258 return {
167259 circular : circular ,
168260 key : traceIndex ,
@@ -399,6 +491,7 @@ function nodeModel(d, n) {
399491 partOfGroup : n . partOfGroup || false ,
400492 group : n . group ,
401493 traceId : d . key ,
494+ trace : d . trace ,
402495 node : n ,
403496 nodePad : d . nodePad ,
404497 nodeLineColor : d . nodeLineColor ,
@@ -425,7 +518,8 @@ function nodeModel(d, n) {
425518 graph : d . graph ,
426519 arrangement : d . arrangement ,
427520 uniqueNodeLabelPathId : [ d . guid , d . key , key ] . join ( '_' ) ,
428- interactionState : d . interactionState
521+ interactionState : d . interactionState ,
522+ figure : d
429523 } ;
430524}
431525
@@ -509,7 +603,7 @@ function attachPointerEvents(selection, sankey, eventSet) {
509603 } ) ;
510604}
511605
512- function attachDragHandler ( sankeyNode , sankeyLink , callbacks ) {
606+ function attachDragHandler ( sankeyNode , sankeyLink , callbacks , gd ) {
513607 var dragBehavior = d3 . behavior . drag ( )
514608 . origin ( function ( d ) {
515609 return {
@@ -520,6 +614,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
520614
521615 . on ( 'dragstart' , function ( d ) {
522616 if ( d . arrangement === 'fixed' ) return ;
617+ Lib . ensureSingle ( gd . _fullLayout . _infolayer , 'g' , 'dragcover' , function ( s ) {
618+ gd . _fullLayout . _dragCover = s ;
619+ } ) ;
523620 Lib . raiseToTop ( this ) ;
524621 d . interactionState . dragInProgress = d . node ;
525622
@@ -533,9 +630,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
533630 if ( d . forceLayouts [ forceKey ] ) {
534631 d . forceLayouts [ forceKey ] . alpha ( 1 ) ;
535632 } else { // make a forceLayout if needed
536- attachForce ( sankeyNode , forceKey , d ) ;
633+ attachForce ( sankeyNode , forceKey , d , gd ) ;
537634 }
538- startForce ( sankeyNode , sankeyLink , d , forceKey ) ;
635+ startForce ( sankeyNode , sankeyLink , d , forceKey , gd ) ;
539636 }
540637 } )
541638
@@ -553,8 +650,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
553650 d . node . x0 = x - d . visibleWidth / 2 ;
554651 d . node . x1 = x + d . visibleWidth / 2 ;
555652 }
556- d . node . y0 = Math . max ( 0 , Math . min ( d . size - d . visibleHeight , y ) ) ;
557- d . node . y1 = d . node . y0 + d . visibleHeight ;
653+ y = Math . max ( 0 , Math . min ( d . size - d . visibleHeight / 2 , y ) ) ;
654+ d . node . y0 = y - d . visibleHeight / 2 ;
655+ d . node . y1 = y + d . visibleHeight / 2 ;
558656 }
559657
560658 saveCurrentDragPosition ( d . node ) ;
@@ -565,19 +663,21 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
565663 } )
566664
567665 . on ( 'dragend' , function ( d ) {
666+ if ( d . arrangement === 'fixed' ) return ;
568667 d . interactionState . dragInProgress = false ;
569668 for ( var i = 0 ; i < d . node . childrenNodes . length ; i ++ ) {
570669 d . node . childrenNodes [ i ] . x = d . node . x ;
571670 d . node . childrenNodes [ i ] . y = d . node . y ;
572671 }
672+ if ( d . arrangement !== 'snap' ) persistFinalNodePositions ( d , gd ) ;
573673 } ) ;
574674
575675 sankeyNode
576676 . on ( '.drag' , null ) // remove possible previous handlers
577677 . call ( dragBehavior ) ;
578678}
579679
580- function attachForce ( sankeyNode , forceKey , d ) {
680+ function attachForce ( sankeyNode , forceKey , d , gd ) {
581681 // Attach force to nodes in the same column (same x coordinate)
582682 switchToForceFormat ( d . graph . nodes ) ;
583683 var nodes = d . graph . nodes
@@ -590,11 +690,11 @@ function attachForce(sankeyNode, forceKey, d) {
590690 . radius ( function ( n ) { return n . dy / 2 + d . nodePad / 2 ; } )
591691 . strength ( 1 )
592692 . iterations ( c . forceIterations ) )
593- . force ( 'constrain' , snappingForce ( sankeyNode , forceKey , nodes , d ) )
693+ . force ( 'constrain' , snappingForce ( sankeyNode , forceKey , nodes , d , gd ) )
594694 . stop ( ) ;
595695}
596696
597- function startForce ( sankeyNode , sankeyLink , d , forceKey ) {
697+ function startForce ( sankeyNode , sankeyLink , d , forceKey , gd ) {
598698 window . requestAnimationFrame ( function faster ( ) {
599699 var i ;
600700 for ( i = 0 ; i < c . forceTicksPerFrame ; i ++ ) {
@@ -609,6 +709,14 @@ function startForce(sankeyNode, sankeyLink, d, forceKey) {
609709
610710 if ( d . forceLayouts [ forceKey ] . alpha ( ) > 0 ) {
611711 window . requestAnimationFrame ( faster ) ;
712+ } else {
713+ // Make sure the final x position is equal to its original value
714+ // because the force simulation will have numerical error
715+ var x = d . node . originalX ;
716+ d . node . x0 = x - d . visibleWidth / 2 ;
717+ d . node . x1 = x + d . visibleWidth / 2 ;
718+
719+ persistFinalNodePositions ( d , gd ) ;
612720 }
613721 } ) ;
614722}
@@ -628,13 +736,31 @@ function snappingForce(sankeyNode, forceKey, nodes, d) {
628736 maxVelocity = Math . max ( maxVelocity , Math . abs ( n . vx ) , Math . abs ( n . vy ) ) ;
629737 }
630738 if ( ! d . interactionState . dragInProgress && maxVelocity < 0.1 && d . forceLayouts [ forceKey ] . alpha ( ) > 0 ) {
631- d . forceLayouts [ forceKey ] . alpha ( 0 ) ;
739+ d . forceLayouts [ forceKey ] . alpha ( 0 ) ; // This will stop the animation loop
632740 }
633741 } ;
634742}
635743
636744// basic data utilities
637745
746+ function persistFinalNodePositions ( d , gd ) {
747+ var x = [ ] ;
748+ var y = [ ] ;
749+ for ( var i = 0 ; i < d . graph . nodes . length ; i ++ ) {
750+ var nodeX = ( d . graph . nodes [ i ] . x0 + d . graph . nodes [ i ] . x1 ) / 2 ;
751+ var nodeY = ( d . graph . nodes [ i ] . y0 + d . graph . nodes [ i ] . y1 ) / 2 ;
752+ x . push ( nodeX / d . figure . width ) ;
753+ y . push ( nodeY / d . figure . height ) ;
754+ }
755+ Registry . call ( '_guiRestyle' , gd , {
756+ 'node.x' : [ x ] ,
757+ 'node.y' : [ y ]
758+ } , d . trace . index )
759+ . then ( function ( ) {
760+ if ( gd . _fullLayout . _dragCover ) gd . _fullLayout . _dragCover . remove ( ) ;
761+ } ) ;
762+ }
763+
638764function persistOriginalPlace ( nodes ) {
639765 var distinctLayerPositions = [ ] ;
640766 var i ;
@@ -664,8 +790,8 @@ function sameLayer(d) {
664790function switchToForceFormat ( nodes ) {
665791 // force uses x, y as centers
666792 for ( var i = 0 ; i < nodes . length ; i ++ ) {
667- nodes [ i ] . y = nodes [ i ] . y0 + nodes [ i ] . dy / 2 ;
668- nodes [ i ] . x = nodes [ i ] . x0 + nodes [ i ] . dx / 2 ;
793+ nodes [ i ] . y = ( nodes [ i ] . y0 + nodes [ i ] . y1 ) / 2 ;
794+ nodes [ i ] . x = ( nodes [ i ] . x0 + nodes [ i ] . x1 ) / 2 ;
669795 }
670796}
671797
@@ -688,6 +814,9 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
688814 firstRender = true ;
689815 } ) ;
690816
817+ // To prevent animation on dragging
818+ var dragcover = gd . _fullLayout . _dragCover ;
819+
691820 var styledData = calcData
692821 . filter ( function ( d ) { return unwrap ( d ) . trace . visible ; } )
693822 . map ( sankeyModel . bind ( null , layout ) ) ;
@@ -752,7 +881,7 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
752881 . attr ( 'd' , linkPath ( ) ) ;
753882
754883 sankeyLink
755- . style ( 'opacity' , function ( ) { return ( gd . _context . staticPlot || firstRender ) ? 1 : 0 ; } )
884+ . style ( 'opacity' , function ( ) { return ( gd . _context . staticPlot || firstRender || dragcover ) ? 1 : 0 ; } )
756885 . transition ( )
757886 . ease ( c . ease ) . duration ( c . duration )
758887 . style ( 'opacity' , 1 ) ;
@@ -795,7 +924,7 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
795924
796925 sankeyNode
797926 . call ( attachPointerEvents , sankey , callbacks . nodeEvents )
798- . call ( attachDragHandler , sankeyLink , callbacks ) ; // has to be here as it binds sankeyLink
927+ . call ( attachDragHandler , sankeyLink , callbacks , gd ) ; // has to be here as it binds sankeyLink
799928
800929 sankeyNode . transition ( )
801930 . ease ( c . ease ) . duration ( c . duration )
0 commit comments