Files
reliquary/plugins/g4b_ai/skills/markdown-embedded-svg/references/layout-legibility.md
T
g4borg 61af13521c 🤖 Restructure SVG skill: extract references, add legibility rules
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>
2026-05-19 20:46:12 +02:00

84 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<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: #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 `<rect>` 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 600750px 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.