| name | model-schema-diagram |
| description | Draw an entity-relationship / schema diagram as a self-contained inline SVG (no CDN, no image files, no mermaid) that drops straight into an HTML doc or stands alone. Renders each table as a colored-header box listing PK/FK/key columns, connects them with arrow-marker lines that point to the "many" side, color-codes relationships by layer, and adds a legend plus a collapsible ASCII fallback. Use when asked for a "schema diagram", "ER diagram", "data-model diagram", "draw the tables", or to add a visual to a data-model doc. |
Model Schema Diagram
Produces a clean, hand-laid inline SVG ER diagram. No external renderer, no <img>, no mermaid/CDN — just SVG markup that works inside the warm-paper HTML doc style or on its own. Pairs with the data-model-doc and html-doc-style skills.
Principles
- Self-contained. Pure SVG + a few CSS rules in the same file. It must render offline with zero assets.
- Layered left-to-right. Columns by role: reference/lookup → core entities → allocation/join + parallel structures. Lines then mostly flow one direction, which is what keeps the diagram readable.
- Boxes are compact. Header (table name) + only the load-bearing rows: PK, FKs, and 1–2 defining columns. Not every column — the doc's tables carry the full list.
- Arrow points to the many side. Each relationship line ends with an arrowhead at the child (FK holder). State this in the legend; skip crow's-feet — they're fiddly in SVG and add little at this scale.
- Color by source layer, not per-table, so the eye groups relationships: reference, core/deal, core/partner, internal join, parallel (e.g. profit pool).
Geometry (fixed grid — keeps everything aligned)
- Header height 20px, row height 16px. Box height =
20 + rows*16.
- Header text baseline
y+14, font 11px bold; row text baseline y + 20 + idx*16 + 12, font 10px; monospace throughout (ui-monospace, Menlo, Consolas, monospace).
- Column widths: reference ~150, core ~168, wide join names ~200 (size to the longest table name × ~6.6px + padding).
viewBox with a few px of negative left padding (e.g. viewBox="-14 -6 850 566") so a self-FK loop on a left-edge table isn't clipped.
- Container
div with overflow-x:auto and the svg given min-width so it scrolls rather than squashes on narrow screens.
Box render pattern
Outer rounded rect (white fill, light stroke) → header rect (layer color, same rx) → header text (white) → one <text> per row. Tag keys with a colored <tspan> suffix:
<rect x="320" y="24" width="168" height="68" rx="5" fill="#fff" stroke="#d8d4cc"/>
<rect x="320" y="24" width="168" height="20" rx="5" fill="#2d5f8b"/>
<text x="328" y="38" fill="#fff" font-size="11" font-weight="700">tblPartner</text>
<text x="328" y="56">partnerid<tspan dx="6" fill="#6d28d9" font-weight="700">PK</tspan></text>
<text x="328" y="72">fofficeid<tspan dx="6" fill="#2d5f8b" font-weight="700">FK</tspan></text>
<text x="328" y="88">partnerCode</text>
Suffix colors: PK #6d28d9, FK #2d5f8b, UNIQUE #b25e1f (e.g. UQ).
Relationship lines
Define one arrow marker per layer color, then draw <path>s from a point on the parent's edge to a row on the child's left edge:
<defs>
<marker id="aBlue" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7.5" markerHeight="7.5" orient="auto-start-reverse">
<path d="M0,0 L8,4 L0,8 z" fill="#2d5f8b"/>
</marker>
</defs>
<g fill="none" stroke-width="1.4" opacity="0.9">
<path d="M488,250 L620,280" stroke="#2d5f8b" marker-end="url(#aBlue)"/>
</g>
- Aim each line at the child's specific FK row (target
y = that row's center) so it reads as "this FK → that PK".
- Self-reference: a short cubic out the left edge and back, e.g.
M14,116 C-12,108 -12,150 14,148, with a small self label.
- Straight diagonals are fine and far easier to author correctly than elbow routing. Minimize crossings by placing a join table's feeders adjacent to it (e.g. put the lookup that feeds a join near that join, not in the far reference column) rather than by drawing clever bends.
Legend + ASCII fallback
Add a .legend row: one swatch per relationship color with its meaning, plus a note that the arrow points to the many side. Then include a compact ASCII version of the same map inside a collapsible <details><summary>Compact text view</summary>…</details> — it survives copy-paste into plain-text contexts and is a useful sanity check on the relationships.
Suggested CSS (add once to the doc's <style>):
.diagram { background:var(--panel); border:1px solid var(--rule); border-radius:8px; padding:14px; overflow-x:auto; box-shadow:var(--shadow); }
.diagram svg { display:block; min-width:760px; width:100%; height:auto; }
.legend { display:flex; flex-wrap:wrap; gap:8px 16px; margin:10px 2px 2px; font-size:11.5px; color:var(--muted); }
.legend span { display:inline-flex; align-items:center; gap:6px; }
.legend i { width:22px; border-top:2px solid currentColor; display:inline-block; }
Build order
- List the tables and assign each to a layer (color).
- Assign columns; stack boxes per layer using the fixed geometry; compute each box's
(x, y, height).
- Enumerate relationships as (parent edge point → child FK-row point), choose the color by parent layer.
- Emit
defs (markers) → line <g> → box <g>s.
- Add legend + collapsible ASCII view.
- Sanity-check coordinates: no two boxes overlap; every line starts/ends on a real box edge; the self-loop isn't clipped by the viewBox.
Verification
Gotchas
- SVG
marker fill does not reliably inherit the line's stroke — define a separate marker per color rather than expecting one arrowhead to recolor itself.
- Round only the outer/header rects (
rx); a fully rounded header over a rounded body can leave tiny notches — at this scale it's invisible, but keep header and body rx equal.
- Long table names (e.g. a
tbl{A}{B}Join) set the minimum column width — size the widest column to the longest name, don't let text overflow the box.
- If embedding in a data-model doc, the diagram replaces/augments the ER-map section; keep the section's intro paragraph and put the ASCII map in the
<details>.