08-27-周三_17-09-29

This commit is contained in:
2025-08-27 17:10:05 +08:00
commit 86df397d8f
12735 changed files with 1145479 additions and 0 deletions

89
node_modules/dagre-layout/lib/rank/feasible-tree.js generated vendored Normal file
View File

@@ -0,0 +1,89 @@
import _ from 'lodash'
import { Graph } from 'graphlibrary'
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.
*/
function feasibleTree (g) {
const t = new Graph({ directed: false })
// Choose arbitrary node from which to start our tree
const start = g.nodes()[0]
const size = g.nodeCount()
t.setNode(start, {})
let edge
let delta
while (tightTree(t, g) < size) {
edge = findMinSlackEdge(t, g)
delta = t.hasNode(edge.v) ? 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.
*/
function tightTree (t, g) {
function dfs (v) {
_.forEach(g.nodeEdges(v), function (e) {
const edgeV = e.v
const w = (v === edgeV) ? e.w : edgeV
if (!t.hasNode(w) && !slack(g, e)) {
t.setNode(w, {})
t.setEdge(v, w, {})
dfs(w)
}
})
}
_.forEach(t.nodes(), dfs)
return t.nodeCount()
}
/*
* Finds the edge with the smallest slack that is incident on tree and returns
* it.
*/
function findMinSlackEdge (t, g) {
return _.minBy(g.edges(), function (e) {
if (t.hasNode(e.v) !== t.hasNode(e.w)) {
return slack(g, e)
}
})
}
function shiftRanks (t, g, delta) {
_.forEach(t.nodes(), function (v) {
g.node(v).rank += delta
})
}
export default feasibleTree

45
node_modules/dagre-layout/lib/rank/index.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import { longestPath } from './util'
import feasibleTree from './feasible-tree'
import networkSimplex from './network-simplex'
/*
* Assigns a rank to each node in the input graph that respects the "minlen"
* constraint specified on edges between nodes.
*
* This basic structure is derived from Gansner, et al., "A Technique for
* Drawing Directed Graphs."
*
* Pre-conditions:
*
* 1. Graph must be a connected DAG
* 2. Graph nodes must be objects
* 3. Graph edges must have "weight" and "minlen" attributes
*
* Post-conditions:
*
* 1. Graph nodes will have a "rank" attribute based on the results of the
* algorithm. Ranks can start at any index (including negative), we'll
* fix them up later.
*/
function rank (g) {
switch (g.graph().ranker) {
case 'network-simplex': networkSimplexRanker(g); break
case 'tight-tree': tightTreeRanker(g); break
case 'longest-path': longestPathRanker(g); break
default: networkSimplexRanker(g)
}
}
// A fast and simple ranker, but results are far from optimal.
const longestPathRanker = longestPath
function tightTreeRanker (g) {
longestPath(g)
feasibleTree(g)
}
function networkSimplexRanker (g) {
networkSimplex(g)
}
export default rank

234
node_modules/dagre-layout/lib/rank/network-simplex.js generated vendored Normal file
View File

@@ -0,0 +1,234 @@
import _ from 'lodash'
import { alg } from 'graphlibrary'
import feasibleTree from './feasible-tree'
import { slack, longestPath as initRank } from './util'
import { simplify } from '../util'
const { preorder, postorder } = alg
// Expose some internals for testing purposes
networkSimplex.initLowLimValues = initLowLimValues
networkSimplex.initCutValues = initCutValues
networkSimplex.calcCutValue = calcCutValue
networkSimplex.leaveEdge = leaveEdge
networkSimplex.enterEdge = enterEdge
networkSimplex.exchangeEdges = exchangeEdges
/*
* The network simplex algorithm assigns ranks to each node in the input graph
* and iteratively improves the ranking to reduce the length of edges.
*
* Preconditions:
*
* 1. The input graph must be a DAG.
* 2. All nodes in the graph must have an object value.
* 3. All edges in the graph must have "minlen" and "weight" attributes.
*
* Postconditions:
*
* 1. All nodes in the graph will have an assigned "rank" attribute that has
* been optimized by the network simplex algorithm. Ranks start at 0.
*
*
* A rough sketch of the algorithm is as follows:
*
* 1. Assign initial ranks to each node. We use the longest path algorithm,
* which assigns ranks to the lowest position possible. In general this
* leads to very wide bottom ranks and unnecessarily long edges.
* 2. Construct a feasible tight tree. A tight tree is one such that all
* edges in the tree have no slack (difference between length of edge
* and minlen for the edge). This by itself greatly improves the assigned
* rankings by shorting edges.
* 3. Iteratively find edges that have negative cut values. Generally a
* negative cut value indicates that the edge could be removed and a new
* tree edge could be added to produce a more compact graph.
*
* Much of the algorithms here are derived from Gansner, et al., "A Technique
* for Drawing Directed Graphs." The structure of the file roughly follows the
* structure of the overall algorithm.
*/
function networkSimplex (g) {
g = simplify(g)
initRank(g)
const t = feasibleTree(g)
initLowLimValues(t)
initCutValues(t, g)
let e
let f
while ((e = leaveEdge(t))) {
f = enterEdge(t, g, e)
exchangeEdges(t, g, e, f)
}
}
/*
* Initializes cut values for all edges in the tree.
*/
function initCutValues (t, g) {
let vs = postorder(t, t.nodes())
vs = vs.slice(0, vs.length - 1)
_.forEach(vs, function (v) {
assignCutValue(t, g, v)
})
}
function assignCutValue (t, g, child) {
const childLab = t.node(child)
const parent = childLab.parent
t.edge(child, parent).cutvalue = calcCutValue(t, g, child)
}
/*
* Given the tight tree, its graph, and a child in the graph calculate and
* return the cut value for the edge between the child and its parent.
*/
function calcCutValue (t, g, child) {
const childLab = t.node(child)
const parent = childLab.parent
// True if the child is on the tail end of the edge in the directed graph
let childIsTail = true
// The graph's view of the tree edge we're inspecting
let graphEdge = g.edge(child, parent)
// The accumulated cut value for the edge between this node and its parent
let cutValue = 0
if (!graphEdge) {
childIsTail = false
graphEdge = g.edge(parent, child)
}
cutValue = graphEdge.weight
_.forEach(g.nodeEdges(child), function (e) {
const isOutEdge = e.v === child
const other = isOutEdge ? e.w : e.v
if (other !== parent) {
const pointsToHead = isOutEdge === childIsTail
const otherWeight = g.edge(e).weight
cutValue += pointsToHead ? otherWeight : -otherWeight
if (isTreeEdge(t, child, other)) {
const otherCutValue = t.edge(child, other).cutvalue
cutValue += pointsToHead ? -otherCutValue : otherCutValue
}
}
})
return cutValue
}
function initLowLimValues (tree, root) {
if (arguments.length < 2) {
root = tree.nodes()[0]
}
dfsAssignLowLim(tree, {}, 1, root)
}
function dfsAssignLowLim (tree, visited, nextLim, v, parent) {
const low = nextLim
const label = tree.node(v)
visited[v] = true
_.forEach(tree.neighbors(v), function (w) {
if (!_.has(visited, w)) {
nextLim = dfsAssignLowLim(tree, visited, nextLim, w, v)
}
})
label.low = low
label.lim = nextLim++
if (parent) {
label.parent = parent
} else {
// TODO should be able to remove this when we incrementally update low lim
delete label.parent
}
return nextLim
}
function leaveEdge (tree) {
return _.find(tree.edges(), function (e) {
return tree.edge(e).cutvalue < 0
})
}
function enterEdge (t, g, edge) {
let v = edge.v
let w = edge.w
// For the rest of this function we assume that v is the tail and w is the
// head, so if we don't have this edge in the graph we should flip it to
// match the correct orientation.
if (!g.hasEdge(v, w)) {
v = edge.w
w = edge.v
}
const vLabel = t.node(v)
const wLabel = t.node(w)
let tailLabel = vLabel
let flip = false
// If the root is in the tail of the edge then we need to flip the logic that
// checks for the head and tail nodes in the candidates function below.
if (vLabel.lim > wLabel.lim) {
tailLabel = wLabel
flip = true
}
const candidates = _.filter(g.edges(), function (edge) {
return flip === isDescendant(t, t.node(edge.v), tailLabel) &&
flip !== isDescendant(t, t.node(edge.w), tailLabel)
})
return _.minBy(candidates, function (edge) { return slack(g, edge) })
}
function exchangeEdges (t, g, e, f) {
const v = e.v
const w = e.w
t.removeEdge(v, w)
t.setEdge(f.v, f.w, {})
initLowLimValues(t)
initCutValues(t, g)
updateRanks(t, g)
}
function updateRanks (t, g) {
const root = _.find(t.nodes(), function (v) { return !g.node(v).parent })
let vs = preorder(t, root)
vs = vs.slice(1)
_.forEach(vs, function (v) {
const parent = t.node(v).parent
let edge = g.edge(v, parent)
let flipped = false
if (!edge) {
edge = g.edge(parent, v)
flipped = true
}
g.node(v).rank = g.node(parent).rank + (flipped ? edge.minlen : -edge.minlen)
})
}
/*
* Returns true if the edge is in the tree.
*/
function isTreeEdge (tree, u, v) {
return tree.hasEdge(u, v)
}
/*
* Returns true if the specified node is descendant of the root node per the
* assigned low and lim attributes in the tree.
*/
function isDescendant (tree, vLabel, rootLabel) {
return rootLabel.low <= vLabel.lim && vLabel.lim <= rootLabel.lim
}
export default networkSimplex

55
node_modules/dagre-layout/lib/rank/util.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import _ from 'lodash'
/*
* Initializes ranks for the input graph using the longest path algorithm. This
* algorithm scales well and is fast in practice, it yields rather poor
* solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
* ranks wide and leaving edges longer than necessary. However, due to its
* speed, this algorithm is good for getting an initial ranking that can be fed
* into other algorithms.
*
* This algorithm does not normalize layers because it will be used by other
* algorithms in most cases. If using this algorithm directly, be sure to
* run normalize at the end.
*
* Pre-conditions:
*
* 1. Input graph is a DAG.
* 2. Input graph node labels can be assigned properties.
*
* Post-conditions:
*
* 1. Each node will be assign an (unnormalized) "rank" property.
*/
export function longestPath (g) {
const visited = {}
function dfs (v) {
const label = g.node(v)
if (_.has(visited, v)) {
return label.rank
}
visited[v] = true
const rank = _.min(_.map(g.outEdges(v), function (e) {
return dfs(e.w) - g.edge(e).minlen
})) || 0
return (label.rank = rank)
}
_.forEach(g.sources(), dfs)
}
/*
* Returns the amount of slack for the given edge. The slack is defined as the
* difference between the length of the edge and its minimum length.
*/
export function slack (g, e) {
return g.node(e.w).rank - g.node(e.v).rank - g.edge(e).minlen
}
export default {
longestPath: longestPath,
slack: slack
}