diff --git a/editor/composables/useLayout.ts b/editor/composables/useLayout.ts index 878e0b5..481a880 100644 --- a/editor/composables/useLayout.ts +++ b/editor/composables/useLayout.ts @@ -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 { - 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) + 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() - 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() + 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 }) } } diff --git a/package-lock.json b/package-lock.json index 1227e2b..2a5febf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "moviegame", "version": "0.1.0", "dependencies": { + "@types/dagre": "^0.7.54", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.48.2", + "dagre": "^0.8.5", "dexie": "^4.4.3", "pinia": "^2.1.0", "vue": "^3.4.0" @@ -762,6 +764,11 @@ "win32" ] }, + "node_modules/@types/dagre": { + "version": "0.7.54", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.54.tgz", + "integrity": "sha512-QjcRY+adGbYvBFS7cwv5txhVIwX1XXIUswWl+kSQTbI6NjgZydrZkEKX/etzVd7i+bCsCb40Z/xlBY5eoFuvWQ==" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1130,6 +1137,15 @@ "node": ">=12" } }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -1209,6 +1225,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dependencies": { + "lodash": "^4.17.15" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1218,6 +1242,11 @@ "he": "bin/he" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index a7292d2..693e32b 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "preview": "vite preview" }, "dependencies": { + "@types/dagre": "^0.7.54", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", "@vue-flow/core": "^1.48.2", + "dagre": "^0.8.5", "dexie": "^4.4.3", "pinia": "^2.1.0", "vue": "^3.4.0"