diff --git a/plots/radar-basic/implementations/julia/makie.jl b/plots/radar-basic/implementations/julia/makie.jl new file mode 100644 index 0000000000..2d10fc3b6c --- /dev/null +++ b/plots/radar-basic/implementations/julia/makie.jl @@ -0,0 +1,112 @@ +# anyplot.ai +# radar-basic: Basic Radar Chart +# Library: makie 0.21.9 | Julia 1.11.9 +# Quality: 87/100 | Created: 2026-07-24 + +using CairoMakie +using Colors + +# --- Theme tokens ----------------------------------------------------------- +const THEME = get(ENV, "ANYPLOT_THEME", "light") +const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17" +const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8" +const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0" +const GRID = RGBAf(INK.r, INK.g, INK.b, 0.15) +const IMPRINT_PALETTE = [ + colorant"#009E73", # 1 — first categorical series (anyplot brand green) + colorant"#C475FD", # 2 — lavender (second series) +] + +# --- Data --------------------------------------------------------------- +# Two soccer players compared across six performance attributes (0-100 scale) +categories = ["Speed", "Passing", "Shooting", "Defense", "Stamina", "Vision"] +player_a = [88.0, 72.0, 90.0, 65.0, 78.0, 82.0] +player_b = [75.0, 85.0, 68.0, 88.0, 90.0, 70.0] + +n = length(categories) +angles = [pi / 2 - 2 * pi * (i - 1) / n for i in 1:n] +grid_levels = [20, 40, 60, 80, 100] + +# Largest gap between the two players — used to anchor a focal-point annotation +gaps = abs.(player_a .- player_b) +focus_idx = argmax(gaps) +focus_angle = angles[focus_idx] +focus_lo, focus_hi = extrema((player_a[focus_idx], player_b[focus_idx])) + +# --- Plot ----------------------------------------------------------------- +fig = Figure( + size = (1200, 1200), + fontsize = 14, + backgroundcolor = PAGE_BG, +) + +ax = Axis( + fig[1, 1]; + title = "radar-basic · julia · makie · anyplot.ai", + titlesize = 20, + titlecolor = INK, + aspect = DataAspect(), + backgroundcolor = PAGE_BG, +) +hidedecorations!(ax) +hidespines!(ax) +limits!(ax, -145, 145, -134, 148) + +# Concentric gridline polygons at 20/40/60/80/100 +for level in grid_levels + xs = [level * cos(a) for a in angles] + ys = [level * sin(a) for a in angles] + lines!(ax, [xs; xs[1]], [ys; ys[1]]; color = GRID, linewidth = 1.2) +end + +# Radial spokes, one per category +for a in angles + lines!(ax, [0.0, 100 * cos(a)], [0.0, 100 * sin(a)]; color = GRID, linewidth = 1.2) +end + +# Gridline value labels along the top spoke +for level in grid_levels + text!(ax, 6, Float64(level); text = string(level), fontsize = 13, + color = INK_SOFT, align = (:left, :center), font = :bold) +end + +# Category labels at the outer edge +label_radius = 118 +for (i, cat) in enumerate(categories) + a = angles[i] + halign = cos(a) > 0.3 ? :left : (cos(a) < -0.3 ? :right : :center) + text!(ax, label_radius * cos(a), label_radius * sin(a); text = cat, + fontsize = 15, color = INK, align = (halign, :center)) +end + +# Series polygons — filled with transparency, closed back to the first point +for (values, color, name) in ( + (player_a, IMPRINT_PALETTE[1], "Player A"), + (player_b, IMPRINT_PALETTE[2], "Player B"), +) + xs = [v * cos(a) for (v, a) in zip(values, angles)] + ys = [v * sin(a) for (v, a) in zip(values, angles)] + poly!(ax, Point2f.([xs; xs[1]], [ys; ys[1]]); + color = (color, 0.25), strokecolor = color, strokewidth = 3, label = name) + scatter!(ax, xs, ys; color = color, markersize = 14, strokewidth = 0) +end + +# Focal-point annotation: bracket the widest gap between the two players +bracket!( + ax, + focus_lo * cos(focus_angle), focus_lo * sin(focus_angle), + focus_hi * cos(focus_angle), focus_hi * sin(focus_angle); + text = "largest gap · $(round(Int, gaps[focus_idx])) pts", + fontsize = 13, + color = INK_SOFT, + textcolor = INK, + offset = 14, + orientation = :down, + style = :curly, +) + +axislegend(ax, position = :lb, labelsize = 14, labelcolor = INK, + framevisible = false, backgroundcolor = (PAGE_BG, 0.0)) + +# --- Save ------------------------------------------------------------------- +save("plot-$(THEME).png", fig; px_per_unit = 2) diff --git a/plots/radar-basic/metadata/julia/makie.yaml b/plots/radar-basic/metadata/julia/makie.yaml new file mode 100644 index 0000000000..363e2a81dd --- /dev/null +++ b/plots/radar-basic/metadata/julia/makie.yaml @@ -0,0 +1,276 @@ +library: makie +language: julia +specification_id: radar-basic +created: '2026-07-24T16:17:13Z' +updated: '2026-07-24T20:34:14Z' +generated_by: claude-sonnet +workflow_run: 30108172451 +issue: 744 +language_version: 1.11.9 +library_version: 0.21.9 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/radar-basic/julia/makie/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/radar-basic/julia/makie/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 87 +review: + strengths: + - 'Correct Imprint palette compliance in both themes: Player A is brand green #009E73, + Player B is lavender #C475FD (position 2), and page backgrounds match #FAF8F1 + (light) / #1A1A17 (dark) exactly with theme-correct text/grid chrome.' + - Clean, idiomatic Makie construction of a radar chart from primitives (poly!, scatter!, + lines!, text!) with DataAspect() for correct proportions and hidedecorations!/hidespines! + for a minimal, publication-style frame — Makie has no native radar recipe, so + this manual construction is the right approach. + - Realistic soccer-player dataset showing genuine trade-offs across all six axes + (Player A leads Speed/Shooting/Vision, Player B leads Passing/Defense/Stamina) + rather than one player dominating everywhere, satisfying DQ-01 feature coverage. + - Polygons are correctly closed back to the first point and filled with alpha=0.25 + exactly as the spec's Notes section requests, keeping the overlap region between + the two players legible. + 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 (where focus_idx=4 points, 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 — the two + overlapping filled polygons alone don't guide the viewer to the most interesting + comparison (the 23-point Defense gap between the players). + - The mandated title is short (41 chars, no descriptive prefix) and occupies only + ~28% of canvas width, well under the typical 50-70% guidance for non-mandated-length + titles — consider adding an optional descriptive prefix (e.g. 'Player Comparison + · radar-basic · julia · makie · anyplot.ai') to better balance the generous canvas + whitespace at the top. + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1), matches spec — not pure white. + Chrome: Title "radar-basic · julia · makie · anyplot.ai" in bold dark ink, clearly readable, centered top. Six category labels (Speed, Passing, Shooting, Defense, Stamina, Vision) at the outer edge of the hexagonal grid, dark ink, all legible with correct halign per position. Gridline value labels (20/40/60/80/100) along the top (Speed) spoke in bold soft-ink, small but readable. Legend ("Player A" / "Player B") bottom-left, frameless, readable swatches. + Data: Player A polygon outlined/filled in brand green #009E73 (alpha 0.25 fill, strokewidth 3, markersize 14 dots); Player B in lavender #C475FD, same styling. Both polygons correctly closed back to their first vertex. Concentric gridline hexagons at 20/40/60/80/100 and radial spokes in subtle low-alpha ink-gray, clearly visible but not competing with data. + Legibility verdict: PASS — all text readable against the light background; no light-on-light issues. + Note: the code's bracket!() "largest gap" annotation near the Defense axis does NOT appear anywhere in this render (confirmed via full-width pixel crop around the Defense spoke) — see weaknesses. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17), matches spec — not pure black. + Chrome: Title and category labels rendered in light ink (#F0EFE8), clearly legible against the dark background — no dark-on-dark failures observed. Gridline value labels in soft light-gray ink, readable. Legend readable with light text. + Data: Player A green #009E73 and Player B lavender #C475FD — identical hex values to the light render, confirming data colors are theme-invariant; only chrome (background, text, grid) flips as required. Fill alpha and stroke widths match the light render. + Legibility verdict: PASS — all text readable against the dark background; no dark-on-dark failures. + Note: same as light render, the bracket!() annotation is absent from this render too. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: 'All font sizes explicitly set (titlesize=20, fontsize=13-15); readable + in both themes at full size. Small deduction: gridline value labels and + category labels at 13-15pt native will read small once scaled to ~400px + mobile.' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlap between text, data, legend, or grid in either render. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: markersize=14 and linewidth=3 are well-suited for a sparse 6-axis + / 2-series radar; both polygons clearly visible and distinguishable. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Green vs lavender is CVD-safe per the Imprint palette; only 2 series + so color alone is sufficient per style-guide guidance. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas is exactly 2400x2400 (square, matches DataAspect radar use + case); plot fills a well-balanced portion of the canvas; legend sits near + the plot, not isolated. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Six category axis labels are descriptive; the 20-100 gridline scale + is self-explanatory for a 0-100 rating radar per the spec. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73, second #C475FD (Imprint position 2); backgrounds + #FAF8F1/#1A1A17 exactly; identical data colors across themes; chrome fully + theme-correct in both renders.' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Thoughtful custom radar construction, clear typographic hierarchy + (bold title, bold value labels), Imprint palette — clearly above library + defaults. + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: Spines and default decorations fully hidden, grid at low alpha, generous + whitespace around the hexagon, frameless legend. + - id: DE-03 + name: Data Storytelling + score: 2 + max: 6 + passed: false + comment: The code attempts a focal-point 'largest gap' bracket annotation + on the Defense axis, but it does not render in either theme (verified by + pixel inspection) — so no actual visual hierarchy is delivered; the viewer + must find the story themselves from the two overlapping polygons. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct radar/spider chart with filled polygons on concentric axes. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Filled polygons with alpha~0.25, gridlines at 20/40/60/80/100, outer-edge + axis labels, distinct colors + legend, closed polygons — all present per + spec Notes. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: All 6 categories and both series correctly mapped to their respective + spokes. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title exactly matches 'radar-basic · julia · makie · anyplot.ai'; + legend labels 'Player A'/'Player B' match the data series. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Both players trade leads across the six attributes (no single player + dominates every axis), showing genuine multivariate comparison. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Soccer player performance comparison — real-world plausible and neutral. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 0-100 rating scale with values in the 65-90 range is realistic for + professional-player attribute ratings. + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Flat imports → data → plot → save structure, no functions/classes. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Data is hardcoded (deterministic), no randomness to seed. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: CairoMakie and Colors both actively used (colorant macro, plotting + primitives). + - id: CQ-04 + name: Code Elegance + score: 1 + max: 2 + passed: true + comment: Overall clean, but the bracket!() annotation block is dead weight + in the shipped output — it computes and calls but produces no visible effect, + so it isn't earning its place in the code. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves 'plot-$(THEME).png' via save(..., px_per_unit=2) exactly per + convention. + library_mastery: + score: 6 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Good use of poly!/scatter!/lines!/text!/axislegend for a chart type + Makie has no built-in recipe for; one point off for the non-functional bracket! + call. + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: Attempted a distinctive Makie-specific feature (bracket! annotation) + that would have earned real credit, but it does not render — DataAspect()/hidespines! + usage alone is fairly generic. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - polar-projection + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - minimal-chrome + - alpha-blending