d3-sankey.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2017 Mike Bostock.
  2. (function (global, factory) {
  3. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-shape')) :
  4. typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-shape'], factory) :
  5. (factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3));
  6. }(this, (function (exports,d3Array,d3Collection,d3Shape) { 'use strict';
  7. function targetDepth(d) {
  8. return d.target.depth;
  9. }
  10. function left(node) {
  11. return node.depth;
  12. }
  13. function right(node, n) {
  14. return n - 1 - node.height;
  15. }
  16. function justify(node, n) {
  17. return node.sourceLinks.length ? node.depth : n - 1;
  18. }
  19. function center(node) {
  20. return node.targetLinks.length ? node.depth
  21. : node.sourceLinks.length ? d3Array.min(node.sourceLinks, targetDepth) - 1
  22. : 0;
  23. }
  24. function constant(x) {
  25. return function() {
  26. return x;
  27. };
  28. }
  29. function ascendingSourceBreadth(a, b) {
  30. return ascendingBreadth(a.source, b.source) || a.index - b.index;
  31. }
  32. function ascendingTargetBreadth(a, b) {
  33. return ascendingBreadth(a.target, b.target) || a.index - b.index;
  34. }
  35. function ascendingBreadth(a, b) {
  36. return a.y0 - b.y0;
  37. }
  38. function value(d) {
  39. return d.value;
  40. }
  41. function nodeCenter(node) {
  42. return (node.y0 + node.y1) / 2;
  43. }
  44. function weightedSource(link) {
  45. return nodeCenter(link.source) * link.value;
  46. }
  47. function weightedTarget(link) {
  48. return nodeCenter(link.target) * link.value;
  49. }
  50. function defaultId(d) {
  51. return d.index;
  52. }
  53. function defaultNodes(graph) {
  54. return graph.nodes;
  55. }
  56. function defaultLinks(graph) {
  57. return graph.links;
  58. }
  59. function find(nodeById, id) {
  60. var node = nodeById.get(id);
  61. if (!node) throw new Error("missing: " + id);
  62. return node;
  63. }
  64. var sankey = function() {
  65. var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
  66. dx = 24, // nodeWidth
  67. py = 8, // nodePadding
  68. id = defaultId,
  69. align = justify,
  70. nodes = defaultNodes,
  71. links = defaultLinks,
  72. iterations = 32;
  73. function sankey() {
  74. var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)};
  75. computeNodeLinks(graph);
  76. computeNodeValues(graph);
  77. computeNodeDepths(graph);
  78. computeNodeBreadths(graph, iterations);
  79. computeLinkBreadths(graph);
  80. return graph;
  81. }
  82. sankey.update = function(graph) {
  83. computeLinkBreadths(graph);
  84. return graph;
  85. };
  86. sankey.nodeId = function(_) {
  87. return arguments.length ? (id = typeof _ === "function" ? _ : constant(_), sankey) : id;
  88. };
  89. sankey.nodeAlign = function(_) {
  90. return arguments.length ? (align = typeof _ === "function" ? _ : constant(_), sankey) : align;
  91. };
  92. sankey.nodeWidth = function(_) {
  93. return arguments.length ? (dx = +_, sankey) : dx;
  94. };
  95. sankey.nodePadding = function(_) {
  96. return arguments.length ? (py = +_, sankey) : py;
  97. };
  98. sankey.nodes = function(_) {
  99. return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes;
  100. };
  101. sankey.links = function(_) {
  102. return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links;
  103. };
  104. sankey.size = function(_) {
  105. return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0];
  106. };
  107. sankey.extent = function(_) {
  108. return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]];
  109. };
  110. sankey.iterations = function(_) {
  111. return arguments.length ? (iterations = +_, sankey) : iterations;
  112. };
  113. // Populate the sourceLinks and targetLinks for each node.
  114. // Also, if the source and target are not objects, assume they are indices.
  115. function computeNodeLinks(graph) {
  116. graph.nodes.forEach(function(node, i) {
  117. node.index = i;
  118. node.sourceLinks = [];
  119. node.targetLinks = [];
  120. });
  121. var nodeById = d3Collection.map(graph.nodes, id);
  122. graph.links.forEach(function(link, i) {
  123. link.index = i;
  124. var source = link.source, target = link.target;
  125. if (typeof source !== "object") source = link.source = find(nodeById, source);
  126. if (typeof target !== "object") target = link.target = find(nodeById, target);
  127. source.sourceLinks.push(link);
  128. target.targetLinks.push(link);
  129. });
  130. }
  131. // Compute the value (size) of each node by summing the associated links.
  132. function computeNodeValues(graph) {
  133. graph.nodes.forEach(function(node) {
  134. node.value = Math.max(
  135. d3Array.sum(node.sourceLinks, value),
  136. d3Array.sum(node.targetLinks, value)
  137. );
  138. });
  139. }
  140. // Iteratively assign the depth (x-position) for each node.
  141. // Nodes are assigned the maximum depth of incoming neighbors plus one;
  142. // nodes with no incoming links are assigned depth zero, while
  143. // nodes with no outgoing links are assigned the maximum depth.
  144. function computeNodeDepths(graph) {
  145. var nodes, next, x;
  146. for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
  147. nodes.forEach(function(node) {
  148. node.depth = x;
  149. node.sourceLinks.forEach(function(link) {
  150. if (next.indexOf(link.target) < 0) {
  151. next.push(link.target);
  152. }
  153. });
  154. });
  155. }
  156. for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
  157. nodes.forEach(function(node) {
  158. node.height = x;
  159. node.targetLinks.forEach(function(link) {
  160. if (next.indexOf(link.source) < 0) {
  161. next.push(link.source);
  162. }
  163. });
  164. });
  165. }
  166. var kx = (x1 - x0 - dx) / (x - 1);
  167. graph.nodes.forEach(function(node) {
  168. node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx;
  169. });
  170. }
  171. function computeNodeBreadths(graph) {
  172. var columns = d3Collection.nest()
  173. .key(function(d) { return d.x0; })
  174. .sortKeys(d3Array.ascending)
  175. .entries(graph.nodes)
  176. .map(function(d) { return d.values; });
  177. //
  178. initializeNodeBreadth();
  179. resolveCollisions();
  180. for (var alpha = 1, n = iterations; n > 0; --n) {
  181. relaxRightToLeft(alpha *= 0.99);
  182. resolveCollisions();
  183. relaxLeftToRight(alpha);
  184. resolveCollisions();
  185. }
  186. function initializeNodeBreadth() {
  187. var ky = d3Array.min(columns, function(nodes) {
  188. return (y1 - y0 - (nodes.length - 1) * py) / d3Array.sum(nodes, value);
  189. });
  190. columns.forEach(function(nodes) {
  191. nodes.forEach(function(node, i) {
  192. node.y1 = (node.y0 = i) + node.value * ky;
  193. });
  194. });
  195. graph.links.forEach(function(link) {
  196. link.width = link.value * ky;
  197. });
  198. }
  199. function relaxLeftToRight(alpha) {
  200. columns.forEach(function(nodes) {
  201. nodes.forEach(function(node) {
  202. if (node.targetLinks.length) {
  203. var dy = (d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
  204. node.y0 += dy, node.y1 += dy;
  205. }
  206. });
  207. });
  208. }
  209. function relaxRightToLeft(alpha) {
  210. columns.slice().reverse().forEach(function(nodes) {
  211. nodes.forEach(function(node) {
  212. if (node.sourceLinks.length) {
  213. var dy = (d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
  214. node.y0 += dy, node.y1 += dy;
  215. }
  216. });
  217. });
  218. }
  219. function resolveCollisions() {
  220. columns.forEach(function(nodes) {
  221. var node,
  222. dy,
  223. y = y0,
  224. n = nodes.length,
  225. i;
  226. // Push any overlapping nodes down.
  227. nodes.sort(ascendingBreadth);
  228. for (i = 0; i < n; ++i) {
  229. node = nodes[i];
  230. dy = y - node.y0;
  231. if (dy > 0) node.y0 += dy, node.y1 += dy;
  232. y = node.y1 + py;
  233. }
  234. // If the bottommost node goes outside the bounds, push it back up.
  235. dy = y - py - y1;
  236. if (dy > 0) {
  237. y = (node.y0 -= dy), node.y1 -= dy;
  238. // Push any overlapping nodes back up.
  239. for (i = n - 2; i >= 0; --i) {
  240. node = nodes[i];
  241. dy = node.y1 + py - y;
  242. if (dy > 0) node.y0 -= dy, node.y1 -= dy;
  243. y = node.y0;
  244. }
  245. }
  246. });
  247. }
  248. }
  249. function computeLinkBreadths(graph) {
  250. graph.nodes.forEach(function(node) {
  251. node.sourceLinks.sort(ascendingTargetBreadth);
  252. node.targetLinks.sort(ascendingSourceBreadth);
  253. });
  254. graph.nodes.forEach(function(node) {
  255. var y0 = node.y0, y1 = y0;
  256. node.sourceLinks.forEach(function(link) {
  257. link.y0 = y0 + link.width / 2, y0 += link.width;
  258. });
  259. node.targetLinks.forEach(function(link) {
  260. link.y1 = y1 + link.width / 2, y1 += link.width;
  261. });
  262. });
  263. }
  264. return sankey;
  265. };
  266. function horizontalSource(d) {
  267. return [d.source.x1, d.y0];
  268. }
  269. function horizontalTarget(d) {
  270. return [d.target.x0, d.y1];
  271. }
  272. var sankeyLinkHorizontal = function() {
  273. return d3Shape.linkHorizontal()
  274. .source(horizontalSource)
  275. .target(horizontalTarget);
  276. };
  277. exports.sankey = sankey;
  278. exports.sankeyCenter = center;
  279. exports.sankeyLeft = left;
  280. exports.sankeyRight = right;
  281. exports.sankeyJustify = justify;
  282. exports.sankeyLinkHorizontal = sankeyLinkHorizontal;
  283. Object.defineProperty(exports, '__esModule', { value: true });
  284. })));