Files
diagrams-drawio/references/rules-layout.md
T

26 KiB
Raw Blame History

diagrams-drawio — Layout Rules

Connector placement and layout rules for draw.io diagrams generated by this skill.
These rules are MANDATORY — apply them when generating any diagram.


General layout rules — MANDATORY

  • Use a 10pt grid; align all shapes to grid increments

  • Leave ≥ 40px horizontal and ≥ 40px vertical gaps between shape columns/rows for connector corridors

  • Keep connector labels short; place them in clear corridors, never overlapping shapes or other labels

  • Before finalising, verify: no shape overlap, no connector-shape overlap, no label overlap, no page clipping

  • Universal 40pt spacing rule — every shape must have at least 40pt of clear space on all four sides:

    • From parent header bottom: child y = startSize + 40 (measured from top of swimlane including header)
    • From parent sides: child x ≥ 40 from the left/right inner border of the swimlane
    • From parent bottom: bottom of last child row + 40 = swimlane total height
    • Between siblings (same nesting level): gap between any two adjacent shapes (horizontal or vertical) = 40pt exactly — all sibling gaps must be identical

    Swimlane height formulas:

    • Single row: total_height = startSize + 40 + child_height + 40
    • Two rows: total_height = startSize + 40 + row1_h + 40 + row2_h + 40
    • Round up total to the nearest multiple of 40 if needed; absorb any remainder into the bottom gap (≥ 40)

    Examples:

    • startSize=40, 1 row h=40: total = 40+40+40+40 = 160 (children at y=80, children w=160 with x-gap=40 between them)
    • startSize=40, 1 row h=80: total = 40+40+80+40 = 200 (children at y=80)
    • startSize=40, 2 rows h=40 each: total = 40+40+40+40+40+40 = 240 (row1 at y=80, row2 at y=160)

    After generating or editing a diagram, run page-spacing-audit and page-swimlane-audit to verify.


Diagram Layers and Connector Rules — MANDATORY

Layer model

Layer Contents Connector may overlap?
0 — Background / containers Swimlanes, zone boundaries, grouping rectangles YES — connectors may pass through containers
1 — Primary shapes Core nodes: main system boxes, components, use cases NEVER
2 — Actor shapes Human actors (stick figures), external IT systems NEVER
3 — Connectors Edges, waypoints, routing corridors AVOID — crossing other connectors is a last resort
4 — Labels / annotations Edge labels, callouts, legend text, title blocks NEVER

Core rule: a connector may only overlap Layer 0. It must NEVER overlap Layers 1, 2, or 4.

Label-crossing prohibition — MANDATORY

A connector must NEVER pass through the textual label of any shape — regardless of which layer the shape belongs to.

  • Container shapes (Layer 0 / swimlanes) have a header bar at the top that contains the label. Connectors must not pass through this header area
  • For swimlanes with startSize=40, the header occupies y to y+40. Any connector that enters or exits vertically through the header is forbidden; route it through the side or bottom instead
  • Leaf shapes (Layer 1/2) also carry labels — their entire bounding box is off-limits (existing rule), so this adds no extra constraint for those
  • To check: identify every shape whose label bounding box (header bar for containers, full box for leaves) intersects a connector segment. Reroute any that does

Corner-cutting prohibition — MANDATORY

A connector must NEVER route across the corner of any shape — regardless of which layer the shape belongs to.

A corner cut occurs when an orthogonal connector exits from one face of a shape and its first waypoint is positioned diagonally (same quadrant as the corner) relative to that exit point, causing the L-shaped segment to visually "clip" the corner region. This is forbidden even when the corner is technically outside the shape bounding box.

Rule: after choosing the exit face and direction of travel, the first waypoint must be placed such that the initial segment travels parallel to or away from the corner edge, not at an angle toward it.

Exit face First segment must go Forbidden direction
Bottom Down (↓) or laterally clear Up (↑) toward corner
Top Up (↑) or laterally clear Down (↓) toward corner
Left Left (←) or clear Right (→) toward corner
Right Right (→) or clear Left (←) toward corner

