Automatic Port Spread in Archify: How the Routing Algorithm Distributes Connection Points
Archify's automatic port spread is a renderer-level algorithm that distributes multiple relationship connection points across a component's side to prevent visual overlap, applying calculated offsets only when no explicit routing controls are specified.
Automatic port spread is one of Archify's core layout primitives. When multiple edges originate from or terminate at the same side of a component, the algorithm groups, sorts, and offsets each port to create clean, deterministic fan-out and fan-in patterns—without requiring manual placement of via points, channelX/Y constraints, or labelAt overrides.
How Automatic Port Spread Works
The implementation lives in archify/renderers/shared/geometry.mjs, specifically the automaticPortSpread function (lines 1020–1055). This utility operates as a preprocessing step before the main routing pipeline generates final SVG or HTML paths.
The Five-Step Algorithm
1. Group relationships by side
The function first collects relationships that share the same component side into distinct groups. Each group represents all edges entering or exiting a specific face of a rectangle.
2. Sort by counterpart coordinate
Within each group, relationships are sorted by the opposite endpoint's coordinate. This sorting ensures deterministic output—the same diagram structure always produces identical port placements.
3. Compute usable space
The algorithm calculates available space as extent – gutter * 2, where extent is the length of the component side and gutter is a configurable minimum margin (default 16px). A safe maxSpacing is derived from this usable space.
4. Calculate per-port offsets
Each relationship receives an offset from the canonical anchor point (computed via anchor(item.rect, item.side)). The offset magnitude depends on the relationship's index in the sorted group: middle items get minimal displacement, while outer items spread toward the edges.
5. Return adjusted endpoints
The function returns a map of relationship IDs to their adjusted endpoint coordinates, which the renderer later incorporates into polyline paths.
Anchor Calculation and Side Selection
The anchor utility (lines 1010–1025 in geometry.mjs) determines the base position for any (rect, side) pair. For automatic port spread, this anchor becomes the zero-point from which offsets are applied:
// Canonical anchor for a right-side exit
const basePoint = anchor(hubRect, 'right'); // → { x: 220, y: 310 }
// Spread-adjusted port for third relationship in group
const spreadPoint = { x: 220, y: 324 }; // base.y + 14px offset
Conditional Application: Respecting Explicit Routes
Automatic port spread applies only to "plain" relationships—those without explicit routing controls. The algorithm checks for these overrides before activating:
via— explicit path waypointschannelXorchannelY— forced outside-channel routinglabelAt— label positioning that affects geometry- Custom
routefunctions
When any of these are present, Archify preserves the author-specified geometry and skips automatic spreading. This behavior ensures that manual fine-tuning always takes precedence over algorithmic defaults.
Integration with the Routing Pipeline
After port spread calculation, results feed into Archify's standard routing pipeline. The final path generation may apply additional transformations:
Rhythm-Bridge Fallback
If a calculated route would violate Archify's 8px/16px rhythm constraints, the renderer invokes automaticPortRhythmBridge (lines 1027–1038). This fallback constructs an outside-channel route that maintains visual alignment with the diagram's grid system.
The rhythm bridge operates as a safety net: it guarantees that even aggressive port spreads or unusual component placements produce valid, rhythm-compliant output.
Practical Example: Hub-and-Spoke Layout
Consider a backend hub connecting to three external services:
const doc = {
schema_version: 1,
diagram_type: 'architecture',
meta: { title: 'Automatic fan-out' },
components: [
{ id: 'hub', type: 'backend', label: 'Hub', pos: [100, 280], size: [120, 60] },
{ id: 'svcA', type: 'external', label: 'A', pos: [500, 100], size: [120, 60] },
{ id: 'svcB', type: 'external', label: 'B', pos: [500, 280], size: [120, 60] },
{ id: 'svcC', type: 'external', label: 'C', pos: [500, 460], size: [120, 60] },
],
connections: [
{ id: 'to-A', from: 'hub', to: 'svcA' },
{ id: 'to-B', from: 'hub', to: 'svcB' },
{ id: 'to-C', from: 'hub', to: 'svcC' },
],
};
Rendering this diagram triggers automatic port spread on the hub's right side. The three connections receive distinct ports:
| Connection | Port Coordinate | Offset from Center |
|---|---|---|
to-A (top) |
(220, 296) | -14px |
to-B (middle) |
(220, 310) | 0px |
to-C (bottom) |
(220, 324) | +14px |
These coordinates derive from the spread algorithm's sorting (by destination Y-coordinate) and spacing calculations.
Direct API Access
While primarily used internally, the spread function can be accessed for custom renderers or testing:
import { automaticPortSpread } from './archify/renderers/shared/geometry.mjs';
// boxes: Map<componentId, { x, y, width, height, cx, cy }>
const spreadMap = automaticPortSpread(relations, boxes, {
gutter: 16, // minimum margin from corner
maxSpacing: 14 // maximum distance between adjacent ports
});
// spreadMap: Map<relationId, { x, y }> — adjusted endpoints
Options control the trade-off between edge density and margin preservation. Lower maxSpacing values create tighter groupings; higher values spread connections more aggressively toward component corners.
Renderer Coverage
Automatic port spread is implemented across all Archify diagram types:
| Renderer | File | Application |
|---|---|---|
| Architecture | render-architecture.mjs |
Component dependency diagrams |
| Workflow | render-workflow.mjs |
Lane-based process flows |
| Dataflow | render-dataflow.mjs |
Stage-to-stage data pipelines |
| Lifecycle | render-lifecycle.mjs |
State transition diagrams |
Each renderer invokes the shared automaticPortSpread function with diagram-specific parameters, ensuring consistent behavior across visual conventions.
Summary
- Automatic port spread distributes connection points along component sides to prevent edge overlap in dense diagrams.
- The algorithm lives in
archify/renderers/shared/geometry.mjsand operates through grouping, sorting, spacing calculation, and offset application. - Spread only activates for relationships without explicit
via,channelX/Y,labelAt, or customroutecontrols. - Results feed into standard routing with possible
automaticPortRhythmBridgefallback for rhythm compliance. - The function is used across all Archify renderers (architecture, workflow, dataflow, lifecycle) for consistent automatic layout.
Frequently Asked Questions
How does Archify determine which relationships to group for port spreading?
Archify groups relationships that share the same component side (top, right, bottom, or left) and the same connection direction (incoming or outgoing). Within each group, relationships are sorted by the coordinate of their opposite endpoint—Y-coordinate for left/right sides, X-coordinate for top/bottom sides. This sorting produces deterministic, visually logical ordering where higher connections attach to higher counterpart components.
What happens if automatic port spread violates Archify's rhythm constraints?
When calculated offsets or resulting paths would break the 8px/16px rhythm floor, the renderer invokes automaticPortRhythmBridge (lines 1027–1038 in geometry.mjs). This fallback constructs an outside-channel route that maintains grid alignment. The rhythm bridge preserves logical connectivity while ensuring the final output satisfies Archify's visual consistency requirements.
Can I disable automatic port spread for specific connections?
Yes. Automatic port spread is automatically disabled for any relationship that specifies explicit routing controls: via waypoints, channelX or channelY constraints, labelAt positioning, or a custom route function. To prevent spreading without adding geometry, you can specify an empty via: [] array or set explicit but neutral channel values.
Where can I find test coverage for the port spread algorithm?
Comprehensive tests reside in archify/test/automatic-port-spread.test.mjs. This suite validates spread behavior across all diagram types, verifies deterministic output for identical inputs, checks boundary conditions at component edges, and confirms that explicit routing controls correctly suppress automatic spreading.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →