Architecture Diagram — Best Practices
Structured per ISO/IEC/IEEE 15289:2019 (information item content) and ISO/IEC/IEEE 26515:2018 (agile documentation): metadata header, numbered clauses (Scope / Normative References / Terms / Abbreviations / body / Annexes), revision history.
Table of Contents
- Scope
- Normative References
- Terms and Definitions
- Abbreviations
- Tool Selection
- Layout Principles
- Edge Routing
- Iconography
- Background and Ambience
- Anti-Patterns
- Iteration Process
- References
- Annex A — Revision History
- Annex B — Document Control
1. Scope
1.1 Purpose
This document captures the engineering decisions, layout algorithms, rendering strategies, and known anti-patterns identified while producing the container architecture diagram for Tutorial 01 (NIM inference endpoint). It is intended as a reusable reference for future architecture diagrams within the project.
1.2 Applicability
Applies to:
- Architecture diagrams embedded in tutorial/documentation pages.
- Diagrams generated as part of a build pipeline (Python
uvproject, SVG output). - Diagrams styled for light backgrounds with NVIDIA brand accents
(green
#76b900, orange#ea580c).
Does not apply to:
- One-off whiteboard sketches or freehand drawings.
- Diagrams produced by external tooling outside the diagram codebase (Lucid, Miro, draw.io exports, etc.).
1.3 Reference Implementation
The patterns described herein are enforced by the project in this directory:
2. Normative References
The following documents informed the structural conventions of this specification:
- ISO/IEC/IEEE 15289:2019, Systems and software engineering — Content of life-cycle information items.
- ISO/IEC/IEEE 26515:2018, Developing user documentation in an agile environment.
- ISO 8601:2019, Date and time — Representations for information interchange.
The following non-normative sources informed the technical content:
- SVG 1.1 Specification (W3C, 2nd Edition).
- NVIDIA AIQ Architecture Diagram (reference style, internal sample).
- Stripe Docs and Linear Blog (industry references for clean technical illustration).
3. Terms and Definitions
4. Abbreviations
5. Tool Selection
5.1 Decision Tree
5.2 Mermaid C4 — Known Limitations
Attempted first; replaced after the following defects were confirmed:
Verdict. Mermaid C4 is acceptable for low-fidelity sketches in internal READMEs. Public-facing or iterated diagrams should use the hand-rolled or programmatic path.
5.3 Hand-rolled Inline SVG
Acceptable for the first revision. Becomes unmaintainable past 2 iterations because all coordinates are absolute literals; adding a Component requires re-tracing every Edge.
5.4 Programmatic Generation (Recommended)
A uv project with the following module separation:
To relocate a Component: edit LAYOUT in samples/data.py and regenerate.
To add a connection: append an Edge to the EDGES list and regenerate.
5.5 Diagram DSL (D2-style)
A textual surface for the same Diagram dataclasses §5.4 produces.
Defined in dsl.py; parses .kymo files into Diagram objects.
Use this when authoring or reviewing a diagram by hand — the brace
syntax + arrows read as a flat declaration without Python boilerplate.
Equivalence. The parser is lossless with respect to the dataclass
form: every field on Component, Region, and Edge can be expressed
in DSL, and the round-trip produces byte-identical SVG.
5.5.1 Grammar (synopsis — full spec in KYMO-DSL-001 clause 6)
The DSL has no component, region, or layout keywords. Each
line's shape determines its kind:
Region containers nest. When an inner region is nested inside an outer
region, the inner region's leaf ids flatten into the outer region's
contains so its auto-bounds envelop the nested leaves (KYMO-DSL-001 §7.3.1).
Layout containers do NOT propagate — they're positioning rules, not
ownership.
Comments: # to end-of-line (outside double-quoted strings). A #
immediately followed by a hex digit is treated as a colour literal, not
a comment — so stroke #94a3b8 works inline. Whitespace within lines is
insignificant; line breaks separate statements. Blocks ({ … }) MUST
span multiple lines.
5.5.1.1 Region Border Defaults and Overrides
The outer / inner style enum picks defaults; the dash / stroke
options override individual rects without inventing new enum values.
Examples:
The override is emitted as an inline style="…" attribute on the
<rect>, which beats the CSS class. To revert, remove the option.
5.5.1.2 Asymmetric Padding (padding-bottom)
The region label is rendered ABOVE the rect (at y = rect.y - 10),
which adds roughly 25 px of visual weight to the top edge that the
bottom does not have. With symmetric padding (h, v), regions feel
visually top-heavy — top reads as more spacious than bottom.
To balance, set padding-bottom larger than the vertical padding. A
practical rule:
padding-bottom = padding[1] + 8–12
…matching the extra visual weight of the label. Example:
Internally only padding[1] (the vertical padding) governs the TOP
extent; padding-bottom (when set) replaces it for the BOTTOM extent
only. When unset, bottom falls back to padding[1] (symmetric).
5.5.2 Example (full diagram, condensed)
5.5.3 Wiring into the build
.kymo files are loaded directly by packages/python/src/kymo/cli.py:
cli.py calls parse_dsl(...) → layout(...) → resolve_alignments(...) → render(...).
5.5.4 When NOT to use the DSL
6. Layout Principles
6.0 Local Alignment (Parent / Child + Auto-bounded Regions)
6.0.1 Parent / Child Component Alignment
Components may declare a parent reference + align side instead of an
absolute pos. The renderer resolves these relationships before
rendering, so moving a parent moves every descendant by the same delta.
Rationale. Hand-positioning dozens of components yields fragile coordinates — adjusting one cell requires retracing every neighbour. Parent/child alignment expresses intent ("Planning sits below Orchestrator") rather than position, so structural edits stay local.
Fields on Component (model.py):
Edge-to-edge gap (not centre-to-centre). align_gap=50 with
align="right" means 50 px between parent's right border and child's
left border, regardless of either component's width. This is intuitive
("padding") and stable under shape changes.
Label-aware bottom alignment. align="bottom" accounts for the
parent's LABEL_HEIGHT — child is placed below the label area, not
below the icon, so child icons never collide with parent's subtitle text.
Resolution. alignment.resolve_alignments(diagram) walks the
components depth-first. Each child is positioned only after its parent.
Cycles raise ValueError. Called once per build, after auto-layout (or
after manual placement of anchors) and before render.
Pattern: chain-of-siblings.
Moving orch by +30 moves the entire chain +30. Inserting a new
component between two siblings requires only re-pointing one parent
reference.
When to use absolute pos vs. alignment.
For AIQ (18 components), 5 absolute anchors + 13 children gives a layout that is fully editable by adjusting ~5 numbers.
6.0.2 Auto-bounded Regions (contains)
A Region may declare contains: list[str] (component ids) instead of
explicit bounds. The renderer computes the bounding box automatically
as the envelope of every listed component INCLUDING its label area.
Fields on Region (model.py):
Compliant:
The bounds adapt as orch/researcher/planner move (e.g., when their
parent anchor is shifted, or when a new sibling is added). No hand-tuning.
Why label area matters: A naive bounding-box of icon centres + half-widths
will cut through subtitle text below the bottom-most icon. The resolver
adds LABEL_HEIGHT[shape] to the bottom extent, so the region's bottom
border always sits below the bottom-most subtitle.
When to use explicit bounds vs. contains.
6.0.3 Auto-layout Regions (Figma-style)
A region may declare a layout direction + pos anchor; the resolver
then positions every component listed in contains along that axis.
Equivalent to Figma's auto-layout frame: declare intent (direction +
gap + alignment), not coordinates.
Fields on Region (model.py):
Cross-axis behaviour. For layout="horizontal", the resolver finds
the tallest child's half_h and positions every child so its centre Y
sits on that line (align="center"). align="start" top-aligns, end
bottom-aligns. Vertical layout uses the widest child analogously.
Compliant: invisible layout group. Use a visible=False region as a
pure positioning primitive — no border drawn, but children are stacked
automatically.
Five RAG components positioned with one declaration. To shift the
entire row 30 px right, change pos to (280, 620).
Compliant: visible region with auto-layout (region border + label drawn around an auto-stacked row).
When to use which mode (Table A):
Combinability. A component placed by an auto-layout region can be
referenced as parent by another component (the auto-layout resolver
runs first, so the parent's position is final by the time the child is
resolved). Example: in aiq.py, orch is positioned by routing_chain
auto-layout, and researcher then uses parent="orch", align="right".
Resolution order (alignment.resolve_alignments):
_resolve_auto_layouts— every region withlayoutset positions itscontainschildren along the axis._resolve_component_alignments— depth-first walk of components withparent/align; positions computed from parent's final pos._resolve_region_bounds— every region withcontains(but nolayout) has its bounding box computed to envelope its children.
This ordering means auto-layout outputs feed into both parent/child alignment and auto-bounded region computation; cycles between the three modes are not possible.
Non-compliant: layout without anchor.
layout requires pos. Without an anchor, the resolver has no origin
for the stack.
6.1 Whitespace Minimisation Without Crowding
The shipped layout in layout.py uses the following spacing constants
(Table 1). These have been empirically tuned and reduce the canvas from
1228 × 728 to 1062 × 658 (−13 % area).
Table 1 — Layout spacing constants
Going tighter than the above causes label collisions.
6.2 Per-Row Height Alignment Across Regions
For same-row cross-region Edges to render as straight horizontals, row heights are computed as the maximum across all regions:
This ensures Row 0 of every Region sits at the same Y-coordinate.
6.3 Per-Region Width Follows Content
Each Region's width is max(row width within that region). Regions are
not forced to a common width. Naturally wider Regions (e.g.,
code-server with a 2-cell row) remain wider than single-column Regions.
6.4 Anchor Computation Must Account for Label Area
Labels are rendered below the icon. A bottom anchor that returns
(cx, cy + icon_half) places the edge endpoint inside the label text.
Non-compliant:
Compliant:
This correction was the single highest-impact visual fix during iteration: edge labels (e.g., "Mount weights") ceased to overlap component subtitles (e.g., "NIM Microservice").
6.5 Asymmetric Label Extent in Route Computation
Symmetric half-heights (row_heights[i] / 2) miscalculate the true
content footprint. Use actual extents:
For over and under routes, compute the across-segment Y from real
extents:
6.6 Grid System and Snap
6.6.1 Base Unit
All component centres, region edges, and waypoints SHOULD snap to an 8-pixel grid (coarse anchors snap to 16 or 24). The dot-grid background (§9.1) is drawn at 24 px — a 3 × multiple — so visually-aligned components also land on visible reference dots.
Why 8. It is the lowest common multiple of typical icon sizes (64, 72, 80), label heights (40, 48), and accent spacing constants (16, 24, 32, 48). Snapping to 8 keeps every relationship in the diagram expressible as a small integer.
6.6.2 Snap Rule
For any component placed by absolute pos=(x, y):
For components placed by parent+align, the snap is inherited from the
anchor parent — so a single misaligned anchor breaks the whole chain.
6.6.3 Audit of Existing Constants (Table 1)
The constants in §6.1 predate this clause and were tuned empirically. Status against the 8-grid:
Migration policy. Do NOT retroactively re-tune container or aiq —
their layouts are already approved. New diagrams (e.g., aws) MUST use
8-grid-aligned constants from the outset.
6.6.4 Canvas Dimensions
Canvas width and height MUST be multiples of 8 (preferably 16). Common
sizes: 1024×640, 1280×720, 1280×800, 1440×900. These align with display
aspect ratios and make embedded thumbnails crisp at 50% / 25% scale.
6.7 Visual Hierarchy and Reading Flow
6.7.1 Reading Direction
Every diagram has ONE primary reading axis:
Diagrams MUST NOT zigzag (L → R then R → L on the next row). A zigzag forces the eye to backtrack, which a reader interprets as "I missed something." If a diagram has multiple flows, separate them spatially into distinct rails (each rail itself reads in one direction).
6.7.2 Visual Weight = Importance
The HERO of a diagram — the orchestrator, the API surface, the thing the diagram is "about" — MUST be visually heavier than its neighbours. Three mechanisms:
- Size. Hero shape is 20–30 % larger than peers (e.g.,
cube-bigvscube, oraws-tile-herovsaws-tile). - Halo / glow. A faint coloured circle behind the icon (already used
for the NIM Microservice; see
icons._halo). - Position. Hero sits at the visual centre of mass — typically the row that the most edges converge on.
Anti-pattern: every component the same size with the same shadow. The reader cannot tell where to start.
6.7.3 Numbered Step Badges
When a diagram describes a SEQUENCE (e.g., "user request flows through 1
→ 2 → 3"), annotate the relevant edges or components with circled
numerals: ①, ②, ③. Implement as small shape="badge" components
positioned on or beside the edge.
Use sparingly — never more than ~5 badges, and only when the sequence is non-obvious from arrows alone. Excessive numbering competes with the arrows themselves.
6.7.4 Grouping: Container vs Region
Two distinct grouping primitives, used for different semantics:
Containers MAY nest inside Regions (e.g., your-company.com container
inside the us-east-1 Region). The reverse — Region inside Container —
is forbidden, because administrative boundaries always dominate logical
ones.
Top-left badge (Region.icon). A Region SHOULD declare an icon key
identifying the administrative domain it represents. The renderer draws
the icon at the top-left of the rect with the region label inline beside
it (a single visual unit):
Reader benefit: the icon is recognised before the text is read — a glanceable cue that "this box is an AWS region" vs "this box is an app domain." Without the icon, two nested rectangles look the same and the reader must parse two text labels to distinguish them.
The badge MUST sit OUTSIDE the rect (above the top-left corner), not inside, so it does not steal content space from the components within.
6.7.5 Label Discipline
- Name — 1–3 words, the canonical product/component name.
- Subtitle — 1 line, ≤ 24 characters. If the subtitle wraps, the diagram is overcrowded; either shorten the text or widen the cell gap.
- Edge label — verb or noun phrase, ≤ 16 characters (
Mount weights,fine-tunable). Longer text on edges is unreadable at thumbnail scale.
6.7.6 Region-to-Region Label Clearance
A region's label is rendered ABOVE its rectangle at y = rect.y - 10
(see to_svg.py::render_region). This means the label's visible glyphs
occupy a vertical band roughly [rect.y - 22, rect.y - 4] — and this
band is OUTSIDE the rect, in the "between regions" whitespace.
Consequence. When two regions stack vertically, the lower region's label can be overlapped by the upper region's bottom border (or even its content's label area) if the vertical gap is too small.
Rule. Maintain a minimum 25 px clear vertical gap between an upper region's bottom edge and the lower region's rect.top, so the lower region's label has clean space to render in. With default font-size 13 px + 10 px baseline offset, 25 px is the minimum non-overlapping clearance; 30 px+ is comfortable.
Compliant (AIQ).
Non-compliant.
Diagnosis check. When eyeballing a fresh layout: hold a ruler at
each region's rect.y and verify there is no other region's rect or
label glyph occupying the band [rect.y - 25, rect.y]. The clearance
problem is invisible at first glance — the label simply looks like part
of the upper region until you zoom in.
Why no auto-fix? Adding cross-region collision avoidance would
introduce ordering ambiguity (which region "wins" when bounds conflict?)
and break the independence of _resolve_region_bounds. The cost of
manual y-spacing is a one-line constant; the cost of auto-correction is
unbounded layout instability.
7. Edge Routing
7.1 Routing Strategies
Table 2 — Edge route options
7.1.1 Edge Endpoints (Components vs Regions)
Edge.src and Edge.dst may target either a component or a region.
The resolver looks up by id in diagram.components first, then
diagram.regions (component ids win on collision).
Compliant: component → region. Arrow lands on the region's outer border at the requested anchor side. Use when the SEMANTIC target is a group of services rather than a specific service inside it.
Compliant: region → region. Useful for high-level system maps ("VPC → Internet Gateway", "GitHub → AWS Account").
When to target a region vs a component (Table 3):
Region anchor geometry. Region anchors are computed from the
resolved bounds, so this works equally well with auto-bounded
regions (§6.0.2) and Figma-style auto-layout regions (§6.0.3):
Resolution ordering matters: alignment.resolve_alignments finalises
every region's bounds BEFORE render_edge runs, so edge anchors on
auto-bounded regions are always against final geometry.
Caveat. Anchoring at center of a region routes the arrow into the
region's interior (likely crossing components inside) — avoid unless
deliberately rendering a "drop-in" arrow with via waypoints that exit
cleanly. Edge style of region.layout="vertical" cross-axis arrows
should usually use top/bottom, not left/right.
7.2 Rounded Corner Rendering
Sharp 90° elbows render rigidly. At each interior vertex, insert a quadratic Bézier with radius ≈ 10 px:
Clamp the radius to min(r, segment_length / 2) to avoid overshoot on
short segments.
7.3 Arrowhead Style
Open V chevrons (stroke only) are preferred over filled triangles:
Mandatory: markerUnits="userSpaceOnUse". Without it, the marker's
stroke scales with the parent path's stroke-width, producing
inconsistent arrowhead sizes.
7.4 Structural Coherence (Disruptive Arrows)
An Edge whose length is more than 3× the median edge length is a candidate for replacement, as it visually dominates the composition.
Worked example. The cross-region inference Edge from HTTP Client to
API Catalog spans all three regions. It was removed from the rendered
diagram. The semantic connection is expressed through component
subtitles:
HTTP Clientsubtitle:requests → API CatalogAPI Catalogsubtitle:integrate.api.nvidia.com ← HTTP
Both endpoints share the external-orange box styling. Information is preserved; visual rhythm is unbroken.
7.5 Cubic Bézier for Divergent Branches
When two Edges share a source and split to distinct destinations, L-shapes overlap at the source. Use S-curves with diverging offsets:
Control-point distance of chord_length / 3 gives a balanced swoop.
7.6 Orthogonality Rule (No Diagonal Segments)
Hard rule. Every edge segment in a routed path MUST be either purely
horizontal (Δy = 0) or purely vertical (Δx = 0). Diagonal segments
(non-zero Δx AND Δy) are NOT permitted, with the single exception of
route="curve" cubic Béziers where the diagonal is intentional and
smooth.
Why. Orthogonal flows read as structured systems; diagonal segments suggest randomness or sloppy routing. They also conflict with rounded- corner rendering (§7.2), which assumes axis-aligned segments meeting at 90°.
7.6.1 Common Diagonal-Creating Bugs
7.6.2 Compliant Pattern (loop-back rail)
Path produced: (333,168) → (333,125) → (55,125) → (55,162). All three
segments perpendicular. Final ASCII:
7.6.3 Detection Heuristic
When reviewing a path produced by route_edge, walk every adjacent pair
(p_i, p_{i+1}). If both p_i.x != p_{i+1}.x AND p_i.y != p_{i+1}.y,
that segment is diagonal — fail the review.
A grep-friendly check: scan the rendered SVG for <path … d="…"> where
the path contains an L command with neither x nor y matching the
previous coordinate. Such patterns indicate a diagonal segment slipped
through.
8. Iconography
8.1 Isometric Cubes with Matrix-Transformed Glyphs
Cubes have a slanted front face (parallelogram). Glyphs drawn in flat
coordinates appear "stuck on". Wrap the inner content in a 2D affine
matrix mapping [0, 1]² → front face:
A flat <rect> becomes an isometric-correct parallelogram. The same
glyph source serves any cube size; the matrix scales.
8.2 Stroke Width Handling
Do not use vector-effect="non-scaling-stroke" with matrix
transforms — browser support is inconsistent (observed: 1.6 declaration
rendering at ~70 px on Chrome). Author stroke widths in unit space:
8.3 Subtle Gradient Treatment
Flat fills appear dated. Apply per-face linear gradients with a slight top→bottom darken:
A single <linearGradient> definition reused N times has negligible
file-size cost.
8.4 Shadow Application
Apply feDropShadow to icon groups only. Do not apply to text
(muddies legibility) or to arrows (visual noise):
9. Background and Ambience
9.1 Engineering-Paper Dot Grid
A single SVG <pattern> containing a small low-opacity dot:
The 0.045 opacity is the threshold above which the pattern competes with content and below which it is imperceptible.
9.2 Region Boundary Styling
Regions use:
- Fill:
rgba(15, 23, 42, 0.02)(near-invisible tint). - Stroke: 1.5 px,
stroke-dasharray: 6 5. - Label: positioned above the rectangle, UPPERCASE, letter-spaced.
This groups Cells without dominating them.
10. Anti-Patterns
Table 3 — Common smells and their fixes
11. Iteration Process
A single generate-render-review cycle takes approximately 30 ms
(uv run kymo <file>) plus the browser-reload time. This permits 20+
revisions per session at acceptable cost.
The five revisions that produced the greatest visual quality gain were:
- Migration from inline-SVG to code-driven layout.
- Correction of the bottom anchor to account for label area.
- Adoption of open V arrowheads and rounded corners.
- Replacement of rigid L-shapes with smooth S-curves on branching edges.
- Removal of the giant cross-region arrow that broke visual rhythm.
12. References
packages/python/src/kymo/model.py—Component,Region,Edge,Diagramdefinitions.packages/python/src/kymo/layout.py—_route_over,_route_under,cell_size, layout entry point.packages/python/src/kymo/to_svg.py—points_to_rounded_path,smooth_curve,edge_label_pos.packages/python/src/kymo/icons.py—_cube,_box, glyph library, isometric matrix.samples/data.py— current diagram instance.out/container-diagram.svg— current build artefact.samples/AIQ-arch-light.png— external reference (NVIDIA AIQ).
Annex A — Revision History
Table A.1 — Document revisions
Annex B — Document Control
B.1 Storage and Retrieval
This document is version-controlled within the project repository at
docs/diagrams/best-practices.md. Authoritative source is the
working tree of the main branch; archived versions are accessible via
the repository history (git log).
B.2 Distribution
Distribution is implicit — the document is checked in alongside the implementation it describes. Any engineer with read access to the repository has access to the current revision.
B.3 Change Control
Substantive changes (clauses 5–11) require a regeneration of
out/container-diagram.svg and visual confirmation that the rendered
output remains compliant. Editorial changes (typography, clarification)
do not.
A new revision MUST:
- Update the Version field in the header table.
- Update the Issue Date field.
- Append a row to Annex A — Revision History.
- Bump the major version on breaking changes to interfaces in
packages/python/src/kymo/{model,layout,to_svg}.py.
B.4 Review
The document is reviewed:
- Continuously by anyone editing
packages/python/(changes to behaviour must be reflected here). - At least annually by the project owner.
- Upon any major architecture-rendering stack change (e.g., switching from raw SVG to a templating library).