Prefer-downward routing — MANDATORY

When a connector can reach its target by routing either upward or downward, always prefer the downward route.

Routing upward is only permitted when:

  • The target is physically above the source (the upward direction is the natural one), OR
  • Routing downward would create a connector-shape overlap that cannot otherwise be avoided

Violation example: a connector exits the bottom of a source shape, immediately routes up through the source's parent container header, travels horizontally, then comes back down — this is forbidden. The first segment after exiting the bottom face must go down, not up.

Decision rule:

  1. Identify whether target y_center is above or below source y_center
  2. If below (or same level): exit source bottom or side → route downward → enter target top or side
  3. If above: exit source top or side → route upward → enter target bottom or side
  4. If the natural downward path is blocked, use a side-exit (left or right) corridor to bypass, then continue downward — do NOT reverse direction to go upward

Header-edge prohibition — MANDATORY

A connector must NEVER run along (or within 1px of) the bottom edge of a swimlane/container header bar.

The header bar of a swimlane occupies the band [shape.y .. shape.y + startSize]. Its bottom edge is the line y = shape.y + startSize. Connectors that travel horizontally across this line are visually indistinguishable from the header border and create a confusing, cluttered diagram.

  • For a swimlane with startSize=40, the forbidden horizontal band is within 1px of shape.y + 40
  • Connectors entering or exiting the swimlane via a vertical segment that straddles the header-bottom edge are also forbidden
  • Fix: reroute the horizontal segment to travel either:
    • Inside the swimlane body (below shape.y + startSize + TOL), or
    • Outside / above the swimlane entirely (above shape.y - TOL), or
    • Via a side corridor that avoids the header band altogether

Detection: page-connectors-validation action reports headerEdgeViolations — always run it before finishing any diagram that contains swimlanes or container shapes.

Situation Fix
Horizontal segment at y ≈ container.y + startSize Move segment down into the swimlane body or up above the container
Vertical segment straddles header-bottom edge from inside Re-enter the swimlane via left or right face instead
Multiple connectors fanning across the Data Layer header Use a horizontal corridor at y = container.y + startSize + 40 (center of the 40pt gap) or route via side bypass

Connector-crossing avoidance — MANDATORY

Connectors must not cross each other. Crossing another connector is a last resort — acceptable only when no feasible reroute exists. When a crossing is unavoidable, it must be a clean 90° crossing. Connectors crossing other connectors is always preferable to a connector crossing a shape label or shape corner.

Cause Fix
Multiple left-side sources fan into one target Route through a shared left-side vertical corridor; stagger y-entry points
Sources on both sides of a central shape Use separate left-corridor and right-corridor
Sources in different rows heading to the same column Give each source its own horizontal lane in the corridor
Long connector crosses shorter ones Route long connectors through Y_TOP (above all shapes)

Crossing-free strategy:

  1. Identify source groups and target groups
  2. Assign each source group its own corridor lane
  3. Route connectors in each group in parallel through their lane
  4. Trace every connector pair — if any two intersect at a non-edge point, redesign

If a crossing truly cannot be avoided, prefer a right-angle (90°) crossing over an oblique one.

Minimal waypoints rule — MANDATORY

Add waypoints only when the direct path would clip a Layer 1/2/4 shape.

Situation Waypoints?
Source and target at same horizontal level, path is clear NO
Adjacent columns with clear gap between them NO
Source mid_y falls within target's y-range, path is clear NO
Path would clip an intermediate shape YES — minimum waypoints to route around it
Fan-out from a central shape to a stacked column YES — use Pattern E

Before adding a waypoint, ask: "Would the direct auto-routed path clip any Layer 1/2/4 shape?" If no — no waypoints. If yes — first try repositioning shapes (layout-first principle).

Keep all connectors the same edge style (edgeStyle=orthogonalEdgeStyle). Never change edge style to work around a layout problem — fix the layout instead.

Layout-first principle

Redesign the layout to eliminate waypoints before adding them.

