1d3.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((width - 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};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.