# Class Diagram (UML) For showing static structure: what types exist, what they contain, and how they relate. The classic UML class diagram, adapted to baoyu-diagram's flat-rect design system. ## When to use - "Draw the class diagram for X" / "show me the UML" - "What are the types and how are they related" - "Show the inheritance hierarchy" - "Diagram the domain model / schema / type system" - "Interface + implementations" **When not to use:** - If the user wants to see *behavior* (what happens when X calls Y), use sequence (`sequence.md`). - If the user wants to see *data storage* with foreign keys and cardinalities only, that's an ER diagram — baoyu doesn't do ERDs in v1, suggest mermaid. - If the user wants to see *runtime object instances* with field values, you're drawing an object diagram — use the class template with `th`-underlined instance names (`underline` class not in the template; use `text-decoration="underline"` inline on ``) and skip the method compartment. ## Planning Before writing SVG: 1. **List the classes.** 3–8 total. More than 8 and you should split into multiple diagrams grouped by package or subsystem. 2. **For each class, list attributes and methods.** Keep each line ≤30 chars. If a signature doesn't fit, abbreviate. 3. **Pick the visibility for each member.** `+` public, `−` private, `#` protected, `~` package-private. 4. **List the relationships.** Who extends whom, who implements which interface, who has what. 5. **Pick colors by kind of class**, not by position. See "Color rule" below. 6. **Compute box widths** using the class-specific formula below. 7. **Lay out the classes** — parents / interfaces on top, children below. See "Layout" below. ## Class box template Each class is a **3-compartment rect**: name (top), attributes (middle), methods (bottom). Horizontal divider lines separate the compartments. ### Geometry | Element | Value | |------------------------|-----------------------------------------------| | Rect corner radius | `rx="6"` | | Min width | 160 px | | Name compartment height | 32 px (single line, centered) | | Attribute/method row height | 18 px per line + 8 px top/bottom padding | | Horizontal divider | `` using `arr` class at 0.5 stroke | ### Width formula The box width must fit the longest line across all three compartments: ``` longest = max(name_chars × 8, max(attribute_line_chars) × 7, max(method_line_chars) × 7) width = longest + 24 ``` - 8 px/char for 14px `th` (the class name) - 7 px/char for 12px `ts` (attributes and methods — they use the subtitle class for compact line height) Round up to the nearest 10. Minimum width is 160 regardless of label length. ### Height formula ``` name_h = 32 attr_h = 16 + (attribute_count × 18) method_h = 16 + (method_count × 18) total_h = name_h + attr_h + method_h ``` If the class has no attributes, show an empty attribute compartment with `h = 16`. Never collapse a compartment — readers expect all three. ### SVG template ```svg User + id: UUID + email: string − passwordHash: string + login(pwd): Token + logout(): void ``` - Name is `th` 14px centered. - Attributes and methods are `ts` 12px, **left-aligned at `rect_x + 12`**. - Divider lines use `class="arr"` with inline `stroke-width="0.5"` (the default 1.5 is too heavy for an internal divider). - All text inside the class inherits the ramp's text color from the `c-{ramp}` wrapper — no hardcoded fills. ### Stereotypes For interfaces, abstract classes, and enums, prepend a stereotype line inside the name compartment: ```svg «interface» Drawable ... ``` The stereotype line is 12px `ts`, italic is encoded via the guillemets `«...»` characters — do not use inline `font-style="italic"` since baoyu's typography table doesn't include an italic class for `ts` outside poster captions. When you add a stereotype, bump the name compartment height to 48 to fit both lines. Common stereotypes: - `«interface»` — interface type, no fields, method signatures only. - `«abstract»` — abstract class with at least one abstract method. - `«enumeration»` — enum with value constants in the attribute compartment. - `«datatype»` — value type with no behavior (rare; usually just a plain class). Abstract class names may optionally use the existing `th` weight — baoyu does not render italics for abstract, the stereotype carries the meaning. ## Relationship arrows Six UML relationship styles. Each maps to a specific arrow rendering. All connectors use `class="arr"` (or `arr-alt` for dashed variants) so they inherit dark-mode stroke colors. | Relationship | Line style | Arrowhead | From | Typical label | |------------------|------------|-------------------|---------------------|-------------------------------------| | Inheritance | solid | hollow triangle | child → parent | (none) | | Implementation | dashed | hollow triangle | class → interface | (none) | | Association | solid | open arrow `>` | from → to | multiplicity `1`, `0..*`, `1..*` | | Aggregation | solid | hollow diamond | container → part | (none, diamond on container side) | | Composition | solid | filled diamond | container → part | (none, diamond on container side) | | Dependency | dashed | open arrow `>` | user → used | optional label like `«uses»` | ### Arrow head definitions (add to ``) The template's default `` renders an open arrow — that works for association and dependency. For inheritance/implementation/aggregation/composition you need four additional markers. Add these to `` **only if the diagram uses them**: ```svg ``` **Why `fill="none"` instead of a hardcoded hex.** Hollow markers need to stay hollow in both light and dark mode, and any hardcoded hex color is frozen at author time — it can't respond to the template's `@media (prefers-color-scheme: dark)` block. `fill="none"` means the triangle/diamond is outlined only and the interior is transparent. At 1.2px stroke on a 10×10 marker, the 1.5px line passing behind the marker is invisible to the reader. For the filled composition diamond, `fill="context-stroke"` picks up whichever stroke color the attached line is using, so it inherits dark-mode correctly through the `class="arr"` rules. ### Using the markers ```svg 1 0..* ``` **Pitfall**: the `marker-end` goes on the **parent** end for inheritance/implementation (the triangle points at the parent), and on the **container** end for aggregation/composition (the diamond sits on the container). Direction of `line`/`path` dictates which end is `start` vs `end` — always draw the arrow *toward* the marker. ## Color rule **One ramp per kind of class**, not per position. This is the same exception as sequence diagrams — identity is a category, so using multiple ramps is fine, but each ramp must correspond to a stable *role*. Default ramp assignments: | Class kind | Ramp | Why | |-------------------|-----------|---------------------------------------| | Concrete class | `c-gray` | The default — most classes are here | | Abstract class | `c-teal` | Signals "not directly instantiable" | | Interface | `c-purple` | Signals "pure contract, no state" | | Enum / datatype | `c-amber` | Signals "constants / value type" | Cap at **3 ramps per diagram**. If you have concrete classes + interfaces + enums, use gray + purple + amber. If every class is concrete, all gray is fine and looks cleanest. Do not assign ramps by package or by author — readers won't learn the code. **Legend implicit**: the ramps are anchored by the class kinds themselves. A reader who sees a purple `«interface» Drawable` box learns the convention immediately. No explicit legend needed unless the diagram uses >3 ramps (which shouldn't happen). ## Layout Class diagrams are **2D grid** layouts, not linear flows. Parents live above children, interfaces sit on the side of their implementors, and association arrows run horizontally. ### Default: top-down inheritance tree Parents / interfaces on top, children at the bottom: ``` [ «interface» Drawable ] [ Shape (abstract) ] | | | implements | extends +---------+--------+---------+ | | [ Circle ] [ Square ] ``` - Tier 1 (y=60): base types (interfaces, abstract base classes) - Tier 2 (y=240): concrete subtypes - Tier 3 (y=420): composed types / enums Vertical gap between tiers: **40 px minimum**. ### 2-wide / 3-wide rows For 4–6 classes per tier, pack horizontally with 20px gaps: ``` x=40 x=260 x=480 (width 180 each, gap 20) ``` For >6 classes, you're building a too-big diagram — split by package or subsystem into separate diagrams and link them with prose. ### Association arrows Association arrows are **horizontal or L-bend**, running between classes in the same tier (or adjacent tiers). Do not let an association arrow cross an inheritance arrow — if they cross, reroute the weaker relationship (usually the association) with an L-bend. ### viewBox height ``` H = tier_count × 200 + 40 ``` Where 200 px is the per-tier budget (class box height ~140 + 40 px vertical gap + 20 px margin). Round up after placing the last tier's actual box height — if a class in the last tier is extra-tall, bump H to fit. Width stays at 680. If a tier of 3-wide boxes doesn't fit at minimum width, narrow the boxes to 180 each (total 540 + 40 gap = 580, centered at x=50). ## Worked example — Shape hierarchy A small 4-class domain: a `Drawable` interface, an abstract `Shape` class, and two concrete subclasses `Circle` and `Square`. **Plan:** 1. Classes: `Drawable` (interface), `Shape` (abstract), `Circle`, `Square` — 4 classes, 2 tiers. 2. Attributes/methods: - `Drawable`: `+ draw(): void` - `Shape`: `# color: Color`, `# position: Point`, `+ area(): number` (abstract) - `Circle`: `− radius: number`, `+ area(): number` (override) - `Square`: `− side: number`, `+ area(): number` (override) 3. Relationships: `Shape` implements `Drawable` (dashed + hollow triangle); `Circle` and `Square` extend `Shape` (solid + hollow triangle). 4. Colors: `Drawable` = `c-purple` (interface); `Shape` = `c-teal` (abstract); `Circle` / `Square` = `c-gray` (concrete). 3 ramps. 5. Widths: - `Drawable`: name 8 chars, longest method "+ draw(): void" = 14 chars → `max(64, 98) + 24 = 122` → min 160 - `Shape`: name 5 chars, longest "+ area(): number" = 16 chars → `max(40, 112) + 24 = 136` → min 160 - `Circle`/`Square`: ~13 chars longest → min 160 6. Tier 1 (y=60): `Drawable` at x=130, `Shape` at x=390 (row of 2, both width 160, gap 100). 7. Tier 2 (y=260): `Circle` at x=130, `Square` at x=390 (row of 2, width 160, gap 100). 8. Arrows: - `Shape → Drawable` (implements): horizontal dashed line from Shape's left edge (x=390, y=110) to Drawable's right edge (x=290, y=110). - `Circle → Shape` (extends): L-bend from Circle's top (x=210, y=260) up to ymid=220, right to x=470, up to Shape's bottom (x=470, y=200). - `Square → Shape` (extends): straight vertical from Square's top (x=470, y=260) to Shape's bottom (x=470, y=200). **Coordinates (abbreviated; production code would fill in every ``):** ```svg «interface» Drawable + draw(): void «abstract» Shape # color: Color # position: Point + area(): number Circle − radius: number + area(): number Square − side: number + area(): number ``` viewBox H: bottom of last box is `260 + 110 = 370`, so `H = 370 + 20 = 390`. The example uses 420 for a 30px bottom margin, which is also fine. ## Checklist (class-specific) On top of the standard `pitfalls.md` checks: 1. Every class has all **three compartments** (name / attrs / methods), even if one is empty. 2. The class **name is `th` centered**, attributes/methods are **`ts` left-aligned at `rect_x + 12`**. 3. Every attribute/method starts with a **visibility marker** (`+ − # ~`). 4. Dividers use `class="arr"` with **inline `stroke-width="0.5"`**. 5. Inheritance and implementation arrows point **toward the parent/interface** (triangle on parent side). 6. Aggregation and composition diamonds sit on the **container side**, not the part side. 7. Associations carry **multiplicity labels** at both ends (`1`, `0..*`, `1..*`) when they aren't obvious. 8. Dashed arrows are `arr-alt`, solid arrows are `arr` — never mix. 9. Color ramp budget ≤3, one per class kind (concrete / abstract / interface / enum). 10. Association arrows **do not cross inheritance arrows** — if they do, re-route the association.