feat: replace BFS layout with dagre for professional graph layout

This commit is contained in:
2026-06-08 13:01:17 +08:00
parent e323f7973a
commit b542660095
3 changed files with 58 additions and 55 deletions

View File

@@ -1,3 +1,5 @@
import dagre from 'dagre'
interface NodeInfo {
id: string
label: string
@@ -8,72 +10,42 @@ interface EdgeInfo {
target: string
}
const H_GAP = 300
const V_GAP = 140
const PAD = 60
const NODE_W = 180
const NODE_H = 60
export function computePositions(
nodes: NodeInfo[],
edges: EdgeInfo[],
startScene: string,
_startScene: string,
): Map<string, { x: number; y: number }> {
const positions = new Map<string, { x: number; y: number }>()
const adj = new Map<string, string[]>()
for (const n of nodes) adj.set(n.id, [])
for (const e of edges) {
const list = adj.get(e.source)
if (list) list.push(e.target)
}
const level = new Map<string, number>()
const visited = new Set<string>()
const queue: string[] = []
if (startScene && adj.has(startScene)) {
level.set(startScene, 0)
visited.add(startScene)
queue.push(startScene)
}
while (queue.length > 0) {
const id = queue.shift()!
const cur = level.get(id)!
for (const t of adj.get(id)!) {
if (!visited.has(t)) {
visited.add(t)
level.set(t, cur + 1)
queue.push(t)
}
}
}
let maxLevel = -1
for (const l of level.values()) maxLevel = Math.max(maxLevel, l)
const g = new dagre.graphlib.Graph()
g.setGraph({
rankdir: 'LR',
nodesep: 50,
ranksep: 240,
marginx: 60,
marginy: 60,
})
g.setDefaultEdgeLabel(() => ({}))
for (const n of nodes) {
if (!level.has(n.id)) {
maxLevel++
level.set(n.id, maxLevel)
g.setNode(n.id, { width: NODE_W, height: NODE_H })
}
const nodeIds = new Set(nodes.map((n) => n.id))
for (const e of edges) {
if (nodeIds.has(e.source) && nodeIds.has(e.target)) {
g.setEdge(e.source, e.target)
}
}
const byLevel = new Map<number, string[]>()
for (const [id, lv] of level) {
const arr = byLevel.get(lv) || []
arr.push(id)
byLevel.set(lv, arr)
}
dagre.layout(g)
const levels = [...byLevel.entries()].sort((a, b) => a[0] - b[0])
for (const [lv, ids] of levels) {
ids.sort()
const count = ids.length
for (let i = 0; i < count; i++) {
const x = lv * H_GAP + PAD
const y = i * V_GAP + PAD
positions.set(ids[i], { x, y })
const positions = new Map<string, { x: number; y: number }>()
for (const n of nodes) {
const node = g.node(n.id)
if (node) {
positions.set(n.id, { x: node.x - NODE_W / 2, y: node.y - NODE_H / 2 })
}
}