Root nodes in fan-in groups are placed without target-aware grouping, causing edge overlap
Bug
When a graph has multiple fan-in groups feeding into different target nodes, all root nodes are placed in a flat horizontal sequence regardless of which target they connect to. This causes edge paths from different fan-in groups to overlap, making the diagram hard to read.
Reproduction
graph TD
A1 --> A
A2 --> A
B1 --> B
B2 --> B
A --> C
B --> Cimport { renderMermaidASCII } from 'beautiful-mermaid';
const code = `graph TD
A1 --> A
A2 --> A
B1 --> B
B2 --> B
A --> C
B --> C`;
console.log(renderMermaidASCII(code));Current output (conceptual)
A1 A2 B1 B2 <-- all roots on one row, no grouping
└────┼────┴────┘ <-- paths overlap, can't tell A-group from B-group
| |
A BExpected output (conceptual)
A1 A2 B1 B2
└────┘ └────┘
| |
A BRoot nodes feeding into the same target should be placed contiguously, with fan-in targets aligned near their respective root groups.
Analysis
The root cause is in createMapping in src/ascii/grid.ts (around lines 448-455). Root nodes are placed sequentially with a fixed +4 increment, without considering which downstream target each root feeds into:
for (const node of externalRootNodes) {
const requested: GridCoord = dir === 'LR'
? { x: 0, y: highestPositionPerLevel[0]\! }
: { x: highestPositionPerLevel[0]\!, y: 0 }
reserveSpotInGrid(graph, graph.nodes[node.index]\!, requested)
highestPositionPerLevel[0] = highestPositionPerLevel[0]\! + 4
}For the example above, A1, A2, B1, B2 all get placed at x = 0, 4, 8, 12 regardless of A being at one position and B at another.
Additionally, the child placement loop places children at the next available slot on their level (highestPositionPerLevel[childLevel]), ignoring the parent's perpendicular position. This means fan-in targets like B end up far from their root group (e.g. B1 at x=8, B2 at x=12, but B at x=4).
This also contributes to the pathfinding cost described in #64 — when roots are poorly positioned relative to their targets, edge routing becomes unnecessarily complex.
Suggested fix
Two changes in src/ascii/grid.ts:
Group roots by immediate downstream target before placement. Roots feeding into the same target are placed contiguously, so their edge spans don't interleave with other groups.
Align fan-in targets with their parents. When placing a child that has multiple incoming edges (in-degree > 1), use
Math.max(levelTracker, parentPerpendicular)instead of just the level tracker. This keeps fan-in targets near their root groups rather than at the next available slot.
Edge cases to consider
- A root node with edges to multiple targets should only appear once (group by first child as a heuristic)
- Single fan-in group: no grouping effect needed, behavior unchanged from current
- LR direction: the same issue exists vertically; the fix should apply symmetrically
Environment
- beautiful-mermaid 1.1.3
Source: lukilabs/beautiful-mermaid