* BFS order with "next" (main flow) edge first at each node. First column is strictly * Start → step1 → step2 → … along the main flow; no 4 above 3.
(nodes: FlowNode[], edges: FlowEdge[])
| 413 | * Start → step1 → step2 → … along the main flow; no 4 above 3. |
| 414 | */ |
| 415 | function topoOrderIds(nodes: FlowNode[], edges: FlowEdge[]): string[] { |
| 416 | const { inEdges, outEdgesWithHandle } = buildGraph(nodes, edges); |
| 417 | const sources = nodes.filter((n) => (inEdges.get(n.id)?.length ?? 0) === 0).map((n) => n.id); |
| 418 | if (sources.length === 0) return nodes.map((n) => n.id); |
| 419 | |
| 420 | const inDeg = new Map<string, number>(); |
| 421 | nodes.forEach((n) => inDeg.set(n.id, inEdges.get(n.id)!.length)); |
| 422 | |
| 423 | const order: string[] = []; |
| 424 | const queue: string[] = [...sources]; |
| 425 | |
| 426 | while (queue.length > 0) { |
| 427 | const id = queue.shift()!; |
| 428 | order.push(id); |
| 429 | const out = (outEdgesWithHandle.get(id) ?? []).slice(); |
| 430 | out.sort((a, b) => { |
| 431 | const p = nextFlowPriority(a.sourceHandle) - nextFlowPriority(b.sourceHandle); |
| 432 | if (p !== 0) return p; |
| 433 | return a.target.localeCompare(b.target, undefined, { numeric: true }); |
| 434 | }); |
| 435 | for (const { target: targetId } of out) { |
| 436 | const d = (inDeg.get(targetId) ?? 0) - 1; |
| 437 | inDeg.set(targetId, d); |
| 438 | if (d === 0) queue.push(targetId); |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | return order.length === nodes.length ? order : nodes.map((n) => n.id); |
| 443 | } |
| 444 | |
| 445 | /** |
| 446 | * Place nodes in columns using greedy bin-packing by height budget. |
no test coverage detected