sankey.js 7.2 KB

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