From fdf8d7b713416122be9fc34af72bc6cf4cde8911 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Aug 2026 17:03:35 +0000 Subject: [PATCH 1/3] feat(d3): implement radar-multi --- .../implementations/javascript/d3.js | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 plots/radar-multi/implementations/javascript/d3.js diff --git a/plots/radar-multi/implementations/javascript/d3.js b/plots/radar-multi/implementations/javascript/d3.js new file mode 100644 index 0000000000..956d8775bf --- /dev/null +++ b/plots/radar-multi/implementations/javascript/d3.js @@ -0,0 +1,199 @@ +// anyplot.ai +// radar-multi: Multi-Series Radar Chart +// Library: d3 7.9.0 | JavaScript 22 +// Quality: pending | Created: 2026-08-17 + +//# anyplot-orientation: square + +const t = window.ANYPLOT_TOKENS; +const { width, height } = window.ANYPLOT_SIZE; + +// --- Data: Quarterly Performance Review — Competency Scores (0-100) -------- +const categories = [ + "Communication", + "Technical Skill", + "Leadership", + "Creativity", + "Problem Solving", + "Teamwork", +]; +const series = [ + { name: "Alicia Chen", values: [85, 70, 90, 60, 75, 80] }, + { name: "Marcus Reyes", values: [60, 95, 55, 70, 88, 65] }, + { name: "Priya Nair", values: [75, 65, 70, 92, 68, 85] }, +]; + +const maxValue = 100; +const ringCount = 5; // gridlines at 20, 40, 60, 80, 100 +const angleSlice = (Math.PI * 2) / categories.length; + +// --- Layout ------------------------------------------------------------------ +const titleH = 90; +const legendH = 90; +const margin = 170; +const radius = Math.min(width, height - titleH - legendH) / 2 - margin; +const centerX = width / 2; +const centerY = titleH + (height - titleH - legendH) / 2; + +const rScale = d3.scaleLinear().domain([0, maxValue]).range([0, radius]); + +function angleFor(i) { + return angleSlice * i - Math.PI / 2; +} +function pointFor(i, value) { + const a = angleFor(i); + const r = rScale(value); + return [centerX + r * Math.cos(a), centerY + r * Math.sin(a)]; +} + +// --- SVG mount ---------------------------------------------------------------- +const svg = d3.select("#container").append("svg").attr("width", width).attr("height", height); + +// --- Grid rings ----------------------------------------------------------------- +const gridGroup = svg.append("g"); +for (let lvl = 1; lvl <= ringCount; lvl++) { + const value = (maxValue / ringCount) * lvl; + const points = categories.map((_, i) => pointFor(i, value)); + gridGroup + .append("polygon") + .attr("points", points.map((p) => p.join(",")).join(" ")) + .attr("fill", "none") + .attr("stroke", t.grid) + .attr("stroke-width", 1); +} + +// --- Axis spokes + category labels -------------------------------------------- +const axisGroup = svg.append("g"); +categories.forEach((cat, i) => { + const [x, y] = pointFor(i, maxValue); + axisGroup + .append("line") + .attr("x1", centerX) + .attr("y1", centerY) + .attr("x2", x) + .attr("y2", y) + .attr("stroke", t.grid) + .attr("stroke-width", 1); + + const a = angleFor(i); + const labelR = radius + 36; + const lx = centerX + labelR * Math.cos(a); + const ly = centerY + labelR * Math.sin(a); + let anchor = "middle"; + if (Math.cos(a) > 0.15) anchor = "start"; + else if (Math.cos(a) < -0.15) anchor = "end"; + axisGroup + .append("text") + .attr("x", lx) + .attr("y", ly) + .attr("text-anchor", anchor) + .attr("dominant-baseline", "middle") + .attr("fill", t.ink) + .style("font-size", "18px") + .text(cat); +}); + +// --- Series polygons ------------------------------------------------------------ +const radarLine = d3 + .lineRadial() + .radius((d) => rScale(d)) + .angle((d, i) => angleSlice * i) + .curve(d3.curveLinearClosed); + +const seriesGroup = svg.append("g").attr("transform", `translate(${centerX},${centerY})`); + +series.forEach((s, si) => { + const color = t.palette[si]; + seriesGroup + .append("path") + .attr("d", radarLine(s.values)) + .attr("fill", color) + .attr("fill-opacity", 0.22) + .attr("stroke", color) + .attr("stroke-width", 3.5) + .attr("stroke-linejoin", "round"); + + s.values.forEach((value, i) => { + const a = angleSlice * i - Math.PI / 2; + const r = rScale(value); + seriesGroup + .append("circle") + .attr("cx", r * Math.cos(a)) + .attr("cy", r * Math.sin(a)) + .attr("r", 6) + .attr("fill", color) + .attr("stroke", t.pageBg) + .attr("stroke-width", 1.5); + }); +}); + +// --- Ring value labels (drawn on top of series, haloed for legibility) -------- +const ringLabelGroup = svg.append("g"); +for (let lvl = 1; lvl <= ringCount; lvl++) { + const value = (maxValue / ringCount) * lvl; + const label = ringLabelGroup + .append("text") + .attr("x", centerX + 8) + .attr("y", centerY - rScale(value) - 4) + .attr("fill", t.inkSoft) + .style("font-size", "13px") + .text(value.toFixed(0)); + const bbox = label.node().getBBox(); + ringLabelGroup + .insert("rect", () => label.node()) + .attr("x", bbox.x - 3) + .attr("y", bbox.y - 2) + .attr("width", bbox.width + 6) + .attr("height", bbox.height + 4) + .attr("fill", t.pageBg) + .attr("opacity", 0.85); +} + +// --- Title ------------------------------------------------------------------ +svg + .append("text") + .attr("x", width / 2) + .attr("y", 54) + .attr("text-anchor", "middle") + .attr("fill", t.ink) + .style("font-size", "26px") + .style("font-weight", "600") + .text("radar-multi · javascript · d3 · anyplot.ai"); + +// --- Legend (centered, measured after render) -------------------------------- +const legendY = height - 44; +const swatchSize = 20; +const itemGap = 44; + +const legendItems = svg + .selectAll("g.legend-item") + .data(series) + .join("g") + .attr("class", "legend-item"); + +legendItems + .append("rect") + .attr("width", swatchSize) + .attr("height", swatchSize) + .attr("rx", 4) + .attr("fill", (d, i) => t.palette[i]); + +legendItems + .append("text") + .attr("x", swatchSize + 10) + .attr("y", swatchSize / 2) + .attr("dominant-baseline", "middle") + .attr("fill", t.ink) + .style("font-size", "18px") + .text((d) => d.name); + +const itemWidths = []; +legendItems.each(function () { + itemWidths.push(this.getBBox().width); +}); +const totalWidth = itemWidths.reduce((a, b) => a + b, 0) + itemGap * (itemWidths.length - 1); +let cursorX = centerX - totalWidth / 2; +legendItems.each(function (d, i) { + d3.select(this).attr("transform", `translate(${cursorX},${legendY})`); + cursorX += itemWidths[i] + itemGap; +}); From e21eb2ba484ba8e2da42b84e1feb3cd67808eddf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Aug 2026 17:03:44 +0000 Subject: [PATCH 2/3] chore(d3): add metadata for radar-multi --- plots/radar-multi/metadata/javascript/d3.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/radar-multi/metadata/javascript/d3.yaml diff --git a/plots/radar-multi/metadata/javascript/d3.yaml b/plots/radar-multi/metadata/javascript/d3.yaml new file mode 100644 index 0000000000..56d62c9461 --- /dev/null +++ b/plots/radar-multi/metadata/javascript/d3.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for d3 implementation of radar-multi +# Auto-generated by impl-generate.yml + +library: d3 +language: javascript +specification_id: radar-multi +created: '2026-08-17T17:03:44Z' +updated: '2026-08-17T17:03:44Z' +generated_by: claude-sonnet +workflow_run: 32047983049 +issue: 2026 +language_version: 22.23.2 +library_version: 7.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-dark.html +quality_score: null +review: + strengths: [] + weaknesses: [] From b4c8551f1a4c0b4672836d77028fae35ccfb481e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Aug 2026 17:09:31 +0000 Subject: [PATCH 3/3] chore(d3): update quality score 85 and review feedback for radar-multi --- .../implementations/javascript/d3.js | 4 +- plots/radar-multi/metadata/javascript/d3.yaml | 252 +++++++++++++++++- 2 files changed, 247 insertions(+), 9 deletions(-) diff --git a/plots/radar-multi/implementations/javascript/d3.js b/plots/radar-multi/implementations/javascript/d3.js index 956d8775bf..d901d55302 100644 --- a/plots/radar-multi/implementations/javascript/d3.js +++ b/plots/radar-multi/implementations/javascript/d3.js @@ -1,7 +1,7 @@ // anyplot.ai // radar-multi: Multi-Series Radar Chart -// Library: d3 7.9.0 | JavaScript 22 -// Quality: pending | Created: 2026-08-17 +// Library: d3 7.9.0 | JavaScript 22.23.2 +// Quality: 85/100 | Created: 2026-08-17 //# anyplot-orientation: square diff --git a/plots/radar-multi/metadata/javascript/d3.yaml b/plots/radar-multi/metadata/javascript/d3.yaml index 56d62c9461..00012a433b 100644 --- a/plots/radar-multi/metadata/javascript/d3.yaml +++ b/plots/radar-multi/metadata/javascript/d3.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for d3 implementation of radar-multi -# Auto-generated by impl-generate.yml - library: d3 language: javascript specification_id: radar-multi created: '2026-08-17T17:03:44Z' -updated: '2026-08-17T17:03:44Z' +updated: '2026-08-17T17:09:31Z' generated_by: claude-sonnet workflow_run: 32047983049 issue: 2026 @@ -15,7 +12,248 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/radar-mul preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/radar-multi/javascript/d3/plot-dark.html -quality_score: null +quality_score: 85 review: - strengths: [] - weaknesses: [] + strengths: + - 'Correct Imprint palette in canonical order (green #009E73, lavender #C475FD, + blue #4467A3), identical across light and dark renders, with theme-correct #FAF8F1 + / #1A1A17 backgrounds and chrome' + - Ring value labels (20/40/60/80/100) use haloed background boxes so they stay legible + even where overlapping translucent polygons cross them — a thoughtful legibility + solution + - Realistic, neutral quarterly-review competency dataset with genuinely differentiated + shapes per person (each has distinct high/low competencies), satisfying the spec's + comparison-clarity intent + - 'All spec notes honored: filled polygons at alpha 0.22, closed via curveLinearClosed, + both fill and stroke per polygon, gridlines at the specified 20-unit interval, + axis labels at the outer edge, legend identifying each series' + weaknesses: + - 'Library Mastery: only the legend uses D3''s idiomatic `.data().join()` pattern + — grid rings, axis spokes/labels, series polygons, and per-point circles are all + built with manual `forEach` + `.append()` loops instead of data joins. Convert + these to `selection.data(...).join(...)` to better demonstrate D3''s data-binding + strengths.' + - 'Data Storytelling: the three overlapping polygons are easy to compare but there''s + no visual emphasis or focal point (no highlighting of a standout competency or + an overall leader). Consider a subtle callout, size/opacity emphasis, or ranked + ordering to guide the viewer toward an insight.' + - 'Canvas utilization: the square canvas has substantial unused whitespace in all + four corners due to margin=170 plus the titleH(90)/legendH(90) reserves. Reduce + the margin or grow the radius so the radar shape fills more of the 2400x2400 canvas + per the Layout & Canvas guideline.' + - 'Code structure: angleFor/pointFor are small helper functions; a stricter KISS + read would inline the trig or minimize the helper surface.' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white, matches #FAF8F1 — not pure white. + Chrome: Bold dark title "radar-multi · javascript · d3 · anyplot.ai" centered at top; six category axis labels (Communication, Technical Skill, Leadership, Creativity, Problem Solving, Teamwork) in dark ink around the outer edge; hexagonal gridlines at 20/40/60/80/100 in a subtle light-gray stroke; ring value labels (20-100) sit on small haloed boxes so they stay readable where series overlap; centered legend below the chart with three color swatches and names. All text is clearly readable against the light background. + Data: Three overlapping filled polygons (alpha ~0.22) with 3.5px strokes and small circular markers (white/page-bg stroke) at each vertex — Alicia Chen in brand green #009E73, Marcus Reyes in lavender #C475FD, Priya Nair in blue #4467A3. Each polygon has a visibly distinct shape (different strengths/weaknesses per person), showing genuine data variation. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black, matches #1A1A17 — not pure black. + Chrome: Same title, axis labels, gridlines, ring labels, and legend, now rendered in light ink (#F0EFE8-family) against the dark surface. Ring value labels sit on small dark haloed boxes that still contrast against the translucent color fills behind them. All text is clearly legible — no dark-on-dark instances found. + Data: Same three polygons in the identical green/lavender/blue hues as the light render — confirms data colors are theme-independent; only chrome (background, text, grid) flipped. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All font sizes explicitly set via style('font-size'); readable in + both themes and proportioned well + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text/data collisions; haloed ring labels prevent overlap with + polygon fills + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: 6px markers and 3.5px strokes are well-suited to the sparse 6-point + series + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Distinct hues with alpha 0.22 fill and page-bg-stroked markers give + good separation + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: Radar shape reads clearly but margin=170 plus title/legend reserves + leave notable corner whitespace on the square canvas + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: All six competency axes clearly labeled; ring labels give the 0-100 + scale + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: Canonical Imprint order (green/lavender/blue), identical across themes, + theme-correct backgrounds + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: false + comment: Thoughtful touches (haloed ring labels, rounded legend swatches) + push above a bare default, but not fully publication-ready + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: Subtle grid, generous polygon alpha, defined markers — refined but + corner whitespace keeps it short of perfect + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: false + comment: Color contrast lets viewers compare series but there is no focal + point or emphasis calling out an insight + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct multi-series radar/spider chart with overlaid polygons + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Filled polygons with alpha, distinct colors, legend, gridlines at + 20-unit steps, outer-edge axis labels, closed polygons with fill+stroke + — all spec notes satisfied + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Categories correctly mapped to the six axes; all series values plotted + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches mandated format; legend labels match series names exactly + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Each of the three series has a genuinely distinct shape/profile, + not scaled copies + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Neutral quarterly performance review scenario with plausible competency + scores + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 0-100 scale with sensible, realistic score values throughout + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: true + comment: Mostly linear; two small geometry helpers (angleFor/pointFor) are + justified but keep this from a perfect score + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fully deterministic hard-coded data + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: Only the d3 global is used, no unused imports + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, no fake UI or over-engineering + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: 'Correctly mounts into #container per the harness contract, no manual + saving' + library_mastery: + score: 6 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 3 + max: 5 + passed: false + comment: Uses d3.lineRadial/scaleLinear correctly but only the legend uses + the .data().join() pattern; grid, spokes, labels, polygons, and points are + all manual forEach+append loops + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: false + comment: d3.lineRadial with curveLinearClosed is a genuine D3-specific radial + shape generator not trivially replicated elsewhere + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - polar-projection + - custom-legend + - manual-ticks + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending + - edge-highlighting