# Quantitative Polar Chart Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a verified 28th visual type that renders one cyclic quantitative series as radial lollipops with angle=category and linear radius=magnitude.
**Architecture:** The public contract lives in `references/type-polar.md`; three static HTML examples carry machine-readable `data-polar-*` metadata. A dependency-free Python verifier recomputes all endpoints and rejects hubs, nonlinear radius, visible zero rays, invalid values, and variant drift. Existing routing, gallery, README, CI, and package manifests are updated as one minor release.
**Tech Stack:** Python standard library 3.10+, static HTML/CSS, inline SVG, existing repository validation scripts, headless Google Chrome for the README screenshot.
**Spec:** `docs/superpowers/specs/2026-08-18-polar-chart-design.md`
## Global Constraints
- Angle encodes category; radius encodes magnitude with `radius = R * value / max`.
- The shared scale starts at exactly zero; `max` is finite and greater than zero.
- Zero renders with no value ray and no endpoint marker; its numeric label remains visible.
- One series, 4–8 unique ordered categories, and at most one focal category.
- No filled quantitative wedges, donut hub, gradients, negative values, logarithmic scale, interaction, animation, or external assets.
- All three examples use the same values, `viewBox="0 0 1000 520"`, center `(500,230)`, radius `160`, max `100`, start angle `-90`, and clockwise order.
- Static examples must satisfy the accessible SVG contract and existing skin rules.
- No new runtime or test dependency.
- Add a minor plugin release: `2.4.4 → 2.5.0` unless `origin/main` advances first; after a rebase, rerun `python3 scripts/bump-plugin-version.py --minor` from the new base.
---
### Task 1: Build the quantitative polar verifier with adversarial tests
**Files:**
- Create: `scripts/test-verify-polar.py`
- Create: `scripts/verify-polar.py`
**Interfaces:**
- Produces: `check(path: Path) -> list[str]`, `check_variants(paths: Sequence[Path]) -> list[str]`, and CLI `python3 scripts/verify-polar.py [file ...]`.
- Consumes later: Task 2 uses the verifier as the RED/GREEN gate for the three examples; Task 4 adds both commands to CI.
- [ ] **Step 1: Write the failing verifier tests**
Create `scripts/test-verify-polar.py` with a fixture builder for a four-category chart. The valid fixture has center `(100,100)`, `R=100`, max `100`, angles `-90/0/90/180`, and values `0/25/50/100`:
```python
#!/usr/bin/env python3
from __future__ import annotations
import importlib.util
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
VERIFIER = ROOT / "scripts/verify-polar.py"
def load_verifier():
spec = importlib.util.spec_from_file_location("verify_polar", VERIFIER)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def category(index: int, value: int, endpoint: tuple[float, float] | None) -> str:
body = '{}'.format(value)
if endpoint is not None:
x, y = endpoint
body = (
f''
f'' + body
)
return (
f'{body}'
)
def document(categories: str, extra_svg: str = "") -> str:
return (
'
'
''
)
VALID = document(
category(0, 0, None)
+ category(1, 25, (125, 100))
+ category(2, 50, (100, 150))
+ category(3, 100, (0, 100))
)
```
In `main()`, write each fixture to a temporary file and assert these results:
```python
cases = [
("25 percent hub", VALID.replace('data-polar-inner-radius="0"', 'data-polar-inner-radius="25"'), "inner radius"),
("sqrt radius", VALID.replace('x2="125"', 'x2="150"').replace('cx="125"', 'cx="150"'), "linear endpoint"),
("visible zero ray", VALID.replace('0', '0'), "zero category"),
("out of range", VALID.replace('data-polar-value="100"', 'data-polar-value="101"'), "outside 0..100"),
("duplicate index", VALID.replace('data-polar-index="3"', 'data-polar-index="2"'), "indices"),
("quantitative wedge", VALID.replace('', ''), "wedge"),
]
```
Assert `check(valid_path) == []`. For every invalid case, assert at least one finding exists and one finding contains the named substring; do not assert the total count because a malformed value may correctly violate more than one invariant.
Also create three valid files and change the dark fixture's `data-polar-value="25"` to `26`; assert `check_variants()` reports `variant data drift`.
- [ ] **Step 2: Run the test to verify RED**
Run:
```bash
python3 scripts/test-verify-polar.py
```
Expected: fail while importing missing `scripts/verify-polar.py`.
- [ ] **Step 3: Implement the minimal verifier**
Create `scripts/verify-polar.py` using only `dataclasses`, `html.parser`, `math`, `pathlib`, `sys`, and `typing`.
Define these data structures and parser output:
```python
@dataclass
class Category:
name: str
index: int
value: float
focal: bool
rays: list[dict[str, str]]
markers: list[dict[str, str]]
value_labels: int
@dataclass
class Chart:
attrs: dict[str, str]
categories: list[Category]
wedge_count: int
```
`PolarParser(HTMLParser)` must capture exactly one element carrying `data-polar-chart`; capture `g[data-polar-category]`, its `line[data-polar-ray]`, `circle[data-polar-marker]`, and `text[data-polar-value-label]`; count any `path[data-polar-wedge]`.
Implement `check()` with these exact invariants:
```python
TOLERANCE = 0.75
required = (
"data-polar-cx", "data-polar-cy", "data-polar-radius",
"data-polar-min", "data-polar-max", "data-polar-start-angle",
"data-polar-clockwise", "data-polar-radius-encoding",
"data-polar-inner-radius",
)
expected_angle = radians(start_angle) + (1 if clockwise else -1) * 2 * pi * index / count
expected_radius = radius * value / maximum
expected_x = cx + expected_radius * cos(expected_angle)
expected_y = cy + expected_radius * sin(expected_angle)
```
Return findings for: missing/duplicate chart; missing or non-numeric metadata; min not zero; max/radius not positive; non-linear encoding; non-zero inner radius; category count outside 4–8; non-contiguous indices; duplicate trimmed names; more than one focal; value outside range; value-label count not one; zero carrying a ray or marker; positive value not carrying exactly one ray and marker; ray start not at center; ray/marker endpoint outside tolerance; any wedge.
Implement `check_variants()` by comparing this signature for all paths:
```python
signature = (
cx, cy, radius, minimum, maximum, start_angle, clockwise,
tuple((c.index, c.name, c.value, c.focal) for c in sorted(categories, key=lambda c: c.index)),
)
```
With no CLI arguments, validate the exact shipped paths `example-polar.html`, `example-polar-dark.html`, and `example-polar-full.html`; with arguments, validate those files. Print each finding as `FAIL : ` and return 1 if any finding exists; otherwise print `OK ` and return 0.
- [ ] **Step 4: Run GREEN and confirm the shipped-file gate is still RED**
Run:
```bash
python3 scripts/test-verify-polar.py
python3 scripts/verify-polar.py
```
Expected: adversarial tests pass; shipped verifier fails because the three example files do not exist yet.
- [ ] **Step 5: Commit the verifier slice**
```bash
git add scripts/test-verify-polar.py scripts/verify-polar.py
git commit -m "test(polar): verify linear radius encoding"
```
---
### Task 2: Add the type reference and three verified static examples
**Files:**
- Create: `skills/diagram-design/references/type-polar.md`
- Create: `skills/diagram-design/assets/example-polar.html`
- Create: `skills/diagram-design/assets/example-polar-dark.html`
- Create: `skills/diagram-design/assets/example-polar-full.html`
**Interfaces:**
- Consumes: `scripts/verify-polar.py` and the approved spec.
- Produces: three identical `data-polar-*` datasets consumed by Task 3 gallery/docs wiring.
- [ ] **Step 1: Preserve the missing-example RED result**
Run:
```bash
python3 scripts/verify-polar.py
```
Expected: fail naming all three missing example files.
- [ ] **Step 2: Write `type-polar.md` from the approved contract**
Create these sections in order:
```markdown
# Polar Chart
**Best for:** one quantitative series across 4–8 categories whose clockwise order is meaningful.
## Input contract
## Quantitative encoding
### Zero
## Layout conventions
## Geometry
## Visual treatment
## Complexity budget
## When not to use
## Anti-patterns
## Examples
```
Copy the formulas, zero behavior, exclusions, scale validation, label-anchor rules, and `1000×520` reference geometry verbatim from the spec. The anti-pattern list must explicitly reject donut hubs, filled wedges, tangent labels, multiple series, truncated scales, sorted-by-value categories, and treating missing as zero. Route multiple series to Radar, non-cyclic categories to Bar, and more than eight time buckets to Line.
- [ ] **Step 3: Build the minimal-light example with exact geometry**
Start from `skills/diagram-design/assets/template.html`. Use this dataset and endpoint table; retain three decimals where shown so the verifier tolerance is meaningful:
| i | label | value | spoke end | value end | label point (`R+28`) |
|---|---|---:|---|---|---|
| 0 | 00–03 | 32 | `500,70` | `500,178.8` | `500,42` |
| 1 | 03–06 | 18 | `613.137,116.863` | `520.365,209.635` | `632.936,97.064` |
| 2 | 06–09 | 24 | `660,230` | `538.4,230` | `688,230` |
| 3 | 09–12 | 58 | `613.137,343.137` | `565.62,295.62` | `632.936,362.936` |
| 4 | 12–15 | 100 focal | `500,390` | `500,390` | `500,418` |
| 5 | 15–18 | 82 | `386.863,343.137` | `407.227,322.773` | `367.064,362.936` |
| 6 | 18–21 | 76 | `340,230` | `378.4,230` | `312,230` |
| 7 | 21–24 | 45 | `386.863,116.863` | `449.088,179.088` | `367.064,97.064` |
The primary SVG opening must be:
```html