08-27-周三_17-09-29
This commit is contained in:
394
node_modules/dagre-layout/lib/layout.js
generated
vendored
Normal file
394
node_modules/dagre-layout/lib/layout.js
generated
vendored
Normal file
@@ -0,0 +1,394 @@
|
||||
import _ from 'lodash'
|
||||
import { Graph } from 'graphlibrary'
|
||||
|
||||
import acyclic from './acyclic'
|
||||
import normalize from './normalize'
|
||||
import rank from './rank'
|
||||
import util, { normalizeRanks, removeEmptyRanks } from './util'
|
||||
import parentDummyChains from './parent-dummy-chains'
|
||||
import nestingGraph from './nesting-graph'
|
||||
import addBorderSegments from './add-border-segments'
|
||||
import coordinateSystem from './coordinate-system'
|
||||
import order from './order'
|
||||
import position from './position'
|
||||
|
||||
function layout (g, opts) {
|
||||
const time = opts && opts.debugTiming ? util.time : util.notime
|
||||
time('layout', function () {
|
||||
const layoutGraph = time(' buildLayoutGraph',
|
||||
function () { return buildLayoutGraph(g) })
|
||||
time(' runLayout', function () { runLayout(layoutGraph, time) })
|
||||
time(' updateInputGraph', function () { updateInputGraph(g, layoutGraph) })
|
||||
})
|
||||
}
|
||||
|
||||
function runLayout (g, time) {
|
||||
time(' makeSpaceForEdgeLabels', function () { makeSpaceForEdgeLabels(g) })
|
||||
time(' removeSelfEdges', function () { removeSelfEdges(g) })
|
||||
time(' acyclic', function () { acyclic.run(g) })
|
||||
time(' nestingGraph.run', function () { nestingGraph.run(g) })
|
||||
time(' rank', function () { rank(util.asNonCompoundGraph(g)) })
|
||||
time(' injectEdgeLabelProxies', function () { injectEdgeLabelProxies(g) })
|
||||
time(' removeEmptyRanks', function () { removeEmptyRanks(g) })
|
||||
time(' nestingGraph.cleanup', function () { nestingGraph.cleanup(g) })
|
||||
time(' normalizeRanks', function () { normalizeRanks(g) })
|
||||
time(' assignRankMinMax', function () { assignRankMinMax(g) })
|
||||
time(' removeEdgeLabelProxies', function () { removeEdgeLabelProxies(g) })
|
||||
time(' normalize.run', function () { normalize.run(g) })
|
||||
time(' parentDummyChains', function () { parentDummyChains(g) })
|
||||
time(' addBorderSegments', function () { addBorderSegments(g) })
|
||||
time(' order', function () { order(g) })
|
||||
time(' insertSelfEdges', function () { insertSelfEdges(g) })
|
||||
time(' adjustCoordinateSystem', function () { coordinateSystem.adjust(g) })
|
||||
time(' position', function () { position(g) })
|
||||
time(' positionSelfEdges', function () { positionSelfEdges(g) })
|
||||
time(' removeBorderNodes', function () { removeBorderNodes(g) })
|
||||
time(' normalize.undo', function () { normalize.undo(g) })
|
||||
time(' fixupEdgeLabelCoords', function () { fixupEdgeLabelCoords(g) })
|
||||
time(' undoCoordinateSystem', function () { coordinateSystem.undo(g) })
|
||||
time(' translateGraph', function () { translateGraph(g) })
|
||||
time(' assignNodeIntersects', function () { assignNodeIntersects(g) })
|
||||
time(' reversePoints', function () { reversePointsForReversedEdges(g) })
|
||||
time(' acyclic.undo', function () { acyclic.undo(g) })
|
||||
}
|
||||
|
||||
/*
|
||||
* Copies final layout information from the layout graph back to the input
|
||||
* graph. This process only copies whitelisted attributes from the layout graph
|
||||
* to the input graph, so it serves as a good place to determine what
|
||||
* attributes can influence layout.
|
||||
*/
|
||||
function updateInputGraph (inputGraph, layoutGraph) {
|
||||
_.forEach(inputGraph.nodes(), function (v) {
|
||||
const inputLabel = inputGraph.node(v)
|
||||
const layoutLabel = layoutGraph.node(v)
|
||||
|
||||
if (inputLabel) {
|
||||
inputLabel.x = layoutLabel.x
|
||||
inputLabel.y = layoutLabel.y
|
||||
|
||||
if (layoutGraph.children(v).length) {
|
||||
inputLabel.width = layoutLabel.width
|
||||
inputLabel.height = layoutLabel.height
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
_.forEach(inputGraph.edges(), function (e) {
|
||||
const inputLabel = inputGraph.edge(e)
|
||||
const layoutLabel = layoutGraph.edge(e)
|
||||
|
||||
inputLabel.points = layoutLabel.points
|
||||
if (_.has(layoutLabel, 'x')) {
|
||||
inputLabel.x = layoutLabel.x
|
||||
inputLabel.y = layoutLabel.y
|
||||
}
|
||||
})
|
||||
|
||||
inputGraph.graph().width = layoutGraph.graph().width
|
||||
inputGraph.graph().height = layoutGraph.graph().height
|
||||
}
|
||||
|
||||
const graphNumAttrs = ['nodesep', 'edgesep', 'ranksep', 'marginx', 'marginy']
|
||||
const graphDefaults = { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: 'tb' }
|
||||
const graphAttrs = ['acyclicer', 'ranker', 'rankdir', 'align']
|
||||
const nodeNumAttrs = ['width', 'height']
|
||||
const nodeDefaults = { width: 0, height: 0 }
|
||||
const edgeNumAttrs = ['minlen', 'weight', 'width', 'height', 'labeloffset']
|
||||
const edgeDefaults = {
|
||||
minlen: 1,
|
||||
weight: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
labeloffset: 10,
|
||||
labelpos: 'r'
|
||||
}
|
||||
const edgeAttrs = ['labelpos']
|
||||
|
||||
/*
|
||||
* Constructs a new graph from the input graph, which can be used for layout.
|
||||
* This process copies only whitelisted attributes from the input graph to the
|
||||
* layout graph. Thus this function serves as a good place to determine what
|
||||
* attributes can influence layout.
|
||||
*/
|
||||
function buildLayoutGraph (inputGraph) {
|
||||
const g = new Graph({ multigraph: true, compound: true })
|
||||
const graph = canonicalize(inputGraph.graph())
|
||||
|
||||
g.setGraph(_.merge({},
|
||||
graphDefaults,
|
||||
selectNumberAttrs(graph, graphNumAttrs),
|
||||
_.pick(graph, graphAttrs)))
|
||||
|
||||
_.forEach(inputGraph.nodes(), function (v) {
|
||||
const node = canonicalize(inputGraph.node(v))
|
||||
g.setNode(v, _.defaults(selectNumberAttrs(node, nodeNumAttrs), nodeDefaults))
|
||||
g.setParent(v, inputGraph.parent(v))
|
||||
})
|
||||
|
||||
_.forEach(inputGraph.edges(), function (e) {
|
||||
const edge = canonicalize(inputGraph.edge(e))
|
||||
g.setEdge(e, _.merge({},
|
||||
edgeDefaults,
|
||||
selectNumberAttrs(edge, edgeNumAttrs),
|
||||
_.pick(edge, edgeAttrs)))
|
||||
})
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
/*
|
||||
* This idea comes from the Gansner paper: to account for edge labels in our
|
||||
* layout we split each rank in half by doubling minlen and halving ranksep.
|
||||
* Then we can place labels at these mid-points between nodes.
|
||||
*
|
||||
* We also add some minimal padding to the width to push the label for the edge
|
||||
* away from the edge itself a bit.
|
||||
*/
|
||||
function makeSpaceForEdgeLabels (g) {
|
||||
const graph = g.graph()
|
||||
graph.ranksep /= 2
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
edge.minlen *= 2
|
||||
if (edge.labelpos.toLowerCase() !== 'c') {
|
||||
if (graph.rankdir === 'TB' || graph.rankdir === 'BT') {
|
||||
edge.width += edge.labeloffset
|
||||
} else {
|
||||
edge.height += edge.labeloffset
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates temporary dummy nodes that capture the rank in which each edge's
|
||||
* label is going to, if it has one of non-zero width and height. We do this
|
||||
* so that we can safely remove empty ranks while preserving balance for the
|
||||
* label's position.
|
||||
*/
|
||||
function injectEdgeLabelProxies (g) {
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
if (edge.width && edge.height) {
|
||||
const v = g.node(e.v)
|
||||
const w = g.node(e.w)
|
||||
const label = { rank: (w.rank - v.rank) / 2 + v.rank, e: e }
|
||||
util.addDummyNode(g, 'edge-proxy', label, '_ep')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function assignRankMinMax (g) {
|
||||
let maxRank = 0
|
||||
_.forEach(g.nodes(), function (v) {
|
||||
const node = g.node(v)
|
||||
if (node.borderTop) {
|
||||
node.minRank = g.node(node.borderTop).rank
|
||||
node.maxRank = g.node(node.borderBottom).rank
|
||||
maxRank = Math.max(maxRank, node.maxRank)
|
||||
}
|
||||
})
|
||||
g.graph().maxRank = maxRank
|
||||
}
|
||||
|
||||
function removeEdgeLabelProxies (g) {
|
||||
_.forEach(g.nodes(), function (v) {
|
||||
const node = g.node(v)
|
||||
if (node.dummy === 'edge-proxy') {
|
||||
g.edge(node.e).labelRank = node.rank
|
||||
g.removeNode(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function translateGraph (g) {
|
||||
let minX = Number.POSITIVE_INFINITY
|
||||
let maxX = 0
|
||||
let minY = Number.POSITIVE_INFINITY
|
||||
let maxY = 0
|
||||
const graphLabel = g.graph()
|
||||
const marginX = graphLabel.marginx || 0
|
||||
const marginY = graphLabel.marginy || 0
|
||||
|
||||
function getExtremes (attrs) {
|
||||
const x = attrs.x
|
||||
const y = attrs.y
|
||||
const w = attrs.width
|
||||
const h = attrs.height
|
||||
minX = Math.min(minX, x - w / 2)
|
||||
maxX = Math.max(maxX, x + w / 2)
|
||||
minY = Math.min(minY, y - h / 2)
|
||||
maxY = Math.max(maxY, y + h / 2)
|
||||
}
|
||||
|
||||
_.forEach(g.nodes(), function (v) { getExtremes(g.node(v)) })
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
if (_.has(edge, 'x')) {
|
||||
getExtremes(edge)
|
||||
}
|
||||
})
|
||||
|
||||
minX -= marginX
|
||||
minY -= marginY
|
||||
|
||||
_.forEach(g.nodes(), function (v) {
|
||||
const node = g.node(v)
|
||||
node.x -= minX
|
||||
node.y -= minY
|
||||
})
|
||||
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
_.forEach(edge.points, function (p) {
|
||||
p.x -= minX
|
||||
p.y -= minY
|
||||
})
|
||||
if (_.has(edge, 'x')) { edge.x -= minX }
|
||||
if (_.has(edge, 'y')) { edge.y -= minY }
|
||||
})
|
||||
|
||||
graphLabel.width = maxX - minX + marginX
|
||||
graphLabel.height = maxY - minY + marginY
|
||||
}
|
||||
|
||||
function assignNodeIntersects (g) {
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
const nodeV = g.node(e.v)
|
||||
const nodeW = g.node(e.w)
|
||||
let p1 = null
|
||||
let p2 = null
|
||||
if (!edge.points) {
|
||||
edge.points = []
|
||||
p1 = nodeW
|
||||
p2 = nodeV
|
||||
} else {
|
||||
p1 = edge.points[0]
|
||||
p2 = edge.points[edge.points.length - 1]
|
||||
}
|
||||
edge.points.unshift(util.intersectRect(nodeV, p1))
|
||||
edge.points.push(util.intersectRect(nodeW, p2))
|
||||
})
|
||||
}
|
||||
|
||||
function fixupEdgeLabelCoords (g) {
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
if (_.has(edge, 'x')) {
|
||||
if (edge.labelpos === 'l' || edge.labelpos === 'r') {
|
||||
edge.width -= edge.labeloffset
|
||||
}
|
||||
switch (edge.labelpos) {
|
||||
case 'l': edge.x -= edge.width / 2 + edge.labeloffset; break
|
||||
case 'r': edge.x += edge.width / 2 + edge.labeloffset; break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function reversePointsForReversedEdges (g) {
|
||||
_.forEach(g.edges(), function (e) {
|
||||
const edge = g.edge(e)
|
||||
if (edge.reversed) {
|
||||
edge.points.reverse()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removeBorderNodes (g) {
|
||||
_.forEach(g.nodes(), function (v) {
|
||||
if (g.children(v).length) {
|
||||
const node = g.node(v)
|
||||
const t = g.node(node.borderTop)
|
||||
const b = g.node(node.borderBottom)
|
||||
const l = g.node(_.last(node.borderLeft))
|
||||
const r = g.node(_.last(node.borderRight))
|
||||
|
||||
node.width = Math.abs(r.x - l.x)
|
||||
node.height = Math.abs(b.y - t.y)
|
||||
node.x = l.x + node.width / 2
|
||||
node.y = t.y + node.height / 2
|
||||
}
|
||||
})
|
||||
|
||||
_.forEach(g.nodes(), function (v) {
|
||||
if (g.node(v).dummy === 'border') {
|
||||
g.removeNode(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function removeSelfEdges (g) {
|
||||
_.forEach(g.edges(), function (e) {
|
||||
if (e.v === e.w) {
|
||||
const node = g.node(e.v)
|
||||
if (!node.selfEdges) {
|
||||
node.selfEdges = []
|
||||
}
|
||||
node.selfEdges.push({ e: e, label: g.edge(e) })
|
||||
g.removeEdge(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function insertSelfEdges (g) {
|
||||
const layers = util.buildLayerMatrix(g)
|
||||
_.forEach(layers, function (layer) {
|
||||
let orderShift = 0
|
||||
_.forEach(layer, function (v, i) {
|
||||
const node = g.node(v)
|
||||
node.order = i + orderShift
|
||||
_.forEach(node.selfEdges, function (selfEdge) {
|
||||
util.addDummyNode(g, 'selfedge', {
|
||||
width: selfEdge.label.width,
|
||||
height: selfEdge.label.height,
|
||||
rank: node.rank,
|
||||
order: i + (++orderShift),
|
||||
e: selfEdge.e,
|
||||
label: selfEdge.label
|
||||
}, '_se')
|
||||
})
|
||||
delete node.selfEdges
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function positionSelfEdges (g) {
|
||||
_.forEach(g.nodes(), function (v) {
|
||||
const node = g.node(v)
|
||||
if (node.dummy === 'selfedge') {
|
||||
const selfNode = g.node(node.e.v)
|
||||
const x = selfNode.x + selfNode.width / 2
|
||||
const y = selfNode.y
|
||||
const dx = node.x - x
|
||||
const dy = selfNode.height / 2
|
||||
g.setEdge(node.e, node.label)
|
||||
g.removeNode(v)
|
||||
node.label.points = [
|
||||
{ x: x + 2 * dx / 3, y: y - dy },
|
||||
{ x: x + 5 * dx / 6, y: y - dy },
|
||||
{ x: x + dx, y: y },
|
||||
{ x: x + 5 * dx / 6, y: y + dy },
|
||||
{ x: x + 2 * dx / 3, y: y + dy }
|
||||
]
|
||||
node.label.x = node.x
|
||||
node.label.y = node.y
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function selectNumberAttrs (obj, attrs) {
|
||||
return _.mapValues(_.pick(obj, attrs), Number)
|
||||
}
|
||||
|
||||
function canonicalize (attrs) {
|
||||
const newAttrs = {}
|
||||
_.forEach(attrs, function (v, k) {
|
||||
newAttrs[k.toLowerCase()] = v
|
||||
})
|
||||
return newAttrs
|
||||
}
|
||||
|
||||
export default layout
|
Reference in New Issue
Block a user