Polar Chart Quantitative Encoding and Gate Testing: A Complete Technical Guide
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. 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-cxanddata-polar-cy— chart center coordinatesdata-polar-radius— maximum radial extent Rdata-polar-minanddata-polar-max— value domain boundariesdata-polar-start-angle— angular zero point in degreesdata-polar-clockwise— direction of angular progression (true|false)data-polar-inner-radius— must be0(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 namedata-polar-index— contiguous integer0..N-1data-polar-value— finite numeric within[min, max]data-polar-focal— at most one category may marktrue
The category contains three child elements:
- Ray
<line>— stem from center to value-proportional radius, stroke-width0.8px - Marker
<circle>— radius4px(5pxfor focal), stroke-width1.2px - Value label
<text>— text content must exactly matchdata-polar-value
Grid and Decorative Elements
- Five rings
<circle data-polar-ring>— concentric circles at radiiR·k/5fork=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, a dependency-free Python script implementing a two-phase pipeline: parsing then verification.
PolarParser Implementation
Lines 887–894 define the PolarParser class subclassing html.parser.HTMLParser:
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:
- Root detection — locates
<svg data-polar-chart>and instantiates aChartobject (1029–1050) - Category collection — pushes each
<g data-polar-category>onto a stack (1080–1089) - Element recording — tracks rays, markers, rings, backgrounds, rules, and rejects unsupported elements (1100–1130)
Validation Orchestration
The public entry point check(path: Path) -> list[str] at lines 606–607 coordinates verification:
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 | Exact match for enumerated values |
| Ring geometry | 558–575 | 0.75px or R/20, whichever is smaller |
| Category indexing | 891–901 | Contiguity and uniqueness |
| Spoke geometry | 1289–1306 | Coordinate tolerance |
| Ray length | 1318–1339 | Endpoint position |
| Marker/label | 1345–1385 | Radius, stroke, text match |
The geometry validation recomputes expected positions using polar-to-cartesian conversion:
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) 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:
<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:
python3 scripts/verify-polar.py skills/diagram-design/assets/example-polar.html
Validate all default variants (light, dark, full):
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).
Automated Testing
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.
Source File Reference
| File | Purpose |
|---|---|
scripts/verify-polar.py |
Core gate: parser and validation engine |
scripts/test-verify-polar.py |
Automated adversarial test suite |
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 |
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-maxto determine ray length scripts/verify-polar.pyimplements 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, 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. 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.
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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →