@dagrejs/dagre 1.1.0 → 1.1.2
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.
- package/dist/dagre.js +161 -150
- package/dist/dagre.min.js +60 -60
- package/lib/acyclic.js +18 -10
- package/lib/add-border-segments.js +11 -19
- package/lib/coordinate-system.js +15 -5
- package/lib/data/list.js +7 -8
- package/lib/debug.js +14 -25
- package/lib/greedy-fas.js +30 -35
- package/lib/layout.js +102 -105
- package/lib/nesting-graph.js +21 -18
- package/lib/normalize.js +18 -22
- package/lib/order/add-subgraph-constraints.js +2 -6
- package/lib/order/barycenter.js +6 -14
- package/lib/order/build-layer-graph.js +13 -19
- package/lib/order/cross-count.js +10 -13
- package/lib/order/index.js +23 -23
- package/lib/order/init-order.js +7 -8
- package/lib/order/resolve-conflicts.js +19 -9
- package/lib/order/sort-subgraph.js +22 -16
- package/lib/order/sort.js +12 -13
- package/lib/parent-dummy-chains.js +19 -17
- package/lib/position/bk.js +84 -40
- package/lib/position/index.js +9 -10
- package/lib/rank/feasible-tree.js +17 -14
- package/lib/rank/index.js +15 -25
- package/lib/rank/network-simplex.js +39 -18
- package/lib/rank/util.js +12 -6
- package/lib/util.js +57 -42
- package/lib/version.js +1 -8
- package/package.json +3 -15
- package/lib/index.js +0 -38
- package/mjs-lib/acyclic.js +0 -62
- package/mjs-lib/add-border-segments.js +0 -35
- package/mjs-lib/coordinate-system.js +0 -65
- package/mjs-lib/data/list.js +0 -56
- package/mjs-lib/debug.js +0 -30
- package/mjs-lib/greedy-fas.js +0 -125
- package/mjs-lib/index.js +0 -9
- package/mjs-lib/layout.js +0 -405
- package/mjs-lib/nesting-graph.js +0 -120
- package/mjs-lib/normalize.js +0 -84
- package/mjs-lib/order/add-subgraph-constraints.js +0 -49
- package/mjs-lib/order/barycenter.js +0 -24
- package/mjs-lib/order/build-layer-graph.js +0 -71
- package/mjs-lib/order/cross-count.js +0 -64
- package/mjs-lib/order/index.js +0 -70
- package/mjs-lib/order/init-order.js +0 -34
- package/mjs-lib/order/resolve-conflicts.js +0 -116
- package/mjs-lib/order/sort-subgraph.js +0 -71
- package/mjs-lib/order/sort.js +0 -54
- package/mjs-lib/parent-dummy-chains.js +0 -82
- package/mjs-lib/position/bk.js +0 -409
- package/mjs-lib/position/index.js +0 -30
- package/mjs-lib/rank/feasible-tree.js +0 -93
- package/mjs-lib/rank/index.js +0 -46
- package/mjs-lib/rank/network-simplex.js +0 -233
- package/mjs-lib/rank/util.js +0 -58
- package/mjs-lib/util.js +0 -305
- package/mjs-lib/version.js +0 -1
package/mjs-lib/nesting-graph.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import * as util from "./util.js";
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
|
|
5
|
-
* adds appropriate edges to ensure that all cluster nodes are placed between
|
|
6
|
-
* these boundaries, and ensures that the graph is connected.
|
|
7
|
-
*
|
|
8
|
-
* In addition we ensure, through the use of the minlen property, that nodes
|
|
9
|
-
* and subgraph border nodes to not end up on the same rank.
|
|
10
|
-
*
|
|
11
|
-
* Preconditions:
|
|
12
|
-
*
|
|
13
|
-
* 1. Input graph is a DAG
|
|
14
|
-
* 2. Nodes in the input graph has a minlen attribute
|
|
15
|
-
*
|
|
16
|
-
* Postconditions:
|
|
17
|
-
*
|
|
18
|
-
* 1. Input graph is connected.
|
|
19
|
-
* 2. Dummy nodes are added for the tops and bottoms of subgraphs.
|
|
20
|
-
* 3. The minlen attribute for nodes is adjusted to ensure nodes do not
|
|
21
|
-
* get placed on the same rank as subgraph border nodes.
|
|
22
|
-
*
|
|
23
|
-
* The nesting graph idea comes from Sander, "Layout of Compound Directed
|
|
24
|
-
* Graphs."
|
|
25
|
-
*/
|
|
26
|
-
export function run(g) {
|
|
27
|
-
let root = util.addDummyNode(g, "root", {}, "_root");
|
|
28
|
-
let depths = treeDepths(g);
|
|
29
|
-
let height = Math.max(...Object.values(depths)) - 1; // Note: depths is an Object not an array
|
|
30
|
-
let nodeSep = 2 * height + 1;
|
|
31
|
-
|
|
32
|
-
g.graph().nestingRoot = root;
|
|
33
|
-
|
|
34
|
-
// Multiply minlen by nodeSep to align nodes on non-border ranks.
|
|
35
|
-
g.edges().forEach(e => g.edge(e).minlen *= nodeSep);
|
|
36
|
-
|
|
37
|
-
// Calculate a weight that is sufficient to keep subgraphs vertically compact
|
|
38
|
-
let weight = sumWeights(g) + 1;
|
|
39
|
-
|
|
40
|
-
// Create border nodes and link them up
|
|
41
|
-
g.children().forEach(child => dfs(g, root, nodeSep, weight, height, depths, child));
|
|
42
|
-
|
|
43
|
-
// Save the multiplier for node layers for later removal of empty border
|
|
44
|
-
// layers.
|
|
45
|
-
g.graph().nodeRankFactor = nodeSep;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function dfs(g, root, nodeSep, weight, height, depths, v) {
|
|
49
|
-
let children = g.children(v);
|
|
50
|
-
if (!children.length) {
|
|
51
|
-
if (v !== root) {
|
|
52
|
-
g.setEdge(root, v, { weight: 0, minlen: nodeSep });
|
|
53
|
-
}
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let top = util.addBorderNode(g, "_bt");
|
|
58
|
-
let bottom = util.addBorderNode(g, "_bb");
|
|
59
|
-
let label = g.node(v);
|
|
60
|
-
|
|
61
|
-
g.setParent(top, v);
|
|
62
|
-
label.borderTop = top;
|
|
63
|
-
g.setParent(bottom, v);
|
|
64
|
-
label.borderBottom = bottom;
|
|
65
|
-
|
|
66
|
-
children.forEach(child => {
|
|
67
|
-
dfs(g, root, nodeSep, weight, height, depths, child);
|
|
68
|
-
|
|
69
|
-
let childNode = g.node(child);
|
|
70
|
-
let childTop = childNode.borderTop ? childNode.borderTop : child;
|
|
71
|
-
let childBottom = childNode.borderBottom ? childNode.borderBottom : child;
|
|
72
|
-
let thisWeight = childNode.borderTop ? weight : 2 * weight;
|
|
73
|
-
let minlen = childTop !== childBottom ? 1 : height - depths[v] + 1;
|
|
74
|
-
|
|
75
|
-
g.setEdge(top, childTop, {
|
|
76
|
-
weight: thisWeight,
|
|
77
|
-
minlen: minlen,
|
|
78
|
-
nestingEdge: true
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
g.setEdge(childBottom, bottom, {
|
|
82
|
-
weight: thisWeight,
|
|
83
|
-
minlen: minlen,
|
|
84
|
-
nestingEdge: true
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (!g.parent(v)) {
|
|
89
|
-
g.setEdge(root, top, { weight: 0, minlen: height + depths[v] });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function treeDepths(g) {
|
|
94
|
-
var depths = {};
|
|
95
|
-
function dfs(v, depth) {
|
|
96
|
-
var children = g.children(v);
|
|
97
|
-
if (children && children.length) {
|
|
98
|
-
children.forEach(child => dfs(child, depth + 1));
|
|
99
|
-
}
|
|
100
|
-
depths[v] = depth;
|
|
101
|
-
}
|
|
102
|
-
g.children().forEach(v => dfs(v, 1));
|
|
103
|
-
return depths;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function sumWeights(g) {
|
|
107
|
-
return g.edges().reduce((acc, e) => acc + g.edge(e).weight, 0);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function cleanup(g) {
|
|
111
|
-
var graphLabel = g.graph();
|
|
112
|
-
g.removeNode(graphLabel.nestingRoot);
|
|
113
|
-
delete graphLabel.nestingRoot;
|
|
114
|
-
g.edges().forEach(e => {
|
|
115
|
-
var edge = g.edge(e);
|
|
116
|
-
if (edge.nestingEdge) {
|
|
117
|
-
g.removeEdge(e);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
package/mjs-lib/normalize.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,71 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
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
|
-
}
|
package/mjs-lib/order/index.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
}
|