From 14edcb293ff4d977e0007a455fcf4b389196af29 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 06:06:42 +0000 Subject: [PATCH 1/5] feat(chartjs): implement area-elevation-profile --- .../implementations/javascript/chartjs.js | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 plots/area-elevation-profile/implementations/javascript/chartjs.js diff --git a/plots/area-elevation-profile/implementations/javascript/chartjs.js b/plots/area-elevation-profile/implementations/javascript/chartjs.js new file mode 100644 index 0000000000..c7ef2a479d --- /dev/null +++ b/plots/area-elevation-profile/implementations/javascript/chartjs.js @@ -0,0 +1,228 @@ +// anyplot.ai +// area-elevation-profile: Terrain Elevation Profile Along Transect +// Library: chartjs 4.4.7 | JavaScript 22 +// Quality: pending | Created: 2026-06-10 + +//# anyplot-orientation: landscape +const t = window.ANYPLOT_TOKENS; + +// Alpine trail — control points (distance km, elevation m) +const ctrlPoints = [ + { d: 0, e: 940 }, + { d: 6, e: 1120 }, + { d: 14, e: 1620 }, + { d: 20, e: 2100 }, + { d: 27, e: 1820 }, + { d: 33, e: 2450 }, + { d: 40, e: 2200 }, + { d: 47, e: 1700 }, + { d: 56, e: 2300 }, + { d: 63, e: 1880 }, + { d: 70, e: 1280 }, + { d: 76, e: 1050 }, + { d: 80, e: 880 }, +]; + +// Landmarks to annotate (trailhead, passes, summit, hut, end) +const landmarks = [ + { d: 0, e: 940, name: "Trailhead", elev: "940 m" }, + { d: 20, e: 2100, name: "First Pass", elev: "2100 m" }, + { d: 33, e: 2450, name: "Summit Peak", elev: "2450 m" }, + { d: 47, e: 1700, name: "Mountain Hut", elev: "1700 m" }, + { d: 56, e: 2300, name: "Second Pass", elev: "2300 m" }, + { d: 80, e: 880, name: "Trail End", elev: "880 m" }, +]; + +// Cubic Hermite spline interpolation for smooth elevation profile +function hermite(x, pts) { + const n = pts.length; + if (x <= pts[0].d) return pts[0].e; + if (x >= pts[n - 1].d) return pts[n - 1].e; + let i = 1; + while (i < n - 1 && pts[i].d < x) i++; + i--; + const p0 = pts[Math.max(0, i - 1)]; + const p1 = pts[i]; + const p2 = pts[i + 1]; + const p3 = pts[Math.min(n - 1, i + 2)]; + const tl = (x - p1.d) / (p2.d - p1.d); + const dt = p2.d - p1.d; + const m1 = (p2.e - p0.e) / (p2.d - p0.d); + const m2 = (p3.e - p1.e) / (p3.d - p1.d); + const h00 = 2*tl*tl*tl - 3*tl*tl + 1; + const h10 = tl*tl*tl - 2*tl*tl + tl; + const h01 = -2*tl*tl*tl + 3*tl*tl; + const h11 = tl*tl*tl - tl*tl; + return h00*p1.e + h10*dt*m1 + h01*p2.e + h11*dt*m2; +} + +// Sample 200 points along the trail +const N = 200; +const distances = []; +const elevations = []; +for (let i = 0; i < N; i++) { + const d = (i / (N - 1)) * 80; + distances.push(parseFloat(d.toFixed(3))); + elevations.push(Math.round(hermite(d, ctrlPoints))); +} + +// Mount canvas +const canvas = document.createElement("canvas"); +document.getElementById("container").appendChild(canvas); + +// Custom plugin: draws vertical dashed guide lines + label boxes at each landmark +const landmarkPlugin = { + id: "landmarks", + afterDraw(chart) { + const { ctx: c, chartArea, scales } = chart; + const xSc = scales.x; + const ySc = scales.y; + c.save(); + + landmarks.forEach(lm => { + const xPx = xSc.getPixelForValue(lm.d); + const yPx = ySc.getPixelForValue(lm.e); + const lineH = 17; + const pad = 5; + const gap = 9; + + // Dashed vertical guide from terrain dot down to x-axis baseline + c.beginPath(); + c.setLineDash([5, 4]); + c.strokeStyle = t.inkSoft + "90"; + c.lineWidth = 1.5; + c.moveTo(xPx, yPx + 6); + c.lineTo(xPx, chartArea.bottom); + c.stroke(); + c.setLineDash([]); + + // Hollow dot at terrain elevation + c.beginPath(); + c.arc(xPx, yPx, 5, 0, 2 * Math.PI); + c.fillStyle = t.pageBg; + c.fill(); + c.beginPath(); + c.arc(xPx, yPx, 5, 0, 2 * Math.PI); + c.strokeStyle = t.ink; + c.lineWidth = 2; + c.stroke(); + + // Measure label box dimensions + c.font = "bold 13px sans-serif"; + const nameW = c.measureText(lm.name).width; + c.font = "12px sans-serif"; + const elevW = c.measureText(lm.elev).width; + const boxW = Math.max(nameW, elevW) + pad * 2; + const boxH = lineH * 2 + pad; + + // Place label above dot; flip below if too close to chart top + const flipBelow = (yPx - gap - boxH) < chartArea.top + 5; + const boxTop = flipBelow ? yPx + gap : yPx - gap - boxH; + + // Clamp box horizontally to stay within chart area (handles edge landmarks) + let boxLeft = xPx - boxW / 2; + boxLeft = Math.max(chartArea.left, Math.min(chartArea.right - boxW, boxLeft)); + const boxCenterX = boxLeft + boxW / 2; + + // Background box for readability + c.fillStyle = t.elevatedBg; + c.beginPath(); + c.roundRect(boxLeft, boxTop, boxW, boxH, 3); + c.fill(); + + // Landmark name (bold) + c.font = "bold 13px sans-serif"; + c.fillStyle = t.ink; + c.textAlign = "center"; + c.textBaseline = "top"; + c.fillText(lm.name, boxCenterX, boxTop + pad); + + // Elevation value (regular, secondary) + c.font = "12px sans-serif"; + c.fillStyle = t.inkSoft; + c.fillText(lm.elev, boxCenterX, boxTop + pad + lineH); + }); + + c.restore(); + }, +}; + +new Chart(canvas, { + type: "line", + data: { + labels: distances, + datasets: [{ + label: "Elevation", + data: elevations, + borderColor: t.palette[0], + borderWidth: 2.5, + fill: "start", + backgroundColor(ctx) { + const chart = ctx.chart; + const { ctx: c, chartArea } = chart; + if (!chartArea) return t.palette[0] + "60"; + const grad = c.createLinearGradient(0, chartArea.top, 0, chartArea.bottom); + grad.addColorStop(0, t.palette[0] + "BB"); + grad.addColorStop(1, t.palette[0] + "18"); + return grad; + }, + pointRadius: 0, + tension: 0.3, + }], + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: false, + plugins: { + title: { + display: true, + text: "area-elevation-profile · javascript · chartjs · anyplot.ai", + color: t.ink, + font: { size: 22, weight: "500" }, + padding: { top: 16, bottom: 8 }, + }, + legend: { display: false }, + }, + scales: { + x: { + type: "linear", + min: 0, + max: 80, + ticks: { + color: t.inkSoft, + font: { size: 14 }, + stepSize: 10, + callback: (v) => v + " km", + }, + grid: { color: t.grid }, + title: { + display: true, + text: "Distance (km)", + color: t.ink, + font: { size: 16 }, + }, + border: { color: t.inkSoft }, + }, + y: { + min: 400, + max: 2800, + ticks: { + color: t.inkSoft, + font: { size: 14 }, + stepSize: 400, + callback: (v) => v + " m", + }, + grid: { color: t.grid }, + title: { + display: true, + text: "Elevation (m)", + color: t.ink, + font: { size: 16 }, + }, + border: { color: t.inkSoft }, + }, + }, + }, + plugins: [landmarkPlugin], +}); From 24651b0a54ee9c86ccc9da97c9f9f183b3de084a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 06:06:54 +0000 Subject: [PATCH 2/5] chore(chartjs): add metadata for area-elevation-profile --- .../metadata/javascript/chartjs.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/area-elevation-profile/metadata/javascript/chartjs.yaml diff --git a/plots/area-elevation-profile/metadata/javascript/chartjs.yaml b/plots/area-elevation-profile/metadata/javascript/chartjs.yaml new file mode 100644 index 0000000000..b4a3f1f9f1 --- /dev/null +++ b/plots/area-elevation-profile/metadata/javascript/chartjs.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for chartjs implementation of area-elevation-profile +# Auto-generated by impl-generate.yml + +library: chartjs +language: javascript +specification_id: area-elevation-profile +created: '2026-06-10T06:06:54Z' +updated: '2026-06-10T06:06:54Z' +generated_by: claude-sonnet +workflow_run: 27256385447 +issue: 4578 +language_version: 22.22.3 +library_version: 4.4.7 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-dark.html +quality_score: null +review: + strengths: [] + weaknesses: [] From c6f97fd831a6dbbb73cf5285f8c7a9576e9268bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 06:13:21 +0000 Subject: [PATCH 3/5] chore(chartjs): update quality score 87 and review feedback for area-elevation-profile --- .../implementations/javascript/chartjs.js | 4 +- .../metadata/javascript/chartjs.yaml | 258 +++++++++++++++++- 2 files changed, 253 insertions(+), 9 deletions(-) diff --git a/plots/area-elevation-profile/implementations/javascript/chartjs.js b/plots/area-elevation-profile/implementations/javascript/chartjs.js index c7ef2a479d..98fda38516 100644 --- a/plots/area-elevation-profile/implementations/javascript/chartjs.js +++ b/plots/area-elevation-profile/implementations/javascript/chartjs.js @@ -1,7 +1,7 @@ // anyplot.ai // area-elevation-profile: Terrain Elevation Profile Along Transect -// Library: chartjs 4.4.7 | JavaScript 22 -// Quality: pending | Created: 2026-06-10 +// Library: chartjs 4.4.7 | JavaScript 22.22.3 +// Quality: 87/100 | Created: 2026-06-10 //# anyplot-orientation: landscape const t = window.ANYPLOT_TOKENS; diff --git a/plots/area-elevation-profile/metadata/javascript/chartjs.yaml b/plots/area-elevation-profile/metadata/javascript/chartjs.yaml index b4a3f1f9f1..e5f2afc745 100644 --- a/plots/area-elevation-profile/metadata/javascript/chartjs.yaml +++ b/plots/area-elevation-profile/metadata/javascript/chartjs.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for chartjs implementation of area-elevation-profile -# Auto-generated by impl-generate.yml - library: chartjs language: javascript specification_id: area-elevation-profile created: '2026-06-10T06:06:54Z' -updated: '2026-06-10T06:06:54Z' +updated: '2026-06-10T06:13:20Z' generated_by: claude-sonnet workflow_run: 27256385447 issue: 4578 @@ -15,7 +12,254 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/area-elev preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-dark.html -quality_score: null +quality_score: 87 review: - strengths: [] - weaknesses: [] + strengths: + - Gradient area fill with opaque-to-transparent fade creates an authentic terrain + silhouette effect + - Custom Chart.js afterDraw plugin for landmark annotations with precise coordinate + conversion (getPixelForValue) is idiomatic and sophisticated + - Hermite spline interpolation produces a smooth, realistic elevation profile from + sparse control points + - Horizontal clamping logic for label boxes prevents any annotation from spilling + outside the chart area + - 'Perfect dual-theme adaptation: warm off-white light bg, near-black dark bg, elevated-bg + label boxes switch correctly, all chrome tokens wired through' + weaknesses: + - 'Spec requires noting a vertical exaggeration factor on the plot (e.g., ''VE: + ~10×'') — the implementation does not annotate or acknowledge the exaggeration, + which the spec explicitly calls for' + - Landmark label font sizes are fixed at 13 px/12 px CSS (26/24 px at 2× scale), + which is adequate but on the smaller end for a 3200 px canvas; increasing to 15 + px/14 px would improve readability when the chart is embedded at gallery size + - Both x and y grid lines are displayed (full grid), which adds some visual clutter; + the style guide prefers y-axis-only grid for line/area charts — removing the x + grid would reduce noise + - No top/right spine removal — Chart.js border is still visible on all four sides; + setting borderColor to transparent for the top/right sides (or the outer chart + border) would give a cleaner L-shaped frame look + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct light surface; not pure white. + Chrome: Title "area-elevation-profile · javascript · chartjs · anyplot.ai" in dark ink, clearly readable. X-axis label "Distance (km)" and Y-axis label "Elevation (m)" both dark ink, clearly readable. Tick labels ("0 km"–"80 km" and "400 m"–"2800 m") in soft-dark ink, all readable at full resolution. + Data: Single green area (#009E73) with gradient fill — opaque at the profile line, fading to near-transparent at the base. The terrain silhouette reads clearly. Six landmark annotations: hollow ring dots at each peak/pass/trailhead, dashed vertical guide lines, and small label boxes (warm off-white elevated background with dark ink "name bold + elevation regular" typography). Layout: Trailhead (0 km, 940 m), First Pass (20 km, 2100 m), Summit Peak (33 km, 2450 m), Mountain Hut (47 km, 1700 m), Second Pass (56 km, 2300 m), Trail End (80 km, 880 m). + Legibility verdict: PASS — all title, axis, and annotation text is clearly legible against the light background. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct dark surface; not pure black. + Chrome: Title text is light/cream-colored on the dark background — clearly readable. Axis labels and tick labels use the light inkSoft token — all readable. No dark-on-dark failures observed. + Data: Same brand-green (#009E73) area fill with gradient — identical data color to light render; only chrome flips. Landmark label boxes switch to the elevated dark background (#242420) with light ink text, maintaining excellent readability on the dark surface. Hollow dot outlines (in t.ink = cream) contrast well against the near-black background. + Legibility verdict: PASS — all text is clearly legible against the dark background; no dark-on-dark failures. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: Title (22px), axis labels (16px), ticks (14px) all clearly readable + in both themes. Landmark label boxes (13/12px CSS = 26/24px native) adequate + but slightly small for gallery-size embed. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text overlaps detected; horizontal clamping logic keeps all label + boxes within chart area. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Green area fill and profile line are prominent. Hollow landmark dots + clearly visible. 200-point line with no markers is appropriate for this + data density. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Single series using Imprint palette[0] (#009E73). No red-green as + sole signal. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas gate passed. Title ~62% of width. No overflow. Good whitespace + around chart. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Distance (km) and Elevation (m) with units. Title follows exact required + format. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First and only series is #009E73. Light bg = #FAF8F1, dark bg = + #1A1A17. Data colors identical across themes. Chrome tokens correctly wired.' + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Gradient fill (palette[0]+BB to palette[0]+18) is a thoughtful terrain + silhouette technique. Custom plugin with hollow dots, dashed guide lines, + and typographically differentiated label boxes shows real design intent + above defaults. + - id: DE-02 + name: Visual Refinement + score: 3 + max: 6 + passed: true + comment: Grid present on both axes (somewhat busy; y-only preferred for line + charts). No explicit spine removal. Low-opacity grid and elevated-bg label + boxes are refined details. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Terrain narrative flows naturally from Trailhead to Trail End with + annotated waypoints. Gradient fill emphasizes terrain mass. Summit Peak + at 2450m reads as clear focal point. + spec_compliance: + score: 14 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct area/line chart for elevation profile with terrain silhouette + fill. + - id: SC-02 + name: Required Features + score: 3 + max: 4 + passed: true + comment: 'Area fill ✓, landmark vertical marker lines ✓, label boxes with + names and elevations ✓, start/end labeled ✓. Missing: vertical exaggeration + factor note on the plot (spec explicitly requires ''note it on the plot'').' + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Distance on x-axis (0-80 km), elevation on y-axis (400-2800 m). All + data shown correctly. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title is exactly 'area-elevation-profile · javascript · chartjs · + anyplot.ai'. Legend hidden for single-series chart. + data_quality: + score: 14 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 5 + max: 6 + passed: true + comment: Shows terrain profile, gradient fill silhouette, 6 landmark annotations + with vertical guides, start/end labels, smooth interpolation. Missing VE + notation reduces coverage. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Alpine trail with realistic km distances (80 km) and elevation range + (880-2450 m). Neutral hiking context, no controversial content. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 80 km trail with 880-2450 m elevation is realistic for alpine terrain. + Hermite interpolation produces natural-looking contours. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Hermite function is a data generation utility only; chart config + is flat. No unnecessary abstraction. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Deterministic data from fixed control points; no randomness. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports; Chart.js is a browser global. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Custom plugin is appropriate Chart.js extension pattern. No fake + interactivity. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: 'Follows harness contract; no explicit save calls. Appends exactly + one canvas to #container.' + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: 'Uses plugin API (afterDraw), getPixelForValue() for coordinate conversion, + backgroundColor as function for gradient factory, fill: ''start'' for area + fill. All idiomatic Chart.js patterns.' + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: Custom afterDraw plugin, gradient factory via createLinearGradient + inside Chart.js rendering lifecycle, chartArea coordinate system for pixel-accurate + annotations — all Chart.js-specific capabilities used well. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - annotations + patterns: + - data-generation + dataprep: + - interpolation + styling: + - gradient-fill + - alpha-blending From 8d89a1aa24bd7bc6a08eb6aaa4651f2a595b79b4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 06:17:58 +0000 Subject: [PATCH 4/5] fix(chartjs): address review feedback for area-elevation-profile Attempt 1/3 - fixes based on AI review --- .../implementations/javascript/chartjs.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/plots/area-elevation-profile/implementations/javascript/chartjs.js b/plots/area-elevation-profile/implementations/javascript/chartjs.js index 98fda38516..ada81201c4 100644 --- a/plots/area-elevation-profile/implementations/javascript/chartjs.js +++ b/plots/area-elevation-profile/implementations/javascript/chartjs.js @@ -108,9 +108,9 @@ const landmarkPlugin = { c.stroke(); // Measure label box dimensions - c.font = "bold 13px sans-serif"; + c.font = "bold 15px sans-serif"; const nameW = c.measureText(lm.name).width; - c.font = "12px sans-serif"; + c.font = "14px sans-serif"; const elevW = c.measureText(lm.elev).width; const boxW = Math.max(nameW, elevW) + pad * 2; const boxH = lineH * 2 + pad; @@ -131,14 +131,14 @@ const landmarkPlugin = { c.fill(); // Landmark name (bold) - c.font = "bold 13px sans-serif"; + c.font = "bold 15px sans-serif"; c.fillStyle = t.ink; c.textAlign = "center"; c.textBaseline = "top"; c.fillText(lm.name, boxCenterX, boxTop + pad); // Elevation value (regular, secondary) - c.font = "12px sans-serif"; + c.font = "14px sans-serif"; c.fillStyle = t.inkSoft; c.fillText(lm.elev, boxCenterX, boxTop + pad + lineH); }); @@ -180,7 +180,14 @@ new Chart(canvas, { text: "area-elevation-profile · javascript · chartjs · anyplot.ai", color: t.ink, font: { size: 22, weight: "500" }, - padding: { top: 16, bottom: 8 }, + padding: { top: 16, bottom: 4 }, + }, + subtitle: { + display: true, + text: "Vertical exaggeration ≈ 10×", + color: t.inkSoft, + font: { size: 13 }, + padding: { bottom: 10 }, }, legend: { display: false }, }, @@ -195,7 +202,7 @@ new Chart(canvas, { stepSize: 10, callback: (v) => v + " km", }, - grid: { color: t.grid }, + grid: { display: false }, title: { display: true, text: "Distance (km)", From 0df2cf722d3b204b50571a4048a5064d04cc6056 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Jun 2026 06:25:54 +0000 Subject: [PATCH 5/5] chore(chartjs): update quality score 90 and review feedback for area-elevation-profile --- .../implementations/javascript/chartjs.js | 2 +- .../metadata/javascript/chartjs.yaml | 201 +++++++++--------- 2 files changed, 105 insertions(+), 98 deletions(-) diff --git a/plots/area-elevation-profile/implementations/javascript/chartjs.js b/plots/area-elevation-profile/implementations/javascript/chartjs.js index ada81201c4..d62ddaf196 100644 --- a/plots/area-elevation-profile/implementations/javascript/chartjs.js +++ b/plots/area-elevation-profile/implementations/javascript/chartjs.js @@ -1,7 +1,7 @@ // anyplot.ai // area-elevation-profile: Terrain Elevation Profile Along Transect // Library: chartjs 4.4.7 | JavaScript 22.22.3 -// Quality: 87/100 | Created: 2026-06-10 +// Quality: 90/100 | Created: 2026-06-10 //# anyplot-orientation: landscape const t = window.ANYPLOT_TOKENS; diff --git a/plots/area-elevation-profile/metadata/javascript/chartjs.yaml b/plots/area-elevation-profile/metadata/javascript/chartjs.yaml index e5f2afc745..fa84772dcd 100644 --- a/plots/area-elevation-profile/metadata/javascript/chartjs.yaml +++ b/plots/area-elevation-profile/metadata/javascript/chartjs.yaml @@ -2,7 +2,7 @@ library: chartjs language: javascript specification_id: area-elevation-profile created: '2026-06-10T06:06:54Z' -updated: '2026-06-10T06:13:20Z' +updated: '2026-06-10T06:25:53Z' generated_by: claude-sonnet workflow_run: 27256385447 issue: 4578 @@ -12,47 +12,54 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/area-elev preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/area-elevation-profile/javascript/chartjs/plot-dark.html -quality_score: 87 +quality_score: 90 review: strengths: - - Gradient area fill with opaque-to-transparent fade creates an authentic terrain - silhouette effect - - Custom Chart.js afterDraw plugin for landmark annotations with precise coordinate - conversion (getPixelForValue) is idiomatic and sophisticated - - Hermite spline interpolation produces a smooth, realistic elevation profile from - sparse control points - - Horizontal clamping logic for label boxes prevents any annotation from spilling - outside the chart area - - 'Perfect dual-theme adaptation: warm off-white light bg, near-black dark bg, elevated-bg - label boxes switch correctly, all chrome tokens wired through' + - Beautiful gradient area fill using Chart.js backgroundColor function with chartArea + reference — idiomatic and visually polished terrain silhouette + - 'Custom landmarkPlugin using afterDraw with canvas 2D API: hollow dots, dashed + vertical guides, and themed rounded-corner label boxes demonstrate deep Chart.js + expertise' + - Cubic Hermite spline interpolation over 13 hard-coded control points produces + a smooth, realistic Alpine terrain profile without any external dependency + - 'Full theme adaptation throughout: pageBg, elevatedBg, ink, inkSoft, grid tokens + all applied correctly — both renders are visually correct and readable' + - Label clamping logic (Math.max/min on boxLeft) prevents landmark boxes from clipping + at chart edges; flip-below logic handles summit-level labels near the chart top + - Vertical exaggeration noted via subtitle; single-series legend correctly hidden; + x-axis gridlines hidden keeping the chart clean weaknesses: - - 'Spec requires noting a vertical exaggeration factor on the plot (e.g., ''VE: - ~10×'') — the implementation does not annotate or acknowledge the exaggeration, - which the spec explicitly calls for' - - Landmark label font sizes are fixed at 13 px/12 px CSS (26/24 px at 2× scale), - which is adequate but on the smaller end for a 3200 px canvas; increasing to 15 - px/14 px would improve readability when the chart is embedded at gallery size - - Both x and y grid lines are displayed (full grid), which adds some visual clutter; - the style guide prefers y-axis-only grid for line/area charts — removing the x - grid would reduce noise - - No top/right spine removal — Chart.js border is still visible on all four sides; - setting borderColor to transparent for the top/right sides (or the outer chart - border) would give a cleaner L-shaped frame look + - 'DE-01: Design is professional and above defaults, but lacks a strong visual signature + — the optional slope-based gradient coloring (green flat → red steep) mentioned + in the spec would add a distinctive second data dimension to the fill and elevate + the chart to publication-ready level' + - 'DE-02: Axis border lines (border: { color: t.inkSoft }) on both x and y scales + create a subtle box frame around the data area; removing the top and right equivalent + borders (or using borderColor: ''transparent'' on the right/top sides) would align + with the style guide''s L-shaped spine preference and improve refinement' + - 'DE-03: Visual storytelling focuses on landmark labels but misses a climactic + emphasis on the Summit Peak — making the summit label box slightly larger, adding + a distinct color accent (e.g. palette[1] border on the box), or a summit annotation + arrow would guide the viewer''s eye to the narrative high point' + - 'VQ-02: Landmark label boxes for First Pass (20 km) and Summit Peak (33 km) are + horizontally close (~13 km apart); if landmark names were longer they would crowd + — adding a minimum horizontal separation check in the plugin would future-proof + this' image_description: |- Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct light surface; not pure white. - Chrome: Title "area-elevation-profile · javascript · chartjs · anyplot.ai" in dark ink, clearly readable. X-axis label "Distance (km)" and Y-axis label "Elevation (m)" both dark ink, clearly readable. Tick labels ("0 km"–"80 km" and "400 m"–"2800 m") in soft-dark ink, all readable at full resolution. - Data: Single green area (#009E73) with gradient fill — opaque at the profile line, fading to near-transparent at the base. The terrain silhouette reads clearly. Six landmark annotations: hollow ring dots at each peak/pass/trailhead, dashed vertical guide lines, and small label boxes (warm off-white elevated background with dark ink "name bold + elevation regular" typography). Layout: Trailhead (0 km, 940 m), First Pass (20 km, 2100 m), Summit Peak (33 km, 2450 m), Mountain Hut (47 km, 1700 m), Second Pass (56 km, 2300 m), Trail End (80 km, 880 m). - Legibility verdict: PASS — all title, axis, and annotation text is clearly legible against the light background. + Background: Warm off-white (#FAF8F1) — correct theme surface, clearly not pure white + Chrome: Title "area-elevation-profile · javascript · chartjs · anyplot.ai" in dark ink at top (~55% canvas width), subtitle "Vertical exaggeration ≈ 10×" in secondary ink below. X-axis label "Distance (km)" and Y-axis label "Elevation (m)" both in dark ink, visually balanced at 16px CSS. Tick labels in inkSoft (muted dark) at 14px CSS; Y-axis shows 400m–2800m at 400m intervals, X-axis shows 0–80km at 10km intervals. Subtle Y-axis grid lines; no X-axis grid — clean visual floor. + Data: Single terrain profile series in #009E73 (brand green) with gradient area fill from ~73% opacity at top to ~9% at the x-axis baseline — creates a rich terrain silhouette effect. Six landmark annotations (Trailhead, First Pass, Summit Peak, Mountain Hut, Second Pass, Trail End) with hollow circle dots on the terrain line, dashed vertical guides to the x-axis, and elevated-background rounded-corner label boxes showing name (bold) and elevation. + Legibility verdict: PASS — all title, axis, tick, and landmark label text is clearly readable against the warm off-white background; no light-on-light issues Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark surface; not pure black. - Chrome: Title text is light/cream-colored on the dark background — clearly readable. Axis labels and tick labels use the light inkSoft token — all readable. No dark-on-dark failures observed. - Data: Same brand-green (#009E73) area fill with gradient — identical data color to light render; only chrome flips. Landmark label boxes switch to the elevated dark background (#242420) with light ink text, maintaining excellent readability on the dark surface. Hollow dot outlines (in t.ink = cream) contrast well against the near-black background. - Legibility verdict: PASS — all text is clearly legible against the dark background; no dark-on-dark failures. + Background: Warm near-black (#1A1A17) — correct dark theme surface, not pure black + Chrome: Title and subtitle in light ink (#F0EFE8 equivalent); axis labels and tick labels in light secondary ink (#B8B7B0 equivalent) — all text is light-on-dark and fully readable. Y-axis grid lines are subtly visible as faint light rules. Landmark label boxes use elevatedBg (#242420) creating a barely perceptible lift from the background, with ink text readable within. + Data: Terrain profile and gradient fill are identical to light render — #009E73 brand green line and fill with same gradient stops. Data color identity confirmed across both themes; only chrome (background, text, grid) flips. + Legibility verdict: PASS — no dark-on-dark failures detected; brand green #009E73 remains clearly visible on the dark surface; all text elements readable in both landmark boxes and axis chrome criteria_checklist: visual_quality: - score: 29 + score: 28 max: 30 items: - id: VQ-01 @@ -60,83 +67,83 @@ review: score: 7 max: 8 passed: true - comment: Title (22px), axis labels (16px), ticks (14px) all clearly readable - in both themes. Landmark label boxes (13/12px CSS = 26/24px native) adequate - but slightly small for gallery-size embed. + comment: All font sizes explicitly set (title 22px, axis labels 16px, ticks + 14px, landmark labels 14-15px); readable in both themes; landmark labels + at 15px CSS are appropriately sized for the complexity of this chart - id: VQ-02 name: No Overlap - score: 6 + score: 5 max: 6 passed: true - comment: No text overlaps detected; horizontal clamping logic keeps all label - boxes within chart area. + comment: Labels well-placed with flip-below and horizontal clamping; First + Pass and Summit Peak boxes (20km, 33km) are comfortably separated in the + render but close enough to warrant a minor deduction - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Green area fill and profile line are prominent. Hollow landmark dots - clearly visible. 200-point line with no markers is appropriate for this - data density. + comment: Terrain profile and gradient fill are prominently visible; pointRadius=0 + appropriate for 200-point dense profile line - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Single series using Imprint palette[0] (#009E73). No red-green as - sole signal. + comment: 'Single series using #009E73 brand green — CVD-safe, adequate contrast + on both surfaces' - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Canvas gate passed. Title ~62% of width. No overflow. Good whitespace - around chart. + comment: Canvas gate passed; landscape 3200x1800 appropriate for elevation + profile; generous whitespace with good plot-to-canvas ratio - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Distance (km) and Elevation (m) with units. Title follows exact required - format. + comment: Distance (km) and Elevation (m) with units; correct anyplot title + format; subtitle noting vertical exaggeration - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: 'First and only series is #009E73. Light bg = #FAF8F1, dark bg = - #1A1A17. Data colors identical across themes. Chrome tokens correctly wired.' + comment: 'First series t.palette[0] = #009E73; background #FAF8F1 (light) + / #1A1A17 (dark); all chrome tokens theme-adaptive; data colors identical + across both renders' design_excellence: - score: 12 + score: 14 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 passed: true - comment: Gradient fill (palette[0]+BB to palette[0]+18) is a thoughtful terrain - silhouette technique. Custom plugin with hollow dots, dashed guide lines, - and typographically differentiated label boxes shows real design intent - above defaults. + comment: Gradient area fill, custom landmark plugin with rounded boxes and + hollow dots — clearly above defaults; professional mountain-silhouette look; + not publication-ready (no slope coloring, no accent emphasis on summit) - id: DE-02 name: Visual Refinement - score: 3 + score: 4 max: 6 passed: true - comment: Grid present on both axes (somewhat busy; y-only preferred for line - charts). No explicit spine removal. Low-opacity grid and elevated-bg label - boxes are refined details. + comment: Y-axis grid only (x-axis gridless), legend hidden, generous whitespace; + axis border lines create slight box frame that could be further refined + toward L-shaped spines - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: Terrain narrative flows naturally from Trailhead to Trail End with - annotated waypoints. Gradient fill emphasizes terrain mass. Summit Peak - at 2450m reads as clear focal point. + comment: Landmark journey narrative with dashed guides and labeled boxes; + gradient fill conveys terrain depth; no climactic emphasis on Summit Peak + as the narrative high point spec_compliance: - score: 14 + score: 15 max: 15 items: - id: SC-01 @@ -144,30 +151,28 @@ review: score: 5 max: 5 passed: true - comment: Correct area/line chart for elevation profile with terrain silhouette - fill. + comment: Correct area/filled-line chart for elevation profile - id: SC-02 name: Required Features - score: 3 + score: 4 max: 4 passed: true - comment: 'Area fill ✓, landmark vertical marker lines ✓, label boxes with - names and elevations ✓, start/end labeled ✓. Missing: vertical exaggeration - factor note on the plot (spec explicitly requires ''note it on the plot'').' + comment: Area fill, start/end labels (Trailhead/Trail End), landmark annotations + with vertical guides, vertical exaggeration noted in subtitle - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Distance on x-axis (0-80 km), elevation on y-axis (400-2800 m). All - data shown correctly. + comment: Distance (km) on X, Elevation (m) on Y; 200 sample points within + spec's 50-500 range; 80 km trail with 6 landmarks - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title is exactly 'area-elevation-profile · javascript · chartjs · - anyplot.ai'. Legend hidden for single-series chart. + comment: Title exactly 'area-elevation-profile · javascript · chartjs · anyplot.ai'; + no legend (appropriate for single series) data_quality: score: 14 max: 15 @@ -177,23 +182,23 @@ review: score: 5 max: 6 passed: true - comment: Shows terrain profile, gradient fill silhouette, 6 landmark annotations - with vertical guides, start/end labels, smooth interpolation. Missing VE - notation reduces coverage. + comment: Area fill, smooth interpolation, landmark annotations, elevation + labels, start/end labels, vertical exaggeration — all main features. Optional + slope-based gradient coloring from spec not implemented - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Alpine trail with realistic km distances (80 km) and elevation range - (880-2450 m). Neutral hiking context, no controversial content. + comment: Alpine trail scenario; realistic landmark names (passes, summit, + hut, trailhead); neutral outdoor-recreation context - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: 80 km trail with 880-2450 m elevation is realistic for alpine terrain. - Hermite interpolation produces natural-looking contours. + comment: Elevation 880m–2450m realistic for European Alps; 80km multi-stage + trail; elevation range plausible for the described terrain code_quality: score: 10 max: 10 @@ -203,54 +208,55 @@ review: score: 3 max: 3 passed: true - comment: Hermite function is a data generation utility only; chart config - is flat. No unnecessary abstraction. + comment: 'Flat script: data definition → canvas creation → plugin object → + Chart instantiation; no wrapper classes' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Deterministic data from fixed control points; no randomness. + comment: All data hard-coded deterministically; no Math.random() - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: No imports; Chart.js is a browser global. + comment: No imports; uses global Chart and window.ANYPLOT_TOKENS as per harness + contract - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Custom plugin is appropriate Chart.js extension pattern. No fake - interactivity. + comment: Well-structured; hermite spline is appropriately complex for the + use case; no fake UI - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: 'Follows harness contract; no explicit save calls. Appends exactly - one canvas to #container.' + comment: 'Correct Chart.js API; harness handles file output; animation: false + set correctly' library_mastery: - score: 8 + score: 9 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 5 max: 5 passed: true - comment: 'Uses plugin API (afterDraw), getPixelForValue() for coordinate conversion, - backgroundColor as function for gradient factory, fill: ''start'' for area - fill. All idiomatic Chart.js patterns.' + comment: 'Expert Chart.js patterns: gradient backgroundColor as function with + chartArea reference, fill: ''start'' for area, linear scale with tick callbacks, + responsive/maintainAspectRatio/animation settings all correct' - id: LM-02 name: Distinctive Features score: 4 max: 5 passed: true - comment: Custom afterDraw plugin, gradient factory via createLinearGradient - inside Chart.js rendering lifecycle, chartArea coordinate system for pixel-accurate - annotations — all Chart.js-specific capabilities used well. + comment: Custom afterDraw plugin with canvas 2D API (roundRect, arc, setLineDash) + is distinctively Chart.js; chartArea-anchored gradient fill is a pattern + unique to this library's declarative API verdict: APPROVED impl_tags: dependencies: [] @@ -258,6 +264,7 @@ impl_tags: - annotations patterns: - data-generation + - iteration-over-groups dataprep: - interpolation styling: