Module

LayeredLayout.EdgeRouting.HyperEdges

Package
purescript-layered-layout
Repository
markgrafhq/purescript-layered-layout

Port of OrthogonalRoutingGenerator hyperedge segment construction and slot assignment.

Pipeline (matching ELK):

  1. Group edges by (sourceNode, sourcePort) per gap → segments.
  2. For every segment pair compute crossings + conflicts and add a directed HyperEdgeSegmentDependency. Penalties: CONFLICT_PENALTY=1, CROSSING_PENALTY=16. Two thresholds: regular conflictThreshold (0.5 * edgeSpacing) marks a non-critical conflict; the smaller criticalConflictThreshold (0.2 * minimumHorizontalSegmentDistance) marks a CRITICAL conflict — both orderings would force overlap.
  3. breakCriticalCycles: run cycle detection on CRITICAL deps only; feed the leftward criticals to splitSegments (port of HyperEdgeSegmentSplitter). Splitting cuts a segment into two parts joined through a free area, regenerating the deps for the new segments.
  4. breakNonCriticalCycles: run the Eades-Lin-Smyth feedback-arc-set heuristic over the full dep graph; reverse leftward regular deps and remove zero-weight ones.
  5. Topologically number the resulting DAG into routing slots (OrthogonalRoutingGenerator.topologicalNumbering).

The slot index drives each edge's horizontal-trunk y via

y = gapTop + edgeNodeBetweenLayers
          + slot * edgeEdgeBetweenLayers

#SlotInfo Source

type SlotInfo = { gap :: Int, partner :: Maybe { slot :: Int, splitX :: Number }, slot :: Int, slotCount :: Int }

#assignSlots Source

#slotCountByGap Source

slotCountByGap :: Map EdgeId SlotInfo -> Map Int Int

Per-gap routing-slot count, keyed by gap index. The slot count is OrthogonalRoutingGenerator.routeEdges's rankCount + 1: the number of distinct horizontal routing channels the orthogonal router will need in that gap. Straight pass-through segments don't add a rank, but every segment with a horizontal trunk does.

Read the channel counts from an existing plan. Gap sizing and routing