PageSourceSearch

https://data-science.wifo.ac.at/observable/tpeu-de/[email protected]

js wifo.ac.at collected 2026-10-01 10:25:07 UTC 25,067 bytes, 736 lines download raw bytes

1// https://observablehq.com/@bayre/deconstructed-sankey-diagram@1608
2import define1 from "./[email protected]";
3
4function _1(md){return(
5md`# Deconstructed sankey diagram
6
7This is a journey through the source code for [d3-sankey](https://github.com/d3/d3-sankey). In what follows I've traded in require for copy-paste in a bid to better understand how the sankey constructor works, and where the opportunities lie for [its modification](/@bayre/spatially-situated-sankey).
8
9You're almost certainly better off reading the official documentation. 
10
11_see also @tomshanley [1](https://observablehq.com/@tomshanley/sankey-circular-deconstructed) [2](https://observablehq.com/@tomshanley/vertical-sankey-with-elegant-links-to-entry-and-exit-nodes)_`
12)}
13
14function _shape(radio){return(
15radio({ options: ["chunky", "smooth"], value: "chunky" })
16)}
17
18function _chart(d3,width,height,sankey,data,color,format,DOM,shape,sankeyLinkHorizontal)
19{
20  const svg = d3.create("svg")
21      .attr("viewBox", [0, 0, width, height]);
22
23  const {nodes, links} = sankey(data);
24
25  svg.append("g")
26      .attr("stroke", "#000")
27    .selectAll("rect")
28    .data(nodes)
29    .join("rect")
30      .attr("x", d => d.x0)
31      .attr("y", d => d.y0)
32      .attr("height", d => d.y1 - d.y0)
33      .attr("width", d => d.x1 - d.x0)
34      .attr("fill", color)
35    .append("title")
36      .text(d => `${d.name}\n${format(d.value)}`);
37
38  const link = svg.append("g")
39      .attr("fill", "none")
40      .attr("stroke-opacity", 0.5)
41    .selectAll("g")
42    .data(links)
43    .join("g")
44      .style("mix-blend-mode", "multiply");
45
46  const gradient = link.append("linearGradient")
47      .attr("id", d => (d.uid = DOM.uid("link")).id)
48      .attr("gradientUnits", "userSpaceOnUse")
49      .attr("x1", d => d.source.x1)
50      .attr("x2", d => d.target.x0);
51
52  gradient.append("stop")
53      .attr("offset", "0%")
54      .attr("stop-color", d => color(d.source));
55
56  gradient.append("stop")
57      .attr("offset", "100%")
58      .attr("stop-color", d => color(d.target));
59  
60  link.append("path")
61      .attr("d", shape === "chunky" ? d => `M${d.source.x0},${d.y0} L${d.target.x0},${d.y1}Z` : sankeyLinkHorizontal())
62      .attr("stroke", d => d.uid)
63      .attr("stroke-width", d => Math.max(1, d.width));
64
65  link.append("title")
66      .text(d => `${d.source.name} → ${d.target.name}\n${format(d.value)}`);
67
68  svg.append("g")
69      .attr("font-family", "sans-serif")
70      .attr("font-size", 10)
71    .selectAll("text")
72    .data(nodes)
73    .join("text")
74      .attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
75      .attr("y", d => (d.y1 + d.y0) / 2)
76      .attr("dy", "0.35em")
77      .attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
78      .text(d => d.name);
79  
80  return svg.node();
81}
82
83
84function _4(md){return(
85md`---`
86)}
87
88async function _data(FileAttachment)
89{
90  const links = await FileAttachment("energy.csv").csv({typed: true});
91  const nodes = Array.from(new Set(links.flatMap(l => [l.source, l.target])), name => ({name, category: name.replace(/ .*/, "")}));
92  return {nodes, links, units: "TWh"};
93}
94
95
96function _6(md){return(
97md`The [sankey constructor](https://github.com/d3/d3-sankey/blob/bb7233ea0004669878d922e55fe83084ef3cb555/src/sankey.js#L54) provides a series of default values and methods that are used to generate a function that looks something like this:`
98)}
99
100function _sankey(clone,computeNodeLinks,computeNodeValues,computeNodeDepths,computeNodeHeights,computeNodeBreadths,computeLinkBreadths){return(
101function sankey({nodes, links}) {
102  const graph = clone({nodes, links});
103  computeNodeLinks(graph);
104  computeNodeValues(graph);
105  computeNodeDepths(graph);
106  computeNodeHeights(graph);
107  computeNodeBreadths(graph);
108  computeLinkBreadths(graph);
109  return graph;
110}
111)}
112
113function _8(md){return(
114md`In order to get this working in Observable I have pulled out the default values into cells and done away with the methods entirely.`
115)}
116
117function _x0(){return(
1181
119)}
120
121function _x1(width){return(
122width - 1
123)}
124
125function _y0(){return(
1265
127)}
128
129function _y1(height){return(
130height - 5
131)}
132
133function _dx(){return(
13415
135)}
136
137function _dy(){return(
13810
139)}
140
141function _py(){return(
142null
143)}
144
145function _id(){return(
146d => d.name
147)}
148
149function _sort(){return(
150undefined
151)}
152
153function _linkSort(){return(
154undefined
155)}
156
157function _align()
158{
159  return function justify(node, n) {
160    return node.sourceLinks.length ? node.depth : n - 1;
161  }
162}
163
164
165function _20(md){return(
166md`With this in place, it's time to work through the six steps in our sankey function.
167
168First up, we identify the incoming (target) and outgoing (source) links for each node.`
169)}
170
171function _computeNodeLinks(id,find,linkSort){return(
172function computeNodeLinks({nodes, links}) {
173  for (const [i, node] of nodes.entries()) {
174    node.index = i;
175    node.sourceLinks = [];
176    node.targetLinks = [];
177  }
178  const nodeById = new Map(nodes.map((d, i) => [id(d, i, nodes), d]));
179  for (const [i, link] of links.entries()) {
180    link.index = i;
181    let {source, target} = link;
182    if (typeof source !== "object") source = link.source = find(nodeById, source);
183    if (typeof target !== "object") target = link.target = find(nodeById, target);
184    source.sourceLinks.push(link);
185    target.targetLinks.push(link);
186  }
187  if (linkSort != null) {
188    for (const {sourceLinks, targetLinks} of nodes) {
189      sourceLinks.sort(linkSort);
190      targetLinks.sort(linkSort);
191    }
192  }
193}
194)}
195
196function _find(){return(
197function find(nodeById, id) {
198  const node = nodeById.get(id);
199  if (!node) throw new Error("missing: " + id);
200  return node;
201}
202)}
203
204function _step1(clone,data,computeNodeLinks)
205{
206  const graph = clone(data);
207  computeNodeLinks(graph);
208  return graph;
209}
210
211
212function _24(md){return(
213md`Then we assign a value to each node. Unless a \`fixedValue\` is provided, this is calculated as the maximum of the sums of source and target links.`
214)}
215
216function _computeNodeValues(d3,value){return(
217function computeNodeValues({nodes}) {
218  for (const node of nodes) {
219    node.value = node.fixedValue === undefined
220        ? Math.max(d3.sum(node.sourceLinks, value), d3.sum(node.targetLinks, value))
221        : node.fixedValue;
222  }
223}
224)}
225
226function _value(){return(
227function value(d) {
228  return d.value;
229}
230)}
231
232function _step2(clone,data,computeNodeLinks,computeNodeValues)
233{
234  const graph = clone(data);
235  computeNodeLinks(graph);
236  computeNodeValues(graph);
237  return graph;
238}
239
240
241function _28(md){return(
242md`In steps 3 and 4 we compute the graph topology. [Here](https://stackoverflow.com/a/2603707)'s a good explanation of what is meant b
242y depth and height in this context.`
243)}
244
245function _computeNodeDepths(){return(
246function computeNodeDepths({nodes}) {
247  const n = nodes.length;
248  let current = new Set(nodes);
249  let next = new Set;
250  let x = 0;
251  while (current.size) {
252    for (const node of current) {
253      node.depth = x;
254      for (const {target} of node.sourceLinks) {
255        next.add(target);
256      }
257    }
258    if (++x > n) throw new Error("circular link");
259    current = next;
260    next = new Set;
261  }
262}
263)}
264
265function _computeNodeHeights(){return(
266function computeNodeHeights({nodes}) {
267  const n = nodes.length;
268  let current = new Set(nodes);
269  let next = new Set;
270  let x = 0;
271  while (current.size) {
272    for (const node of current) {
273      node.height = x;
274      for (const {source} of node.targetLinks) {
275        next.add(source);
276      }
277    }
278    if (++x > n) throw new Error("circular link");
279    current = next;
280    next = new Set;
281  }
282}
283)}
284
285function _step4(clone,data,computeNodeLinks,computeNodeValues,computeNodeDepths,computeNodeHeights)
286{
287  const graph = clone(data);
288  computeNodeLinks(graph);
289  computeNodeValues(graph);
290  computeNodeDepths(graph); // step3
291  computeNodeHeights(graph);
292  return graph;
293}
294
295
296function _32(md){return(
297md`Now for the geometry (or breadth) of the nodes. 
298
299The main function below has a few dependencies, so we'll work through each of those in turn.`
300)}
301
302function _computeNodeBreadths(computeNodeLayers,dy,y1,y0,d3,py,$0,initializeNodeBreadths,iterations,relaxRightToLeft,relaxLeftToRight){return(
303function computeNodeBreadths(graph) {
304  const columns = computeNodeLayers(graph);
305  let lpy = Math.min(dy, (y1 - y0) / (d3.max(columns, c => c.length) - 1));
306  if (lpy > py) $0.value = lpy; // small modification to allow for py to be redefined
307  initializeNodeBreadths(columns);
308  for (let i = 0; i < iterations; ++i) {
309    const alpha = Math.pow(0.99, i);
310    const beta = Math.max(1 - alpha, (i + 1) / iterations);
311    relaxRightToLeft(columns, alpha, beta);
312    relaxLeftToRight(columns, alpha, beta);
313  }
314}
315)}
316
317function _34(md){return(
318md`\`computeNodeLayers\` determines which column a given node should be in and assigns \`x0\`, \`x1\` accordingly.`
319)}
320
321function _computeNodeLayers(d3,x1,x0,dx,align,sort){return(
322function computeNodeLayers({nodes}) {
323  const x = d3.max(nodes, d => d.depth) + 1;
324  const kx = (x1 - x0 - dx) / (x - 1);
325  const columns = new Array(x);
326  for (const node of nodes) {
327    const i = Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x))));
328    node.layer = i;
329    node.x0 = x0 + i * kx;
330    node.x1 = node.x0 + dx;
331    if (columns[i]) columns[i].push(node);
332    else columns[i] = [node];
333  }
334  if (sort) for (const column of columns) {
335    column.sort(sort);
336  }
337  return columns;
338}
339)}
340
341function _columns(clone,step4,computeNodeLayers)
342{
343  const graph = clone(step4);
344  const columns = computeNodeLayers(graph);
345  return columns;
346}
347
348
349function _37(md){return(
350md`\`initializeNodeBreadths\` is tasked with determining the \`y\` coordinates. It does so by calculating a scale factor \`ky\` based on the available vertical space.`
351)}
352
353function _initializeNodeBreadths(d3,y1,y0,py,value,reorderLinks){return(
354function initializeNodeBreadths(columns) {
355  const ky = d3.min(columns, c => (y1 - y0 - (c.length - 1) * py) / d3.sum(c, value));
356  for (const nodes of columns) {
357    let y = y0;
358    for (const node of nodes) {
359      node.y0 = y;
360      node.y1 = y + node.value * ky;
361      y = node.y1 + py;
362      for (const link of node.sourceLinks) {
363        link.width = link.value * ky;
364      }
365    }
366    y = (y1 - y + py) / (nodes.length + 1);
367    for (let i = 0; i < nodes.length; ++i) {
368      const node = nodes[i];
369      node.y0 += y * (i + 1);
370      node.y1 += y * (i + 1);
371    }
372    reorderLinks(nodes);
373  }
374}
375)}
376
377function _39(md){return(
378md`Once we've determined node breadths we can sort incoming and outgoing links accordingly (unless a link sorting function is specified).`
379)}
380
381function _reorderLinks(linkSort,ascendingTargetBreadth,ascendingSourceBreadth){return(
382function reorderLinks(nodes) {
383  if (linkSort === undefined) {
384    for (const {sourceLinks, targetLinks} of nodes) {
385      sourceLinks.sort(ascendingTargetBreadth);
386      targetLinks.sort(ascendingSourceBreadth);
387    }
388  }
389}
390)}
391
392function _ascendingBreadth(){return(
393function ascendingBreadth(a, b) {
394  return a.y0 - b.y0;
395}
396)}
397
398function _ascendingSourceBreadth(ascendingBreadth){return(
399function ascendingSourceBreadth(a, b) {
400  return ascendingBreadth(a.source, b.source) || a.index - b.index;
401}
402)}
403
404function _ascendingTargetBreadth(ascendingBreadth){return(
405function ascendingTargetBreadth(a, b) {
406  return ascendingBreadth(a.target, b.target) || a.index - b.index;
407}
408)}
409
410function _44(md){return(
411md`But we're not yet finished with [step 5](#computeNodeBreadths). We've determined the initial positions for each node, but they're currently suboptimal. The loop at the end of \`computeNodeBreadths\` uses iterative relaxation to improve the \`y\` positions.
412
413Try setting iterations to \`0\` to see the effect.`
414)}
415
416function _iterations(){return(
4176
418)}
419
420function _46(md){return(
421md`For each iteration nodes are relaxed right-to-left and left-to-right. But what does this mean?
422
423While the function names might suggest they relate to the horizontal plane, they are in fact used to update \`y\` coordinates. \`relaxRightToLeft\` repositions nodes based on their source links and \`relaxLeftToRight\` handles the target links. 
424
425Each function attemtps to reduce tension by moving nodes towards their optimum position. Meanwhile \`resolveCollisions\` ensures that nodes aren't occluded during the process.`
426)}
427
428function _relaxRightToLeft(sourceTop,reorderNodeLinks,sort,ascendingBreadth,resolveCollisions){return(
429function relaxRightToLeft(columns, alpha, beta) {
430  for (let n = columns.length, i = n - 2; i >= 0; --i) {
431    const column = columns[i];
432    for (const source of column) {
433      let y = 0;
434      let w = 0;
435      for (const {target, value} of source.sourceLinks) {
436        let v = value * (target.layer - source.layer);
437        y += sourceTop(source, target) * v;
438        w += v;
439      }
440      if (!(w > 0)) continue;
441      let dy = (y / w - source.y0) * alpha;
442      source.y0 += dy;
443      source.y1 += dy;
444      reorderNodeLinks(source);
445    }
446    if (sort === undefined) column.sort(ascendingBreadth);
447    resolveCollisions(column, beta);
448  }
449}
450)}
451
452function _sourceTop(py){return(
453function sourceTop(source, target) {
454  let y = target.y0 - (target.targetLinks.length - 1) * py / 2;
455  for (const {source: node, width} of target.targetLinks) {
456    if (node === source) break;
457    y += width + py;
458  }
459  for (const {target: node, width} of source.sourceLinks) {
460    if (node === target) break;
461    y -= width;
462  }
463  return y;
464}
465)}
466
467function _relaxLeftToRight(targetTop,reorderNodeLinks,sort,ascendingBreadth,resolveCollisions){return(
468function relaxLeftToRight(columns, alpha, beta) {
469  for (let i = 1, n = columns.length; i < n; ++i) {
470    const column = columns[i];
471    for (const target of column) {
472      let y = 0;
473      let w = 0;
474      for (const {source, value} of target.targetLinks) {
475        let v = value * (target.layer - source.layer);
476        y += targetTop(source, target) * v;
477        w += v;
478      }
479      if (!(w > 0)) continue;
480      let dy = (y / w - target.y0) * alpha;
481      target.y0 += dy;
482      target.y1 += dy;
483      reorderNodeLinks(target);
484    }
485    if (sort === undefined) column.sort(ascendingBreadth);
486    resolveCollisions(column, beta);
487  }
488}
489)}
490
491function _targetTop(py){return(
492function targetTop(source, target) {
493  let y = source.y0 - (source.sourceLinks.length - 1) * py / 2;
494  for (const {target: node, width} of source.sourceLinks) {
495    if (node === target) break;
496    y += width + py;
497  }
498  for (const {source: node, width} of target.targetLinks) {
499    if (node === source) break;
500    y -= width;
501  }
502  return y;
503}
504)}
505
506function _reorderNodeLinks(linkSort,ascendingTargetBreadth,ascendingSourceBreadth){return(
507function reorderNodeLinks({sourceLinks, targetLinks}) {
508  if (linkSort === undefined) {
509    for (const {source: {sourceLinks}} of targetLinks) {
510      sourceLinks.sort(ascendingTargetBreadth);
511    }
512    for (const {target: {targetLinks}} of sourceLinks) {
513      targetLinks.sort(ascendingSourceBreadth);
514    }
515  }
516}
517)}
518
519function _resolveCollisions(resolveCollisionsBottomToTop,py,resolveCollisionsTo
519pToBottom,y1,y0){return(
520function resolveCollisions(nodes, alpha) {
521  const i = nodes.length >> 1;
522  const subject = nodes[i];
523  resolveCollisionsBottomToTop(nodes, subject.y0 - py, i - 1, alpha);
524  resolveCollisionsTopToBottom(nodes, subject.y1 + py, i + 1, alpha);
525  resolveCollisionsBottomToTop(nodes, y1, nodes.length - 1, alpha);
526  resolveCollisionsTopToBottom(nodes, y0, 0, alpha);
527}
528)}
529
530function _resolveCollisionsTopToBottom(py){return(
531function resolveCollisionsTopToBottom(nodes, y, i, alpha) {
532  for (; i < nodes.length; ++i) {
533    const node = nodes[i];
534    const dy = (y - node.y0) * alpha;
535    if (dy > 1e-6) node.y0 += dy, node.y1 += dy;
536    y = node.y1 + py;
537  }
538}
539)}
540
541function _resolveCollisionsBottomToTop(py){return(
542function resolveCollisionsBottomToTop(nodes, y, i, alpha) {
543  for (; i >= 0; --i) {
544    const node = nodes[i];
545    const dy = (node.y1 - y) * alpha;
546    if (dy > 1e-6) node.y0 -= dy, node.y1 -= dy;
547    y = node.y0 - py;
548  }
549}
550)}
551
552function _step5(clone,data,computeNodeLinks,computeNodeValues,computeNodeDepths,computeNodeHeights,computeNodeBreadths)
553{
554  const graph = clone(data);
555  computeNodeLinks(graph);
556  computeNodeValues(graph);
557  computeNodeDepths(graph);
558  computeNodeHeights(graph);
559  computeNodeBreadths(graph);
560  return graph;
561}
562
563
564function _56(md){return(
565md`Finally, we derive link breadths from node breadths.`
566)}
567
568function _computeLinkBreadths(){return(
569function computeLinkBreadths({nodes}) {
570  for (const node of nodes) {
571    let y0 = node.y0;
572    let y1 = y0;
573    for (const link of node.sourceLinks) {
574      link.y0 = y0 + link.width / 2;
575      y0 += link.width;
576    }
577    for (const link of node.targetLinks) {
578      link.y1 = y1 + link.width / 2;
579      y1 += link.width;
580    }
581  }
582}
583)}
584
585function _step6(clone,data,computeNodeLinks,computeNodeValues,computeNodeDepths,computeNodeHeights,computeNodeBreadths)
586{
587  const graph = clone(data);
588  computeNodeLinks(graph);
589  computeNodeValues(graph);
590  computeNodeDepths(graph);
591  computeNodeHeights(graph);
592  computeNodeBreadths(graph);
593  computeNodeLinks(graph);
594  return graph;
595}
596
597
598function _59(md){return(
599md`And draw the sankey links using [d3.linkHorizontal](https://github.com/d3/d3-shape#linkHorizontal).`
600)}
601
602function _sankeyLinkHorizontal(d3)
603{
604  function horizontalSource(d) {
605    return [d.source.x1, d.y0];
606  }
607
608  function horizontalTarget(d) {
609    return [d.target.x0, d.y1];
610  }
611
612  return function() {
613    return d3.linkHorizontal()
614        .source(horizontalSource)
615        .target(horizontalTarget);
616  }
617}
618
619
620function _clone(){return(
621function clone({nodes, links}) {
622  return { nodes: nodes.map(d => Object.assign({}, d)), links: links.map(d => Object.assign({}, d)) };
623}
624)}
625
626function _62(md){return(
627md`_original chart cells_`
628)}
629
630function _format(d3,data)
631{
632  const format = d3.format(",.0f");
633  return data.units ? d => `${format(d)} ${data.units}` : format;
634}
635
636
637function _color(d3)
638{
639  const color = d3.scaleOrdinal(d3.schemeCategory10);
640  return d => color(d.category === undefined ? d.name : d.category);
641}
642
643
644function _width(){return(
645954
646)}
647
648function _height(){return(
649600
650)}
651
652function _d3(require){return(
653require("d3@6")
654)}
655
656export default function define(runtime, observer) {
657  const main = runtime.module();
658  function toString() { return this.url; }
659  const fileAttachments = new Map([
660    ["energy.csv", {url: new URL("./files/d6774e9422bd72369f195a30d3a6b33ff9d41676cff4d89c93511e1a458efb3cfd16cbb7ce3fecdd8dd2466121e10c9bfe57fd73c7520bf358d352a92b898614.csv", import.meta.url), mimeType: "text/csv", toString}]
661  ]);
662  main.builtin("FileAttachment", runtime.fileAttachments(name => fileAttachments.get(name)));
663  main.variable(observer()).define(["md"], _1);
664  main.variable(observer("viewof shape")).define("viewof shape", ["radio"], _shape);
665  main.variable(observer("shape")).define("shape", ["Generators", "viewof shape"], (G, _) => G.input(_));
666  main.variable(observer("chart")).define("chart", ["d3","width","height","sankey","data","color","format","DOM","shape","sankeyLinkHorizontal"], _chart);
667  main.variable(observer()).define(["md"], _4);
668  main.variable(observer("data")).define("data", ["FileAttachment"], _data);
669  main.variable(observer()).define(["md"], _6);
670  main.variable(observer("sankey")).define("sankey", ["clone","computeNodeLinks","computeNodeValues","computeNodeDepths","computeNodeHeights","computeNodeBreadths","computeLinkBreadths"], _sankey);
671  main.variable(observer()).define(["md"], _8);
672  main.variable(observer("x0")).define("x0", _x0);
673  main.variable(observer("x1")).define("x1", ["width"], _x1);
674  main.variable(observer("y0")).define("y0", _y0);
675  main.variable(observer("y1")).define("y1", ["height"], _y1);
676  main.variable(observer("dx")).define("dx", _dx);
677  main.variable(observer("dy")).define("dy", _dy);
678  main.define("initial py", _py);
679  main.variable(observer("mutable py")).define("mutable py", ["Mutable", "initial py"], (M, _) => new M(_));
680  main.variable(observer("py")).define("py", ["mutable py"], _ => _.generator);
681  main.variable(observer("id")).define("id", _id);
682  main.variable(observer("sort")).define("sort", _sort);
683  main.variable(observer("linkSort")).define("linkSort", _linkSort);
684  main.variable(observer("align")).define("align", _align);
685  main.variable(observer()).define(["md"], _20);
686  main.variable(observer("computeNodeLinks")).define("computeNodeLinks", ["id","find","linkSort"], _computeNodeLinks);
687  main.variable(observer("find")).define("find", _find);
688  main.variable(observer("step1")).define("step1", ["clone","data","computeNodeLinks"], _step1);
689  main.variable(observer()).define(["md"], _24);
690  main.variable(observer("computeNodeValues")).define("computeNodeValues", ["d3","value"], _computeNodeValues);
691  main.variable(observer("value")).define("value", _value);
692  main.variable(observer("step2")).define("step2", ["clone","data","computeNodeLinks","computeNodeValues"], _step2);
693  main.variable(observer()).define(["md"], _28);
694  main.variable(observer("computeNodeDepths")).define("computeNodeDepths", _computeNodeDepths);
695  main.variable(observer("computeNodeHeights")).define("computeNodeHeights", _computeNodeHeights);
696  main.variable(observer("step4")).define("step4", ["clone","data","computeNodeLinks","computeNodeValues","computeNodeDepths","computeNodeHeights"], _step4);
697  main.variable(observer()).define(["md"], _32);
698  main.variable(observer("computeNodeBreadths")).define("computeNodeBreadths", ["computeNodeLayers","dy","y1","y0","d3","py","mutable py","initializeNodeBreadths","iterations","relaxRightToLeft","relaxLeftToRight"], _computeNodeBreadths);
699  main.variable(observer()).define(["md"], _34);
700  main.variable(observer("computeNodeLayers")).define("computeNodeLayers", ["d3","x1","x0","dx","align","sort"], _computeNodeLayers);
701  main.variable(observer("columns")).define("columns", ["clone","step4","computeNodeLayers"], _columns);
702  main.variable(observer()).define(["md"], _37);
703  main.variable(observer("initializeNodeBreadths")).define("initializeNodeBreadths", ["d3","y1","y0","py","value","reorderLinks"], _initializeNodeBreadths);
704  main.variable(observer()).define(["md"], _39);
705  main.variable(observer("reorderLinks")).define("reorderLinks", ["linkSort","ascendingTargetBreadth","ascendingSourceBreadth"], _reorderLinks);
706  main.variable(observer("ascendingBreadth")).define("ascendingBreadth", _ascendingBreadth);
707  main.variable(observer("ascendingSourceBreadth")).define("ascendingSourceBreadth", ["ascendingBreadth"], _ascendingSourceBreadth);
708  main.variable(observer("ascendingTargetBreadth")).define("ascendingTargetBreadth", ["ascendingBreadth"], _ascendingTargetBreadth);
709  main.variable(observer()).define(["md"], _44);
710  main.variable(observer("iterations")).define("iterations", _iterations);
711  main.variable(observer()).define(["md"], _46);
712  main.variable(observer("relaxRightToLeft")).define("relaxRightToLeft", ["sourceTop","reorderNodeLinks","sort","ascendingBreadth","resolve
712Collisions"], _relaxRightToLeft);
713  main.variable(observer("sourceTop")).define("sourceTop", ["py"], _sourceTop);
714  main.variable(observer("relaxLeftToRight")).define("relaxLeftToRight", ["targetTop","reorderNodeLinks","sort","ascendingBreadth","resolveCollisions"], _relaxLeftToRight);
715  main.variable(observer("targetTop")).define("targetTop", ["py"], _targetTop);
716  main.variable(observer("reorderNodeLinks")).define("reorderNodeLinks", ["linkSort","ascendingTargetBreadth","ascendingSourceBreadth"], _reorderNodeLinks);
717  main.variable(observer("resolveCollisions")).define("resolveCollisions", ["resolveCollisionsBottomToTop","py","resolveCollisionsTopToBottom","y1","y0"], _resolveCollisions);
718  main.variable(observer("resolveCollisionsTopToBottom")).define("resolveCollisionsTopToBottom", ["py"], _resolveCollisionsTopToBottom);
719  main.variable(observer("resolveCollisionsBottomToTop")).define("resolveCollisionsBottomToTop", ["py"], _resolveCollisionsBottomToTop);
720  main.variable(observer("step5")).define("step5", ["clone","data","computeNodeLinks","computeNodeValues","computeNodeDepths","computeNodeHeights","computeNodeBreadths"], _step5);
721  main.variable(observer()).define(["md"], _56);
722  main.variable(observer("computeLinkBreadths")).define("computeLinkBreadths", _computeLinkBreadths);
723  main.variable(observer("step6")).define("step6", ["clone","data","computeNodeLinks","computeNodeValues","computeNodeDepths","computeNodeHeights","computeNodeBreadths"], _step6);
724  main.variable(observer()).define(["md"], _59);
725  main.variable(observer("sankeyLinkHorizontal")).define("sankeyLinkHorizontal", ["d3"], _sankeyLinkHorizontal);
726  main.variable(observer("clone")).define("clone", _clone);
727  main.variable(observer()).define(["md"], _62);
728  main.variable(observer("format")).define("format", ["d3","data"], _format);
729  main.variable(observer("color")).define("color", ["d3"], _color);
730  main.variable(observer("width")).define("width", _width);
731  main.variable(observer("height")).define("height", _height);
732  main.variable(observer("d3")).define("d3", ["require"], _d3);
733  const child1 = runtime.module(define1);
734  main.import("radio", child1);
735  return main;
736}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.