Problem Layout fix
Actor above target's y-range Move target up, or tighten actor spacing so target mid_y aligns
Actor below target's y-range Move target down, or tighten actor spacing
Fan-out column spans much more than source Centre source vertically on the column

Shape size is driven by content, not routing — typically 80–160px tall. If only 1–2 actors fall outside the target's y-range, accept 2 minimal waypoints per connector rather than inflating the shape. Space actors 80–120px apart (centre-to-centre) to keep groups compact.

When auto-routing is forbidden

Never rely on edgeStyle=orthogonalEdgeStyle without explicit waypoints when the path passes through intermediate shapes. Use waypoints only where needed — the goal is the minimum number that achieves zero overlaps and zero crossings.


Connector Routing Patterns

Corridors

Before placing connectors, identify clear corridors — bands free of all shapes.

Vertical corridor between two columns:

GAP_x = (right_edge_of_left_column + left_edge_of_right_column) / 2

Horizontal corridor between two rows:

GAP_y = (bottom_edge_of_upper_row + top_edge_of_lower_row) / 2

Reserve a top corridor (Y_TOP) above all shapes and a bottom corridor (Y_BELOW) below all shapes for cross-diagram connectors.

Absolute coordinates

Connector waypoints are canvas-absolute. For nested containers, sum all ancestor offsets:

abs_x = shape.x + parent.x + grandparent.x + ...
abs_y = shape.y + parent.y + grandparent.y + ...

Overlap verification (Python)

TOL = 2

def segment_overlaps_shape(seg, shape):
    sx1, sy1, sx2, sy2 = shape
    if seg['type'] == 'H':
        Y, xA, xB = seg['y'], min(seg['x1'],seg['x2']), max(seg['x1'],seg['x2'])
        if sy1+TOL < Y < sy2-TOL and max(xA,sx1+TOL) < min(xB,sx2-TOL):
            return True
    elif seg['type'] == 'V':
        X, yA, yB = seg['x'], min(seg['y1'],seg['y2']), max(seg['y1'],seg['y2'])
        if sx1+TOL < X < sx2-TOL and max(yA,sy1+TOL) < min(yB,sy2-TOL):
            return True
    return False

Exclude the terminal shape (source/target) when checking first/last segments.

Pattern A — Top highway

Long connectors that span the full diagram width:

source → V up to Y_TOP → H across → V down to target

Pattern B — Stacked column approach

Multiple targets stacked vertically in a column — never route through the column. Approach each from a corridor to the side:

source → V to Y_TOP → H to GAP_x (beside column) → V to target_mid_y → H into target

Pattern C — Narrow gap corridor

When two adjacent shapes leave a small gap (≥ 10px), that gap is a usable vertical corridor:

corridor_x = (shape_A_right + shape_B_left) / 2

Pattern D — Bypass around a dense row

When a row of shapes blocks a path to shapes below, go around the right edge:

source → H right to RIGHT_BYPASS → V to target_y → H left to target
RIGHT_BYPASS = (rightmost_shape_right + next_column_left) / 2

Pattern E — Fan-out from a central shape to a stacked column

The most common overlap scenario: one central shape connects to N stacked targets on one side.

Solution — vertical fan-out corridor:

  1. GAP = (central_right + column_left) / 2
  2. Exit central shape → H to GAP → V to target_mid_y → H into target
<Array as="points">
  <mxPoint x="{GAP}" y="{source_mid_y}"/>
  <mxPoint x="{GAP}" y="{target_mid_y}"/>
</Array>

Example — source mid_y=200, GAP=820, 5 stacked targets:

Target mid_y Waypoints
Item A 80 (820, 200) → (820, 80)
Item B 200 direct (no waypoints needed)
Item C 320 (820, 200) → (820, 320)
Item D 440 (820, 200) → (820, 440)
Item E 560 (820, 200) → (820, 560)

Constraint: central_right < GAP < column_left. If gap < 40px, move the column right.

Waypoint XML format

