interface NodeInfo { id: string label: string } interface EdgeInfo { source: string target: string } const H_GAP = 300 const V_GAP = 140 const PAD = 60 export function computePositions( nodes: NodeInfo[], edges: EdgeInfo[], startScene: string, ): Map { const positions = new Map() const adj = new Map() 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() const visited = new Set() 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) for (const n of nodes) { if (!level.has(n.id)) { maxLevel++ level.set(n.id, maxLevel) } } const byLevel = new Map() for (const [id, lv] of level) { const arr = byLevel.get(lv) || [] arr.push(id) byLevel.set(lv, arr) } 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 }) } } return positions }