How the Automatic Port Spread Algorithm Works in Archify Architecture Diagrams

Archify uses a deterministic geometric algorithm to evenly distribute connection points (ports) along component boundaries, ensuring relationship arrows attach at consistent, non-overlapping positions without manual coordinate configuration.

Archify is an open-source architecture diagramming tool that renders clean, diffable diagrams from declarative JSON definitions. The automatic port spread algorithm eliminates visual clutter by programmatically calculating where edges meet nodes, producing reproducible layouts across every render.

Why Automatic Port Spreading Matters

In architecture diagrams, components can have dozens of incoming and outgoing relationships. Without intelligent placement, arrows bunch together at the corners of boxes, creating unreadable tangles. The automatic port spread algorithm solves this by treating each side of a component as a one-dimensional line segment and spacing attachment points mathematically along that line.

How the Algorithm Calculates Port Positions

The algorithm runs inside archify/renderers/architecture/render-architecture.mjs and delegates geometric calculations to archify/renderers/shared/port-spread.mjs. It processes the intermediate representation (IR) and outputs final coordinates using five distinct phases.

Step 1 – Collect Incident Edges

For each node in the diagram, the renderer scans the JSON IR to build a complete list of relationships. It identifies every outgoing edge (where the node is the source) and every incoming edge (where the node is the target). This collection includes the edge's canonical ID, directionality, and target component references.

Step 2 – Group by Side

The node's bounding box divides into four sides: top, right, bottom, and left. The algorithm assigns each incident edge to a preferred side based on relationship direction. Source-to-target edges typically map to the right side of the source node and the left side of the target node. Reverse or bidirectional relationships follow schema-defined heuristics encoded in archify/schemas/architecture.schema.json.

Step 3 – Calculate Even Spacing

For each side containing n ports, the algorithm computes offsets using the formula:


offset[i] = i × (sideLength / (n + 1))

Where i ranges from 1 to n. This guarantees ports spread evenly with equal padding from the corners and between adjacent ports. For an 80-pixel tall right side with three ports, the calculation yields positions at 20px, 40px, and 60px along that edge.

Step 4 – Deterministic Ordering

When multiple edges map to the same side, the algorithm orders them using a stable hash of the edge's canonical ID. This deterministic tie-breaking ensures that identical input documents always generate identical SVG output, which supports Archify's artifact-hashing and delta-comparison features. The ordering never relies on runtime memory addresses or timestamps.

Step 5 – Emit Layout Hints

Finally, the renderer writes calculated coordinates into the node's port objects. Each port entry includes:

  • portAt: The normalized position (0.0 to 1.0) along the side
  • portDx and portDy: Absolute coordinate offsets
  • portSegment: The side identifier (top, right, bottom, left)

These values persist in the rendered output, allowing the viewer to draw arrows with exact attach points.

Implementation in the Archify Source Code

The automatic port spread algorithm is implemented across two primary modules:

  • archify/renderers/architecture/render-architecture.mjs: The main renderer that iterates nodes, extracts relationship metadata from the IR, and invokes the spread routine.
  • archify/renderers/shared/port-spread.mjs: The pure geometric function that accepts side dimensions and edge counts, then returns the calculated offset array.

The schema definitions in archify/schemas/architecture.schema.json validate the optional layout and ports fields, while examples/archify-repo.architecture.json demonstrates the algorithm output in a real-world repository diagram.

Code Examples

The following JSON fragment shows how the algorithm outputs port positions for a service component with two outgoing relationships:

{
  "id": "api-service",
  "type": "service",
  "meta": {
    "layout": {
      "x": 400,
      "y": 200,
      "width": 120,
      "height": 80
    }
  },
  "ports": [
    { "id": "to-db", "side": "right", "portAt": 0.33 },
    { "id": "to-cache", "side": "right", "portAt": 0.66 }
  ]
}

Both ports assign to the right side. The algorithm computed portAt values of approximately 0.33 and 0.66, spreading them evenly along the side's height.

This JavaScript recreation demonstrates the core spacing logic found in port-spread.mjs:

function spreadPorts(sideLength, count) {
  const step = sideLength / (count + 1);
  return Array.from({ length: count }, (_, i) => (i + 1) * step);
}

// Usage: 80px side with three ports
const offsets = spreadPorts(80, 3); // Returns [20, 40, 60]

Key Files and References

File Purpose Location
render-architecture.mjs Main renderer that processes nodes and triggers port calculations archify/renderers/architecture/render-architecture.mjs
port-spread.mjs Core algorithm implementation for geometric port distribution archify/renderers/shared/port-spread.mjs
architecture.schema.json JSON schema defining layout and port metadata structures archify/schemas/architecture.schema.json
archify-repo.architecture.json Example diagram demonstrating automatic port spread output examples/archify-repo.architecture.json

Summary

  • The automatic port spread algorithm evenly distributes relationship attachment points along component sides using geometric division.
  • It calculates offsets with the formula i × (sideLength / (n + 1)) to guarantee consistent spacing.
  • Edge ordering uses deterministic hashing of canonical IDs, ensuring reproducible layouts across renders.
  • Output coordinates write to portAt, portDx, portDy, and portSegment fields in the diagram IR.
  • Because placement depends solely on static IR content, diagrams remain fully deterministic and suitable for version control diffing.

Frequently Asked Questions

What determines which side of a component a port appears on?

The side assignment depends on relationship direction. Source-to-target edges default to the right side of source components and the left side of targets. The schema in architecture.schema.json defines these heuristics, and the renderer in render-architecture.mjs applies them during the grouping phase.

Is the port spread consistent across different renders?

Yes. The algorithm uses a stable hash of the edge's canonical ID to order ports on crowded sides. Because it never uses random values or runtime-dependent data, identical input documents produce identical port coordinates every time, which is essential for Archify's reproducible build guarantees.

Can I manually override automatic port positions?

The JSON schema supports optional explicit ports arrays where you can define portAt values manually. However, when these arrays are absent or incomplete, the automatic algorithm in port-spread.mjs computes the positions deterministically based on the node's geometry and incident edge count.

How does the algorithm handle overlapping edges?

The algorithm prevents overlaps by design. By dividing the side length by n + 1 rather than n, it ensures even padding between ports and from the corners. If collision detection is required for complex routing, the geometry engine in the shared renderer applies additional constraints, but the initial spread eliminates the majority of crowding issues through mathematical spacing alone.

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 →