artdaq_node_server  v1_00_08
 All Classes Namespaces Files Variables Pages
sankey.js
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 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 = node.width * 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((size[0] - 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 };