<mxCell id="conn-1" value="label" style="edgeStyle=orthogonalEdgeStyle;html=1;" edge="1" source="A" target="B" parent="1">
  <mxGeometry relative="1" as="geometry">
    <Array as="points">
      <mxPoint x="450" y="40"/>
      <mxPoint x="820" y="40"/>
      <mxPoint x="820" y="320"/>
    </Array>
  </mxGeometry>
</mxCell>

Rules:

  • Waypoints are canvas-absolute
  • Edges are children of root layer (parent="1"), never of a container
  • Last waypoint stops at the corridor boundary — draw.io completes the final stub

Layout checklist

  • ≥ 40px gaps between columns and rows (universal 40pt spacing rule)
  • Y_TOP corridor reserved above all shapes
  • Y_BELOW corridor reserved below all shapes
  • Corridor bands contain no shapes
  • Python overlap verification run; all overlaps fixed
  • Endpoint-touch exclusion applied for first/last segments
  • Every connector pair traced — no path intersections at non-edge points
  • All connector labels in clear corridors, not overlapping shapes or other labels
  • No connector passes through any shape's text label (including swimlane header bars)
  • No connector cuts across a shape corner (first segment after exit travels away from corners)
  • No connector runs along (or within 1px of) the bottom edge of any swimlane/container header bar (page-connectors-validation reports headerEdgeViolations)
  • All connectors route downward when target is below source; upward only when target is above source

Swimlane / Multi-Zone Diagram Routing

When a diagram uses swimlane zones (horizontal bands, each a swimlane container), apply the following rules in addition to all rules above.

Zone layout reference

For a typical layered architecture with startSize=40 swimlanes and 40pt spacing:

zone.y                        ← swimlane top edge
zone.y + 40                   ← header bottom (label occupies y..y+40)
zone.y + 40 + 40 = zone.y+80  ← first child row top (rel y=80 inside container)
zone.y + 80 + child_h         ← first child row bottom
...
zone.y + height               ← swimlane bottom edge

Inter-zone gaps (between adjacent swimlanes) are clean horizontal corridors — use their midpoint as the H-travel y-coordinate for connectors crossing zone boundaries.

Bypass corridors

Always reserve two vertical bypass corridors outside all zones:

Corridor x value Rule
LEFT bypass zone.x - 20 (e.g. x=60 when zones start at x=80) All leftward cross-zone connectors
RIGHT bypass zone.x + zone.width + 20 (e.g. x=1260 when zones end at x=1240) All rightward cross-zone connectors

These bypass corridors run the full canvas height and are free of all shapes. Every connector that must travel between zones should route through one of these corridors.

Multi-row swimlane: horizontal segment placement

When a swimlane has two rows of shapes (row1 and row2), each row has a y-range. Never place a connector H segment at a y-value that falls inside a row's y-range — that will overlap sibling shapes.

Use only these safe H corridors inside a multi-row services swimlane:

Corridor y value When to use
Inter-row gap row1_bottom + (row2_top - row1_bottom)/2 H travel between row1 and row2 shapes
Services-bottom gap row2_bottom + (zone_bottom - row2_bottom)/2 H travel below row2, still inside zone
Inter-zone gaps midpoint of gap between adjacent zones H travel outside zones

Example — services-zone: startSize=40, zone.y=500, row1 abs y=580..620, row2 abs y=660..700, zone bottom=740:

  • Inter-row gap corridor: y=640 (midpoint of y=620..660)
  • Services-bottom corridor: y=720 (midpoint of y=700..740)

Connector routing rules for multi-row swimlanes

Rule 1 — Never route H segments through a row's y-range. If a connector must travel horizontally through the services zone, use y=640 (inter-row) or y=720 (services-bottom), never y=580..620 or y=660..700.

Rule 2 — When exiting a shape in row2 that has sibling shapes to its right in the same row: Do NOT exit from the bottom of the shape and then travel H at y=720 (services-bottom) to the right — this H will cross the V stubs of right-side siblings that exit to the same corridor.

