Skip to content

feat(makie): implement radar-basic#9749

Merged
MarkusNeusinger merged 6 commits into
mainfrom
implementation/radar-basic/makie
Jul 24, 2026
Merged

feat(makie): implement radar-basic#9749
MarkusNeusinger merged 6 commits into
mainfrom
implementation/radar-basic/makie

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Implementation: radar-basic - julia/makie

Implements the julia/makie version of radar-basic.

File: plots/radar-basic/implementations/julia/makie.jl

Parent Issue: #744


🤖 impl-generate workflow

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

AI Review - Attempt 1/3

Image Description

Light render (plot-light.png): Warm off-white background (#FAF8F1, not pure white). Title "radar-basic · julia · makie · anyplot.ai" is bold dark text, fully visible at the top with no clipping. Six category labels (Speed, Passing, Shooting, Defense, Stamina, Vision) sit at the outer edge of the hexagonal grid in dark ink, clearly readable. Small gridline value labels (20, 40, 60, 80, 100) run up the top spoke in soft gray. Concentric hexagonal gridlines and radial spokes are subtle, low-alpha lines. Two overlapping filled polygons: Player A in brand green (#009E73) with markers, Player B in lavender (#C475FD) similarly marked; the overlap blends into a muted blue-gray while both series stay distinguishable. Frameless legend at bottom-left labels both series in dark text. All text is readable against the light background.

Dark render (plot-dark.png): Warm near-black background (#1A1A17, not pure black). Title, category labels, and legend text have flipped to light/off-white ink and remain fully legible. Gridline value labels are a lighter soft gray, still readable. Data colors are confirmed identical to the light render — same #009E73 green and #C475FD lavender — only chrome (background, text, grid) flipped. No dark-on-dark failures: all text and data elements read clearly against the near-black background.

Both paragraphs confirmed — all text legible in both themes, no dark-on-dark or light-on-light issues found.

Score: 88/100

Category Score Max
Visual Quality 29 30
Design Excellence 14 20
Spec Compliance 15 15
Data Quality 15 15
Code Quality 10 10
Library Mastery 5 10
Total 88 100

Visual Quality (29/30)

  • VQ-01: Text Legibility (7/8) - All sizes explicitly set; gridline value labels (11pt) are the smallest element and risk becoming illegible at ~400px mobile scale
  • VQ-02: No Overlap (6/6) - No collisions between text, data, or legend
  • VQ-03: Element Visibility (6/6) - Marker/stroke sizing well-adapted to the sparse 6-point, 2-series dataset
  • VQ-04: Color Accessibility (2/2) - Green/lavender is CVD-safe, overlap remains distinguishable via alpha blending
  • VQ-05: Layout & Canvas (4/4) - Chart + labels span ~70-75% of canvas; minor vertical whitespace imbalance
  • VQ-06: Axis Labels & Title (2/2) - Category labels are descriptive
  • VQ-07: Palette Compliance (2/2) - First series #009E73, second #C475FD; theme-correct backgrounds and chrome in both renders

Design Excellence (14/20)

  • DE-01: Aesthetic Sophistication (5/8) - Fully custom-drawn radar geometry with correct brand palette, above a "configured default" but not yet publication-tier
  • DE-02: Visual Refinement (5/6) - Default decorations/spines fully removed, subtle custom grid, frameless legend; slight vertical imbalance
  • DE-03: Data Storytelling (4/6) - Contrasting profiles create some hierarchy, but no explicit focal point/annotation on the key trade-off

Spec Compliance (15/15)

  • SC-01: Plot Type (5/5) - Correct radar/spider chart
  • SC-02: Required Features (4/4) - Alpha-transparent fills, gridlines at 20/40/60/80/100, outer labels, legend, closed polygons all present
  • SC-03: Data Mapping (3/3) - Six categories correctly mapped to six axes
  • SC-04: Title & Legend (3/3) - Title format correct, legend labels match data

Data Quality (15/15)

  • DQ-01: Feature Coverage (6/6) - Genuinely contrasting player profiles across all 6 axes
  • DQ-02: Realistic Context (5/5) - Soccer player attribute comparison, neutral real-world scenario
  • DQ-03: Appropriate Scale (4/4) - Plausible 0-100 skill ratings

Code Quality (10/10)

  • CQ-01: KISS Structure (3/3)
  • CQ-02: Reproducibility (2/2) - Data is hardcoded/deterministic
  • CQ-03: Clean Imports (2/2)
  • CQ-04: Code Elegance (2/2)
  • CQ-05: Output & API (1/1)

Library Mastery (5/10)

  • LM-01: Idiomatic Usage (4/5) - Correct Axis/DataAspect/poly!/scatter!/text!/axislegend usage, the expected approach given Makie has no native radar recipe
  • LM-02: Distinctive Features (1/5) - Generic manual polar-to-cartesian construction; no Makie-distinctive feature leveraged

Score Caps Applied

  • None

Strengths

  • Correct Imprint palette: Player A #009E73 (brand green, first series), Player B #C475FD (palette position 2); data colors identical between light/dark, only chrome flips
  • Fully custom-built radar from Makie primitives — concentric gridlines, radial spokes, hidden decorations/spines, outer-edge category labels — since Makie has no native radar recipe
  • Realistic, neutral data: two soccer players with genuinely contrasting skill profiles (Player A leads Speed/Shooting, Player B leads Passing/Defense/Stamina/Vision)
  • Spec requirements met precisely: alpha=0.25 fills, closed polygons, gridlines at the requested 20/40/60/80/100 intervals, distinct colors + legend
  • All font sizes explicitly set with a well-proportioned square canvas layout

Weaknesses

  • Gridline value labels (20/40/60/80/100) are the smallest text on the chart (fontsize=11) — will be first to become illegible at ~400px mobile thumbnail; bump to ~13-14pt or bold
  • Design Excellence is solid but not publication-tier — add a data-story cue (e.g. subtle annotation or emphasis on the axis with the largest gap between players) for a clearer focal point
  • Library Mastery is generic — the polar math could be replicated in matplotlib with little change; lean into a Makie-distinctive touch (e.g. band! for a shaded ring, or Legend layout composition)
  • Noticeably larger empty band between the hexagon's bottom vertex and the legend/canvas edge than the margin at the top — tighten vertical whitespace for better canvas balance

Issues Found

  1. LM-02 LOW: Generic manual polar-to-cartesian construction, no Makie-distinctive feature
    • Fix: Leverage a Makie-specific capability (e.g. band! for a shaded confidence ring around a series, or composed Legend layout) so the implementation isn't trivially portable to another library
  2. DE-03 MEDIUM: No explicit focal point despite contrasting player profiles
    • Fix: Add a subtle annotation or visual emphasis (e.g. highlight the axis with the largest gap) to give the chart a clear story instead of two evenly-weighted polygons

AI Feedback for Next Attempt

Strong technical foundation — correct palette, theme handling, spec compliance, and clean code. To clear the Review 1 bar (≥90), focus on Design Excellence and Library Mastery: increase the smallest text (gridline value labels) for mobile legibility, add a Makie-distinctive touch instead of purely generic polar math, and introduce a clearer visual focal point (e.g. annotate the biggest gap between the two players) so the chart tells a story rather than just displaying two overlapping polygons.

Verdict: REJECTED

@github-actions github-actions Bot added quality:88 Quality score: 88/100 ai-rejected Quality not OK, triggers update labels Jul 24, 2026
@github-actions github-actions Bot added ai-attempt-1 First repair attempt and removed ai-rejected Quality not OK, triggers update labels Jul 24, 2026
Attempt 1/4 - fixes based on AI review
@github-actions

Copy link
Copy Markdown
Contributor Author

🔧 Repair Attempt 1/4

Applied fixes based on AI review feedback.

Status: Repair completed, re-triggering review...


🤖 impl-repair

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

AI Review - Attempt 2/3

Image Description

Light render (plot-light.png): Warm off-white background (#FAF8F1). Title "radar-basic · julia · makie · anyplot.ai" in bold dark ink, centered top, clearly readable. Six category labels (Speed, Passing, Shooting, Defense, Stamina, Vision) at the outer edge of the hexagonal grid, dark ink, all legible. Gridline value labels (20/40/60/80/100) along the top spoke in bold soft-ink. Two filled polygons: Player A in brand green #009E73, Player B in lavender #C475FD, both alpha≈0.25 fill with markersize-14 dots, correctly closed back to their first vertex. Legend ("Player A"/"Player B") bottom-left, frameless. All text is readable against the light background — no light-on-light issues. However, the code's bracket!() "largest gap" annotation near the Defense axis does not appear anywhere in this render — confirmed via a full-canvas-width pixel crop around the Defense spoke.

Dark render (plot-dark.png): Warm near-black background (#1A1A17). Title and category labels rendered in light ink (#F0EFE8), clearly legible — no dark-on-dark failures. Gridline labels in soft light-gray. Data colors are identical to the light render (#009E73 / #C475FD) — only chrome (background, text, grid, legend) flips, exactly as required. All text is readable against the dark background. Same as light: the bracket!() annotation is absent here too.

Both paragraphs above are based on directly viewing both PNGs plus targeted pixel-level crops around the Defense axis to verify the annotation claim.

Score: 87/100

Category Score Max
Visual Quality 29 30
Design Excellence 13 20
Spec Compliance 15 15
Data Quality 15 15
Code Quality 9 10
Library Mastery 6 10
Total 87 100

Visual Quality (29/30)

  • VQ-01: Text Legibility (7/8) — sizes explicitly set, readable in both themes; small deduction for gridline/category label sizes reading small at mobile scale
  • VQ-02: No Overlap (6/6)
  • VQ-03: Element Visibility (6/6) — markersize=14, linewidth=3 well-suited for sparse 6-axis/2-series data
  • VQ-04: Color Accessibility (2/2)
  • VQ-05: Layout & Canvas (4/4) — canvas confirmed 2400×2400, well-balanced
  • VQ-06: Axis Labels & Title (2/2)
  • VQ-07: Palette Compliance (2/2) — first series #009E73, second #C475FD, backgrounds exact, identical data colors across themes

Design Excellence (13/20)

  • DE-01: Aesthetic Sophistication (6/8) - Thoughtful custom radar construction, clear typographic hierarchy, Imprint palette
  • DE-02: Visual Refinement (5/6) - Spines/decorations hidden, subtle grid, generous whitespace, frameless legend
  • DE-03: Data Storytelling (2/6) - Focal-point "largest gap" bracket is attempted in code but does not render in either theme; no actual visual hierarchy is delivered

Spec Compliance (15/15)

  • SC-01: Plot Type (5/5)
  • SC-02: Required Features (4/4) — filled polygons w/ alpha, gridlines at 20/40/60/80/100, outer-edge labels, legend, closed polygons
  • SC-03: Data Mapping (3/3)
  • SC-04: Title & Legend (3/3)

Data Quality (15/15)

  • DQ-01: Feature Coverage (6/6) — both players trade leads across the six attributes, no single player dominates
  • DQ-02: Realistic Context (5/5) — soccer player performance comparison
  • DQ-03: Appropriate Scale (4/4) — 0-100 rating scale, realistic 65-90 value range

Code Quality (9/10)

  • CQ-01: KISS Structure (3/3)
  • CQ-02: Reproducibility (2/2) — deterministic hardcoded data
  • CQ-03: Clean Imports (2/2)
  • CQ-04: Code Elegance (1/2) — the bracket!() block is dead weight in the shipped output; computes and calls but produces no visible effect
  • CQ-05: Output & API (1/1)

Library Mastery (6/10)

  • LM-01: Idiomatic Usage (4/5) — good use of poly!/scatter!/lines!/text!/axislegend for a chart type with no built-in Makie recipe; one point off for the non-functional bracket! call
  • LM-02: Distinctive Features (2/5) - Attempted bracket! annotation would have earned real credit, but it doesn't render; DataAspect()/hidespines! alone is fairly generic

Score Caps Applied

  • None

Strengths

  • Correct Imprint palette compliance in both themes: Player A #009E73, Player B #C475FD (position 2), backgrounds match #FAF8F1/#1A1A17 exactly with theme-correct chrome
  • Clean, idiomatic Makie construction of a radar chart from primitives (poly!, scatter!, lines!, text!) with DataAspect() and hidedecorations!/hidespines! — the right approach since Makie has no native radar recipe
  • Realistic soccer-player dataset showing genuine trade-offs across all six axes rather than one player dominating everywhere (DQ-01)
  • Polygons correctly closed back to the first point and filled with alpha≈0.25 exactly as the spec's Notes section requests

Weaknesses

  • The bracket!() "largest gap" focal-point annotation on the Defense axis does not render in either plot-light.png or plot-dark.png. Verified by cropping the full canvas width around the Defense axis (focus_idx=4, gap=23) at high zoom: no curly bracket, no "largest gap · 23 pts" text, nothing beyond the two data-point dots and connecting lines. The code computes focus_idx/focus_lo/focus_hi correctly and calls bracket!(ax, ...) with a text label, but the call produces no visible output — likely a CairoMakie bracket! keyword/orientation mismatch for this Makie version. Debug the bracket! call directly (render a standalone test figure to confirm it draws), or replace it with a manually drawn lines!()+text!() annotation next to the Defense spoke, which is guaranteed to render.
  • Because the annotation silently fails, the implementation currently delivers no real visual hierarchy or focal point (DE-03) despite the code's intent.
  • Minor: the mandated title is short (41 chars, no descriptive prefix) and occupies only ~28% of canvas width; consider adding an optional descriptive prefix (e.g. "Player Comparison · ") to better balance the generous top whitespace — not a hard requirement, just an opportunity.

Issues Found

  1. DE-03 LOW: bracket!() annotation intended as the data-storytelling focal point does not render in either theme
    • Fix: Debug the CairoMakie bracket! call (verify keyword names / orientation for the installed Makie version in a standalone test), or replace with a manual lines!() bracket shape + text!() label next to the Defense spoke
  2. CQ-04 / LM-02 LOW: Non-functional annotation code ships as dead weight and forfeits distinctive-feature credit
    • Fix: Once the annotation renders correctly, this recovers both the Code Elegance and Distinctive Features points

AI Feedback for Next Attempt

The implementation is technically solid and spec-compliant, but the intended "largest gap" bracket annotation on the Defense axis does not render in either plot-light.png or plot-dark.png — verify pixel-by-pixel that bracket!() actually draws before relying on it. Either fix the bracket! call (check keyword/orientation compatibility for this Makie version with a standalone smoke test) or replace it with a manually drawn lines!()+text!() annotation, which is guaranteed to render regardless of API version drift. Getting this annotation to actually appear would meaningfully lift Data Storytelling (DE-03) and Distinctive Features (LM-02).

Verdict: APPROVED

@github-actions github-actions Bot added quality:87 Quality score 87/100 ai-approved Quality OK, ready for merge and removed quality:88 Quality score: 88/100 labels Jul 24, 2026
@MarkusNeusinger
MarkusNeusinger merged commit 2103177 into main Jul 24, 2026
3 checks passed
@MarkusNeusinger
MarkusNeusinger deleted the implementation/radar-basic/makie branch July 24, 2026 20:34
MarkusNeusinger added a commit that referenced this pull request Jul 24, 2026
…illed (#9772)

## What happened

On **2026-07-24, ~16:05–16:26 UTC** GitHub's API browned out — HTTP 502
on the
workflow-dispatch endpoint, HTTP 504 on GraphQL, HTTP 429 on codeload.
Four `radar-basic`
PRs fell out of the pipeline and stayed out, because the calls that hand
work to the next
workflow had no retry:

| PR | Library | State it was left in | Call that died |
|---|---|---|---|
| #9742 | seaborn | `ai-approved` q90 | `impl-merge` → `gh pr view` →
**504** |
| #9744 | pygal | `ai-rejected` q87 | repair dispatch → **504**;
`ai-attempt-1` also lost |
| #9749 | makie | `ai-rejected` q88 | repair dispatch → **502** |
| #9751 | highcharts | `ai-rejected` q88 | repair dispatch → **502** |

`impl-review`'s verdict step is the pipeline's **only hand-off point** —
every downstream
workflow starts from a call made there. So an unretried blip doesn't
merely fail a step; it
leaves a PR carrying a verdict label with nothing listening.

All four have since been recovered manually and merged; this PR stops it
recurring.

## Changes

**`impl-review.yml` — the verdict step**
- every API call retries 3× with linear backoff (same shape as the
existing `Extract PR info`
  retry, added 2026-05-06 for this same class of failure)
- the repair is **dispatched before** the attempt label is added, and
the label can no longer
gate it. Losing the label costs one over-strict review; losing the
dispatch costs the whole PR
- if the dispatch still exhausts its retries, the step drops
`ai-rejected` on the way out:
`ai-rejected` + `ai-attempt-N` matches **no** watchdog case (Case 2
excludes any verdict
label, Case 4 excludes PRs that already have an attempt label), while
the label-less state is
exactly Case 2 — which re-dispatches the repair at the attempt number
the remaining label
  still encodes
- the label read tracks success explicitly instead of inferring it from
empty output, and a
successful read with no verdict label now fails loudly instead of ending
the step green

**Two bugs found while verifying the above**
- **`ai-attempt-4` never existed as a label.** The attempt label encodes
the cascading
threshold (90 → 80 → 70 → 60 → 50), but the repo only has
`ai-attempt-1..3`, so on the 4th
repair the add failed every time and `|| true` swallowed it — #7268 and
#7039 both reached
"Repair Attempt 4/4" carrying only `ai-attempt-1..3`. Every 4th review
therefore re-applied
the ≥ 90 attempt-0 bar, and the attempts-exhausted branch (close PR,
remove stale
implementation) could never be reached. Labels are now created on
demand.
*This also means my first draft of this PR was a hard blocker:* with the
label add fatal, the
  4th repair would never have been dispatched at all.
- **That newly-reachable branch rendered a literal `$SCORE/100`** —
quoted heredoc. Now
expands, with the markdown backticks escaped so they can't become
command substitution.

**`impl-merge.yml`**
- the 5× merge retry re-reads PR state first. A merge that succeeded
server-side but lost its
HTTP response used to fail four more times with "already merged" and
exit 1 — and because
every post-merge step is gated on `should_run == 'true'` (implicit
`success()`), that skipped
GCS promotion, the `impl:{lib}:done` label, closing the issue and the
Postgres sync. A merged
PR with none of its bookkeeping: exactly the silent partial completion
CLAUDE.md warns about

**`auto-update-pr-branches.yml` — Dependabot PRs could never merge while
the pipeline ran**
- the workflow called `update-branch` on Dependabot PRs. That push is
authored by
`github-actions[bot]`, so GitHub gates the resulting runs behind manual
approval
(`action_required`) — and because `main` advances every few minutes
during impl merges, each
branch was re-updated long before anyone could approve. **PR #9674
accumulated 174 runs in
22 h: 162 `action_required` against only 4 green** (the original
`dependabot[bot]` push)
- Dependabot branches are now skipped. They merge fine while behind —
the `main` ruleset is
**not** strict (`strict_required_status_checks_policy: false`); the
file's header claimed the
  opposite and is corrected
- `/dependabot`'s playbook told operators to run that same harmful
`update-branch` by hand; it
  now says the opposite and documents the `@dependabot recreate` remedy

## Verification

`.github/workflows/` has no verification loop (CLAUDE.md), so this was
reviewed by a 4-lens
adversarial pass with an independent refutation round (regression /
shell-under-`bash -e` / retry idempotency / the Dependabot
premise), and every claim was re-checked against the live repo before
acting. That pass is what
caught the `ai-attempt-4` blocker and the false Dependabot-rebase
premise in my first draft.

- YAML parses; `bash -n` clean on the extracted steps
- `gh_retry` semantics exercised under `bash -e`: succeeds first try /
recovers after 2
  failures / multi-arg passthrough / hard failure aborts the step
- the corrected control flow tested for all three cases — normal,
**attempt 4 with a
permanently failing label add (dispatch must still fire)**, and dispatch
exhaustion
  (`ai-rejected` dropped, exit 1)
- confirmed the CI runner's `gh` adds labels fine; only the
locally-installed gh 2.45.0 hits
the `projectCards` GraphQL deprecation, so hard-failing on `--add-label`
is safe in CI
- ruleset strictness, the `ai-attempt-*` label set, and the poisoned
Dependabot head commits
  were all read from the live repo, not assumed

## Follow-up (not in this PR)

- #9674, #9648 and #8820 already have a `github-actions[bot]` merge
commit as their head. Once
this merges their head stops moving, so approving each gated run
**once** is enough — verified
that the required contexts do report as SUCCESS on a bot-authored head
after approval (#9674,
commit `59545076`). Doing it before this merges would just get re-gated
by the next push to
`main`. (`@dependabot recreate` is an equivalent alternative; it also
restores a clean
  `dependabot[bot]` head but re-resolves the versions.)
- Whether `auto-update-pr-branches.yml` should exist at all now that the
ruleset is non-strict
is worth deciding separately — it affects every auto-merge PR, so it
isn't folded in here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-approved Quality OK, ready for merge ai-attempt-1 First repair attempt quality:87 Quality score 87/100

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant