diff --git a/plots/dumbbell-basic/implementations/javascript/chartjs.js b/plots/dumbbell-basic/implementations/javascript/chartjs.js new file mode 100644 index 0000000000..e087d70303 --- /dev/null +++ b/plots/dumbbell-basic/implementations/javascript/chartjs.js @@ -0,0 +1,159 @@ +// anyplot.ai +// dumbbell-basic: Basic Dumbbell Chart +// Library: chartjs 4.4.7 | JavaScript 22.23.1 +// Quality: 92/100 | Created: 2026-06-30 + +const t = window.ANYPLOT_TOKENS; + +// Training scores before and after a skills development program (10 departments) +const rawData = [ + { dept: "Legal", before: 65, after: 72 }, + { dept: "Admin", before: 58, after: 68 }, + { dept: "Finance", before: 69, after: 83 }, + { dept: "HR", before: 70, after: 86 }, + { dept: "Marketing", before: 74, after: 90 }, + { dept: "Support", before: 61, after: 78 }, + { dept: "Operations", before: 57, after: 75 }, + { dept: "Sales", before: 63, after: 81 }, + { dept: "Product", before: 72, after: 91 }, + { dept: "Engineering", before: 68, after: 88 }, +]; + +// Sort ascending by improvement so the largest gain appears at the top of the chart +rawData.sort((a, b) => (a.after - a.before) - (b.after - b.before)); + +const labels = rawData.map(d => d.dept); +const beforeData = rawData.map((d, i) => ({ x: d.before, y: i })); +const afterData = rawData.map((d, i) => ({ x: d.after, y: i })); + +// Mount +const canvas = document.createElement("canvas"); +document.getElementById("container").appendChild(canvas); + +// Plugin: fill full canvas with theme background +const bgPlugin = { + id: "background", + beforeDraw(chart) { + const ctx = chart.ctx; + ctx.save(); + ctx.fillStyle = t.pageBg; + ctx.fillRect(0, 0, chart.width, chart.height); + ctx.restore(); + }, +}; + +// Plugin: draw dumbbell connecting lines behind the dots +const dumbbellPlugin = { + id: "dumbbell", + beforeDatasetsDraw(chart) { + const ctx = chart.ctx; + const meta0 = chart.getDatasetMeta(0); // Before points + const meta1 = chart.getDatasetMeta(1); // After points + + ctx.save(); + ctx.strokeStyle = t.inkSoft; + ctx.lineWidth = 2.5; + ctx.globalAlpha = 0.55; + ctx.lineCap = "round"; + + for (let i = 0; i < meta0.data.length; i++) { + const p0 = meta0.data[i]; + const p1 = meta1.data[i]; + if (!p0 || !p1) continue; + ctx.beginPath(); + ctx.moveTo(p0.x, p0.y); + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + } + ctx.restore(); + }, +}; + +// Chart +new Chart(canvas, { + type: "scatter", + plugins: [bgPlugin, dumbbellPlugin], + data: { + datasets: [ + { + label: "Before Program", + data: beforeData, + backgroundColor: t.palette[0], // #009E73 brand green — Imprint pos 1 + borderColor: t.pageBg, + borderWidth: 2, + pointRadius: 11, + pointHoverRadius: 13, + clip: false, + }, + { + label: "After Program", + data: afterData, + backgroundColor: t.palette[1], // #C475FD lavender — Imprint pos 2 + borderColor: t.pageBg, + borderWidth: 2, + pointRadius: 11, + pointHoverRadius: 13, + clip: false, + }, + ], + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: false, + plugins: { + title: { + display: true, + text: "dumbbell-basic · javascript · chartjs · anyplot.ai", + color: t.ink, + font: { size: 22, weight: "500" }, + padding: { top: 16, bottom: 18 }, + }, + legend: { + position: "top", + labels: { + color: t.ink, + font: { size: 16 }, + boxWidth: 18, + padding: 20, + }, + }, + }, + scales: { + x: { + title: { + display: true, + text: "Training Score", + color: t.ink, + font: { size: 16 }, + padding: { top: 8 }, + }, + ticks: { + color: t.inkSoft, + font: { size: 14 }, + }, + grid: { color: t.grid }, + min: 50, + max: 100, + }, + y: { + type: "linear", + min: -0.5, + max: rawData.length - 0.5, + ticks: { + color: t.inkSoft, + font: { size: 14 }, + stepSize: 1, + callback(value) { + const idx = Math.round(value); + return labels[idx] !== undefined ? labels[idx] : ""; + }, + }, + grid: { color: t.grid }, + }, + }, + layout: { + padding: { left: 10, right: 24, top: 8, bottom: 12 }, + }, + }, +}); diff --git a/plots/dumbbell-basic/metadata/javascript/chartjs.yaml b/plots/dumbbell-basic/metadata/javascript/chartjs.yaml new file mode 100644 index 0000000000..68b6e740d4 --- /dev/null +++ b/plots/dumbbell-basic/metadata/javascript/chartjs.yaml @@ -0,0 +1,253 @@ +library: chartjs +language: javascript +specification_id: dumbbell-basic +created: '2026-06-30T23:03:52Z' +updated: '2026-06-30T23:10:14Z' +generated_by: claude-sonnet +workflow_run: 28481262090 +issue: 945 +language_version: 22.23.1 +library_version: 4.4.7 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/chartjs/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/chartjs/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/chartjs/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/chartjs/plot-dark.html +quality_score: 92 +review: + strengths: + - 'Perfect Imprint palette compliance: brand green (#009E73) first, lavender (#C475FD) + second, correct theme-adaptive backgrounds and chrome across both renders' + - Clever and idiomatic use of Chart.js beforeDatasetsDraw plugin hook for the dumbbell + connecting lines — correct library-native approach for a non-native chart type + - 'Meaningful data storytelling: sorting by improvement delta makes the chart self-explanatory + without annotations' + - Deterministic data with a realistic, neutral business scenario covering a good + range of improvement magnitudes + - 'Dot border trick (borderColor: t.pageBg) adds subtle visual polish' + weaknesses: + - 'Top and right scale borders (spines) are visible on all 4 sides of the chart + area. Style guide calls for an L-frame (remove top/right). Fix: add border: { + display: false } to the x-scale options and border: { display: false } to the + y-scale — or use t.grid as the border color so only the axis-aligned borders show.' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct light surface. + Chrome: Title "dumbbell-basic · javascript · chartjs · anyplot.ai" in dark ink, clearly readable. X-axis label "Training Score" in dark ink. Y-axis tick labels (department names) in dark inkSoft. Legend labels in dark ink. All chrome readable. + Data: "Before Program" dots in brand green (#009E73) — Imprint palette position 1. "After Program" dots in lavender (#C475FD) — Imprint palette position 2. Subtle grey connecting lines at 55% opacity. pointRadius 11 appropriate for 10 data points. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct dark surface. + Chrome: Title, axis label, tick labels, and legend text all render in light colors — clearly readable against the dark background. No dark-on-dark failures observed. + Data: Colors identical to light render — green (#009E73) and lavender (#C475FD) dots unchanged. Connecting lines remain visible. Both series distinguishable from the dark background. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: All font sizes explicitly set (title 22px, legend 16px, axis title + 16px, ticks 14px). Proportions balanced in both renders. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text collisions. 10 departments well-spaced; legend and title + separated. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: pointRadius 11 appropriately prominent for 10 sparse data points. + Connecting lines visible but subtle. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Green and lavender are perceptually distinct and CVD-safe per Imprint + palette design. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Chart fills canvas well. Balanced margins. No content cut off. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: X-axis labelled Training Score (descriptive; training scores are + dimensionless). Y-axis uses category tick labels. Title matches required + format. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Before Program = t.palette[0] (#009E73). After Program = t.palette[1] + (#C475FD). Backgrounds #FAF8F1 / #1A1A17 correct. All chrome uses theme-adaptive + tokens.' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: 'Strong design: correct Imprint palette, custom canvas background + plugin, dot-border trick creates clean separation, intentional data sorting. + Clearly above defaults but not FiveThirtyEight-level.' + - id: DE-02 + name: Visual Refinement + score: 3 + max: 6 + passed: false + comment: Subtle theme-adaptive grid and 55% alpha connecting lines are good. + However scale borders visible on all 4 sides — top/right spines not removed + per style guide. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Sorting ascending by improvement delta so largest gains appear at + top is genuine data storytelling. No additional focal-point emphasis but + structural choice is effective. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Correct dumbbell chart: two dots per row connected by a line via + Chart.js scatter + custom beforeDatasetsDraw plugin.' + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Horizontal orientation with categories on Y-axis, two distinct colours, + connecting lines, sorted by improvement. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Categories on Y, values on X. All 10 data rows visible within the + 50-100 range. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title is 'dumbbell-basic · javascript · chartjs · anyplot.ai'. Legend + labels Before Program / After Program descriptive and match data. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows varied improvement sizes (Legal +7 to Product +19). All 10 + departments within 5-20 range. Before/after contrast clear. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Training scores before/after skills development programme by department + — real, neutral, comprehensible business scenario. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Scores 57-91 on a 50-100 scale. Improvements of 7-19 points are realistic + for corporate training. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Follows data -> mount -> plugins -> chart flow. No top-level functions + or classes. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: All data is hard-coded and deterministic. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports; Chart and ANYPLOT_TOKENS consumed as harness-provided + globals. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, appropriate complexity. Custom plugins are the idiomatic Chart.js + solution for non-native chart types. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: No manual save; harness handles PNG+HTML output. Uses Chart.js 4.x + API correctly. + library_mastery: + score: 9 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: Expertly uses Chart.js scatter type with numeric y-axis + tick callback + for category labels. Plugins follow Chart.js plugin API contract. All required + options set. + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: 'Leverages Chart.js plugin lifecycle hook (beforeDatasetsDraw) for + direct canvas drawing to implement dumbbell connector. Tick callback remapping + is also Chart.js-specific. Minor deduction: partly a workaround for Chart.js + not having a native dumbbell type.' + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: [] + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending + - edge-highlighting