Files
reliquary/plugins/spire/skills/svg/references/layout-legibility.md
T
g4borg e054ffd2bd ✨ Spire plugin — MVP build (phases 1-3)
Complete plugin at plugins/spire/ replacing g4b_ai:
- Igor (session coordinator skill + agent)
- Adept (domain knowledge agent)
- Athanor (dispatching orchestrator, "the oven")
- Tome (generic knowledge layer, opt-in)
- Ported from g4b_ai: workpad, research-tree, research,
  implementation, svg skills + researcher/search/scout agents

All warpspace-specific content stripped. Tome genericized.
Project-specific config via shadowed project.md reference.
2026-09-14 14:28:26 +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 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.