# Layout and Legibility Rules for SVG Diagrams These rules prevent the most common legibility failures in agent-generated SVG: text overflow, poor contrast, and missing margins. They apply to both external `.svg` files and inline SVG. ## Rule 1 — Estimate max-text-width before sizing a container For any text that lives **inside** a bounded shape (cell, box, button), estimate the rendered width of the longest string: - Rough bound: `text_width_px ≈ chars × font_size × 0.6` for proportional fonts at common weights. - If the container is narrower than the longest text, choose one of: - **Widen the container** (preferred when there's space). - **Truncate to a short label** (e.g. a count, a code, or an abbreviation) and put the full text outside (legend, footnote table, sidebar). - **Wrap to multiple `` lines** when truncation loses meaning. - SVG doesn't clip text by default — never assume CSS overflow rules apply. Default to **counts + short labels inside the chart, full names in a legend below**. For dense categorical charts this is almost always the right answer. ## Rule 2 — Text-over-background needs a contrast contract If a `` element renders on top of an arbitrary background, declare which class of background it's designed for: - **Light-bg text** (`fill: #1a1a1a` or similar): legible on whites, light grays, light pastels. - **Dark-bg text** (`fill: #ffffff` or similar): legible on saturated mid-to-dark colors only. - **High-contrast halo** (`paint-order="stroke fill"` + `stroke-width="3"` + `stroke="#fff"`): legible on anything; use when the background is unpredictable or varies. Never apply one fill class across multiple background colors without verifying contrast for each. WCAG large-text minimum is **3:1**; for text-in-data-viz aim for **4.5:1** to leave margin. When colors are picked by category, group them into a "dark fills" palette (use white text) and a "light fills" palette (use dark text). Don't mix. ## Rule 3 — Backgrounds for text on uncertain surfaces When text is placed near or over a colored region whose color depends on data, give the text its own background: - A `` behind the text, sized to its bounding box (compute as `chars × font_size × 0.6 + padding`), with `fill="#fff"` and a small `rx` for softness. - Or a halo via `paint-order="stroke fill"` with a white stroke. For category legends, axis labels, and chart titles — anything that sits in the chart's "frame" rather than inside a data shape — always give them a clean background or place them in the margin. ## Rule 4 — Reserve margin for rotated labels Rotated text (typically column headers at `-30°` to `-45°`) extends beyond its anchor in both x and y. Rule of thumb: an N-character rotated header at θ degrees and font-size F needs: - horizontal reach: `cos(θ) × chars × F × 0.6` - vertical reach: `sin(θ) × chars × F × 0.6` Plus the anchor offset. Always add 20% padding. If the rotated headers extend into the chart body, increase the chart's top margin until they don't. ## Rule 5 — Default chart pattern: sparse cells, full legend below For categorical density / heatmap / matrix charts: ``` [ Title ] [ Rotated category headers (with margin) ] [ Row label │ count │ count │ . │ count │ count │ . ] (cells: just the number, color-coded) [ Row label │ . │ count │ count │ . │ count │ . ] [ ... ] [ ] [ Legend / details table: ] [ Row × Category → action_a, action_b, action_c ] [ Row × Category → action_d ] [ ... ] ``` Counts inside cells; names in the table below. This pattern composes well across renderers and prevents the overflow trap entirely. ## Rule 6 — Stick to a small palette with declared roles Categorical palettes should be picked once, with their **role** declared: - "Dark fills" (use **white** text): `#1f77b4`, `#d62728`, `#9467bd`, `#8c564b` - "Light fills" (use **dark** text): `#aec7e8`, `#ffbb78`, `#98df8a` Avoid mid-tones (`#ff7f0e`, `#2ca02c`, `#e377c2`) unless the text color is matched per-color. If the chart needs more than 6 categories, that's usually a sign the categorisation is too fine — group first. ## Rule 7 — Sanity check the rendered output Before saving, mentally render at the target display width (typically 600–750px effective in Markdown), and ask: - Can I read every label? - Does any text overlap another shape? - Is there a category whose color makes its text invisible? - Are empty cells distinguishable from filled ones at a glance? If any answer is "no", iterate before checking in.