Fix options:

  • a) Exit from the right side of the shape → immediately go to RIGHT bypass x=1260 → V down/up to target (no H inside zone)
  • b) If right-side exit H would cross a sibling shape body: exit right → first waypoint in the column gap to the right (e.g. x=1060 for col5/col6 gap) → V down to services-bottom corridor y=720 → H right to RIGHT bypass → V to target

Rule 3 — Left bypass for connectors going downward to lower zones. When a row1 or row2 shape must connect to a shape in a lower zone (messaging, data), route:

shape_bottom → inter-row corridor y=640 → H LEFT to x=LEFT_BYPASS → V down to target zone corridor → H right to target

This ensures the H segment travels in the clear inter-row gap corridor and never crosses sibling shapes.

Rule 4 — connectorShapeOverlaps with zone containers are structural and unavoidable. Any connector crossing a swimlane zone boundary will be flagged as connector_shape_overlap with the zone container. This is expected and acceptable — focus on eliminating connectorCrossings (two connectors intersecting each other) and overlaps with non-container leaf shapes.

Pattern F — Multi-row swimlane fan-out (api-gateway → many services)

When a shape in an upper zone connects to N shapes spread across multiple columns of a multi-row services swimlane:

api-gateway_bottom → H to LEFT_BYPASS at inter-zone gap y → V down LEFT_BYPASS →
  branch per target:
    - col1 (leftmost): H right from LEFT_BYPASS to target_center_x at inter-row gap y
    - col2..colN: same pattern, extending H further right
    - for row2 targets: V from inter-row corridor down to row2 top, enter from top

All H branches travel at the same y (inter-row gap) — they are parallel, not crossing.

Pattern G — Event bus → services (right bypass fan-in)

When an event bus (bottom of diagram) connects back up to multiple services:

event-bus_right → H right to RIGHT_BYPASS → V up →
  branch per target:
    - service in row1: H left from RIGHT_BYPASS at row1_center_y to target
    - service in row2: stop at services-bottom corridor y=720 → H left → V up 20pt to target bottom
    - stagger y values slightly (e.g. y=720 for one, y=730 for another) to avoid parallel confusion

Critical: The H segments going LEFT from RIGHT_BYPASS at y=720/730 will cross V stubs of row2 services that exit their bottoms and route DOWN and RIGHT to the right bypass. To avoid these crossings:

  • Route those downward connectors via the LEFT bypass instead (Pattern F inverse)
  • OR ensure the rightward services exit via their right side (not bottom), so no V stub exists at y=720..730

Swimlane validation workflow

  1. Run page-connectors-validation after every edit
  2. Fix connectorCrossings first — these are always avoidable
  3. Fix connectorShapeOverlaps with leaf shapes (non-containers) — these are overlaps with actual service boxes
  4. Accept connectorShapeOverlaps with zone containers as structural (unavoidable)
  5. Fix cornerPortViolations, headerEdgeViolations, singlePortViolations — all should reach 0
  6. Target: connectorCrossings=0, cornerPortViolations=0, headerEdgeViolations=0, singlePortViolations=0

Sequence Diagram Layout Rules — MANDATORY

Sequence diagrams have a fundamentally different structure to architecture diagrams. The rules below override or supplement the general connector rules for sequence diagrams only.

Participants (lifeline headers)

  • Participant boxes are rectangular shapes (rounded=0) placed in a single horizontal row at the top of the diagram
  • HUMAN_ACTOR participants use shape=actor, width=40, height=40 — same as all actor shapes
  • Service/system participants use rounded=0, width=120, height=80 (both divisible by 40)
  • Participant spacing: gap between adjacent participant boxes must be ≥ 40pt and divisible by 40. Preferred gap = 80pt, giving a step of width + 80 between participant x-origins
  • Participant x-origin: must be on the 10pt grid; preferred to be on the 40pt grid
  • All participant boxes must have the same height (uniform row)

