How Archify Automatic Port Spread Works: A Deep Dive Into Automatic Port Distribution

Archify Automatic Port Spread automatically distributes connection ports on a shared source component when multiple relationships leave the same side, keeping automatically-routed edges visually distinct without requiring manual anchor configuration.

The Archify Automatic Port Spread feature eliminates visual clutter in diagrams by calculating symmetrical, evenly-spaced port positions for edges that share a common source or target. This behavior is built into the renderer and activates by default for supported diagram types, ensuring clean orthogonal routing while respecting the 8 px/16 px visual rhythm rules defined in the codebase.

When Automatic Port Spread Activates

The renderer applies this feature selectively based on diagram type and edge configuration. According to the authoring contract in archify/references/authoring-contract.md, it runs for architecture, workflow, data-flow, and lifecycle diagrams, and is explicitly skipped in these cases:

  • Single-relationship edges (only one connection from a component side)
  • Sequence diagrams
  • Any edge defining via, channelX, channelY, or labelAt
  • Edges with non-auto route specifications

The rule is documented at line 38: "Automatic Port Spread is a default renderer behavior for architecture, workflow, data-flow, and lifecycle diagrams… It does not apply to … explicit via, channelX, channelY, labelAt, or non-auto routes."

Core Algorithm Implementation

The spread logic lives in archify/renderers/shared/geometry.mjs. The function automaticPortSpread(relations, boxes, { gutter = 16, maxSpacing = 14 }) implements the full algorithm (lines 1022-1048).

The function first collects all automatically-routed edges sharing the same source-side or target-side (lines 1022-1030):

// From geometry.mjs lines 1022-1030
function automaticPortSpread(relations, boxes, options = {}) {
  const { gutter = 16, maxSpacing = 14 } = options;
  // Groups edges by their common source or target side
  const groups = collectEdgeGroups(relations, boxes);
  // Only process groups with 2+ edges
  const multiEdgeGroups = groups.filter(g => g.edges.length > 1);

Step 2: Deterministic Sorting

For each group, edges are sorted by the counterpart's coordinate—vertical position for horizontal sides, horizontal position for vertical sides—ensuring stable, predictable port assignment regardless of input order (lines 1028-1035).

Step 3: Space Calculation and Spreading

The algorithm measures available extent on the component side, reserves the 16 px gutter on each edge, and caps spacing at maxSpacing (14 px) to prevent excessive spread (lines 1037-1040). Each edge receives a symmetrical offset from the side's midpoint (lines 1042-1048).

Rhythm Bridge Fallback for Near-Parallel Edges

Before spread calculation, Archify checks for potential violations of its interior rhythm rules. If two ports would result in segments shorter than interiorSegmentPx (8 px), the automaticPortRhythmBridge function generates an "outside-channel" bridge that respects the rhythm floors (lines 1030-1036 in geometry.mjs). This preprocessing guarantees a valid route exists before the main spread logic runs.

Port Spread Stability Guarantee

The Archify Automatic Port Spread algorithm produces deterministic results. The test suite in archify/test/automatic-port-spread.test.mjs (lines 63-78) explicitly validates this: "automatic port assignment is stable when relationship input order changes." Authors can refactor diagrams without worrying that relationship declaration order will scramble their layout.

Practical Examples

Architecture Diagram Fan-Out

Here's a hub component emitting three connections—the spread automatically assigns distinct ports:

import { render } from 'archify/renderers/architecture/render-architecture.mjs';

const doc = {
  schema_version: 1,
  diagram_type: 'architecture',
  meta: { title: 'Automatic Port Spread Demo' },
  components: [
    { id: 'hub',   type: 'backend',   label: 'Hub',   pos: [100, 280], size: [120, 60] },
    { id: 'upper', type: 'external',  label: 'Upper', pos: [500, 100], size: [120, 60] },
    { id: 'middle',type: 'database',  label: 'Middle',pos: [500, 280], size: [120, 60] },
    { id: 'lower', type: 'cloud',     label: 'Lower', pos: [500, 460], size: [120, 60] },
  ],
  connections: [
    { id: 'to-upper',  from: 'hub', to: 'upper' },
    { id: 'to-middle', from: 'hub', to: 'middle' },
    { id: 'to-lower',  from: 'hub', to: 'lower' },
  ],
};

render('architecture', doc);   // Ports at y-296, 310, 324 on the hub's right side

Workflow Diagram with Lanes

The same spread logic applies across workflow lanes:

import { render } from 'archify/renderers/workflow/render-workflow.mjs';

const doc = {
  schema_version: 1,
  diagram_type: 'workflow',
  meta: { title: 'Workflow Port Spread' },
  lanes: [
    { id: 'upper-lane', label: 'Upper' },
    { id: 'hub-lane',   label: 'Hub'   },
    { id: 'lower-lane', label: 'Lower' },
  ],
  nodes: [
    { id: 'hub',   lane: 'hub-lane',   col: 0, type: 'backend', label: 'Hub' },
    { id: 'upper', lane: 'upper-lane', col: 3, type: 'external', label: 'Upper' },
    { id: 'middle',lane: 'hub-lane',   col: 3, type: 'database', label: 'Middle' },
    { id: 'lower', lane: 'lower-lane', col: 3, type: 'cloud',    label: 'Lower' },
  ],
  edges: [
    { id: 'to-upper',  from: 'hub', to: 'upper' },
    { id: 'to-middle', from: 'hub', to: 'middle' },
    { id: 'to-lower',  from: 'hub', to: 'lower' },
  ],
};

render('workflow', doc);   // Ports spread vertically: y-233, 243, 253

Configuration Parameters

Parameter Default Description
gutter 16 px Reserved space from component corners
maxSpacing 14 px Maximum distance between adjacent ports

These values are hardcoded in geometry.mjs but passed through the options object for potential future customization.

Summary

  • Archify Automatic Port Spread runs automatically for architecture, workflow, data-flow, and lifecycle diagrams with multiple edges sharing a component side.
  • The algorithm in automaticPortSpread() guarantees symmetrical, deterministic port placement regardless of input order.
  • A rhythm bridge fallback prevents sub-8 px segments that would violate visual rhythm rules.
  • Authors retain full control via explicit via, channelX, channelY, labelAt, or non-auto route specifications—the spread only affects automatically routed edges.

Frequently Asked Questions

How is port spread order determined when edges connect to targets at different positions?

The algorithm sorts edges by their counterpart's coordinate before calculating offsets. For a right-side source port, targets are sorted by vertical position; for a top-side port, by horizontal position. This ensures the visual order matches the diagram's spatial layout as implemented in geometry.mjs lines 1028-1035.

Can I disable Automatic Port Spread for specific edges?

Yes. Define any explicit routing control—via points, channelX, channelY, labelAt positioning, or set route to a non-auto value—and the renderer will skip spread calculation for that edge, using your specified anchors instead. This is documented in the authoring contract at line 38.

Why 16 px gutters and 14 px max spacing?

These values align with Archify's broader 8 px/16 px visual rhythm system. The 16 px gutter preserves corner clearance for component styling, while 14 px max spacing prevents excessive spread that would create awkwardly long connector starts. The algorithm falls back to tighter spacing if available extent is limited, always maintaining rhythm-compliant segment lengths.

Does port spread affect diagram compilation performance?

No meaningful impact. The automaticPortSpread function operates on already-collected edge groups during the geometry preparation phase. The sorting and offset calculations are O(n log n) per side-group where n is the edge count—typically small. The test suite validates performance stability across edge reordering scenarios in automatic-port-spread.test.mjs.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →