@dagrejs/dagre 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/index.d.ts +10 -2
  2. package/lib/acyclic.js +10 -18
  3. package/lib/add-border-segments.js +19 -11
  4. package/lib/coordinate-system.js +5 -15
  5. package/lib/data/list.js +8 -7
  6. package/lib/debug.js +25 -14
  7. package/lib/greedy-fas.js +35 -30
  8. package/lib/index.js +38 -0
  9. package/lib/layout.js +105 -102
  10. package/lib/nesting-graph.js +18 -21
  11. package/lib/normalize.js +22 -18
  12. package/lib/order/add-subgraph-constraints.js +6 -2
  13. package/lib/order/barycenter.js +14 -6
  14. package/lib/order/build-layer-graph.js +19 -13
  15. package/lib/order/cross-count.js +13 -10
  16. package/lib/order/index.js +33 -24
  17. package/lib/order/init-order.js +8 -7
  18. package/lib/order/resolve-conflicts.js +9 -19
  19. package/lib/order/sort-subgraph.js +16 -22
  20. package/lib/order/sort.js +13 -12
  21. package/lib/parent-dummy-chains.js +17 -19
  22. package/lib/position/bk.js +42 -84
  23. package/lib/position/index.js +10 -9
  24. package/lib/rank/feasible-tree.js +14 -17
  25. package/lib/rank/index.js +25 -15
  26. package/lib/rank/network-simplex.js +18 -39
  27. package/lib/rank/util.js +6 -12
  28. package/lib/util.js +42 -57
  29. package/lib/version.js +8 -1
  30. package/mjs-lib/acyclic.js +62 -0
  31. package/mjs-lib/add-border-segments.js +35 -0
  32. package/mjs-lib/coordinate-system.js +65 -0
  33. package/mjs-lib/data/list.js +56 -0
  34. package/mjs-lib/debug.js +30 -0
  35. package/mjs-lib/greedy-fas.js +125 -0
  36. package/mjs-lib/index.js +9 -0
  37. package/mjs-lib/layout.js +405 -0
  38. package/mjs-lib/nesting-graph.js +120 -0
  39. package/mjs-lib/normalize.js +84 -0
  40. package/mjs-lib/order/add-subgraph-constraints.js +49 -0
  41. package/mjs-lib/order/barycenter.js +24 -0
  42. package/mjs-lib/order/build-layer-graph.js +71 -0
  43. package/mjs-lib/order/cross-count.js +64 -0
  44. package/mjs-lib/order/index.js +70 -0
  45. package/mjs-lib/order/init-order.js +34 -0
  46. package/mjs-lib/order/resolve-conflicts.js +116 -0
  47. package/mjs-lib/order/sort-subgraph.js +71 -0
  48. package/mjs-lib/order/sort.js +54 -0
  49. package/mjs-lib/parent-dummy-chains.js +82 -0
  50. package/mjs-lib/position/bk.js +409 -0
  51. package/mjs-lib/position/index.js +30 -0
  52. package/mjs-lib/rank/feasible-tree.js +93 -0
  53. package/mjs-lib/rank/index.js +46 -0
  54. package/mjs-lib/rank/network-simplex.js +233 -0
  55. package/mjs-lib/rank/util.js +58 -0
  56. package/mjs-lib/util.js +305 -0
  57. package/mjs-lib/version.js +1 -0
  58. package/package.json +14 -3
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ import * as util from "./util.js";
4
+
5
+ /*
6
+ * Breaks any long edges in the graph into short segments that span 1 layer
7
+ * each. This operation is undoable with the denormalize function.
8
+ *
9
+ * Pre-conditions:
10
+ *
11
+ * 1. The input graph is a DAG.
12
+ * 2. Each node in the graph has a "rank" property.
13
+ *
14
+ * Post-condition:
15
+ *
16
+ * 1. All edges in the graph have a length of 1.
17
+ * 2. Dummy nodes are added where edges have been split into segments.
18
+ * 3. The graph is augmented with a "dummyChains" attribute which contains
19
+ * the first dummy in each chain of dummy nodes produced.
20
+ */
21
+ export function run(g) {
22
+ g.graph().dummyChains = [];
23
+ g.edges().forEach(edge => normalizeEdge(g, edge));
24
+ }
25
+
26
+ function normalizeEdge(g, e) {
27
+ let v = e.v;
28
+ let vRank = g.node(v).rank;
29
+ let w = e.w;
30
+ let wRank = g.node(w).rank;
31
+ let name = e.name;
32
+ let edgeLabel = g.edge(e);
33
+ let labelRank = edgeLabel.labelRank;
34
+
35
+ if (wRank === vRank + 1) return;
36
+
37
+ g.removeEdge(e);
38
+
39
+ let dummy, attrs, i;
40
+ for (i = 0, ++vRank; vRank < wRank; ++i, ++vRank) {
41
+ edgeLabel.points = [];
42
+ attrs = {
43
+ width: 0, height: 0,
44
+ edgeLabel: edgeLabel, edgeObj: e,
45
+ rank: vRank
46
+ };
47
+ dummy = util.addDummyNode(g, "edge", attrs, "_d");
48
+ if (vRank === labelRank) {
49
+ attrs.width = edgeLabel.width;
50
+ attrs.height = edgeLabel.height;
51
+ attrs.dummy = "edge-label";
52
+ attrs.labelpos = edgeLabel.labelpos;
53
+ }
54
+ g.setEdge(v, dummy, { weight: edgeLabel.weight }, name);
55
+ if (i === 0) {
56
+ g.graph().dummyChains.push(dummy);
57
+ }
58
+ v = dummy;
59
+ }
60
+
61
+ g.setEdge(v, w, { weight: edgeLabel.weight }, name);
62
+ }
63
+
64
+ export function undo(g) {
65
+ g.graph().dummyChains.forEach(v => {
66
+ let node = g.node(v);
67
+ let origLabel = node.edgeLabel;
68
+ let w;
69
+ g.setEdge(node.edgeObj, origLabel);
70
+ while (node.dummy) {
71
+ w = g.successors(v)[0];
72
+ g.removeNode(v);
73
+ origLabel.points.push({ x: node.x, y: node.y });
74
+ if (node.dummy === "edge-label") {
75
+ origLabel.x = node.x;
76
+ origLabel.y = node.y;
77
+ origLabel.width = node.width;
78
+ origLabel.height = node.height;
79
+ }
80
+ v = w;
81
+ node = g.node(v);
82
+ }
83
+ });
84
+ }
@@ -0,0 +1,49 @@
1
+ export default function addSubgraphConstraints(g, cg, vs) {
2
+ let prev = {},
3
+ rootPrev;
4
+
5
+ vs.forEach(v => {
6
+ let child = g.parent(v),
7
+ parent,
8
+ prevChild;
9
+ while (child) {
10
+ parent = g.parent(child);
11
+ if (parent) {
12
+ prevChild = prev[parent];
13
+ prev[parent] = child;
14
+ } else {
15
+ prevChild = rootPrev;
16
+ rootPrev = child;
17
+ }
18
+ if (prevChild && prevChild !== child) {
19
+ cg.setEdge(prevChild, child);
20
+ return;
21
+ }
22
+ child = parent;
23
+ }
24
+ });
25
+
26
+ /*
27
+ function dfs(v) {
28
+ var children = v ? g.children(v) : g.children();
29
+ if (children.length) {
30
+ var min = Number.POSITIVE_INFINITY,
31
+ subgraphs = [];
32
+ children.forEach(function(child) {
33
+ var childMin = dfs(child);
34
+ if (g.children(child).length) {
35
+ subgraphs.push({ v: child, order: childMin });
36
+ }
37
+ min = Math.min(min, childMin);
38
+ });
39
+ _.sortBy(subgraphs, "order").reduce(function(prev, curr) {
40
+ cg.setEdge(prev.v, curr.v);
41
+ return curr;
42
+ });
43
+ return min;
44
+ }
45
+ return g.node(v).order;
46
+ }
47
+ dfs(undefined);
48
+ */
49
+ }
@@ -0,0 +1,24 @@
1
+ export default function barycenter(g, movable = []) {
2
+ return movable.map(v => {
3
+ let inV = g.inEdges(v);
4
+ if (!inV.length) {
5
+ return { v: v };
6
+ } else {
7
+ let result = inV.reduce((acc, e) => {
8
+ let edge = g.edge(e),
9
+ nodeU = g.node(e.v);
10
+ return {
11
+ sum: acc.sum + (edge.weight * nodeU.order),
12
+ weight: acc.weight + edge.weight
13
+ };
14
+ }, { sum: 0, weight: 0 });
15
+
16
+ return {
17
+ v: v,
18
+ barycenter: result.sum / result.weight,
19
+ weight: result.weight
20
+ };
21
+ }
22
+ });
23
+ }
24
+
@@ -0,0 +1,71 @@
1
+ import { Graph as Graph } from "@dagrejs/graphlib";
2
+ import * as util from "../util.js";
3
+
4
+ /*
5
+ * Constructs a graph that can be used to sort a layer of nodes. The graph will
6
+ * contain all base and subgraph nodes from the request layer in their original
7
+ * hierarchy and any edges that are incident on these nodes and are of the type
8
+ * requested by the "relationship" parameter.
9
+ *
10
+ * Nodes from the requested rank that do not have parents are assigned a root
11
+ * node in the output graph, which is set in the root graph attribute. This
12
+ * makes it easy to walk the hierarchy of movable nodes during ordering.
13
+ *
14
+ * Pre-conditions:
15
+ *
16
+ * 1. Input graph is a DAG
17
+ * 2. Base nodes in the input graph have a rank attribute
18
+ * 3. Subgraph nodes in the input graph has minRank and maxRank attributes
19
+ * 4. Edges have an assigned weight
20
+ *
21
+ * Post-conditions:
22
+ *
23
+ * 1. Output graph has all nodes in the movable rank with preserved
24
+ * hierarchy.
25
+ * 2. Root nodes in the movable layer are made children of the node
26
+ * indicated by the root attribute of the graph.
27
+ * 3. Non-movable nodes incident on movable nodes, selected by the
28
+ * relationship parameter, are included in the graph (without hierarchy).
29
+ * 4. Edges incident on movable nodes, selected by the relationship
30
+ * parameter, are added to the output graph.
31
+ * 5. The weights for copied edges are aggregated as need, since the output
32
+ * graph is not a multi-graph.
33
+ */
34
+ export default function buildLayerGraph(g, rank, relationship) {
35
+ let root = createRootNode(g),
36
+ result = new Graph({ compound: true }).setGraph({ root: root })
37
+ .setDefaultNodeLabel(v => g.node(v));
38
+
39
+ g.nodes().forEach(v => {
40
+ let node = g.node(v),
41
+ parent = g.parent(v);
42
+
43
+ if (node.rank === rank || node.minRank <= rank && rank <= node.maxRank) {
44
+ result.setNode(v);
45
+ result.setParent(v, parent || root);
46
+
47
+ // This assumes we have only short edges!
48
+ g[relationship](v).forEach(e => {
49
+ let u = e.v === v ? e.w : e.v,
50
+ edge = result.edge(u, v),
51
+ weight = edge !== undefined ? edge.weight : 0;
52
+ result.setEdge(u, v, { weight: g.edge(e).weight + weight });
53
+ });
54
+
55
+ if (node.hasOwnProperty("minRank")) {
56
+ result.setNode(v, {
57
+ borderLeft: node.borderLeft[rank],
58
+ borderRight: node.borderRight[rank]
59
+ });
60
+ }
61
+ }
62
+ });
63
+
64
+ return result;
65
+ }
66
+
67
+ function createRootNode(g) {
68
+ var v;
69
+ while (g.hasNode((v = util.uniqueId("_root"))));
70
+ return v;
71
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ import { zipObject as zipObject } from "../util.js";
4
+
5
+ /*
6
+ * A function that takes a layering (an array of layers, each with an array of
7
+ * ordererd nodes) and a graph and returns a weighted crossing count.
8
+ *
9
+ * Pre-conditions:
10
+ *
11
+ * 1. Input graph must be simple (not a multigraph), directed, and include
12
+ * only simple edges.
13
+ * 2. Edges in the input graph must have assigned weights.
14
+ *
15
+ * Post-conditions:
16
+ *
17
+ * 1. The graph and layering matrix are left unchanged.
18
+ *
19
+ * This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
20
+ */
21
+ export default function crossCount(g, layering) {
22
+ let cc = 0;
23
+ for (let i = 1; i < layering.length; ++i) {
24
+ cc += twoLayerCrossCount(g, layering[i-1], layering[i]);
25
+ }
26
+ return cc;
27
+ }
28
+
29
+ function twoLayerCrossCount(g, northLayer, southLayer) {
30
+ // Sort all of the edges between the north and south layers by their position
31
+ // in the north layer and then the south. Map these edges to the position of
32
+ // their head in the south layer.
33
+ let southPos = zipObject(southLayer, southLayer.map((v, i) => i));
34
+ let southEntries = northLayer.flatMap(v => {
35
+ return g.outEdges(v).map(e => {
36
+ return { pos: southPos[e.w], weight: g.edge(e).weight };
37
+ }).sort((a, b) => a.pos - b.pos);
38
+ });
39
+
40
+ // Build the accumulator tree
41
+ let firstIndex = 1;
42
+ while (firstIndex < southLayer.length) firstIndex <<= 1;
43
+ let treeSize = 2 * firstIndex - 1;
44
+ firstIndex -= 1;
45
+ let tree = new Array(treeSize).fill(0);
46
+
47
+ // Calculate the weighted crossings
48
+ let cc = 0;
49
+ southEntries.forEach(entry => {
50
+ let index = entry.pos + firstIndex;
51
+ tree[index] += entry.weight;
52
+ let weightSum = 0;
53
+ while (index > 0) {
54
+ if (index % 2) {
55
+ weightSum += tree[index + 1];
56
+ }
57
+ index = (index - 1) >> 1;
58
+ tree[index] += entry.weight;
59
+ }
60
+ cc += entry.weight * weightSum;
61
+ });
62
+
63
+ return cc;
64
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ import { default as initOrder } from "./init-order.js";
4
+ import { default as crossCount } from "./cross-count.js";
5
+ import { default as sortSubgraph } from "./sort-subgraph.js";
6
+ import { default as buildLayerGraph } from "./build-layer-graph.js";
7
+ import { default as addSubgraphConstraints } from "./add-subgraph-constraints.js";
8
+ import { Graph as Graph } from "@dagrejs/graphlib";
9
+ import * as util from "../util.js";
10
+
11
+ /*
12
+ * Applies heuristics to minimize edge crossings in the graph and sets the best
13
+ * order solution as an order attribute on each node.
14
+ *
15
+ * Pre-conditions:
16
+ *
17
+ * 1. Graph must be DAG
18
+ * 2. Graph nodes must be objects with a "rank" attribute
19
+ * 3. Graph edges must have the "weight" attribute
20
+ *
21
+ * Post-conditions:
22
+ *
23
+ * 1. Graph nodes will have an "order" attribute based on the results of the
24
+ * algorithm.
25
+ */
26
+ export default function order(g) {
27
+ let maxRank = util.maxRank(g),
28
+ downLayerGraphs = buildLayerGraphs(g, util.range(1, maxRank + 1), "inEdges"),
29
+ upLayerGraphs = buildLayerGraphs(g, util.range(maxRank - 1, -1, -1), "outEdges");
30
+
31
+ let layering = initOrder(g);
32
+ assignOrder(g, layering);
33
+
34
+ let bestCC = Number.POSITIVE_INFINITY,
35
+ best;
36
+
37
+ for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
38
+ sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);
39
+
40
+ layering = util.buildLayerMatrix(g);
41
+ let cc = crossCount(g, layering);
42
+ if (cc < bestCC) {
43
+ lastBest = 0;
44
+ best = Object.assign({}, layering);
45
+ bestCC = cc;
46
+ }
47
+ }
48
+
49
+ assignOrder(g, best);
50
+ }
51
+
52
+ function buildLayerGraphs(g, ranks, relationship) {
53
+ return ranks.map(function(rank) {
54
+ return buildLayerGraph(g, rank, relationship);
55
+ });
56
+ }
57
+
58
+ function sweepLayerGraphs(layerGraphs, biasRight) {
59
+ let cg = new Graph();
60
+ layerGraphs.forEach(function(lg) {
61
+ let root = lg.graph().root;
62
+ let sorted = sortSubgraph(lg, root, cg, biasRight);
63
+ sorted.vs.forEach((v, i) => lg.node(v).order = i);
64
+ addSubgraphConstraints(lg, cg, sorted.vs);
65
+ });
66
+ }
67
+
68
+ function assignOrder(g, layering) {
69
+ Object.values(layering).forEach(layer => layer.forEach((v, i) => g.node(v).order = i));
70
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ import * as util from "../util.js";
4
+
5
+ /*
6
+ * Assigns an initial order value for each node by performing a DFS search
7
+ * starting from nodes in the first rank. Nodes are assigned an order in their
8
+ * rank as they are first visited.
9
+ *
10
+ * This approach comes from Gansner, et al., "A Technique for Drawing Directed
11
+ * Graphs."
12
+ *
13
+ * Returns a layering matrix with an array per layer and each layer sorted by
14
+ * the order of its nodes.
15
+ */
16
+ export default function initOrder(g) {
17
+ let visited = {};
18
+ let simpleNodes = g.nodes().filter(v => !g.children(v).length);
19
+ let maxRank = Math.max(...simpleNodes.map(v => g.node(v).rank));
20
+ let layers = util.range(maxRank + 1).map(() => []);
21
+
22
+ function dfs(v) {
23
+ if (visited[v]) return;
24
+ visited[v] = true;
25
+ let node = g.node(v);
26
+ layers[node.rank].push(v);
27
+ g.successors(v).forEach(dfs);
28
+ }
29
+
30
+ let orderedVs = simpleNodes.sort((a, b) => g.node(a).rank - g.node(b).rank);
31
+ orderedVs.forEach(dfs);
32
+
33
+ return layers;
34
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+
3
+ import * as util from "../util.js";
4
+
5
+ /*
6
+ * Given a list of entries of the form {v, barycenter, weight} and a
7
+ * constraint graph this function will resolve any conflicts between the
8
+ * constraint graph and the barycenters for the entries. If the barycenters for
9
+ * an entry would violate a constraint in the constraint graph then we coalesce
10
+ * the nodes in the conflict into a new node that respects the contraint and
11
+ * aggregates barycenter and weight information.
12
+ *
13
+ * This implementation is based on the description in Forster, "A Fast and
14
+ * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
15
+ * differs in some specific details.
16
+ *
17
+ * Pre-conditions:
18
+ *
19
+ * 1. Each entry has the form {v, barycenter, weight}, or if the node has
20
+ * no barycenter, then {v}.
21
+ *
22
+ * Returns:
23
+ *
24
+ * A new list of entries of the form {vs, i, barycenter, weight}. The list
25
+ * `vs` may either be a singleton or it may be an aggregation of nodes
26
+ * ordered such that they do not violate constraints from the constraint
27
+ * graph. The property `i` is the lowest original index of any of the
28
+ * elements in `vs`.
29
+ */
30
+ export default function resolveConflicts(entries, cg) {
31
+ let mappedEntries = {};
32
+ entries.forEach((entry, i) => {
33
+ let tmp = mappedEntries[entry.v] = {
34
+ indegree: 0,
35
+ "in": [],
36
+ out: [],
37
+ vs: [entry.v],
38
+ i: i
39
+ };
40
+ if (entry.barycenter !== undefined) {
41
+ tmp.barycenter = entry.barycenter;
42
+ tmp.weight = entry.weight;
43
+ }
44
+ });
45
+
46
+ cg.edges().forEach(e => {
47
+ let entryV = mappedEntries[e.v];
48
+ let entryW = mappedEntries[e.w];
49
+ if (entryV !== undefined && entryW !== undefined) {
50
+ entryW.indegree++;
51
+ entryV.out.push(mappedEntries[e.w]);
52
+ }
53
+ });
54
+
55
+ let sourceSet = Object.values(mappedEntries).filter(entry => !entry.indegree);
56
+
57
+ return doResolveConflicts(sourceSet);
58
+ }
59
+
60
+ function doResolveConflicts(sourceSet) {
61
+ let entries = [];
62
+
63
+ function handleIn(vEntry) {
64
+ return uEntry => {
65
+ if (uEntry.merged) {
66
+ return;
67
+ }
68
+ if (uEntry.barycenter === undefined ||
69
+ vEntry.barycenter === undefined ||
70
+ uEntry.barycenter >= vEntry.barycenter) {
71
+ mergeEntries(vEntry, uEntry);
72
+ }
73
+ };
74
+ }
75
+
76
+ function handleOut(vEntry) {
77
+ return wEntry => {
78
+ wEntry["in"].push(vEntry);
79
+ if (--wEntry.indegree === 0) {
80
+ sourceSet.push(wEntry);
81
+ }
82
+ };
83
+ }
84
+
85
+ while (sourceSet.length) {
86
+ let entry = sourceSet.pop();
87
+ entries.push(entry);
88
+ entry["in"].reverse().forEach(handleIn(entry));
89
+ entry.out.forEach(handleOut(entry));
90
+ }
91
+
92
+ return entries.filter(entry => !entry.merged).map(entry => {
93
+ return util.pick(entry, ["vs", "i", "barycenter", "weight"]);
94
+ });
95
+ }
96
+
97
+ function mergeEntries(target, source) {
98
+ let sum = 0;
99
+ let weight = 0;
100
+
101
+ if (target.weight) {
102
+ sum += target.barycenter * target.weight;
103
+ weight += target.weight;
104
+ }
105
+
106
+ if (source.weight) {
107
+ sum += source.barycenter * source.weight;
108
+ weight += source.weight;
109
+ }
110
+
111
+ target.vs = source.vs.concat(target.vs);
112
+ target.barycenter = sum / weight;
113
+ target.weight = weight;
114
+ target.i = Math.min(source.i, target.i);
115
+ source.merged = true;
116
+ }
@@ -0,0 +1,71 @@
1
+ import { default as barycenter } from "./barycenter.js";
2
+ import { default as resolveConflicts } from "./resolve-conflicts.js";
3
+ import { default as sort } from "./sort.js";
4
+
5
+ export default function sortSubgraph(g, v, cg, biasRight) {
6
+ let movable = g.children(v);
7
+ let node = g.node(v);
8
+ let bl = node ? node.borderLeft : undefined;
9
+ let br = node ? node.borderRight: undefined;
10
+ let subgraphs = {};
11
+
12
+ if (bl) {
13
+ movable = movable.filter(w => w !== bl && w !== br);
14
+ }
15
+
16
+ let barycenters = barycenter(g, movable);
17
+ barycenters.forEach(entry => {
18
+ if (g.children(entry.v).length) {
19
+ let subgraphResult = sortSubgraph(g, entry.v, cg, biasRight);
20
+ subgraphs[entry.v] = subgraphResult;
21
+ if (subgraphResult.hasOwnProperty("barycenter")) {
22
+ mergeBarycenters(entry, subgraphResult);
23
+ }
24
+ }
25
+ });
26
+
27
+ let entries = resolveConflicts(barycenters, cg);
28
+ expandSubgraphs(entries, subgraphs);
29
+
30
+ let result = sort(entries, biasRight);
31
+
32
+ if (bl) {
33
+ result.vs = [bl, result.vs, br].flat(true);
34
+ if (g.predecessors(bl).length) {
35
+ let blPred = g.node(g.predecessors(bl)[0]),
36
+ brPred = g.node(g.predecessors(br)[0]);
37
+ if (!result.hasOwnProperty("barycenter")) {
38
+ result.barycenter = 0;
39
+ result.weight = 0;
40
+ }
41
+ result.barycenter = (result.barycenter * result.weight +
42
+ blPred.order + brPred.order) / (result.weight + 2);
43
+ result.weight += 2;
44
+ }
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+ function expandSubgraphs(entries, subgraphs) {
51
+ entries.forEach(entry => {
52
+ entry.vs = entry.vs.flatMap(v => {
53
+ if (subgraphs[v]) {
54
+ return subgraphs[v].vs;
55
+ }
56
+ return v;
57
+ });
58
+ });
59
+ }
60
+
61
+ function mergeBarycenters(target, other) {
62
+ if (target.barycenter !== undefined) {
63
+ target.barycenter = (target.barycenter * target.weight +
64
+ other.barycenter * other.weight) /
65
+ (target.weight + other.weight);
66
+ target.weight += other.weight;
67
+ } else {
68
+ target.barycenter = other.barycenter;
69
+ target.weight = other.weight;
70
+ }
71
+ }
@@ -0,0 +1,54 @@
1
+ import * as util from "../util.js";
2
+
3
+ export default function sort(entries, biasRight) {
4
+ let parts = util.partition(entries, entry => {
5
+ return entry.hasOwnProperty("barycenter");
6
+ });
7
+ let sortable = parts.lhs,
8
+ unsortable = parts.rhs.sort((a, b) => b.i - a.i),
9
+ vs = [],
10
+ sum = 0,
11
+ weight = 0,
12
+ vsIndex = 0;
13
+
14
+ sortable.sort(compareWithBias(!!biasRight));
15
+
16
+ vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
17
+
18
+ sortable.forEach(entry => {
19
+ vsIndex += entry.vs.length;
20
+ vs.push(entry.vs);
21
+ sum += entry.barycenter * entry.weight;
22
+ weight += entry.weight;
23
+ vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
24
+ });
25
+
26
+ let result = { vs: vs.flat(true) };
27
+ if (weight) {
28
+ result.barycenter = sum / weight;
29
+ result.weight = weight;
30
+ }
31
+ return result;
32
+ }
33
+
34
+ function consumeUnsortable(vs, unsortable, index) {
35
+ let last;
36
+ while (unsortable.length && (last = unsortable[unsortable.length - 1]).i <= index) {
37
+ unsortable.pop();
38
+ vs.push(last.vs);
39
+ index++;
40
+ }
41
+ return index;
42
+ }
43
+
44
+ function compareWithBias(bias) {
45
+ return (entryV, entryW) => {
46
+ if (entryV.barycenter < entryW.barycenter) {
47
+ return -1;
48
+ } else if (entryV.barycenter > entryW.barycenter) {
49
+ return 1;
50
+ }
51
+
52
+ return !bias ? entryV.i - entryW.i : entryW.i - entryV.i;
53
+ };
54
+ }