@@ -23,6 +23,8 @@ var repeat = gup.repeat;
23
23
var unwrap = gup . unwrap ;
24
24
var interpolateNumber = require ( 'd3-interpolate' ) . interpolateNumber ;
25
25
26
+ var Registry = require ( '../../registry' ) ;
27
+
26
28
// view models
27
29
28
30
function sankeyModel ( layout , d , traceIndex ) {
@@ -67,13 +69,17 @@ function sankeyModel(layout, d, traceIndex) {
67
69
Lib . warn ( 'node.pad was reduced to ' , sankey . nodePadding ( ) , ' to fit within the figure.' ) ;
68
70
}
69
71
72
+ // Counters for nested loops
73
+ var i , j , k ;
74
+
70
75
// Create transient nodes for animations
71
76
for ( var nodePointNumber in calcData . _groupLookup ) {
72
77
var groupIndex = parseInt ( calcData . _groupLookup [ nodePointNumber ] ) ;
73
78
74
79
// Find node representing groupIndex
75
80
var groupingNode ;
76
- for ( var i = 0 ; i < graph . nodes . length ; i ++ ) {
81
+
82
+ for ( i = 0 ; i < graph . nodes . length ; i ++ ) {
77
83
if ( graph . nodes [ i ] . pointNumber === groupIndex ) {
78
84
groupingNode = graph . nodes [ i ] ;
79
85
break ;
@@ -98,7 +104,6 @@ function sankeyModel(layout, d, traceIndex) {
98
104
}
99
105
100
106
function computeLinkConcentrations ( ) {
101
- var i , j , k ;
102
107
for ( i = 0 ; i < graph . nodes . length ; i ++ ) {
103
108
var node = graph . nodes [ i ] ;
104
109
// Links connecting the same two nodes are part of a flow
@@ -163,6 +168,93 @@ function sankeyModel(layout, d, traceIndex) {
163
168
}
164
169
computeLinkConcentrations ( ) ;
165
170
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
+
166
258
return {
167
259
circular : circular ,
168
260
key : traceIndex ,
@@ -399,6 +491,7 @@ function nodeModel(d, n) {
399
491
partOfGroup : n . partOfGroup || false ,
400
492
group : n . group ,
401
493
traceId : d . key ,
494
+ trace : d . trace ,
402
495
node : n ,
403
496
nodePad : d . nodePad ,
404
497
nodeLineColor : d . nodeLineColor ,
@@ -425,7 +518,8 @@ function nodeModel(d, n) {
425
518
graph : d . graph ,
426
519
arrangement : d . arrangement ,
427
520
uniqueNodeLabelPathId : [ d . guid , d . key , key ] . join ( '_' ) ,
428
- interactionState : d . interactionState
521
+ interactionState : d . interactionState ,
522
+ figure : d
429
523
} ;
430
524
}
431
525
@@ -509,7 +603,7 @@ function attachPointerEvents(selection, sankey, eventSet) {
509
603
} ) ;
510
604
}
511
605
512
- function attachDragHandler ( sankeyNode , sankeyLink , callbacks ) {
606
+ function attachDragHandler ( sankeyNode , sankeyLink , callbacks , gd ) {
513
607
var dragBehavior = d3 . behavior . drag ( )
514
608
. origin ( function ( d ) {
515
609
return {
@@ -520,6 +614,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
520
614
521
615
. on ( 'dragstart' , function ( d ) {
522
616
if ( d . arrangement === 'fixed' ) return ;
617
+ Lib . ensureSingle ( gd . _fullLayout . _infolayer , 'g' , 'dragcover' , function ( s ) {
618
+ gd . _fullLayout . _dragCover = s ;
619
+ } ) ;
523
620
Lib . raiseToTop ( this ) ;
524
621
d . interactionState . dragInProgress = d . node ;
525
622
@@ -533,9 +630,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
533
630
if ( d . forceLayouts [ forceKey ] ) {
534
631
d . forceLayouts [ forceKey ] . alpha ( 1 ) ;
535
632
} else { // make a forceLayout if needed
536
- attachForce ( sankeyNode , forceKey , d ) ;
633
+ attachForce ( sankeyNode , forceKey , d , gd ) ;
537
634
}
538
- startForce ( sankeyNode , sankeyLink , d , forceKey ) ;
635
+ startForce ( sankeyNode , sankeyLink , d , forceKey , gd ) ;
539
636
}
540
637
} )
541
638
@@ -553,8 +650,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
553
650
d . node . x0 = x - d . visibleWidth / 2 ;
554
651
d . node . x1 = x + d . visibleWidth / 2 ;
555
652
}
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 ;
558
656
}
559
657
560
658
saveCurrentDragPosition ( d . node ) ;
@@ -565,19 +663,21 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
565
663
} )
566
664
567
665
. on ( 'dragend' , function ( d ) {
666
+ if ( d . arrangement === 'fixed' ) return ;
568
667
d . interactionState . dragInProgress = false ;
569
668
for ( var i = 0 ; i < d . node . childrenNodes . length ; i ++ ) {
570
669
d . node . childrenNodes [ i ] . x = d . node . x ;
571
670
d . node . childrenNodes [ i ] . y = d . node . y ;
572
671
}
672
+ if ( d . arrangement !== 'snap' ) persistFinalNodePositions ( d , gd ) ;
573
673
} ) ;
574
674
575
675
sankeyNode
576
676
. on ( '.drag' , null ) // remove possible previous handlers
577
677
. call ( dragBehavior ) ;
578
678
}
579
679
580
- function attachForce ( sankeyNode , forceKey , d ) {
680
+ function attachForce ( sankeyNode , forceKey , d , gd ) {
581
681
// Attach force to nodes in the same column (same x coordinate)
582
682
switchToForceFormat ( d . graph . nodes ) ;
583
683
var nodes = d . graph . nodes
@@ -590,11 +690,11 @@ function attachForce(sankeyNode, forceKey, d) {
590
690
. radius ( function ( n ) { return n . dy / 2 + d . nodePad / 2 ; } )
591
691
. strength ( 1 )
592
692
. iterations ( c . forceIterations ) )
593
- . force ( 'constrain' , snappingForce ( sankeyNode , forceKey , nodes , d ) )
693
+ . force ( 'constrain' , snappingForce ( sankeyNode , forceKey , nodes , d , gd ) )
594
694
. stop ( ) ;
595
695
}
596
696
597
- function startForce ( sankeyNode , sankeyLink , d , forceKey ) {
697
+ function startForce ( sankeyNode , sankeyLink , d , forceKey , gd ) {
598
698
window . requestAnimationFrame ( function faster ( ) {
599
699
var i ;
600
700
for ( i = 0 ; i < c . forceTicksPerFrame ; i ++ ) {
@@ -609,6 +709,14 @@ function startForce(sankeyNode, sankeyLink, d, forceKey) {
609
709
610
710
if ( d . forceLayouts [ forceKey ] . alpha ( ) > 0 ) {
611
711
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 ) ;
612
720
}
613
721
} ) ;
614
722
}
@@ -628,13 +736,31 @@ function snappingForce(sankeyNode, forceKey, nodes, d) {
628
736
maxVelocity = Math . max ( maxVelocity , Math . abs ( n . vx ) , Math . abs ( n . vy ) ) ;
629
737
}
630
738
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
632
740
}
633
741
} ;
634
742
}
635
743
636
744
// basic data utilities
637
745
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
+
638
764
function persistOriginalPlace ( nodes ) {
639
765
var distinctLayerPositions = [ ] ;
640
766
var i ;
@@ -664,8 +790,8 @@ function sameLayer(d) {
664
790
function switchToForceFormat ( nodes ) {
665
791
// force uses x, y as centers
666
792
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 ;
669
795
}
670
796
}
671
797
@@ -688,6 +814,9 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
688
814
firstRender = true ;
689
815
} ) ;
690
816
817
+ // To prevent animation on dragging
818
+ var dragcover = gd . _fullLayout . _dragCover ;
819
+
691
820
var styledData = calcData
692
821
. filter ( function ( d ) { return unwrap ( d ) . trace . visible ; } )
693
822
. map ( sankeyModel . bind ( null , layout ) ) ;
@@ -752,7 +881,7 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
752
881
. attr ( 'd' , linkPath ( ) ) ;
753
882
754
883
sankeyLink
755
- . style ( 'opacity' , function ( ) { return ( gd . _context . staticPlot || firstRender ) ? 1 : 0 ; } )
884
+ . style ( 'opacity' , function ( ) { return ( gd . _context . staticPlot || firstRender || dragcover ) ? 1 : 0 ; } )
756
885
. transition ( )
757
886
. ease ( c . ease ) . duration ( c . duration )
758
887
. style ( 'opacity' , 1 ) ;
@@ -795,7 +924,7 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
795
924
796
925
sankeyNode
797
926
. 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
799
928
800
929
sankeyNode . transition ( )
801
930
. ease ( c . ease ) . duration ( c . duration )
0 commit comments