Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/sandbox/RFinance2014/libraries/widgets/nvd3/js/sankey.js
1433 views
1
d3.sankey = function() {
2
var sankey = {},
3
nodeWidth = 24,
4
nodePadding = 8,
5
size = [1, 1],
6
nodes = [],
7
links = [];
8
9
sankey.nodeWidth = function(_) {
10
if (!arguments.length) return nodeWidth;
11
nodeWidth = +_;
12
return sankey;
13
};
14
15
sankey.nodePadding = function(_) {
16
if (!arguments.length) return nodePadding;
17
nodePadding = +_;
18
return sankey;
19
};
20
21
sankey.nodes = function(_) {
22
if (!arguments.length) return nodes;
23
nodes = _;
24
return sankey;
25
};
26
27
sankey.links = function(_) {
28
if (!arguments.length) return links;
29
links = _;
30
return sankey;
31
};
32
33
sankey.size = function(_) {
34
if (!arguments.length) return size;
35
size = _;
36
return sankey;
37
};
38
39
sankey.layout = function(iterations) {
40
computeNodeLinks();
41
computeNodeValues();
42
computeNodeBreadths();
43
computeNodeDepths(iterations);
44
computeLinkDepths();
45
return sankey;
46
};
47
48
sankey.relayout = function() {
49
computeLinkDepths();
50
return sankey;
51
};
52
53
sankey.link = function() {
54
var curvature = .5;
55
56
function link(d) {
57
var x0 = d.source.x + d.source.dx,
58
x1 = d.target.x,
59
xi = d3.interpolateNumber(x0, x1),
60
x2 = xi(curvature),
61
x3 = xi(1 - curvature),
62
y0 = d.source.y + d.sy + d.dy / 2,
63
y1 = d.target.y + d.ty + d.dy / 2;
64
return "M" + x0 + "," + y0
65
+ "C" + x2 + "," + y0
66
+ " " + x3 + "," + y1
67
+ " " + x1 + "," + y1;
68
}
69
70
link.curvature = function(_) {
71
if (!arguments.length) return curvature;
72
curvature = +_;
73
return link;
74
};
75
76
return link;
77
};
78
79
// Populate the sourceLinks and targetLinks for each node.
80
// Also, if the source and target are not objects, assume they are indices.
81
function computeNodeLinks() {
82
nodes.forEach(function(node) {
83
node.sourceLinks = [];
84
node.targetLinks = [];
85
});
86
links.forEach(function(link) {
87
var source = link.source,
88
target = link.target;
89
if (typeof source === "number") source = link.source = nodes[link.source];
90
if (typeof target === "number") target = link.target = nodes[link.target];
91
source.sourceLinks.push(link);
92
target.targetLinks.push(link);
93
});
94
}
95
96
// Compute the value (size) of each node by summing the associated links.
97
function computeNodeValues() {
98
nodes.forEach(function(node) {
99
node.value = Math.max(
100
d3.sum(node.sourceLinks, value),
101
d3.sum(node.targetLinks, value)
102
);
103
});
104
}
105
106
// Iteratively assign the breadth (x-position) for each node.
107
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
108
// nodes with no incoming links are assigned breadth zero, while
109
// nodes with no outgoing links are assigned the maximum breadth.
110
function computeNodeBreadths() {
111
var remainingNodes = nodes,
112
nextNodes,
113
x = 0;
114
115
while (remainingNodes.length) {
116
nextNodes = [];
117
remainingNodes.forEach(function(node) {
118
node.x = x;
119
node.dx = nodeWidth;
120
node.sourceLinks.forEach(function(link) {
121
nextNodes.push(link.target);
122
});
123
});
124
remainingNodes = nextNodes;
125
++x;
126
}
127
128
//
129
moveSinksRight(x);
130
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
131
}
132
133
function moveSourcesRight() {
134
nodes.forEach(function(node) {
135
if (!node.targetLinks.length) {
136
node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
137
}
138
});
139
}
140
141
function moveSinksRight(x) {
142
nodes.forEach(function(node) {
143
if (!node.sourceLinks.length) {
144
node.x = x - 1;
145
}
146
});
147
}
148
149
function scaleNodeBreadths(kx) {
150
nodes.forEach(function(node) {
151
node.x *= kx;
152
});
153
}
154
155
function computeNodeDepths(iterations) {
156
var nodesByBreadth = d3.nest()
157
.key(function(d) { return d.x; })
158
.sortKeys(d3.ascending)
159
.entries(nodes)
160
.map(function(d) { return d.values; });
161
162
//
163
initializeNodeDepth();
164
resolveCollisions();
165
for (var alpha = 1; iterations > 0; --iterations) {
166
relaxRightToLeft(alpha *= .99);
167
resolveCollisions();
168
relaxLeftToRight(alpha);
169
resolveCollisions();
170
}
171
172
function initializeNodeDepth() {
173
var ky = d3.min(nodesByBreadth, function(nodes) {
174
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
175
});
176
177
nodesByBreadth.forEach(function(nodes) {
178
nodes.forEach(function(node, i) {
179
node.y = i;
180
node.dy = node.value * ky;
181
});
182
});
183
184
links.forEach(function(link) {
185
link.dy = link.value * ky;
186
});
187
}
188
189
function relaxLeftToRight(alpha) {
190
nodesByBreadth.forEach(function(nodes, breadth) {
191
nodes.forEach(function(node) {
192
if (node.targetLinks.length) {
193
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
194
node.y += (y - center(node)) * alpha;
195
}
196
});
197
});
198
199
function weightedSource(link) {
200
return center(link.source) * link.value;
201
}
202
}
203
204
function relaxRightToLeft(alpha) {
205
nodesByBreadth.slice().reverse().forEach(function(nodes) {
206
nodes.forEach(function(node) {
207
if (node.sourceLinks.length) {
208
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
209
node.y += (y - center(node)) * alpha;
210
}
211
});
212
});
213
214
function weightedTarget(link) {
215
return center(link.target) * link.value;
216
}
217
}
218
219
function resolveCollisions() {
220
nodesByBreadth.forEach(function(nodes) {
221
var node,
222
dy,
223
y0 = 0,
224
n = nodes.length,
225
i;
226
227
// Push any overlapping nodes down.
228
nodes.sort(ascendingDepth);
229
for (i = 0; i < n; ++i) {
230
node = nodes[i];
231
dy = y0 - node.y;
232
if (dy > 0) node.y += dy;
233
y0 = node.y + node.dy + nodePadding;
234
}
235
236
// If the bottommost node goes outside the bounds, push it back up.
237
dy = y0 - nodePadding - size[1];
238
if (dy > 0) {
239
y0 = node.y -= dy;
240
241
// Push any overlapping nodes back up.
242
for (i = n - 2; i >= 0; --i) {
243
node = nodes[i];
244
dy = node.y + node.dy + nodePadding - y0;
245
if (dy > 0) node.y -= dy;
246
y0 = node.y;
247
}
248
}
249
});
250
}
251
252
function ascendingDepth(a, b) {
253
return a.y - b.y;
254
}
255
}
256
257
function computeLinkDepths() {
258
nodes.forEach(function(node) {
259
node.sourceLinks.sort(ascendingTargetDepth);
260
node.targetLinks.sort(ascendingSourceDepth);
261
});
262
nodes.forEach(function(node) {
263
var sy = 0, ty = 0;
264
node.sourceLinks.forEach(function(link) {
265
link.sy = sy;
266
sy += link.dy;
267
});
268
node.targetLinks.forEach(function(link) {
269
link.ty = ty;
270
ty += link.dy;
271
});
272
});
273
274
function ascendingSourceDepth(a, b) {
275
return a.source.y - b.source.y;
276
}
277
278
function ascendingTargetDepth(a, b) {
279
return a.target.y - b.target.y;
280
}
281
}
282
283
function center(node) {
284
return node.y + node.dy / 2;
285
}
286
287
function value(link) {
288
return link.value;
289
}
290
291
return sankey;
292
};
293
294