00001 d3.sankey = function () {
00002 var sankey = {},
00003 nodeWidth = 24,
00004 nodePadding = 8,
00005 size = [1, 1],
00006 nodes = [],
00007 links = [];
00008
00009 sankey.nodeWidth = function (_) {
00010 if (!arguments.length) return nodeWidth;
00011 nodeWidth = +_;
00012 return sankey;
00013 };
00014
00015 sankey.nodePadding = function (_) {
00016 if (!arguments.length) return nodePadding;
00017 nodePadding = +_;
00018 return sankey;
00019 };
00020
00021 sankey.nodes = function (_) {
00022 if (!arguments.length) return nodes;
00023 nodes = _;
00024 return sankey;
00025 };
00026
00027 sankey.links = function (_) {
00028 if (!arguments.length) return links;
00029 links = _;
00030 return sankey;
00031 };
00032
00033 sankey.size = function (_) {
00034 if (!arguments.length) return size;
00035 size = _;
00036 return sankey;
00037 };
00038
00039 sankey.layout = function (iterations) {
00040 computeNodeLinks();
00041 computeNodeValues();
00042 computeNodeBreadths();
00043 computeNodeDepths(iterations);
00044 computeLinkDepths();
00045 return sankey;
00046 };
00047
00048 sankey.relayout = function () {
00049 computeLinkDepths();
00050 return sankey;
00051 };
00052
00053 sankey.link = function () {
00054 var curvature = .5;
00055
00056 function link(d) {
00057 var x0 = d.source.x + d.source.dx,
00058 x1 = d.target.x,
00059 xi = d3.interpolateNumber(x0, x1),
00060 x2 = xi(curvature),
00061 x3 = xi(1 - curvature),
00062 y0 = d.source.y + d.sy + d.dy / 2,
00063 y1 = d.target.y + d.ty + d.dy / 2;
00064 return "M" + x0 + "," + y0
00065 + "C" + x2 + "," + y0
00066 + " " + x3 + "," + y1
00067 + " " + x1 + "," + y1;
00068 }
00069
00070 link.curvature = function (_) {
00071 if (!arguments.length) return curvature;
00072 curvature = +_;
00073 return link;
00074 };
00075
00076 return link;
00077 };
00078
00079
00080
00081 function computeNodeLinks() {
00082 nodes.forEach(function (node) {
00083 node.sourceLinks = [];
00084 node.targetLinks = [];
00085 });
00086 links.forEach(function (link) {
00087 var source = link.source,
00088 target = link.target;
00089 if (typeof source === "number") source = link.source = nodes[link.source];
00090 if (typeof target === "number") target = link.target = nodes[link.target];
00091 source.sourceLinks.push(link);
00092 target.targetLinks.push(link);
00093 });
00094 }
00095
00096
00097 function computeNodeValues() {
00098 nodes.forEach(function (node) {
00099 node.value = Math.max(
00100 d3.sum(node.sourceLinks, value),
00101 d3.sum(node.targetLinks, value)
00102 );
00103 });
00104 }
00105
00106
00107
00108
00109
00110 function computeNodeBreadths() {
00111 var remainingNodes = nodes,
00112 nextNodes,
00113 x = 0;
00114
00115 while (remainingNodes.length) {
00116 nextNodes = [];
00117 remainingNodes.forEach(function (node) {
00118 node.x = x;
00119 node.dx = node.width * nodeWidth;
00120 node.sourceLinks.forEach(function (link) {
00121 nextNodes.push(link.target);
00122 });
00123 });
00124 remainingNodes = nextNodes;
00125 ++x;
00126 }
00127
00128
00129 moveSinksRight(x);
00130 scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
00131 }
00132
00133 function moveSourcesRight() {
00134 nodes.forEach(function (node) {
00135 if (!node.targetLinks.length) {
00136 node.x = d3.min(node.sourceLinks, function (d) { return d.target.x; }) - 1;
00137 }
00138 });
00139 }
00140
00141 function moveSinksRight(x) {
00142 nodes.forEach(function (node) {
00143 if (!node.sourceLinks.length) {
00144 node.x = x - 1;
00145 }
00146 });
00147 }
00148
00149 function scaleNodeBreadths(kx) {
00150 nodes.forEach(function (node) {
00151 node.x *= kx;
00152 });
00153 }
00154
00155 function computeNodeDepths(iterations) {
00156 var nodesByBreadth = d3.nest()
00157 .key(function (d) { return d.x; })
00158 .sortKeys(d3.ascending)
00159 .entries(nodes)
00160 .map(function (d) { return d.values; });
00161
00162
00163 initializeNodeDepth();
00164 resolveCollisions();
00165 for (var alpha = 1; iterations > 0; --iterations) {
00166 relaxRightToLeft(alpha *= .99);
00167 resolveCollisions();
00168 relaxLeftToRight(alpha);
00169 resolveCollisions();
00170 }
00171
00172 function initializeNodeDepth() {
00173 var ky = d3.min(nodesByBreadth, function (nodes) {
00174 return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
00175 });
00176
00177 nodesByBreadth.forEach(function (nodes) {
00178 nodes.forEach(function (node, i) {
00179 node.y = i;
00180 node.dy = node.value * ky;
00181 });
00182 });
00183
00184 links.forEach(function (link) {
00185 link.dy = link.value * ky;
00186 });
00187 }
00188
00189 function relaxLeftToRight(alpha) {
00190 nodesByBreadth.forEach(function (nodes, breadth) {
00191 nodes.forEach(function (node) {
00192 if (node.targetLinks.length) {
00193 var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
00194 node.y += (y - center(node)) * alpha;
00195 }
00196 });
00197 });
00198
00199 function weightedSource(link) {
00200 return center(link.source) * link.value;
00201 }
00202 }
00203
00204 function relaxRightToLeft(alpha) {
00205 nodesByBreadth.slice().reverse().forEach(function (nodes) {
00206 nodes.forEach(function (node) {
00207 if (node.sourceLinks.length) {
00208 var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
00209 node.y += (y - center(node)) * alpha;
00210 }
00211 });
00212 });
00213
00214 function weightedTarget(link) {
00215 return center(link.target) * link.value;
00216 }
00217 }
00218
00219 function resolveCollisions() {
00220 nodesByBreadth.forEach(function (nodes) {
00221 var node,
00222 dy,
00223 y0 = 0,
00224 n = nodes.length,
00225 i;
00226
00227
00228 nodes.sort(ascendingDepth);
00229 for (i = 0; i < n; ++i) {
00230 node = nodes[i];
00231 dy = y0 - node.y;
00232 if (dy > 0) node.y += dy;
00233 y0 = node.y + node.dy + nodePadding;
00234 }
00235
00236
00237 dy = y0 - nodePadding - size[1];
00238 if (dy > 0) {
00239 y0 = node.y -= dy;
00240
00241
00242 for (i = n - 2; i >= 0; --i) {
00243 node = nodes[i];
00244 dy = node.y + node.dy + nodePadding - y0;
00245 if (dy > 0) node.y -= dy;
00246 y0 = node.y;
00247 }
00248 }
00249 });
00250 }
00251
00252 function ascendingDepth(a, b) {
00253 return a.y - b.y;
00254 }
00255 }
00256
00257 function computeLinkDepths() {
00258 nodes.forEach(function (node) {
00259 node.sourceLinks.sort(ascendingTargetDepth);
00260 node.targetLinks.sort(ascendingSourceDepth);
00261 });
00262 nodes.forEach(function (node) {
00263 var sy = 0, ty = 0;
00264 node.sourceLinks.forEach(function (link) {
00265 link.sy = sy;
00266 sy += link.dy;
00267 });
00268 node.targetLinks.forEach(function (link) {
00269 link.ty = ty;
00270 ty += link.dy;
00271 });
00272 });
00273
00274 function ascendingSourceDepth(a, b) {
00275 return a.source.y - b.source.y;
00276 }
00277
00278 function ascendingTargetDepth(a, b) {
00279 return a.target.y - b.target.y;
00280 }
00281 }
00282
00283 function center(node) {
00284 return node.y + node.dy / 2;
00285 }
00286
00287 function value(link) {
00288 return link.value;
00289 }
00290
00291 return sankey;
00292 };