How the Sequence Renderer Manages Participants, Segments, and Messages in Archify
The Sequence renderer transforms a declarative JSON model into a validated SVG by materializing participants, segments, and messages into geometry objects, then rendering them in a strict z-order that places participants on top.
The archify/renderers/sequence/render-sequence.mjs module implements a three-stage pipeline—materialization, validation, and rendering—that converts abstract sequence diagram definitions into production-ready SVG output. This article examines the internal mechanics of how the renderer handles the three core data types: participants, segments, and messages.
Materialising Participants into Geometry Objects
The renderer first converts raw participant definitions into fully-computed geometry objects stored in a Map for O(1) lookup during message routing.
const participants = new Map(
asArray(sequence.participants).map((participant, index) => [
participant.id,
{
...participant,
index,
cx: participantX(index),
x: participantX(index) - layout.participantW / 2,
y: layout.topY,
width: layout.participantW,
height: layout.participantH,
cy: layout.topY + layout.participantH / 2,
},
])
);
Each participant receives:
cx: Horizontal center (used for message start/end points)x,y: Top-left corner for the rendered boxcy: Vertical center of the participant header- Computed dimensions: Pulled from
layout.participantWandlayout.participantH
The helper participantX(index) (lines 62-64) calculates horizontal placement using layout.leftX + index * layout.colGap, distributing participants evenly across the canvas width.
Building Segment Frames for Visual Grouping
Segments define vertical bands that group related messages into logical phases. The renderer transforms each segment into a frame object with explicit geometry:
const compositionFrames = asArray(sequence.segments).map((segment, index) => ({
id: index,
label: segment.label,
kind: 'segment',
x: 48,
y: segment.from,
width: viewBox[0] - 96,
height: segment.to - segment.from,
radius: 10,
}));
Key properties:
| Property | Purpose |
|---|---|
y / height |
Vertical span from segment.from to segment.to |
width: viewBox[0] - 96 |
Full canvas width minus 48px margins on each side |
radius: 10 |
Corner rounding for the background rectangle |
These frames serve dual purposes: visual backdrop (rendered via renderSegment at lines 20-22) and collision boundaries for label placement algorithms.
Computing Message Geometry and Routing
Message rendering depends on precise geometric calculations that connect participant centers with directional awareness:
function messageGeometry(message) {
const from = participants.get(message.from);
const to = participants.get(message.to);
if (!from || !to || typeof message.y !== 'number') return null;
const direction = to.cx > from.cx ? 1 : -1;
const start = from.cx + direction * 7;
const end = to.cx - direction * 7;
return { start, end, center: (start + end) / 2 };
}
The computation proceeds as follows:
- Lookup: Resolve
message.fromandmessage.toagainst the participantMap - Validation: Ensure both participants exist and
message.yis numeric - Direction: Determine left-to-right (+1) or right-to-left (-1) flow
- Inset: Apply 7px padding from participant edges to avoid visual collision
- Center: Pre-compute midpoint for label placement
The vertical position (message.y) originates from the source JSON and is validated to fall within the readable timeline bounds (lines 61-68).
Deterministic Rendering Order in renderSvg()
The renderSvg() function (lines 13-44) assembles the final SVG through a carefully sequenced series of calls that establish proper visual layering:
- Background grid — Static reference lines
renderSegment— Segment frames as shaded backgroundsrenderLifeline— Vertical dashed lines extending from participantsrenderActivation— Execution bars showing active periodsrenderMessage— Arrows and labels usingmessageGeometry()andmessageLabel()renderSegmentLabel— Phase labels with collision avoidancerenderParticipant— Participant boxes, sublabels, and brand marksrenderLegend— Optional key/explanation block
Critical design decision: Participants render last so they visually override lifelines and message endpoints, matching standard sequence diagram conventions where participant headers appear "in front" of connecting lines.
Validation Pipeline Before SVG Generation
The validateSequence() function (lines 35-91) executes a comprehensive suite of checks that prevent malformed output:
- Identity: Unique participant IDs across the diagram
- Space: Minimum 120px between
lifelineBottomandlifelineTop - Fit: Participant labels must fit within computed box widths
- Legibility:
availableNodeTextWidthensures sublabel readability - Proximity: Message endpoints must be ≥ 60px from participant centers
- Collision:
cleanFlowProblemsandcleanCrossingProblemsdetect arrow overlaps, label intersections, and segment frame conflicts
Failed validations trigger throwDiagnosticProblems, which aborts rendering and returns a detailed error list rather than producing broken SVG.
Complete Input-to-Output Example
This minimal JSON demonstrates the full data model:
{
"participants": [
{ "id": "alice", "label": "Alice", "type": "external" },
{ "id": "bob", "label": "Bob", "type": "external" }
],
"messages": [
{ "from": "alice", "to": "bob", "label": "Hello", "y": 200 }
],
"segments": [
{ "label": "Login Flow", "from": 150, "to": 300 }
]
}
Excerpted SVG output:
<svg viewBox="0 0 920 760" …>
<rect x="48" y="150" width="824" height="150" rx="10" class="c-lane"/>
<path d="M 150 200 L 770 200" class="a-default" …/>
<text x="460" y="190" class="t-backend" font-size="9" text-anchor="middle">Hello</text>
<g …>
<rect x="62" y="72" width="86" height="54" rx="6" class="c-mask"/>
<rect … class="c-external"/>
<text …>Alice</text>
</g>
</svg>
The segment renders as a background rectangle, the message as a horizontal path with centered label, and participants as layered groups with masking rectangles to ensure clean edges.
Source Code Architecture
| File | Responsibility |
|---|---|
archify/renderers/sequence/render-sequence.mjs |
Core pipeline: materialization, validation, SVG assembly |
archify/shared/geometry.mjs |
Collision detection (rectsOverlap, cleanFlowProblems) |
archify/shared/utils.mjs |
Utilities (asArray, textUnits, esc) |
archify/shared/cli.mjs |
SVG serialization (svgRootAttrs, writeDiagram) |
archify/schemas/sequence.schema.json |
JSON Schema for input validation |
Summary
- Materialization converts abstract JSON entities into geometry objects with computed coordinates
- Participants store center points (
cx,cy) that enable efficient message routing - Segments become frame objects with explicit bounds for background rendering and collision detection
- Messages derive horizontal endpoints from participant geometry plus directional insets
- Validation enforces spatial constraints and prevents visual collisions before any SVG is generated
- Rendering order places participants last in the z-stack to achieve conventional diagram aesthetics
Frequently Asked Questions
What data structure stores participants during rendering?
A JavaScript Map keyed by participant ID. This provides O(1) lookup when resolving message.from and message.to references during message geometry computation.
How does the renderer prevent message arrows from overlapping participant boxes?
The messageGeometry() function applies a 7-pixel inset using direction * 7 on both ends, ensuring arrows start and stop slightly inside the participant center lines rather than at exact edges.
Why are participants rendered after messages in the SVG output?
The renderSvg() sequence (lines 13-44) deliberately places renderParticipant near the end so participant headers visually cover lifeline extensions and message endpoints. This matches standard sequence diagram conventions where participant boxes appear "on top."
What happens if validation fails in validateSequence()?
The renderer calls throwDiagnosticProblems, which aborts execution and returns a structured list of errors rather than generating malformed SVG. Checks include unique IDs, minimum spacing, label fit, and collision detection between arrows, labels, and segment frames.
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 →