diff --git a/plots/bar-stacked-percent/implementations/javascript/chartjs.js b/plots/bar-stacked-percent/implementations/javascript/chartjs.js new file mode 100644 index 0000000000..7a04c35dbc --- /dev/null +++ b/plots/bar-stacked-percent/implementations/javascript/chartjs.js @@ -0,0 +1,144 @@ +// anyplot.ai +// bar-stacked-percent: 100% Stacked Bar Chart +// Library: chartjs 4.4.7 | JavaScript 22.23.2 +// Quality: 95/100 | Created: 2026-08-18 + +const t = window.ANYPLOT_TOKENS; +// "muted" anchor (other / rest) isn't in ANYPLOT_TOKENS — theme-adaptive per default-style-guide.md +const MUTED = window.ANYPLOT_THEME === "light" ? "#6B6A63" : "#A8A79F"; + +// --- Data (in-memory, deterministic) ---------------------------------------- +// Quarterly public-cloud infrastructure revenue by provider, $ billions. +// Each bar normalizes its own row to 100% so quarter-over-quarter share +// shifts are comparable even though the total market is growing. +const quarters = ["Q1 2023", "Q2 2023", "Q3 2023", "Q4 2023", "Q1 2024", "Q2 2024"]; +const providers = ["AWS", "Azure", "Google Cloud", "Alibaba Cloud", "Other"]; +const revenueByQuarter = [ + [21.4, 14.3, 6.2, 3.9, 18.7], + [22.1, 15.8, 7.1, 3.8, 19.2], + [23.0, 17.2, 7.9, 3.6, 20.3], + [24.2, 18.9, 8.8, 3.5, 21.6], + [25.6, 20.8, 9.9, 3.3, 22.9], + [26.9, 22.5, 11.2, 3.1, 24.3], +]; + +const shareByQuarter = revenueByQuarter.map((row) => { + const total = row.reduce((sum, value) => sum + value, 0); + return row.map((value) => (value / total) * 100); +}); +const totalByQuarter = revenueByQuarter.map((row) => row.reduce((sum, value) => sum + value, 0)); + +const datasets = providers.map((provider, i) => { + const isLeader = provider === "AWS"; + return { + label: provider, + data: shareByQuarter.map((row) => row[i]), + backgroundColor: provider === "Other" ? MUTED : t.palette[i], + // Subtle page-bg seam between stacked segments (style guide "Bar edges"); the + // leader series gets an ink outline instead, calling out AWS's persistent lead. + borderColor: isLeader ? t.ink : t.pageBg, + borderWidth: isLeader ? 2 : 1, + borderSkipped: false, + categoryPercentage: 0.6, + barPercentage: 0.9, + }; +}); + +// --- Segment percentage labels (custom Chart.js plugin) --------------------- +// Draws a rounded percentage label centered in every segment tall enough to +// hold it legibly, picking dark/light text per-segment from fill luminance +// (YIQ) so labels stay readable across the whole palette in both themes. +const DARK_TEXT = "#1A1A17"; +const LIGHT_TEXT = "#F0EFE8"; +const MIN_LABEL_HEIGHT = 28; // px; smaller segments skip the label to avoid clutter/overflow + +function textColorFor(hex) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + const yiq = (r * 299 + g * 587 + b * 114) / 1000; + return yiq >= 128 ? DARK_TEXT : LIGHT_TEXT; +} + +const segmentPercentLabels = { + id: "segmentPercentLabels", + afterDatasetsDraw(chart) { + const { ctx } = chart; + ctx.save(); + ctx.font = "600 13px sans-serif"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + chart.data.datasets.forEach((dataset, datasetIndex) => { + const meta = chart.getDatasetMeta(datasetIndex); + const labelColor = textColorFor(dataset.backgroundColor); + meta.data.forEach((bar, index) => { + const segmentHeight = Math.abs(bar.base - bar.y); + if (segmentHeight < MIN_LABEL_HEIGHT) return; + ctx.fillStyle = labelColor; + ctx.fillText(`${Math.round(dataset.data[index])}%`, bar.x, (bar.base + bar.y) / 2); + }); + }); + ctx.restore(); + }, +}; + +// --- Mount ------------------------------------------------------------------- +const canvas = document.createElement("canvas"); +document.getElementById("container").appendChild(canvas); + +// --- Chart --------------------------------------------------------------- +new Chart(canvas, { + type: "bar", + data: { labels: quarters, datasets }, + plugins: [segmentPercentLabels], + options: { + responsive: true, + maintainAspectRatio: false, + animation: false, + plugins: { + title: { + display: true, + text: "bar-stacked-percent · javascript · chartjs · anyplot.ai", + color: t.ink, + font: { size: 22, weight: "500" }, + padding: { bottom: 20 }, + }, + legend: { + position: "bottom", + labels: { color: t.ink, font: { size: 16 }, boxWidth: 20, padding: 18 }, + }, + tooltip: { + callbacks: { + title: (items) => `${items[0].label} — Total: $${totalByQuarter[items[0].dataIndex].toFixed(1)}B`, + label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(1)}%`, + }, + }, + }, + scales: { + x: { + stacked: true, + ticks: { color: t.inkSoft, font: { size: 14 } }, + grid: { display: false }, + title: { display: true, text: "Quarter", color: t.ink, font: { size: 16 } }, + }, + y: { + stacked: true, + min: 0, + max: 100, + ticks: { + color: t.inkSoft, + font: { size: 14 }, + stepSize: 20, + callback: (value) => `${value}%`, + }, + grid: { color: t.grid }, + title: { + display: true, + text: "Share of Cloud Infrastructure Revenue", + color: t.ink, + font: { size: 16 }, + }, + }, + }, + }, +}); diff --git a/plots/bar-stacked-percent/metadata/javascript/chartjs.yaml b/plots/bar-stacked-percent/metadata/javascript/chartjs.yaml new file mode 100644 index 0000000000..1b5f853a63 --- /dev/null +++ b/plots/bar-stacked-percent/metadata/javascript/chartjs.yaml @@ -0,0 +1,258 @@ +library: chartjs +language: javascript +specification_id: bar-stacked-percent +created: '2026-08-18T15:04:11Z' +updated: '2026-08-18T15:20:16Z' +generated_by: claude-sonnet +workflow_run: 32151522752 +issue: 2008 +language_version: 22.23.2 +library_version: 4.4.7 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/bar-stacked-percent/javascript/chartjs/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/bar-stacked-percent/javascript/chartjs/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/bar-stacked-percent/javascript/chartjs/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/bar-stacked-percent/javascript/chartjs/plot-dark.html +quality_score: 95 +review: + strengths: + - 'Repair attempt fully addresses every weakness from Attempt 1: percentage labels + now render inside each segment via a custom afterDatasetsDraw plugin, AWS''s persistent + lead is highlighted with a dedicated ink-colored border, subtle page-bg seams + separate the remaining segments, and the tooltip title callback now surfaces the + quarter''s total revenue' + - Per-segment label color is computed from fill luminance (YIQ) rather than hardcoded, + so text stays legible against every one of the five distinct fill colors in both + themes -- verified by hand for all five hues in both light and dark + - 'Full Imprint palette compliance: AWS is #009E73 (position 1), Azure/Google Cloud/Alibaba + Cloud follow canonical positions 2-4, and ''Other'' correctly uses the theme-adaptive + muted anchor rather than consuming a categorical slot' + - Theme-adaptive chrome is flawless in both renders -- no dark-on-dark or light-on-light + failures, and the AWS leader-border correctly flips from black (light) to off-white + (dark) via t.ink + - Correct 100% stacked-bar normalization -- every quarter's segments sum to exactly + 100%, and the underlying raw-dollar totals are exposed via the tooltip for context + - 'Genuine Chart.js plugin-system usage (custom afterDatasetsDraw plugin registered + via `plugins: [segmentPercentLabels]`) is a meaningfully more distinctive showcase + of the library than the Attempt-1 bar-gap tuning' + - Realistic, neutral, business-appropriate dataset (public cloud market share by + provider/quarter) with plausible, gradually shifting proportions + weaknesses: + - Bar segments still use square corners -- a subtle borderRadius on the outer (top/bottom) + corners of each stack would add one more touch of polish, though this is a nice-to-have, + not a defect + - The custom label plugin hardcodes '600 13px sans-serif' instead of referencing + a shared font-family token -- purely cosmetic, no legibility impact observed in + either render + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (~#FAF8F1), not pure white -- correct. + Chrome: Title "bar-stacked-percent · javascript · chartjs · anyplot.ai" in dark ink, centered top, ~55-60% of plot width (no overflow). X-axis title "Quarter" and Y-axis title "Share of Cloud Infrastructure Revenue" both dark and clearly legible. Y-axis ticks at 0/20/40/60/80/100% with a "%" suffix, subtle horizontal-only gridlines (x-axis grid intentionally off). Legend below the plot with all five swatches (AWS, Azure, Google Cloud, Alibaba Cloud, Other), fully readable. + Data: Six stacked bars (Q1 2023 - Q2 2024), each normalized to exactly 100%, bottom-to-top order AWS (brand green #009E73) / Azure (lavender) / Google Cloud (blue) / Alibaba Cloud (ochre) / Other (muted gray, theme-adaptive semantic anchor, not a categorical slot). Every segment carries a rounded percentage label, with per-segment text color (dark or light) chosen for contrast against that segment's fill -- verified correct for all five hues. AWS's bar has a distinct black ink outline on all four sides, calling out its persistent lead versus the other providers' subtle 1px page-bg seams. + Legibility verdict: PASS -- no light-on-light issues anywhere. + + Dark render (plot-dark.png): + Background: Warm near-black (~#1A1A17), not pure black -- correct. + Chrome: Same layout and title, rendered in light ink/off-white -- fully legible. Y-axis ticks, axis titles, and legend text are all clearly visible against the near-black surface. + Data: AWS, Azure, Google Cloud, and Alibaba Cloud fill colors are pixel-identical to the light render, confirming categorical color stability across themes. The "Other" segment correctly flips from dark muted gray to light muted gray (expected, since muted is a theme-adaptive anchor, not a fixed categorical color). Percentage labels remain legible in every segment (their color flips appropriately: e.g. "Other" labels flip from white to dark since its background lightened). AWS's leader-border correctly flips from black ink to off-white ink to stay visible against the dark background. + Legibility verdict: PASS -- no dark-on-dark failures anywhere. + + Canvas dimension gate: /tmp/anyplot-canvas-gate.txt does not exist -- gate passed, dimensions confirmed at the 3200x1800 landscape target. + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: All text readable in both themes; per-segment adaptive label color + (YIQ luminance) verified correct for all five fill colors + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No collisions between text, labels, legend, or data + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: All segments and their labels clearly visible; MIN_LABEL_HEIGHT gate + keeps small segments from clutter (all segments in this data exceed it) + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Adequate contrast, CVD-safe Imprint palette, no red-green sole signal + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: 3200x1800 confirmed (gate file absent), good proportions, nothing + cut off + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Descriptive axis titles with units (%, revenue share) + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: AWS=#009E73 first series, canonical order for Azure/Google/Alibaba, + 'Other' correctly uses theme-adaptive muted anchor, correct backgrounds + both themes + design_excellence: + score: 16 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Custom per-segment percentage labels with luminance-aware text color + and leader-highlight border show real polish beyond library defaults + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: Subtle single-axis grid, generous whitespace, subtle page-bg seams + between segments (style guide 'Bar edges') + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: AWS's ink-outlined bar creates a clear focal point calling out the + persistent market leader; percentage labels make exact composition scannable + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct 100% stacked bar, normalized per-category + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Distinct colors, clear legend, percentage labels within segments + (spec's 'consider adding' suggestion implemented), consistent component + order + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=quarter, Y=normalized % share, stacked segments sum to exactly + 100% + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches required format exactly; legend labels match provider + names + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Composition trend over 6 quarters, all 5 providers, normalized shares + plus raw-total tooltip context + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Plausible, neutral public-cloud market-share data with gradual realistic + share shifts + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Sensible dollar and percentage magnitudes for the domain + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: No unnecessary classes; the one helper plugin is proportionate to + the feature it implements + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Deterministic hardcoded data arrays + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports; uses the provided Chart global only + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Appropriate complexity, no fake UI/interactivity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Correct mount-node contract, animation:false, single canvas append, + current API + library_mastery: + score: 9 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: Idiomatic stacked-bar scales, tooltip callbacks, plugin registration + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: Custom afterDatasetsDraw plugin is a genuine Chart.js-specific extensibility + feature; docked slightly since it's the single distinctive feature rather + than layering several (e.g. borderRadius, scriptable options) + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: + - normalization + styling: + - edge-highlighting