Skip to content

Commit 5672cfd

Browse files
committed
docs(port-ordering): add technical documentation
Signed-off-by: Alexis Jacomy <alexis.jacomy@gmail.com>
1 parent 87cf7cb commit 5672cfd

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Crossing-aware port ordering
2+
3+
This document describes the **Crossing aware** port ordering algorithm. For context on ports, their alignment, and the
4+
available ordering modes, see the [Ports sorting](DATA_MODEL.md#ports-sorting) section in DATA_MODEL.md.
5+
6+
## Overview
7+
8+
The algorithm is a greedy iterative optimizer. It first focuses on minimizing the crossings within each node, and then
9+
looks for alternative solutions that reduce the global amount of crossings between the nodes.
10+
11+
It is a lightweight heuristic, not a full layout optimizer. It only reorders ports on fixed nodes with fixed positions,
12+
and runs fast enough to be applied on every layout update. For a more comprehensive approach to transit map layout
13+
(edge routing, station placement, global crossing minimization), see [LOOM](https://loom.cs.uni-freiburg.de/#stuttgart).
14+
15+
The algorithm works in three phases:
16+
17+
### 1. Connected components
18+
19+
The network is split into independent connected components (via DFS). Each component is optimized separately.
20+
21+
### 2. Port reordering
22+
23+
For each component, a BFS traversal visits every node and reorders its ports. For each pair of ports on the same side,
24+
the first distinguishing criterion wins:
25+
26+
1. Opposite transition node alignment within the node (geometric preference)
27+
2. Port position on the opposite side of the transition, if that side has already been ordered (with elbow correction
28+
for sides that have reversed index ordering)
29+
3. Opposite node position (left-to-right or top-to-bottom)
30+
4. Port position in opposite node (if already ordered by BFS)
31+
5. Trainrun ordering score (tie-breaker, varies between iterations)
32+
33+
Criteria 1 and 2 are designed to eliminate node-internal crossings by sorting ports according to the clockwise rotation
34+
of their transition nodes. This is what minimizes the number of crossings within each node.
35+
36+
Criteria 3 and 4 propagate constraints along the BFS to minimize crossings between nodes. Criterion 5 is a global
37+
tie-breaker injected by the iterative optimizer (see below).
38+
39+
### 3. Iterative optimization
40+
41+
The port reordering step (phase 2) is deterministic for a given trainrun ordering. The optimizer explores different
42+
trainrun orderings to find one that produces fewer crossings between the nodes. It runs multiple iterations (up to
43+
`maxRuns`, default 50):
44+
45+
1. Start with initial trainrun ordering (from node transitions)
46+
2. Reorder all ports using the current trainrun ordering as tie-breaker
47+
3. Count resulting crossings
48+
4. If crossings improved, identify largest crossing groups (contiguous sets of trainruns that cross each other), and
49+
generate new candidate orderings by permuting those groups
50+
5. Pick the next candidate from the stack and repeat
51+
6. Return the configuration with the fewest crossings
52+
53+
This is a heuristic. It uses DFS (stack-based candidate selection) and caps the number of new candidates per step
54+
(`maxNewCandidates`, default 10). It does not guarantee a global optimum.
55+
56+
## Crossing detection
57+
58+
Three types of crossings are detected:
59+
60+
- **Direct**: Two parallel sections share the same pair of nodes, but their port order is inverted on one side.
61+
- **Indirect**: Two sections share one node but go to different destinations, and their port order is inverted.
62+
- **Node-internal**: Two transitions cross inside a node (detected via clockwise rotation check).
63+
64+
Crossings between trainrun sections that do not share any node are not detected. For instance, a section going from top
65+
to bottom that visually crosses another one going from left to right will not be counted. Solving these crossings would
66+
require changing edge paths or node positions, which is outside the scope of port ordering.
67+
68+
## Strengths and limitations
69+
70+
- Works best on **tree-like topologies** (linear networks, branching lines), where BFS propagation is natural.
71+
- Handles **cycles and dense connectivity** less well: the single global trainrun ordering used as tie-breaker cannot
72+
express conflicting local preferences.
73+
- **Prioritizes node-internal crossing elimination** (criteria 1-2 always win over the tie-breaker). This is an
74+
opinionated choice that works well for typical railway diagrams, but may not be ideal on very dense graphs.
75+
76+
## Source files
77+
78+
- [port-ordering.algo.ts](../../src/app/services/util/port-ordering.algo.ts) - Main algorithm (`optimizePorts`,
79+
`reorderNodePorts`)
80+
- [port-ordering.crossings.ts](../../src/app/services/util/port-ordering.crossings.ts) - Crossing detection and
81+
counting
82+
- [port-ordering.components.ts](../../src/app/services/util/port-ordering.components.ts) - Connected components
83+
extraction
84+
- [port-ordering.helpers.ts](../../src/app/services/util/port-ordering.helpers.ts) - Geometry helpers (elbow detection,
85+
alignment)

documentation/technical/DATA_MODEL.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,14 @@ details [VisAVisPortPlacement.placePortsOnSourceAndTargetNode(srcNode, targetNod
159159

160160
##### Ports sorting
161161

162-
The sorting heuristics description can be found in
163-
the [chapter](../CREATE_NODES.md#MultipleTrainruns), it is
164-
worth taking a look there before diving into the source
165-
code [Node.sortPorts()](<./../src/app/models/node.model.ts#:~:text=sortPorts()%20{>) for detailed information.
162+
Ports on each side of a node are sorted to control how edges are visually ordered. Two modes are available,
163+
selectable via radio buttons in the editor tools panel:
164+
165+
- **Alphabetical** (default): Ports are sorted by train category name. See
166+
[Node.sortPorts()](<./../src/app/models/node.model.ts#:~:text=sortPorts()%20{>) and the
167+
[CREATE_NODES chapter](../CREATE_NODES.md#MultipleTrainruns) for details.
168+
- **Crossing aware**: Ports are reordered to minimize edge crossings using global propagation. See
169+
[CROSSING_AWARE_ORDERING.md](CROSSING_AWARE_ORDERING.md) for a full description of the algorithm.
166170

167171
##### Pre-computed paths
168172

0 commit comments

Comments
 (0)