Extract inline SVG rules to references/inline-svg.md for progressive disclosure. Add references/layout-legibility.md with 7 rules for readable SVG output (text sizing, contrast, margins, chart patterns, palettes) based on concrete failure modes from issue #4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.8 KiB
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.6for 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
<tspan>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 <text> element renders on top of an arbitrary background, declare which class of background it's designed for:
- Light-bg text (
fill: #1a1a1aor similar): legible on whites, light grays, light pastels. - Dark-bg text (
fill: #ffffffor 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
<rect>behind the text, sized to its bounding box (compute aschars × font_size × 0.6 + padding), withfill="#fff"and a smallrxfor 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.