Skip to content

Fix parallel edge routing issues in MergeParallelEdges#24

Merged
Malcolmnixon merged 1 commit into
mainfrom
fix/parallel-edges-opposite-direction-merge
Jul 16, 2026
Merged

Fix parallel edge routing issues in MergeParallelEdges#24
Malcolmnixon merged 1 commit into
mainfrom
fix/parallel-edges-opposite-direction-merge

Conversation

@Malcolmnixon

Copy link
Copy Markdown
Member

This pull request introduces several important bug fixes and structural improvements to the layered graph layout engine, primarily addressing issues with the handling of parallel edges in multi-component graphs and ensuring robust propagation of caller-configured options. The changes ensure that all relevant input options are consistently copied when creating per-component subgraphs, preventing silent reversion to defaults and fixing cases where parallel edges were incorrectly collapsed or emitted.

Key improvements and fixes:

Core logic and bug fixes

  • Added LayeredGraph.CreateChild, a single method to create per-component subgraphs that copies all caller-configured input options (BackEdgeEntryApproach, NodeSpacing, MergeParallelEdges) from the parent, preventing silent loss of settings when laying out disconnected components. (src/DemaConsulting.Rendering.Layout/Engine/Layered/LayeredGraph.cs, src/DemaConsulting.Rendering.Layout/Engine/Layered/LayeredGraph.csR187-R224)
  • Updated ComponentPacker to use LayeredGraph.CreateChild instead of manually constructing subgraphs, ensuring all options are inherited and fixing a bug where MergeParallelEdges and NodeSpacing were ignored for components. (src/DemaConsulting.Rendering.Layout/Engine/Layered/ComponentPacker.cs, src/DemaConsulting.Rendering.Layout/Engine/Layered/ComponentPacker.csL367-R371)
  • Fixed parallel edge handling in LayeredLayoutAlgorithm: the emission logic now uses undirected node pairs for grouping and checks the actual set of surviving routed edges, ensuring only genuinely surviving connectors are emitted and that collapsed edges (including those with opposite directions) are never spuriously rendered. (src/DemaConsulting.Rendering.Layout/LayeredLayoutAlgorithm.cs, [1] [2] [3] [4]

Testing and safeguards

  • Added regression tests to ensure that:
    • Parallel edges are retained or collapsed correctly according to MergeParallelEdges, even in multi-component graphs. (test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/ComponentPackerTests.cs, [1]; test/DemaConsulting.Rendering.Layout.Tests/LayeredLayoutAlgorithmTests.cs, [2]
    • All caller-configurable input options on LayeredGraph are always copied by CreateChild, with a reflection-driven test to catch future omissions. (test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/LayeredGraphTests.cs, test/DemaConsulting.Rendering.Layout.Tests/Engine/Layered/LayeredGraphTests.csR65-R143)

These changes significantly improve the reliability and maintainability of the layout engine, ensuring that option propagation is robust and that parallel edge handling is consistent and correct across all scenarios.

Fixes two independent bugs that both manifested as a parallel edge
silently falling back to a raw straight line between two node
centres instead of a properly-anchored orthogonal connector, with
no warning:

1. MergeParallelEdges=true (default): two edges declared in
   opposite directions between the same node pair (e.g. A->B and
   B->A) collapsed inconsistently. CycleBreaker normalizes
   direction before deciding which edges collapse together, so it
   correctly dropped one of them, but LayeredLayoutAlgorithm's
   emission loop compared the raw, un-normalized declared
   direction and did not recognize the pair as collapsed, so it
   still tried to emit the dropped edge (which had no computed
   route). Fixed by using the routes dictionary itself as the
   ground truth for which edges survived, instead of a second,
   direction-blind duplicate rule. Also fixed the adjacent
   collapsed-label-suppression check to key on the undirected node
   pair for the same reason.

2. MergeParallelEdges=false: when the input graph split into 2+
   connected components, ComponentPacker built each component's
   child LayeredGraph inline and only copied BackEdgeEntryApproach,
   silently losing the caller's MergeParallelEdges=false (and
   NodeSpacing) override for every component. Fixed by introducing
   LayeredGraph.CreateChild, a single factory that copies every
   caller-configured input option, backed by a reflection-driven
   completeness test (LayeredGraph_CreateChild_CopiesEveryKnownInputOption)
   that fails the build if a future input option is added without
   being classified as either copied or a computed output -
   structurally preventing this class of bug from recurring.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Malcolmnixon Malcolmnixon added the bug Something isn't working label Jul 16, 2026
@Malcolmnixon
Malcolmnixon merged commit 488e18a into main Jul 16, 2026
6 checks passed
@Malcolmnixon
Malcolmnixon deleted the fix/parallel-edges-opposite-direction-merge branch July 16, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant