diff --git a/plots/dumbbell-basic/implementations/javascript/highcharts.js b/plots/dumbbell-basic/implementations/javascript/highcharts.js new file mode 100644 index 0000000000..fb4b9d0529 --- /dev/null +++ b/plots/dumbbell-basic/implementations/javascript/highcharts.js @@ -0,0 +1,139 @@ +// anyplot.ai +// dumbbell-basic: Basic Dumbbell Chart +// Library: highcharts 12.6.0 | JavaScript 22.23.0 +// Quality: 89/100 | Created: 2026-06-30 + +//# anyplot-orientation: landscape + +const t = window.ANYPLOT_TOKENS; + +// Data — Employee satisfaction scores (0–10) before/after a workplace wellness initiative +const departments = ["Engineering", "Marketing", "Sales", "HR", "Finance", "Operations", "Legal", "Product"]; +const scoreBefore = [6.2, 5.8, 6.5, 7.1, 5.4, 6.8, 6.0, 6.9]; +const scoreAfter = [7.7, 7.4, 8.0, 8.3, 7.0, 8.2, 7.3, 8.4]; + +// Sort ascending by improvement so the largest improvement appears at top +const sortIdx = departments.map((_, i) => i) + .sort((a, b) => (scoreAfter[a] - scoreBefore[a]) - (scoreAfter[b] - scoreBefore[b])); +const labels = sortIdx.map(i => departments[i]); +const vBefore = sortIdx.map(i => scoreBefore[i]); +const vAfter = sortIdx.map(i => scoreAfter[i]); +const n = labels.length; + +// [score, categoryIndex] pairs — x = score, y = integer row index +const afterData = vAfter.map((v, i) => [v, i]); +const beforeData = vBefore.map((v, i) => [v, i]); + +const chart = Highcharts.chart("container", { + chart: { + type: "scatter", + backgroundColor: "transparent", + animation: false, + style: { fontFamily: "inherit" }, + marginLeft: 160, + marginRight: 50, + marginTop: 90, + marginBottom: 70, + }, + credits: { enabled: false }, + colors: t.palette, + title: { + text: "dumbbell-basic · javascript · highcharts · anyplot.ai", + style: { color: t.ink, fontSize: "22px", fontWeight: "600" }, + }, + subtitle: { + text: "Employee satisfaction before/after workplace wellness initiative", + style: { color: t.inkSoft, fontSize: "14px" }, + }, + xAxis: { + title: { + text: "Satisfaction Score (0–10)", + style: { color: t.inkSoft, fontSize: "16px" }, + }, + lineColor: t.inkSoft, + tickColor: t.inkSoft, + gridLineColor: t.grid, + gridLineWidth: 1, + min: 4.5, + max: 9.5, + tickInterval: 1, + labels: { style: { color: t.inkSoft, fontSize: "14px" } }, + }, + yAxis: { + title: { text: null }, + min: -0.5, + max: n - 0.5, + tickPositions: labels.map((_, i) => i), + labels: { + formatter() { return labels[this.value] || ""; }, + style: { color: t.inkSoft, fontSize: "14px" }, + }, + lineColor: t.inkSoft, + tickColor: t.inkSoft, + gridLineColor: "transparent", + }, + legend: { + verticalAlign: "top", + align: "right", + layout: "vertical", + itemStyle: { color: t.inkSoft, fontSize: "14px" }, + itemHoverStyle: { color: t.ink }, + }, + plotOptions: { + series: { animation: false }, + scatter: { + marker: { radius: 9, lineWidth: 0 }, + }, + }, + series: [ + { + name: "After", + data: afterData, + color: t.palette[0], + marker: { symbol: "circle" }, + zIndex: 5, + }, + { + name: "Before", + data: beforeData, + color: t.palette[1], + marker: { symbol: "circle" }, + zIndex: 5, + }, + ], +}); + +// Draw connecting lines between Before and After dots via the SVG renderer +const renderer = chart.renderer; +const plotLeft = chart.plotLeft; +const plotTop = chart.plotTop; + +// Build a lookup from category index → before-series point +const beforeByIdx = {}; +chart.series[1].data.forEach(p => { beforeByIdx[p.y] = p; }); + +// Improvement per sorted row (label top 3 for focal point) +const diffs = vAfter.map((v, i) => +(v - vBefore[i]).toFixed(1)); +const topN = 3; + +chart.series[0].data.forEach((afterPt) => { + const beforePt = beforeByIdx[afterPt.y]; + if (!beforePt) return; + + renderer.path([ + "M", plotLeft + beforePt.plotX, plotTop + beforePt.plotY, + "L", plotLeft + afterPt.plotX, plotTop + afterPt.plotY, + ]) + .attr({ stroke: t.inkSoft, "stroke-width": 2.5, "stroke-opacity": 0.35 }) + .add(); + + // Annotate top-improvement rows with the gain value + if (afterPt.y >= n - topN) { + const midX = plotLeft + (beforePt.plotX + afterPt.plotX) / 2; + const midY = plotTop + afterPt.plotY - 14; + renderer.text(`+${diffs[afterPt.y].toFixed(1)}`, midX, midY) + .attr({ align: "center", zIndex: 6, fill: t.inkSoft }) + .css({ fontSize: "11px", fontWeight: "700" }) + .add(); + } +}); diff --git a/plots/dumbbell-basic/metadata/javascript/highcharts.yaml b/plots/dumbbell-basic/metadata/javascript/highcharts.yaml new file mode 100644 index 0000000000..f620cbf93b --- /dev/null +++ b/plots/dumbbell-basic/metadata/javascript/highcharts.yaml @@ -0,0 +1,244 @@ +library: highcharts +language: javascript +specification_id: dumbbell-basic +created: '2026-06-30T23:00:04Z' +updated: '2026-06-30T23:18:52Z' +generated_by: claude-sonnet +workflow_run: 28480907844 +issue: 945 +language_version: 22.23.0 +library_version: 12.6.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/highcharts/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/highcharts/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/highcharts/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/javascript/highcharts/plot-dark.html +quality_score: 89 +review: + strengths: + - Sorting by improvement delta and annotating the top-3 rows turns a simple before/after + chart into a readable ranking that immediately answers 'who benefited most?' + - 'SVG renderer path drawing for connecting lines is idiomatic core-Highcharts: + bypasses the unavailable highcharts-more dumbbell type without any workaround + or CDN fallback.' + - Full theme-adaptive chrome via t.ink / t.inkSoft / t.grid — both renders are visually + correct with zero dark-on-dark failures. + - 'Perfect Imprint palette application: brand green (#009E73) for the positive ''After'' + outcome is semantically apt.' + weaknesses: + - All satisfaction deltas are positive — adding one department that barely improved + or slightly regressed would better showcase the dumbbell chart's ability to show + directional variation, not just magnitude. + - Annotation font (11px) is slightly smaller than the 14px tick label baseline; + aligning it to 13–14px would improve consistency across the text hierarchy. + - 'The legend box frame is visible (default Highcharts border); removing it (legend: + { borderWidth: 0 }) would polish the minimal-chrome aesthetic.' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct theme surface. + Chrome: Title "dumbbell-basic · javascript · highcharts · anyplot.ai" at 22px bold dark text — fully readable. Subtitle "Employee satisfaction before/after workplace wellness initiative" at 14px inkSoft — readable. X-axis title "Satisfaction Score (0–10)" at 16px — readable. Y-axis category labels (Marketing, Finance, Product, Sales, Engineering, Operations, Legal, HR) at 14px — all readable. X-axis tick labels 5–9 at 14px — readable. Legend labels "After" and "Before" at 14px — readable. Annotation labels +1.6, +1.6, +1.5 at 11px — readable but slightly small. + Data: Brand green (#009E73) dots for "After" series; Imprint lavender (#C475FD) for "Before" series. SVG-rendered connecting lines at opacity 0.35. Horizontal dumbbell layout with 8 departments sorted ascending by improvement. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct theme surface. + Chrome: All text rendered in light color via t.ink / t.inkSoft tokens. Title, subtitle, axis labels, tick labels, category labels, and legend text are all light-colored and clearly readable against the dark background. No dark-on-dark failures detected. + Data: Data colors identical to light render — green (#009E73) for After, lavender (#C475FD) for Before. Both colors remain clearly visible and distinguishable on the dark surface. + Legibility verdict: PASS + 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; both themes fully readable. Minor + deduction: annotation labels at 11px slightly below 14px baseline.' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No collisions between text, dots, or annotations in either render. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Marker radius 9px CSS appropriate for 8 rows; connecting lines clearly + visible at stroke-width 2.5. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Green vs lavender provides strong hue and luminance contrast; CVD-safe. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Explicit margins produce well-balanced whitespace. Canvas gate passed. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: X-axis 'Satisfaction Score (0-10)' descriptive with units; Y-axis + null (self-labeling categories). + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: After = t.palette[0] (#009E73), Before = t.palette[1] (#C475FD). + Background transparent over pageBg. Both themes correct. + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Above default: intentional semantic palette use, sorted data, selective + annotations. Not publication-ready.' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Connecting lines at 0.35 opacity; horizontal grid suppressed; no + marker outlines. Solid refinement, legend border remains. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Sort by improvement creates ranking narrative; top-3 annotations + add focal layer; subtitle provides context. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct horizontal dumbbell / connected dot plot. + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Two distinct-color dots per category, connecting lines, horizontal + orientation, sorted by difference. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Categories on Y-axis, satisfaction score on X-axis, all 8 rows visible. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title exactly 'dumbbell-basic · javascript · highcharts · anyplot.ai'. + Legend labels 'After' and 'Before' match series. + data_quality: + score: 14 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 5 + max: 6 + passed: true + comment: '8 departments with varying improvement magnitudes (1.3-1.6). Minor + deduction: all diffs positive, no mixed-direction variation.' + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Employee satisfaction before/after wellness initiative with named + real-world departments. Neutral, credible. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Baseline 5.4-7.1, post-initiative 7.0-8.4 on 0-10 scale. Improvements + of 1.3-1.6 realistic for successful program. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Linear: data → sort → Highcharts.chart() → SVG renderer loop. No + functions or classes.' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: All data hardcoded; deterministic sort; no RNG. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports; uses only Highcharts global and window.ANYPLOT_TOKENS. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, well-commented, appropriately complex. SVG renderer used correctly. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Harness emits plot-light.png/dark.png + HTML. Current Highcharts + 12.6.0 API. + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: Scatter series (correct core-bundle approach when dumbbell requires + highcharts-more), animation:false both locations, no explicit size, credits + disabled. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Uses chart.renderer.path() for connecting lines and renderer.text() + for annotations — distinctly Highcharts SVG-renderer features. + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - annotations + - html-export + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending