@@ -34,7 +34,36 @@ function convertToD3Sankey(trace) {
34
34
components [ cscale . label ] = scale ;
35
35
}
36
36
37
- var nodeCount = nodeSpec . label . length ;
37
+ var maxNodeId = 0 ;
38
+ for ( i = 0 ; i < linkSpec . value . length ; i ++ ) {
39
+ if ( linkSpec . source [ i ] > maxNodeId ) maxNodeId = linkSpec . source [ i ] ;
40
+ if ( linkSpec . target [ i ] > maxNodeId ) maxNodeId = linkSpec . target [ i ] ;
41
+ }
42
+ var nodeCount = maxNodeId + 1 ;
43
+
44
+ // Group nodes
45
+ var j ;
46
+ var groups = trace . node . groups ;
47
+ var groupLookup = { } ;
48
+ for ( i = 0 ; i < groups . length ; i ++ ) {
49
+ var group = groups [ i ] ;
50
+ // Build a lookup table to quickly find in which group a node is
51
+ for ( j = 0 ; j < group . length ; j ++ ) {
52
+ var nodeIndex = group [ j ] ;
53
+ var groupIndex = nodeCount + i ;
54
+ if ( groupLookup . hasOwnProperty ( nodeIndex ) ) {
55
+ Lib . warn ( 'Node ' + nodeIndex + ' is already part of a group.' ) ;
56
+ } else {
57
+ groupLookup [ nodeIndex ] = groupIndex ;
58
+ }
59
+ }
60
+ }
61
+
62
+ // Process links
63
+ var groupedLinks = {
64
+ source : [ ] ,
65
+ target : [ ]
66
+ } ;
38
67
for ( i = 0 ; i < linkSpec . value . length ; i ++ ) {
39
68
var val = linkSpec . value [ i ] ;
40
69
// remove negative values, but keep zeros with special treatment
@@ -44,6 +73,21 @@ function convertToD3Sankey(trace) {
44
73
continue ;
45
74
}
46
75
76
+ // Remove links that are within the same group
77
+ if ( groupLookup . hasOwnProperty ( source ) && groupLookup . hasOwnProperty ( target ) && groupLookup [ source ] === groupLookup [ target ] ) {
78
+ continue ;
79
+ }
80
+
81
+ // if link targets a node in the group, relink target to that group
82
+ if ( groupLookup . hasOwnProperty ( target ) ) {
83
+ target = groupLookup [ target ] ;
84
+ }
85
+
86
+ // if link originates from a node in a group, relink source to that group
87
+ if ( groupLookup . hasOwnProperty ( source ) ) {
88
+ source = groupLookup [ source ] ;
89
+ }
90
+
47
91
source = + source ;
48
92
target = + target ;
49
93
linkedNodes [ source ] = linkedNodes [ target ] = true ;
@@ -63,42 +107,46 @@ function convertToD3Sankey(trace) {
63
107
target : target ,
64
108
value : + val
65
109
} ) ;
110
+
111
+ groupedLinks . source . push ( source ) ;
112
+ groupedLinks . target . push ( target ) ;
66
113
}
67
114
115
+ // Process nodes
116
+ var totalCount = nodeCount + groups . length ;
68
117
var hasNodeColorArray = isArrayOrTypedArray ( nodeSpec . color ) ;
69
118
var nodes = [ ] ;
70
- var removedNodes = false ;
71
- var nodeIndices = { } ;
72
-
73
- for ( i = 0 ; i < nodeCount ; i ++ ) {
74
- if ( linkedNodes [ i ] ) {
75
- var l = nodeSpec . label [ i ] ;
76
- nodeIndices [ i ] = nodes . length ;
77
- nodes . push ( {
78
- pointNumber : i ,
79
- label : l ,
80
- color : hasNodeColorArray ? nodeSpec . color [ i ] : nodeSpec . color
81
- } ) ;
82
- } else removedNodes = true ;
119
+ for ( i = 0 ; i < totalCount ; i ++ ) {
120
+ if ( ! linkedNodes [ i ] ) continue ;
121
+ var l = nodeSpec . label [ i ] ;
122
+
123
+ nodes . push ( {
124
+ group : ( i > nodeCount - 1 ) ,
125
+ childrenNodes : [ ] ,
126
+ pointNumber : i ,
127
+ label : l ,
128
+ color : hasNodeColorArray ? nodeSpec . color [ i ] : nodeSpec . color
129
+ } ) ;
83
130
}
84
131
85
- // need to re-index links now, since we didn't put all the nodes in
86
- if ( removedNodes ) {
87
- for ( i = 0 ; i < links . length ; i ++ ) {
88
- links [ i ] . source = nodeIndices [ links [ i ] . source ] ;
89
- links [ i ] . target = nodeIndices [ links [ i ] . target ] ;
90
- }
132
+ // Check if we have circularity on the resulting graph
133
+ var circular = false ;
134
+ if ( circularityPresent ( totalCount , groupedLinks . source , groupedLinks . target ) ) {
135
+ circular = true ;
91
136
}
92
137
93
138
return {
139
+ circular : circular ,
94
140
links : links ,
95
- nodes : nodes
141
+ nodes : nodes ,
142
+
143
+ // Data structure for groups
144
+ groups : groups ,
145
+ groupLookup : groupLookup
96
146
} ;
97
147
}
98
148
99
- function circularityPresent ( nodeList , sources , targets ) {
100
-
101
- var nodeLen = nodeList . length ;
149
+ function circularityPresent ( nodeLen , sources , targets ) {
102
150
var nodes = Lib . init2dArray ( nodeLen , 0 ) ;
103
151
104
152
for ( var i = 0 ; i < Math . min ( sources . length , targets . length ) ; i ++ ) {
@@ -120,16 +168,15 @@ function circularityPresent(nodeList, sources, targets) {
120
168
}
121
169
122
170
module . exports = function calc ( gd , trace ) {
123
- var circular = false ;
124
- if ( circularityPresent ( trace . node . label , trace . link . source , trace . link . target ) ) {
125
- circular = true ;
126
- }
127
-
128
171
var result = convertToD3Sankey ( trace ) ;
129
172
130
173
return wrap ( {
131
- circular : circular ,
174
+ circular : result . circular ,
132
175
_nodes : result . nodes ,
133
- _links : result . links
176
+ _links : result . links ,
177
+
178
+ // Data structure for grouping
179
+ _groups : result . groups ,
180
+ _groupLookup : result . groupLookup ,
134
181
} ) ;
135
182
} ;
0 commit comments