Advertisement
Guest User

Untitled

a guest
Oct 17th, 2019
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.51 KB | None | 0 0
  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 xs = d.source.x + d.source.dx,
  58. xt = d.target.x,
  59. xi = d3.interpolateNumber(xs, xt),
  60. xsc = xi(curvature),
  61. xtc = xi(1 - curvature),
  62. ys = d.source.y + d.sy + d.dy / 2,
  63. yt = d.target.y + d.ty + d.dy / 2;
  64.  
  65. if (!d.cycleBreaker) {
  66. return "M" + xs + "," + ys
  67. + "C" + xsc + "," + ys
  68. + " " + xtc + "," + yt
  69. + " " + xt + "," + yt;
  70. }
  71. else {
  72. xsc = xi(-0.5*curvature);
  73. xtc = xi(1 + 0.5*curvature);
  74. var xm = xi(0.5);
  75. var ym = d3.interpolateNumber(ys, yt)(-.5);
  76. return "M" + xs + "," + ys
  77. + "C" + xsc + "," + ys
  78. + " " + xsc + "," + ym
  79. + " " + xm + "," + ym
  80. + "S" + xtc + "," + yt
  81. + " " + xt + "," + yt;
  82.  
  83. }
  84. }
  85.  
  86. link.curvature = function(_) {
  87. if (!arguments.length) return curvature;
  88. curvature = +_;
  89. return link;
  90. };
  91.  
  92. return link;
  93. };
  94.  
  95. // Populate the sourceLinks and targetLinks for each node.
  96. // Also, if the source and target are not objects, assume they are indices.
  97. function computeNodeLinks() {
  98. nodes.forEach(function(node) {
  99. node.sourceLinks = [];
  100. node.targetLinks = [];
  101. });
  102. links.forEach(function(link) {
  103. var source = link.source,
  104. target = link.target;
  105. if (typeof source === "number") source = link.source = nodes[link.source];
  106. if (typeof target === "number") target = link.target = nodes[link.target];
  107. source.sourceLinks.push(link);
  108. target.targetLinks.push(link);
  109. });
  110. }
  111.  
  112. // Compute the value (size) of each node by summing the associated links.
  113. function computeNodeValues() {
  114. nodes.forEach(function(node) {
  115. node.value = Math.max(
  116. d3.sum(node.sourceLinks, value),
  117. d3.sum(node.targetLinks, value)
  118. );
  119. });
  120. }
  121.  
  122. // Iteratively assign the breadth (x-position) for each node.
  123. // Nodes are assigned the maximum breadth of incoming neighbors plus one;
  124. // nodes with no incoming links are assigned breadth zero, while
  125. // nodes with no outgoing links are assigned the maximum breadth.
  126. function computeNodeBreadths() {
  127. var remainingNodes = nodes,
  128. nextNodes,
  129. x = 0;
  130.  
  131. while (remainingNodes.length) {
  132. nextNodes = [];
  133. remainingNodes.forEach(function(node) {
  134. node.x = x;
  135. node.dx = nodeWidth;
  136. node.sourceLinks.forEach(function(link) {
  137. if (nextNodes.indexOf(link.target) < 0 && !link.cycleBreaker) {
  138. nextNodes.push(link.target);
  139. }
  140. });
  141. });
  142. if (nextNodes.length == remainingNodes.length) {
  143. console.warn('Detected cycles in the graph.');
  144. findAndMarkCycleBreaker(nextNodes);
  145. }
  146. else {
  147. remainingNodes = nextNodes;
  148. ++x;
  149. }
  150. }
  151.  
  152. //
  153. moveSinksRight(x);
  154. scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
  155. }
  156.  
  157. // Find a link that breaks a cycle in the graph (if any).
  158. function findAndMarkCycleBreaker(nodes) {
  159. // Go through all nodes from the given subset and traverse links searching for cycles.
  160. var link;
  161. for (var n=nodes.length - 1; n >= 0; n--) {
  162. link = depthFirstCycleSearch(nodes[n], []);
  163. if (link) {
  164. return link;
  165. }
  166. }
  167.  
  168. // Depth-first search to find a link that is part of a cycle.
  169. function depthFirstCycleSearch(cursorNode, path) {
  170. var target, link;
  171. for (var n = cursorNode.sourceLinks.length - 1; n >= 0; n--) {
  172. link = cursorNode.sourceLinks[n];
  173. if (link.cycleBreaker) {
  174. // Skip already known cycle breakers.
  175. continue;
  176. }
  177. // Check if target makes a cycle with current path.
  178. target = link.target;
  179. if (path.indexOf(target) > -1) {
  180. // Mark this link as a known cycle breaker.
  181. link.cycleBreaker = true;
  182. // Stop further search if we found a cycle breaker.
  183. return link;
  184. }
  185. // Recurse deeper.
  186. path.push(cursorNode);
  187. link = depthFirstCycleSearch(target, path);
  188. path.pop();
  189. // Stop further search if we found a cycle breaker.
  190. if (link) {
  191. return link;
  192. }
  193. }
  194. }
  195. }
  196.  
  197.  
  198. function moveSourcesRight() {
  199. nodes.forEach(function(node) {
  200. if (!node.targetLinks.length) {
  201. node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
  202. }
  203. });
  204. }
  205.  
  206. function moveSinksRight(x) {
  207. nodes.forEach(function(node) {
  208. if (!node.sourceLinks.length) {
  209. node.x = x - 1;
  210. }
  211. });
  212. }
  213.  
  214. function scaleNodeBreadths(kx) {
  215. nodes.forEach(function(node) {
  216. node.x *= kx;
  217. });
  218. }
  219.  
  220. function computeNodeDepths(iterations) {
  221. var nodesByBreadth = d3.nest()
  222. .key(function(d) { return d.x; })
  223. .sortKeys(d3.ascending)
  224. .entries(nodes)
  225. .map(function(d) { return d.values; });
  226.  
  227. //
  228. initializeNodeDepth();
  229. resolveCollisions();
  230. for (var alpha = 1; iterations > 0; --iterations) {
  231. relaxRightToLeft(alpha *= .99);
  232. resolveCollisions();
  233. relaxLeftToRight(alpha);
  234. resolveCollisions();
  235. }
  236.  
  237. function initializeNodeDepth() {
  238. var ky = d3.min(nodesByBreadth, function(nodes) {
  239. return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
  240. });
  241.  
  242. nodesByBreadth.forEach(function(nodes) {
  243. nodes.forEach(function(node, i) {
  244. node.y = i;
  245. node.dy = node.value * ky;
  246. });
  247. });
  248.  
  249. links.forEach(function(link) {
  250. link.dy = link.value * ky;
  251. });
  252. }
  253.  
  254. function relaxLeftToRight(alpha) {
  255. nodesByBreadth.forEach(function(nodes, breadth) {
  256. nodes.forEach(function(node) {
  257. if (node.targetLinks.length) {
  258. var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
  259. node.y += (y - center(node)) * alpha;
  260. }
  261. });
  262. });
  263.  
  264. function weightedSource(link) {
  265. return center(link.source) * link.value;
  266. }
  267. }
  268.  
  269. function relaxRightToLeft(alpha) {
  270. nodesByBreadth.slice().reverse().forEach(function(nodes) {
  271. nodes.forEach(function(node) {
  272. if (node.sourceLinks.length) {
  273. var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
  274. node.y += (y - center(node)) * alpha;
  275. }
  276. });
  277. });
  278.  
  279. function weightedTarget(link) {
  280. return center(link.target) * link.value;
  281. }
  282. }
  283.  
  284. function resolveCollisions() {
  285. nodesByBreadth.forEach(function(nodes) {
  286. var node,
  287. dy,
  288. y0 = 0,
  289. n = nodes.length,
  290. i;
  291.  
  292. // Push any overlapping nodes down.
  293. nodes.sort(ascendingDepth);
  294. for (i = 0; i < n; ++i) {
  295. node = nodes[i];
  296. dy = y0 - node.y;
  297. if (dy > 0) node.y += dy;
  298. y0 = node.y + node.dy + nodePadding;
  299. }
  300.  
  301. // If the bottommost node goes outside the bounds, push it back up.
  302. dy = y0 - nodePadding - size[1];
  303. if (dy > 0) {
  304. y0 = node.y -= dy;
  305.  
  306. // Push any overlapping nodes back up.
  307. for (i = n - 2; i >= 0; --i) {
  308. node = nodes[i];
  309. dy = node.y + node.dy + nodePadding - y0;
  310. if (dy > 0) node.y -= dy;
  311. y0 = node.y;
  312. }
  313. }
  314. });
  315. }
  316.  
  317. function ascendingDepth(a, b) {
  318. return a.y - b.y;
  319. }
  320. }
  321.  
  322. function computeLinkDepths() {
  323. nodes.forEach(function(node) {
  324. node.sourceLinks.sort(ascendingTargetDepth);
  325. node.targetLinks.sort(ascendingSourceDepth);
  326. });
  327. nodes.forEach(function(node) {
  328. var sy = 0, ty = 0;
  329. node.sourceLinks.forEach(function(link) {
  330. link.sy = sy;
  331. sy += link.dy;
  332. });
  333. node.targetLinks.forEach(function(link) {
  334. link.ty = ty;
  335. ty += link.dy;
  336. });
  337. });
  338.  
  339. function ascendingSourceDepth(a, b) {
  340. return a.source.y - b.source.y;
  341. }
  342.  
  343. function ascendingTargetDepth(a, b) {
  344. return a.target.y - b.target.y;
  345. }
  346. }
  347.  
  348. function center(node) {
  349. return node.y + node.dy / 2;
  350. }
  351.  
  352. function value(link) {
  353. return link.value;
  354. }
  355.  
  356. return sankey;
  357. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement