You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
5.0 KiB
164 lines
5.0 KiB
import { Graph } from '@antv/graphlib';
|
|
import { minBy } from '../util';
|
|
import { slack } from './util';
|
|
/*
|
|
* Constructs a spanning tree with tight edges and adjusted the input node's
|
|
* ranks to achieve this. A tight edge is one that is has a length that matches
|
|
* its "minlen" attribute.
|
|
*
|
|
* The basic structure for this function is derived from Gansner, et al., "A
|
|
* Technique for Drawing Directed Graphs."
|
|
*
|
|
* Pre-conditions:
|
|
*
|
|
* 1. Graph must be a DAG.
|
|
* 2. Graph must be connected.
|
|
* 3. Graph must have at least one node.
|
|
* 5. Graph nodes must have been previously assigned a "rank" property that
|
|
* respects the "minlen" property of incident edges.
|
|
* 6. Graph edges must have a "minlen" property.
|
|
*
|
|
* Post-conditions:
|
|
*
|
|
* - Graph nodes will have their rank adjusted to ensure that all edges are
|
|
* tight.
|
|
*
|
|
* Returns a tree (undirected graph) that is constructed using only "tight"
|
|
* edges.
|
|
*/
|
|
const feasibleTree = (g) => {
|
|
const t = new Graph({
|
|
tree: [],
|
|
});
|
|
// Choose arbitrary node from which to start our tree
|
|
const start = g.getAllNodes()[0];
|
|
const size = g.getAllNodes().length;
|
|
t.addNode(start);
|
|
let edge;
|
|
let delta;
|
|
while (tightTree(t, g) < size) {
|
|
edge = findMinSlackEdge(t, g);
|
|
delta = t.hasNode(edge.source) ? slack(g, edge) : -slack(g, edge);
|
|
shiftRanks(t, g, delta);
|
|
}
|
|
return t;
|
|
};
|
|
/*
|
|
* Finds a maximal tree of tight edges and returns the number of nodes in the
|
|
* tree.
|
|
*/
|
|
const tightTree = (t, g) => {
|
|
const dfs = (v) => {
|
|
g.getRelatedEdges(v, 'both').forEach((e) => {
|
|
const edgeV = e.source;
|
|
const w = v === edgeV ? e.target : edgeV;
|
|
if (!t.hasNode(w) && !slack(g, e)) {
|
|
t.addNode({
|
|
id: w,
|
|
data: {},
|
|
});
|
|
t.addEdge({
|
|
id: e.id,
|
|
source: v,
|
|
target: w,
|
|
data: {},
|
|
});
|
|
dfs(w);
|
|
}
|
|
});
|
|
};
|
|
t.getAllNodes().forEach((n) => dfs(n.id));
|
|
return t.getAllNodes().length;
|
|
};
|
|
/*
|
|
* Constructs a spanning tree with tight edges and adjusted the input node's
|
|
* ranks to achieve this. A tight edge is one that is has a length that matches
|
|
* its "minlen" attribute.
|
|
*
|
|
* The basic structure for this function is derived from Gansner, et al., "A
|
|
* Technique for Drawing Directed Graphs."
|
|
*
|
|
* Pre-conditions:
|
|
*
|
|
* 1. Graph must be a DAG.
|
|
* 2. Graph must be connected.
|
|
* 3. Graph must have at least one node.
|
|
* 5. Graph nodes must have been previously assigned a "rank" property that
|
|
* respects the "minlen" property of incident edges.
|
|
* 6. Graph edges must have a "minlen" property.
|
|
*
|
|
* Post-conditions:
|
|
*
|
|
* - Graph nodes will have their rank adjusted to ensure that all edges are
|
|
* tight.
|
|
*
|
|
* Returns a tree (undirected graph) that is constructed using only "tight"
|
|
* edges.
|
|
*/
|
|
const feasibleTreeWithLayer = (g) => {
|
|
const t = new Graph({ tree: [] });
|
|
// Choose arbitrary node from which to start our tree
|
|
const start = g.getAllNodes()[0];
|
|
const size = g.getAllNodes().length;
|
|
t.addNode(start);
|
|
let edge;
|
|
let delta;
|
|
while (tightTreeWithLayer(t, g) < size) {
|
|
edge = findMinSlackEdge(t, g);
|
|
delta = t.hasNode(edge.source) ? slack(g, edge) : -slack(g, edge);
|
|
shiftRanks(t, g, delta);
|
|
}
|
|
return t;
|
|
};
|
|
/*
|
|
* Finds a maximal tree of tight edges and returns the number of nodes in the
|
|
* tree.
|
|
*/
|
|
const tightTreeWithLayer = (t, g) => {
|
|
const dfs = (v) => {
|
|
var _a;
|
|
(_a = g.getRelatedEdges(v, 'both')) === null || _a === void 0 ? void 0 : _a.forEach((e) => {
|
|
const edgeV = e.source;
|
|
const w = v === edgeV ? e.target : edgeV;
|
|
// 对于指定layer的,直接加入tight-tree,不参与调整
|
|
if (!t.hasNode(w) &&
|
|
(g.getNode(w).data.layer !== undefined || !slack(g, e))) {
|
|
t.addNode({
|
|
id: w,
|
|
data: {},
|
|
});
|
|
t.addEdge({
|
|
id: e.id,
|
|
source: v,
|
|
target: w,
|
|
data: {},
|
|
});
|
|
dfs(w);
|
|
}
|
|
});
|
|
};
|
|
t.getAllNodes().forEach((n) => dfs(n.id));
|
|
return t.getAllNodes().length;
|
|
};
|
|
/*
|
|
* Finds the edge with the smallest slack that is incident on tree and returns
|
|
* it.
|
|
*/
|
|
const findMinSlackEdge = (t, g) => {
|
|
return minBy(g.getAllEdges(), (e) => {
|
|
if (t.hasNode(e.source) !== t.hasNode(e.target)) {
|
|
return slack(g, e);
|
|
}
|
|
return Infinity;
|
|
});
|
|
};
|
|
const shiftRanks = (t, g, delta) => {
|
|
t.getAllNodes().forEach((tn) => {
|
|
const v = g.getNode(tn.id);
|
|
if (!v.data.rank)
|
|
v.data.rank = 0;
|
|
v.data.rank += delta;
|
|
});
|
|
};
|
|
export { feasibleTree, feasibleTreeWithLayer };
|
|
//# sourceMappingURL=feasible-tree.js.map
|