# Polar Chart Quantitative Encoding and Gate Testing: A Complete Technical Guide

> Master polar chart quantitative encoding and gate testing. Discover how SVG attributes and Python validation ensure accurate geometric data representation in this comprehensive technical guide.

- Repository: [Cathryn Lavery/diagram-design](https://github.com/cathrynlavery/diagram-design)
- Tags: technical-guide
- Published: 2026-09-05

---

**A polar chart in the diagram-design repository encodes numeric values as radial distances using strict `data-polar-*` SVG attributes, validated by a Python gate that parses geometry and rejects any deviation from the contract.**

The **diagram-design** project implements a rigorous quantitative visualization system where polar charts represent single numeric series through angular sectors and radial rays. Every generated SVG carries machine-readable metadata that a lightweight gate parses to verify geometric accuracy and semantic consistency. This article explains the encoding mechanics and the validation pipeline that enforces them.

## Polar Chart Quantitative Encoding Rules

The polar chart type follows a declarative contract defined in [`skills/diagram-design/references/type-polar.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/type-polar.md). Each SVG element must carry specific `data-polar-*` attributes that encode both visual properties and semantic meaning.

### Root SVG Metadata

The `<svg>` element establishes the coordinate system with eight required attributes:

- `data-polar-cx` and `data-polar-cy` — chart center coordinates
- `data-polar-radius` — maximum radial extent **R**
- `data-polar-min` and `data-polar-max` — value domain boundaries
- `data-polar-start-angle` — angular zero point in degrees
- `data-polar-clockwise` — direction of angular progression (`true`|`false`)
- `data-polar-inner-radius` — must be `0` (no donut support)

Optional: `data-polar-radius-encoding` permits only `linear` values. Non-linear encodings trigger validation failures.

### Category Structure

Each data point nests inside a `<g>` element with:

- `data-polar-category` — trimmed, unique category name
- `data-polar-index` — contiguous integer `0..N-1`
- `data-polar-value` — finite numeric within `[min, max]`
- `data-polar-focal` — at most one category may mark `true`

The category contains three child elements:

1. **Ray `<line>`** — stem from center to value-proportional radius, stroke-width `0.8px`
2. **Marker `<circle>`** — radius `4px` (`5px` for focal), stroke-width `1.2px`
3. **Value label `<text>`** — text content must exactly match `data-polar-value`

### Grid and Decorative Elements

- **Five rings `<circle data-polar-ring>`** — concentric circles at radii `R·k/5` for `k=1..5`
- **Optional background `<rect data-polar-background>`** — full-coverage rectangle
- **Optional rule `<line data-polar-rule>`** — at most one reference line

Any element not explicitly permitted—`<path>`, `<polygon>`, `<image>`, inline `style`, `class` attributes, CSS imports, or transforms—causes immediate gate failure.

## Gate Testing Architecture

The validation system lives in [`scripts/verify-polar.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py), a dependency-free Python script implementing a two-phase pipeline: **parsing** then **verification**.

### PolarParser Implementation

Lines [887–894](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L887) define the `PolarParser` class subclassing `html.parser.HTMLParser`:

```python
class PolarParser(HTMLParser):
    def __init__(self):
        super().__init__(convert_charrefs=True)
        self.chart = None
        self.category_stack = []
        self.findings = []

```

The parser executes through these stages:

1. **Root detection** — locates `<svg data-polar-chart>` and instantiates a `Chart` object ([1029–1050](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1029-L1050))
2. **Category collection** — pushes each `<g data-polar-category>` onto a stack ([1080–1089](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1080-L1089))
3. **Element recording** — tracks rays, markers, rings, backgrounds, rules, and rejects unsupported elements ([1100–1130](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1100-L1130))

### Validation Orchestration

The public entry point `check(path: Path) -> list[str]` at lines [606–607](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L606-L607) coordinates verification:

```python
def check(path: Path) -> list[str]:
    parser = PolarParser()
    parser.feed(path.read_text())
    return parser.findings if parser.findings else ["OK"]

```

Key validation steps include:

| Check | Implementation | Tolerance |
|-------|---------------|-----------|
| Metadata extraction | [514–538](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L514-L538) | Exact match for enumerated values |
| Ring geometry | [558–575](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L558-L575) | `0.75px` or `R/20`, whichever is smaller |
| Category indexing | [891–901](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L891-L901) | Contiguity and uniqueness |
| Spoke geometry | [1289–1306](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1289-L1306) | Coordinate tolerance |
| Ray length | [1318–1339](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1318-L1339) | Endpoint position |
| Marker/label | [1345–1385](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1345-L1385) | Radius, stroke, text match |

The geometry validation recomputes expected positions using polar-to-cartesian conversion:

```python
expected_angle = radians(start_angle) + (1 if clockwise else -1) * 2 * pi * index / N
expected_x = cx + radius * cos(expected_angle)
expected_y = cy + radius * sin(expected_angle)

```

Ray endpoints must satisfy `r = R * value / max` where `value` is the category's `data-polar-value`.

### Variant Drift Detection

The `check_variants(paths)` function ([637–645](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L637-L645)) validates multiple SVG variants (light, dark, full themes) and compares their **data signatures**. Any discrepancy triggers a single "variant data drift" failure, ensuring visual variants share identical underlying data.

## Practical Implementation

### Minimal Valid Polar Chart

The canonical example resides in [`skills/diagram-design/assets/example-polar.html`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/assets/example-polar.html):

```html
<svg data-polar-chart
     data-polar-cx="500" data-polar-cy="230"
     data-polar-radius="160" data-polar-min="0"
     data-polar-max="100" data-polar-start-angle="-90"
     data-polar-clockwise="true"
     data-polar-radius-encoding="linear"
     data-polar-inner-radius="0">
  <!-- Grid rings -->
  <circle data-polar-ring r="32" cx="500" cy="230" fill="none" stroke-width="0.8"/>
  <circle data-polar-ring r="64" cx="500" cy="230" fill="none" stroke-width="0.8"/>
  <circle data-polar-ring r="96" cx="500" cy="230" fill="none" stroke-width="0.8"/>
  <circle data-polar-ring r="128" cx="500" cy="230" fill="none" stroke-width="0.8"/>
  <circle data-polar-ring r="160" cx="500" cy="230" fill="none" stroke-width="0.8"/>
  
  <!-- Category: value 32 at 20% radius -->
  <g data-polar-category="00-03" data-polar-index="0" data-polar-value="32">
    <line data-polar-ray x1="500" y1="230" x2="551.2" y2="230" stroke-width="0.8"/>
    <circle data-polar-marker cx="551.2" cy="230" r="4" stroke-width="1.2"/>
    <text data-polar-value-label x="551.2" y="210">32</text>
  </g>
</svg>

```

### Running Gate Validation

Validate a single file:

```bash
python3 scripts/verify-polar.py skills/diagram-design/assets/example-polar.html

```

Validate all default variants (light, dark, full):

```bash
python3 scripts/verify-polar.py

```

The script prints `OK <path>` on success or `FAIL <path>: <message>` per violation, returning exit code `0` or `1` respectively ([889–898](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L889-L898)).

### Automated Testing

[`scripts/test-verify-polar.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/test-verify-polar.py) exercises the gate with fixtures covering:

- Valid four-category baseline (`0, 25, 50, 100`)
- Failure: non-zero inner radius
- Failure: visible zero-length ray
- Failure: out-of-range value
- Failure: duplicate index
- Failure: illegal wedge element

CI runs these tests automatically via [`.github/workflows/ci.yml`](https://github.com/cathrynlavery/diagram-design/blob/main/.github/workflows/ci.yml).

## Source File Reference

| File | Purpose |
|------|---------|
| [`scripts/verify-polar.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py) | Core gate: parser and validation engine |
| [`scripts/test-verify-polar.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/test-verify-polar.py) | Automated adversarial test suite |
| [`skills/diagram-design/references/type-polar.md`](https://github.com/cathrynlavery/diagram-design/blob/main/skills/diagram-design/references/type-polar.md) | Human-readable contract specification |
| `skills/diagram-design/assets/example-polar*.html` | Canonical valid examples (light/dark/full) |
| [`docs/superpowers/specs/2026-08-18-polar-chart-design.md`](https://github.com/cathrynlavery/diagram-design/blob/main/docs/superpowers/specs/2026-08-18-polar-chart-design.md) | Design rationale and evolution |

## Summary

- **Polar chart quantitative encoding** maps single numeric series to radial distances using strict `data-polar-*` attributes on SVG elements
- **Seven element types** are permitted: root `<svg>`, category `<g>`, ray `<line>`, marker `<circle>`, ring `<circle>`, background `<rect>`, and rule `<line>`
- **Linear radius encoding** is mandatory; values are normalized against `data-polar-max` to determine ray length
- **[`scripts/verify-polar.py`](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py)** implements a dependency-free gate that parses SVG, recomputes expected geometry, and reports violations with sub-pixel tolerance
- **Variant drift detection** guarantees that light, dark, and full theme variants share identical data signatures
- **Zero-tolerance policy** for forbidden elements, transforms, inline styles, and URL-valued attributes ensures predictable rendering

## Frequently Asked Questions

### What happens if a polar chart uses a non-linear radius encoding?

The gate rejects any `data-polar-radius-encoding` value other than `linear`. According to [`type-polar.md`](https://github.com/cathrynlavery/diagram-design/blob/main/type-polar.md), only linear interpolation from center to maximum radius is permitted. Attempting logarithmic or power scales triggers a `FAIL` message citing the invalid attribute value.

### How does the gate handle floating-point precision in coordinate comparisons?

Validation uses a tolerance of `0.75px` or `R/20`, whichever is smaller, as implemented at lines [991–1004](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L991-L1004). This accommodates reasonable SVG generation rounding while detecting meaningful geometric errors.

### Can the focal marker be larger than standard markers?

Yes. The focal category—at most one per chart—receives a marker radius of `5px` versus `4px` for standard categories. Both use stroke-width `1.2px`. The gate validates these exact measurements in `check()` at lines [1345–1385](https://github.com/cathrynlavery/diagram-design/blob/main/scripts/verify-polar.py#L1345-L1385).

### Why does the contract forbid CSS classes and transforms?

These restrictions guarantee **deterministic rendering** across environments. According to the design spec, inline styles and transforms introduce variability that breaks the quantitative encoding contract—visual appearance must derive solely from explicit `data-polar-*` attributes and computed geometry.