Lifelines

  • Each participant has exactly one lifeline — a vertical dashed edge (dashed=1; endArrow=none) centered on the participant
  • Lifeline x-coordinate = participant.x + participant.width / 2 (center of participant)
  • Lifeline starts at y = participant.y + participant.height (bottom edge of participant box) and ends above the legend/footer area. Stop lifelines at least 40pt above the legend box top edge
  • Lifeline color matches the participant stroke color
  • Lifelines are Layer-0 elements — they are background structural elements, equivalent to swimlane containers. Connectors (message arrows) may cross lifelines freely — this is the defining visual characteristic of a sequence diagram

Message arrows

  • Message arrows are horizontal edges (endArrow=block; endFill=1) drawn at exact y-coordinates, traveling between lifeline x-positions
  • Each message occupies its own y-row — messages must never share the same y-coordinate
  • Vertical step between messages: minimum 20pt, preferred 20–40pt to ensure labels don't overlap
  • Message arrow y-coordinates must be on the 10pt grid
  • Label placement: edge label is above the arrow line; keep it short (≤ 30 chars) to avoid overlap with lifelines it crosses
  • Return messages (response arrows) travel in the opposite horizontal direction; they are drawn 20pt below the corresponding request arrow

Step / phase labels

  • Phase separator labels ("1. Search & Browse", etc.) are text shapes (style=text) positioned in a left margin column that must not overlap any lifeline
  • Left margin column: x = 20, width ≤ participant_leftmost_x - 20 - 4 (at least 4pt clear gap from the leftmost lifeline)
  • Phase labels are placed at the y-coordinate of the first message in that phase, minus 20pt

Lifeline × message crossing — STRUCTURAL EXEMPTION

Lifeline × message arrow crossings are structural and expected in sequence diagrams. They must NOT be treated as violations. The page-connectors-validation tool will report these as connectorCrossings — accept them without fix.

Specifically:

  • A vertical lifeline edge crossing a horizontal message arrow edge = structural, expected, acceptable
  • Only report as a true crossing violation: two message arrows crossing each other (both horizontal) or two lifelines crossing each other (impossible by construction)

This exemption applies only to lifeline edges (identified by: dashed=1; endArrow=none; vertical segment spanning many messages). All other crossing rules remain fully in force.

Legend container — STRUCTURAL EXEMPTION

Legend sample arrows (short colored stubs showing connector styles) placed inside a plain container shape will be reported as connectorShapeOverlaps with the container. This is structural and expected — the legend connectors are intentionally drawn inside the container boundary.

Accept connectorShapeOverlaps where:

  • The overlapping edge is a legend sample stub (short, non-connected edge with parent=legend-box)
  • The overlapping shape is the legend container itself (id=legend-box)

All other connectorShapeOverlaps with non-container leaf shapes must still be fixed.

Sequence diagram validation targets

Metric Target Notes
connectorCrossings (lifeline × message) Accepted as structural Expected behavior of sequence diagrams
connectorCrossings (message × message) 0 Two horizontal messages must never cross
connectorShapeOverlaps (legend stubs × legend box) Accepted as structural Legend connectors inside their container
connectorShapeOverlaps (message × participant box) 0 Message arrows must not pass through participant boxes
connectorShapeOverlaps (lifeline × step-label shape) 0 Step labels must be in a clear left margin column
cornerPortViolations 0
headerEdgeViolations 0
singlePortViolations 0
overlappingPairs (shape bbox) 0 No shape bounding boxes overlap

Sequence diagram layout checklist

  • All participants in one horizontal row, uniform height, gaps divisible by 40
  • HUMAN_ACTOR participants: shape=actor, width=40, height=40
  • Service participants: rounded=0, width=120, height=80
  • Lifelines centered on participants, stop 40pt above legend
  • Lifelines use dashed=1; endArrow=none; color matches participant stroke
  • Each message at a unique y-coordinate, on 10pt grid, min 20pt apart
  • Step/phase labels in left margin column (x=20, clear of all lifelines)
  • No message × message crossings (horizontal × horizontal)
  • No message overlapping a participant box
  • Legend items are children of a plain container (not swimlane)
  • page-shape-bbox-validation reports overlappingPairs=0
  • page-connectors-validation reports cornerPortViolations=0, headerEdgeViolations=0, singlePortViolations=0