From cd41e7bf1f8fa8a2018d9406c94cb5867595b3b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 15:42:37 +0000 Subject: [PATCH 1/5] feat(chartjs): implement choropleth-basic --- .../implementations/javascript/chartjs.js | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 plots/choropleth-basic/implementations/javascript/chartjs.js diff --git a/plots/choropleth-basic/implementations/javascript/chartjs.js b/plots/choropleth-basic/implementations/javascript/chartjs.js new file mode 100644 index 00000000000..194a5270607 --- /dev/null +++ b/plots/choropleth-basic/implementations/javascript/chartjs.js @@ -0,0 +1,195 @@ +// anyplot.ai +// choropleth-basic: Choropleth Map with Regional Coloring +// Library: chartjs 4.4.7 | JavaScript 22 +// Quality: pending | Created: 2026-09-02 + +// Chart.js has no built-in geographic/shape geometry (that lives only in the +// chartjs-chart-geo plugin, which is not installed in this runtime — see +// prompts/library/chartjs.md "No Workarounds"). This renders the same +// region -> value story as a tile map: one square per region, positioned by +// its real-world longitude/latitude, colored on the Imprint sequential scale. +// Region "boundaries" are the square's stroke; missing data renders as a +// muted gray tile, per the specification's data-handling note. +const t = window.ANYPLOT_TOKENS; +const MUTED = t.theme === "dark" ? "#A8A79F" : "#6B6A63"; + +// --- Data (in-memory, deterministic) ---------------------------------------- +// Renewable share of electricity generation (%) by country, with approximate +// centroid coordinates. Two countries carry no reading to demonstrate the +// missing-data handling the spec calls for. +const countries = [ + { code: "CA", lon: -106.3, lat: 56.1, value: 68 }, + { code: "US", lon: -95.7, lat: 37.1, value: 21 }, + { code: "MX", lon: -102.6, lat: 23.6, value: 24 }, + { code: "BR", lon: -51.9, lat: -14.2, value: 84 }, + { code: "AR", lon: -63.6, lat: -38.4, value: 33 }, + { code: "CO", lon: -74.1, lat: 4.6, value: 70 }, + { code: "PE", lon: -75.2, lat: -9.2, value: 60 }, + { code: "CL", lon: -71.5, lat: -35.7, value: 46 }, + { code: "GB", lon: -3.4, lat: 55.4, value: 43 }, + { code: "FR", lon: 2.2, lat: 46.6, value: 27 }, + { code: "DE", lon: 10.5, lat: 51.2, value: 46 }, + { code: "ES", lon: -3.7, lat: 40.5, value: 50 }, + { code: "IT", lon: 12.6, lat: 41.9, value: 41 }, + { code: "NO", lon: 8.5, lat: 60.5, value: 98 }, + { code: "SE", lon: 18.6, lat: 60.1, value: 68 }, + { code: "PL", lon: 19.1, lat: 51.9, value: 17 }, + { code: "RU", lon: 105.3, lat: 61.5, value: 20 }, + { code: "TR", lon: 35.2, lat: 38.9, value: 44 }, + { code: "EG", lon: 30.8, lat: 26.8, value: 12 }, + { code: "NG", lon: 8.7, lat: 9.1, value: null }, + { code: "ZA", lon: 22.9, lat: -30.6, value: 9 }, + { code: "KE", lon: 37.9, lat: -0.0, value: 90 }, + { code: "MA", lon: -7.1, lat: 31.8, value: 20 }, + { code: "SA", lon: 45.1, lat: 23.9, value: 1 }, + { code: "IN", lon: 78.9, lat: 20.6, value: 23 }, + { code: "CN", lon: 104.1, lat: 35.9, value: 31 }, + { code: "JP", lon: 138.3, lat: 36.2, value: 22 }, + { code: "KR", lon: 127.8, lat: 35.9, value: 9 }, + { code: "ID", lon: 113.9, lat: -0.8, value: null }, + { code: "TH", lon: 100.9, lat: 15.9, value: 18 }, + { code: "VN", lon: 108.8, lat: 14.1, value: 40 }, + { code: "AU", lon: 133.8, lat: -25.3, value: 32 }, + { code: "NZ", lon: 174.9, lat: -40.9, value: 87 }, +]; + +// --- Imprint sequential scale (green -> blue), binned for a readable legend +const hexToRgb = (hex) => ({ + r: parseInt(hex.slice(1, 3), 16), + g: parseInt(hex.slice(3, 5), 16), + b: parseInt(hex.slice(5, 7), 16), +}); +const lerp = (a, b, f) => Math.round(a + (b - a) * f); +const seqRgb = (frac) => { + const c0 = hexToRgb(t.seq[0]); + const c1 = hexToRgb(t.seq[1]); + return { r: lerp(c0.r, c1.r, frac), g: lerp(c0.g, c1.g, frac), b: lerp(c0.b, c1.b, frac) }; +}; +const toCss = ({ r, g, b }) => `rgb(${r}, ${g}, ${b})`; +// WCAG-ish relative luminance so a code label always reads against its own tile, +// regardless of theme (tile fill colors are identical across themes). +const luminance = ({ r, g, b }) => 0.299 * r + 0.587 * g + 0.114 * b; +const LABEL_LIGHT = "#FFFDF6"; +const LABEL_DARK = "#1A1A17"; + +const bins = [ + { label: "< 10%", max: 10 }, + { label: "10–25%", max: 25 }, + { label: "25–40%", max: 40 }, + { label: "40–65%", max: 65 }, + { label: "≥ 65%", max: Infinity }, +].map((bin, i, arr) => { + const rgb = seqRgb(i / (arr.length - 1)); + return { ...bin, color: toCss(rgb), textColor: luminance(rgb) > 140 ? LABEL_DARK : LABEL_LIGHT }; +}); + +const binIndex = (value) => bins.findIndex((bin) => value <= bin.max); +const mutedTextColor = luminance(hexToRgb(MUTED)) > 140 ? LABEL_DARK : LABEL_LIGHT; + +// --- Mount ------------------------------------------------------------------- +const canvas = document.createElement("canvas"); +document.getElementById("container").appendChild(canvas); + +// A tile-map has no room for chartjs-chart-geo's boundary shapes (not +// installed — see prompts/library/chartjs.md), so region codes are drawn +// directly onto each square with a plain Chart.js plugin (native canvas +// access, no external package) instead. +const regionLabelPlugin = { + id: "regionLabels", + afterDatasetsDraw(chart) { + const { ctx } = chart; + chart.data.datasets.forEach((dataset, dsIndex) => { + const meta = chart.getDatasetMeta(dsIndex); + meta.data.forEach((point, i) => { + const raw = dataset.data[i]; + ctx.save(); + ctx.font = "600 12px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"; + ctx.fillStyle = dataset.textColor; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(raw.code, point.x, point.y); + ctx.restore(); + }); + }); + }, +}; + +// --- Chart --------------------------------------------------------------- +const datasets = bins.map((bin, i) => ({ + label: `${bin.label} renewable`, + data: countries.filter((c) => c.value !== null && binIndex(c.value) === i), + backgroundColor: bin.color, + textColor: bin.textColor, + borderColor: t.ink, + borderWidth: 1.5, + pointStyle: "rect", + pointRadius: 20, + pointHoverRadius: 22, +})); +datasets.push({ + label: "No data", + data: countries.filter((c) => c.value === null), + backgroundColor: MUTED, + textColor: mutedTextColor, + borderColor: t.ink, + borderWidth: 1.5, + pointStyle: "rect", + pointRadius: 20, + pointHoverRadius: 22, +}); + +const title = "Renewable Electricity Share · choropleth-basic · javascript · chartjs · anyplot.ai"; +const titleFontSize = Math.round(22 * Math.min(1, 67 / title.length)); + +new Chart(canvas, { + type: "scatter", + data: { + datasets: datasets.map((d) => ({ + ...d, + data: d.data.map((c) => ({ x: c.lon, y: c.lat, code: c.code, value: c.value })), + })), + }, + plugins: [regionLabelPlugin], + options: { + responsive: true, + maintainAspectRatio: false, + animation: false, + layout: { padding: { top: 8, bottom: 8, left: 16, right: 16 } }, + plugins: { + title: { display: true, text: title, color: t.ink, font: { size: titleFontSize, weight: "500" } }, + legend: { + position: "bottom", + labels: { color: t.inkSoft, font: { size: 16 }, boxWidth: 22, boxHeight: 22, padding: 18 }, + }, + tooltip: { + callbacks: { + title: () => "", + label: (ctx) => + ctx.raw.value === null || ctx.raw.value === undefined + ? `${ctx.raw.code}: no data` + : `${ctx.raw.code}: ${ctx.raw.value}% renewable`, + }, + }, + }, + scales: { + x: { + type: "linear", + min: -170, + max: 179, + display: true, + border: { display: false }, + ticks: { display: false }, + grid: { color: t.grid }, + }, + y: { + type: "linear", + min: -58, + max: 82, + display: true, + border: { display: false }, + ticks: { display: false }, + grid: { color: t.grid }, + }, + }, + }, +}); From d7078f170fa174d45c6df372d896fe95e709c097 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 15:42:50 +0000 Subject: [PATCH 2/5] chore(chartjs): add metadata for choropleth-basic --- .../metadata/javascript/chartjs.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/choropleth-basic/metadata/javascript/chartjs.yaml diff --git a/plots/choropleth-basic/metadata/javascript/chartjs.yaml b/plots/choropleth-basic/metadata/javascript/chartjs.yaml new file mode 100644 index 00000000000..5dee0aedbb5 --- /dev/null +++ b/plots/choropleth-basic/metadata/javascript/chartjs.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for chartjs implementation of choropleth-basic +# Auto-generated by impl-generate.yml + +library: chartjs +language: javascript +specification_id: choropleth-basic +created: '2026-09-02T15:42:50Z' +updated: '2026-09-02T15:42:50Z' +generated_by: claude-sonnet +workflow_run: 33647965470 +issue: 3069 +language_version: 22.23.2 +library_version: 4.4.7 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-dark.html +quality_score: null +review: + strengths: [] + weaknesses: [] From 5ef51d82779cc0a7f538633c577d5d9993d28140 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 15:48:14 +0000 Subject: [PATCH 3/5] chore(chartjs): update quality score 79 and review feedback for choropleth-basic --- .../implementations/javascript/chartjs.js | 4 +- .../metadata/javascript/chartjs.yaml | 276 +++++++++++++++++- 2 files changed, 271 insertions(+), 9 deletions(-) diff --git a/plots/choropleth-basic/implementations/javascript/chartjs.js b/plots/choropleth-basic/implementations/javascript/chartjs.js index 194a5270607..39cf1c981b3 100644 --- a/plots/choropleth-basic/implementations/javascript/chartjs.js +++ b/plots/choropleth-basic/implementations/javascript/chartjs.js @@ -1,7 +1,7 @@ // anyplot.ai // choropleth-basic: Choropleth Map with Regional Coloring -// Library: chartjs 4.4.7 | JavaScript 22 -// Quality: pending | Created: 2026-09-02 +// Library: chartjs 4.4.7 | JavaScript 22.23.2 +// Quality: 79/100 | Created: 2026-09-02 // Chart.js has no built-in geographic/shape geometry (that lives only in the // chartjs-chart-geo plugin, which is not installed in this runtime — see diff --git a/plots/choropleth-basic/metadata/javascript/chartjs.yaml b/plots/choropleth-basic/metadata/javascript/chartjs.yaml index 5dee0aedbb5..afa326e284a 100644 --- a/plots/choropleth-basic/metadata/javascript/chartjs.yaml +++ b/plots/choropleth-basic/metadata/javascript/chartjs.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for chartjs implementation of choropleth-basic -# Auto-generated by impl-generate.yml - library: chartjs language: javascript specification_id: choropleth-basic created: '2026-09-02T15:42:50Z' -updated: '2026-09-02T15:42:50Z' +updated: '2026-09-02T15:48:14Z' generated_by: claude-sonnet workflow_run: 33647965470 issue: 3069 @@ -15,7 +12,272 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/choroplet preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-dark.html -quality_score: null +quality_score: 79 review: - strengths: [] - weaknesses: [] + strengths: + - 'Excellent theme-adaptive rendering: data tile colors are pixel-identical between + light and dark renders, while chrome (background #FAF8F1/#1A1A17, title, legend + text) correctly flips with no dark-on-dark or light-on-light failures' + - Highly realistic, factually accurate renewable-electricity-share data for 32 countries + spanning every continent (Norway 98%, Brazil 84%, Saudi Arabia 1%, South Korea + 9%) that closely matches real-world figures + - 'Honest, well-documented handling of Chart.js''s lack of native geo/polygon support: + no chartjs-chart-geo plugin, no fake interactivity, code comments transparently + explain the tile-map substitution' + - 'Missing-data handling implemented exactly per spec: two countries render as muted + gray ''No data'' tiles with a dedicated legend entry' + - Per-tile text color is chosen via a luminance check so every 2-letter country + code stays legible regardless of the underlying sequential fill color + - Clean, deterministic, near-KISS code; correct mandated title format; legend labels + precisely match the five value bins plus the no-data category + weaknesses: + - 'SC-01/SC-02: This is not a true choropleth - there are no shaded region polygons/boundaries + and no map projection or basemap. It reads as a scatter/tile grid of colored squares + positioned by longitude/latitude rather than a geographic map. Chart.js core genuinely + cannot fill geographic polygons without the disallowed chartjs-chart-geo plugin, + so a literal choropleth may be infeasible - but the repair loop should get closer: + draw a simple continent-outline basemap as additional line datasets (an array + of [lon, lat] points per landmass stroked with t.grid) so the tiles read as sitting + on Earth instead of floating in blank space.' + - 'Sequential color direction is semantically backward: the worst bin (''< 10% renewable'') + is rendered in brand green #009E73 while the best bin (''>= 65% renewable'') is + blue - most readers associate green with high renewable share, so mapping green + to the lowest performers undercuts the story. Reverse the imprint_seq interpolation + (seqRgb should go from t.seq[1] toward t.seq[0] as the bin value increases, or + otherwise re-map which bin gets which end of the scale) so high-renewable countries + read as more green.' + - 'DE-01/DE-02: currently looks like a plain scatter grid rather than a polished + map - no basemap texture or land/ocean distinction, and the visible Cartesian + gridlines read as chart gridlines rather than a map graticule. A subtle basemap + outline (see above) and toning down or removing the x/y grid would raise aesthetic + sophistication and visual refinement.' + - 'VQ-05: canvas utilization is uneven with large empty stretches (e.g., northern + and far-southeastern regions) because the linear axis bounds (-170..179 / -58..82) + are wider than the actual data''s bounding box - tightening the min/max to the + real data extent would reduce wasted whitespace and better fill the canvas.' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white, matches the required #FAF8F1 surface (not pure white, not dark). + Chrome: Title "Renewable Electricity Share · choropleth-basic · javascript · chartjs · anyplot.ai" in dark ink, centered top, clearly legible. Bottom legend has six horizontal swatches ("< 10% renewable" ... "No data") with dark-ink labels, fully readable. Faint Cartesian gridlines cross the canvas (a chart-style grid, not a map graticule). + Data: 32 country tiles (squares with rounded rect point style) positioned by longitude/latitude, filled with the Imprint sequential scale (green -> blue) binned into 5 buckets, plus 2 muted-gray "No data" tiles (NG, ID). Each tile carries a 2-letter country code in a contrast-appropriate text color (light or dark depending on tile luminance). No landmass outlines, ocean/land distinction, or region boundaries are present - the layout reads as a scatter/tile map rather than a shaded-polygon choropleth. + Legibility verdict: PASS - all title, legend, and in-tile text is clearly readable against the light background; no light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black, matches the required #1A1A17 surface (not pure black, not light). + Chrome: Title and legend text render in light ink, clearly visible against the dark background. Gridlines are the same faint style, still subtle and non-competing. + Data: Tile fill colors are pixel-identical to the light render (confirmed - only chrome flipped, not data). In-tile country-code text colors are still legible per-tile thanks to the luminance-based text-color logic; no dark-on-dark failures observed on any tile, including the muted-gray "No data" tiles. + Legibility verdict: PASS - all text remains readable in the dark theme; theme adaptation is correctly implemented throughout. + criteria_checklist: + visual_quality: + score: 26 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 6 + max: 8 + passed: true + comment: Title fontsize scales with title length and is clearly legible; legend + labels at 16px are readable; in-tile 12px country codes are small relative + to the 3200x1800 canvas but remain legible in both themes + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping tiles, labels, or legend entries in either render + - id: VQ-03 + name: Element Visibility + score: 5 + max: 6 + passed: true + comment: Uniform 20px-radius square tiles are clearly visible for 32 data + points; size does not vary with data value but density is moderate so this + is acceptable + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Per-tile luminance-based text color plus redundant text labels (country + codes) avoid any hue-only signal; good contrast in both themes + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: Tiles and legend are well distributed but axis bounds are wider than + the actual data extent, leaving uneven empty margins in the north and far + southeast + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Cartesian axes are intentionally unlabeled (appropriate for a map-style + plot); the legend itself carries descriptive, unit-bearing labels ('% renewable') + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: Correctly built from imprint_seq (t.seq[0]->t.seq[1]) for single-polarity + continuous data; identical data colors across themes; both backgrounds theme-correct; + no other colormap used + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: true + comment: Well-configured but reads as a plain scatter/tile grid rather than + a polished map - no basemap, land/ocean distinction, or cartographic texture + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Subtle grid, generous legend padding, edge-highlighted tiles; grid + still reads as chart gridlines rather than a map graticule + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Real geographic clustering (Nordic countries high, Middle East low) + gives some visual hierarchy, though the green-for-low/blue-for-high color + mapping works against the intended story + spec_compliance: + score: 10 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 2 + max: 5 + passed: false + comment: Spec requires a choropleth (shaded geographic regions); implementation + is a coordinate-positioned tile/scatter map with no region polygons - a + fundamentally different, though honestly-labeled, visualization approach + necessitated by Chart.js's lack of core geo support + - id: SC-02 + name: Required Features + score: 2 + max: 4 + passed: false + comment: Color legend present and missing-data handling present (both required); + no map projection and no region boundaries (both required by spec notes) + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=longitude, Y=latitude, color=value; all 32 data points visible, + correctly mapped + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches '{Descriptive Title} · {spec-id} · {language} · {library} + · anyplot.ai' format exactly; legend labels correctly describe the bins + and no-data category + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Full value range from 1% to 98% renewable share, geographically diverse + across all continents, includes explicit missing-data cases + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Renewable electricity share by country is a real, neutral, comprehensible + energy/statistics scenario + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Values closely track real-world figures (Norway 98% hydro, Brazil + 84%, Saudi Arabia 1%, South Korea 9%, Poland 17%) - factually well researched + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: true + comment: Mostly linear data->mount->chart flow, but several small helper arrow + functions (hexToRgb, lerp, seqRgb, luminance, binIndex) are needed to build + the binned continuous scale + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fully deterministic hardcoded data array + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No imports beyond the provided globals; nothing unused + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean and appropriately complex for the color-scale/legend construction + it performs; no fake UI or fake interactivity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: 'Follows the mount-node contract correctly: creates and appends canvas, + animation:false, no manual file save' + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Idiomatic use of per-bin datasets for automatic legend generation, + tooltip callbacks, and the documented plugin lifecycle hook (afterDatasetsDraw) + rather than any low-level workaround + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Custom Chart.js plugin using getDatasetMeta/afterDatasetsDraw for + direct canvas text rendering is a Chart.js-specific technique, though the + overall tile-map approach itself is not unique to this library + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: + - binning + styling: + - custom-colormap + - edge-highlighting From e3bfa8850c5b807a37b6945daba3e183573d5d87 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 15:56:59 +0000 Subject: [PATCH 4/5] fix(chartjs): address review feedback for choropleth-basic Attempt 1/4 - fixes based on AI review --- .../implementations/javascript/chartjs.js | 87 ++++++++++++++++--- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/plots/choropleth-basic/implementations/javascript/chartjs.js b/plots/choropleth-basic/implementations/javascript/chartjs.js index 39cf1c981b3..7af8dce3af1 100644 --- a/plots/choropleth-basic/implementations/javascript/chartjs.js +++ b/plots/choropleth-basic/implementations/javascript/chartjs.js @@ -79,13 +79,60 @@ const bins = [ { label: "40–65%", max: 65 }, { label: "≥ 65%", max: Infinity }, ].map((bin, i, arr) => { - const rgb = seqRgb(i / (arr.length - 1)); + // Highest-renewable bins read as brand green, lowest as blue, so the color + // story matches the metric (more green = more renewable). + const rgb = seqRgb(1 - i / (arr.length - 1)); return { ...bin, color: toCss(rgb), textColor: luminance(rgb) > 140 ? LABEL_DARK : LABEL_LIGHT }; }); const binIndex = (value) => bins.findIndex((bin) => value <= bin.max); const mutedTextColor = luminance(hexToRgb(MUTED)) > 140 ? LABEL_DARK : LABEL_LIGHT; +// --- Basemap ----------------------------------------------------------------- +// Chart.js core has no polygon-fill geometry, so this draws a lightweight +// continent-outline graticule (stroked line datasets, no fill) behind the +// tiles purely for geographic orientation — it is not a projected basemap. +const CONTINENTS = [ + [ + [-125, 49], [-95, 78], [-75, 68], [-60, 50], [-52, 47], [-65, 44], + [-75, 35], [-80, 26], [-97, 26], [-90, 15], [-105, 20], [-115, 29], [-125, 49], + ], + [ + [-77, 10], [-60, 10], [-50, 0], [-35, -5], [-40, -20], [-48, -25], + [-58, -34], [-68, -47], [-72, -40], [-70, -20], [-79, -5], [-77, 10], + ], + [ + [-17, 15], [0, 5], [10, 4], [15, -5], [13, -18], [18, -34], [30, -30], + [40, -15], [42, 0], [45, 10], [38, 15], [33, 31], [10, 37], [-17, 15], + ], + [ + [-9, 43], [0, 50], [10, 54], [20, 55], [30, 60], [40, 65], [30, 45], + [20, 40], [10, 38], [-5, 36], [-9, 43], + ], + [ + [26, 40], [40, 45], [60, 55], [80, 55], [100, 50], [120, 50], [140, 55], + [145, 45], [130, 35], [120, 25], [105, 10], [95, 5], [80, 10], [70, 20], + [60, 25], [50, 30], [35, 30], [26, 40], + ], + [ + [113, -22], [122, -18], [135, -12], [145, -16], [153, -28], [150, -38], + [140, -38], [130, -32], [115, -34], [113, -22], + ], +]; +const basemapDatasets = CONTINENTS.map((outline, i) => ({ + type: "line", + label: `basemap-${i}`, + skipLegend: true, + data: outline.map(([lon, lat]) => ({ x: lon, y: lat })), + borderColor: t.grid, + backgroundColor: "transparent", + borderWidth: 1.5, + pointRadius: 0, + pointHoverRadius: 0, + fill: false, + tension: 0, +})); + // --- Mount ------------------------------------------------------------------- const canvas = document.createElement("canvas"); document.getElementById("container").appendChild(canvas); @@ -99,6 +146,7 @@ const regionLabelPlugin = { afterDatasetsDraw(chart) { const { ctx } = chart; chart.data.datasets.forEach((dataset, dsIndex) => { + if (dataset.skipLegend) return; // basemap outline, not a data tile const meta = chart.getDatasetMeta(dsIndex); meta.data.forEach((point, i) => { const raw = dataset.data[i]; @@ -144,10 +192,14 @@ const titleFontSize = Math.round(22 * Math.min(1, 67 / title.length)); new Chart(canvas, { type: "scatter", data: { - datasets: datasets.map((d) => ({ - ...d, - data: d.data.map((c) => ({ x: c.lon, y: c.lat, code: c.code, value: c.value })), - })), + // Basemap outlines first so tile squares draw on top of them. + datasets: [ + ...basemapDatasets, + ...datasets.map((d) => ({ + ...d, + data: d.data.map((c) => ({ x: c.lon, y: c.lat, code: c.code, value: c.value })), + })), + ], }, plugins: [regionLabelPlugin], options: { @@ -159,9 +211,17 @@ new Chart(canvas, { title: { display: true, text: title, color: t.ink, font: { size: titleFontSize, weight: "500" } }, legend: { position: "bottom", - labels: { color: t.inkSoft, font: { size: 16 }, boxWidth: 22, boxHeight: 22, padding: 18 }, + labels: { + color: t.inkSoft, + font: { size: 16 }, + boxWidth: 22, + boxHeight: 22, + padding: 18, + filter: (item, data) => !data.datasets[item.datasetIndex].skipLegend, + }, }, tooltip: { + filter: (item) => !item.dataset.skipLegend, callbacks: { title: () => "", label: (ctx) => @@ -172,23 +232,26 @@ new Chart(canvas, { }, }, scales: { + // Tightened to the real longitude/latitude extent of the 32 countries + // (plus a small margin) rather than the full -180..180/-90..90 globe, + // so the canvas isn't dominated by empty ocean. x: { type: "linear", - min: -170, - max: 179, + min: -118, + max: 182, display: true, border: { display: false }, ticks: { display: false }, - grid: { color: t.grid }, + grid: { display: false }, }, y: { type: "linear", - min: -58, - max: 82, + min: -48, + max: 68, display: true, border: { display: false }, ticks: { display: false }, - grid: { color: t.grid }, + grid: { display: false }, }, }, }, From fa3de18efd1cbd75c7dd9c4ba8ba6983c51bbe3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Sep 2026 16:01:26 +0000 Subject: [PATCH 5/5] chore(chartjs): update quality score 85 and review feedback for choropleth-basic --- .../implementations/javascript/chartjs.js | 2 +- .../metadata/javascript/chartjs.yaml | 198 ++++++++---------- 2 files changed, 84 insertions(+), 116 deletions(-) diff --git a/plots/choropleth-basic/implementations/javascript/chartjs.js b/plots/choropleth-basic/implementations/javascript/chartjs.js index 7af8dce3af1..9dee29a8d31 100644 --- a/plots/choropleth-basic/implementations/javascript/chartjs.js +++ b/plots/choropleth-basic/implementations/javascript/chartjs.js @@ -1,7 +1,7 @@ // anyplot.ai // choropleth-basic: Choropleth Map with Regional Coloring // Library: chartjs 4.4.7 | JavaScript 22.23.2 -// Quality: 79/100 | Created: 2026-09-02 +// Quality: 85/100 | Created: 2026-09-02 // Chart.js has no built-in geographic/shape geometry (that lives only in the // chartjs-chart-geo plugin, which is not installed in this runtime — see diff --git a/plots/choropleth-basic/metadata/javascript/chartjs.yaml b/plots/choropleth-basic/metadata/javascript/chartjs.yaml index afa326e284a..95f558a7d6f 100644 --- a/plots/choropleth-basic/metadata/javascript/chartjs.yaml +++ b/plots/choropleth-basic/metadata/javascript/chartjs.yaml @@ -2,7 +2,7 @@ library: chartjs language: javascript specification_id: choropleth-basic created: '2026-09-02T15:42:50Z' -updated: '2026-09-02T15:48:14Z' +updated: '2026-09-02T16:01:26Z' generated_by: claude-sonnet workflow_run: 33647965470 issue: 3069 @@ -12,64 +12,47 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/choroplet preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/chartjs/plot-dark.html -quality_score: 79 +quality_score: 85 review: strengths: + - 'All three attempt-1 weaknesses were addressed directly: continent-outline basemap + adds geographic context, sequential color direction now runs green-for-high, and + axis bounds are tightened to the real data extent' - 'Excellent theme-adaptive rendering: data tile colors are pixel-identical between - light and dark renders, while chrome (background #FAF8F1/#1A1A17, title, legend - text) correctly flips with no dark-on-dark or light-on-light failures' + light and dark renders, while chrome correctly flips with no dark-on-dark or light-on-light + failures' - Highly realistic, factually accurate renewable-electricity-share data for 32 countries - spanning every continent (Norway 98%, Brazil 84%, Saudi Arabia 1%, South Korea - 9%) that closely matches real-world figures - - 'Honest, well-documented handling of Chart.js''s lack of native geo/polygon support: - no chartjs-chart-geo plugin, no fake interactivity, code comments transparently - explain the tile-map substitution' + spanning every continent + - Honest, well-documented handling of Chart.js's lack of native geo/polygon support + - no chartjs-chart-geo plugin, no fake interactivity - 'Missing-data handling implemented exactly per spec: two countries render as muted - gray ''No data'' tiles with a dedicated legend entry' - - Per-tile text color is chosen via a luminance check so every 2-letter country - code stays legible regardless of the underlying sequential fill color - - Clean, deterministic, near-KISS code; correct mandated title format; legend labels - precisely match the five value bins plus the no-data category + gray No data tiles with a dedicated legend entry' + - Per-tile text color chosen via luminance check so every country code stays legible + regardless of the underlying fill color weaknesses: - - 'SC-01/SC-02: This is not a true choropleth - there are no shaded region polygons/boundaries - and no map projection or basemap. It reads as a scatter/tile grid of colored squares - positioned by longitude/latitude rather than a geographic map. Chart.js core genuinely - cannot fill geographic polygons without the disallowed chartjs-chart-geo plugin, - so a literal choropleth may be infeasible - but the repair loop should get closer: - draw a simple continent-outline basemap as additional line datasets (an array - of [lon, lat] points per landmass stroked with t.grid) so the tiles read as sitting - on Earth instead of floating in blank space.' - - 'Sequential color direction is semantically backward: the worst bin (''< 10% renewable'') - is rendered in brand green #009E73 while the best bin (''>= 65% renewable'') is - blue - most readers associate green with high renewable share, so mapping green - to the lowest performers undercuts the story. Reverse the imprint_seq interpolation - (seqRgb should go from t.seq[1] toward t.seq[0] as the bin value increases, or - otherwise re-map which bin gets which end of the scale) so high-renewable countries - read as more green.' - - 'DE-01/DE-02: currently looks like a plain scatter grid rather than a polished - map - no basemap texture or land/ocean distinction, and the visible Cartesian - gridlines read as chart gridlines rather than a map graticule. A subtle basemap - outline (see above) and toning down or removing the x/y grid would raise aesthetic - sophistication and visual refinement.' - - 'VQ-05: canvas utilization is uneven with large empty stretches (e.g., northern - and far-southeastern regions) because the linear axis bounds (-170..179 / -58..82) - are wider than the actual data''s bounding box - tightening the min/max to the - real data extent would reduce wasted whitespace and better fill the canvas.' + - 'SC-01/SC-02: Still not a literal choropleth - no filled region polygons or map + projection, though this is a genuine Chart.js core limitation and the basemap + is the right compensating move' + - 'CQ-01: The half-dozen small helper functions (hexToRgb, lerp, seqRgb, toCss, + luminance, binIndex) push slightly past pure KISS, though each is justified by + the binned-continuous-scale requirement' + - Continent outlines are stylized/approximate rather than accurate coastlines - + acceptable given the constraints, but worth noting as a simplification image_description: |- Light render (plot-light.png): - Background: Warm off-white, matches the required #FAF8F1 surface (not pure white, not dark). - Chrome: Title "Renewable Electricity Share · choropleth-basic · javascript · chartjs · anyplot.ai" in dark ink, centered top, clearly legible. Bottom legend has six horizontal swatches ("< 10% renewable" ... "No data") with dark-ink labels, fully readable. Faint Cartesian gridlines cross the canvas (a chart-style grid, not a map graticule). - Data: 32 country tiles (squares with rounded rect point style) positioned by longitude/latitude, filled with the Imprint sequential scale (green -> blue) binned into 5 buckets, plus 2 muted-gray "No data" tiles (NG, ID). Each tile carries a 2-letter country code in a contrast-appropriate text color (light or dark depending on tile luminance). No landmass outlines, ocean/land distinction, or region boundaries are present - the layout reads as a scatter/tile map rather than a shaded-polygon choropleth. - Legibility verdict: PASS - all title, legend, and in-tile text is clearly readable against the light background; no light-on-light issues. + Background: Warm off-white, matches #FAF8F1, not pure white. + Chrome: Title "Renewable Electricity Share · choropleth-basic · javascript · chartjs · anyplot.ai" in dark ink, centered top, clearly legible. Bottom legend with six horizontal swatches and dark-ink labels, all readable. A lightweight continent-outline graticule (thin gray strokes, no fill) sits behind the tiles for geographic context. + Data: 32 country tiles (square markers) positioned by longitude/latitude, filled with the Imprint sequential scale (imprint_seq) binned into 5 buckets. Direction is correct: high-renewable countries (Norway, Brazil, Colombia, Kenya, New Zealand) render brand green #009E73; low-renewable countries (Saudi Arabia, South Africa, South Korea) render blue. 2 muted-gray "No data" tiles (NG, ID) with a dedicated legend entry. Each tile carries a legible 2-letter country code. + Legibility verdict: PASS Dark render (plot-dark.png): - Background: Warm near-black, matches the required #1A1A17 surface (not pure black, not light). - Chrome: Title and legend text render in light ink, clearly visible against the dark background. Gridlines are the same faint style, still subtle and non-competing. - Data: Tile fill colors are pixel-identical to the light render (confirmed - only chrome flipped, not data). In-tile country-code text colors are still legible per-tile thanks to the luminance-based text-color logic; no dark-on-dark failures observed on any tile, including the muted-gray "No data" tiles. - Legibility verdict: PASS - all text remains readable in the dark theme; theme adaptation is correctly implemented throughout. + Background: Warm near-black, matches #1A1A17, not pure black. + Chrome: Title and legend render in light ink, clearly visible. Continent outlines remain visible (stroked in the theme's grid color) against the dark background. + Data: Tile fill colors are pixel-identical to the light render - confirmed, only chrome flipped. In-tile country-code text colors remain legible per-tile via luminance-based logic; no dark-on-dark failures on any tile, including the muted-gray "No data" tiles. + Legibility verdict: PASS criteria_checklist: visual_quality: - score: 26 + score: 27 max: 30 items: - id: VQ-01 @@ -77,114 +60,103 @@ review: score: 6 max: 8 passed: true - comment: Title fontsize scales with title length and is clearly legible; legend - labels at 16px are readable; in-tile 12px country codes are small relative - to the 3200x1800 canvas but remain legible in both themes + comment: Title scales with length and is clearly legible; legend at 16px; + in-tile 12px country codes are small but legible in both themes - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No overlapping tiles, labels, or legend entries in either render + comment: No overlapping tiles, labels, legend entries, or basemap strokes - id: VQ-03 name: Element Visibility score: 5 max: 6 passed: true - comment: Uniform 20px-radius square tiles are clearly visible for 32 data - points; size does not vary with data value but density is moderate so this - is acceptable + comment: Uniform 20px-radius tiles clearly visible for 32 points; size doesn't + vary with value - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Per-tile luminance-based text color plus redundant text labels (country - codes) avoid any hue-only signal; good contrast in both themes + comment: Luminance-based per-tile text color plus redundant country-code labels + avoid hue-only signaling - id: VQ-05 name: Layout & Canvas - score: 3 + score: 4 max: 4 passed: true - comment: Tiles and legend are well distributed but axis bounds are wider than - the actual data extent, leaving uneven empty margins in the north and far - southeast + comment: Axis bounds now tightened to the real data extent, removing excess + margin flagged in attempt 1 - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Cartesian axes are intentionally unlabeled (appropriate for a map-style - plot); the legend itself carries descriptive, unit-bearing labels ('% renewable') + comment: Cartesian axes intentionally unlabeled for map style; legend carries + descriptive labels - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: Correctly built from imprint_seq (t.seq[0]->t.seq[1]) for single-polarity - continuous data; identical data colors across themes; both backgrounds theme-correct; - no other colormap used + comment: Built from imprint_seq, direction now correct (green = high); identical + data colors across themes design_excellence: - score: 12 + score: 15 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 4 + score: 5 max: 8 passed: true - comment: Well-configured but reads as a plain scatter/tile grid rather than - a polished map - no basemap, land/ocean distinction, or cartographic texture + comment: Continent-outline basemap meaningfully improves polish; still a positioned-tile + map, not a filled-region choropleth - id: DE-02 name: Visual Refinement - score: 4 + score: 5 max: 6 passed: true - comment: Subtle grid, generous legend padding, edge-highlighted tiles; grid - still reads as chart gridlines rather than a map graticule + comment: Basemap strokes read as a map graticule; tiles remain crisply edge-highlighted - id: DE-03 name: Data Storytelling - score: 4 + score: 5 max: 6 passed: true - comment: Real geographic clustering (Nordic countries high, Middle East low) - gives some visual hierarchy, though the green-for-low/blue-for-high color - mapping works against the intended story + comment: Green-for-high/blue-for-low now matches reader intuition; geographic + layout creates clear narrative spec_compliance: - score: 10 + score: 12 max: 15 items: - id: SC-01 name: Plot Type - score: 2 + score: 3 max: 5 - passed: false - comment: Spec requires a choropleth (shaded geographic regions); implementation - is a coordinate-positioned tile/scatter map with no region polygons - a - fundamentally different, though honestly-labeled, visualization approach - necessitated by Chart.js's lack of core geo support + passed: true + comment: Not a true choropleth (no filled polygons), but the continent-outline + basemap closes the gap given Chart.js core's limitations - id: SC-02 name: Required Features - score: 2 + score: 3 max: 4 - passed: false - comment: Color legend present and missing-data handling present (both required); - no map projection and no region boundaries (both required by spec notes) + passed: true + comment: Legend, missing-data handling, and geographic basemap context present; + no real map projection or region boundaries - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=longitude, Y=latitude, color=value; all 32 data points visible, - correctly mapped + comment: X=longitude, Y=latitude, color=value; all 32 points correctly visible - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title matches '{Descriptive Title} · {spec-id} · {language} · {library} - · anyplot.ai' format exactly; legend labels correctly describe the bins - and no-data category + comment: Title format correct; legend labels correctly describe bins + no-data data_quality: score: 15 max: 15 @@ -194,22 +166,20 @@ review: score: 6 max: 6 passed: true - comment: Full value range from 1% to 98% renewable share, geographically diverse - across all continents, includes explicit missing-data cases + comment: Full 1%-98% range, all continents represented, includes missing data - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Renewable electricity share by country is a real, neutral, comprehensible - energy/statistics scenario + comment: Renewable electricity share is a real, neutral, comprehensible scenario - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Values closely track real-world figures (Norway 98% hydro, Brazil - 84%, Saudi Arabia 1%, South Korea 9%, Poland 17%) - factually well researched + comment: Values closely track real-world figures (Norway 98%, Brazil 84%, + Saudi Arabia 1%, South Korea 9%) code_quality: score: 9 max: 10 @@ -219,35 +189,32 @@ review: score: 2 max: 3 passed: true - comment: Mostly linear data->mount->chart flow, but several small helper arrow - functions (hexToRgb, lerp, seqRgb, luminance, binIndex) are needed to build - the binned continuous scale + comment: Mostly linear flow, but several small helper functions for the binned + continuous scale and basemap - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic hardcoded data array + comment: Fully deterministic hardcoded data - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: No imports beyond the provided globals; nothing unused + comment: No unused imports - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Clean and appropriately complex for the color-scale/legend construction - it performs; no fake UI or fake interactivity + comment: Appropriately complex, no fake functionality - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: 'Follows the mount-node contract correctly: creates and appends canvas, - animation:false, no manual file save' + comment: Follows the mount-node contract correctly library_mastery: score: 7 max: 10 @@ -257,22 +224,22 @@ review: score: 4 max: 5 passed: true - comment: Idiomatic use of per-bin datasets for automatic legend generation, - tooltip callbacks, and the documented plugin lifecycle hook (afterDatasetsDraw) - rather than any low-level workaround + comment: Per-bin datasets for automatic legend, tooltip callbacks, documented + plugin lifecycle hook - id: LM-02 name: Distinctive Features score: 3 max: 5 - passed: true - comment: Custom Chart.js plugin using getDatasetMeta/afterDatasetsDraw for - direct canvas text rendering is a Chart.js-specific technique, though the - overall tile-map approach itself is not unique to this library - verdict: REJECTED + passed: false + comment: Custom plugin for label drawing plus line-dataset basemap is Chart.js-specific + composition, though tile-map substitution itself isn't unique to the library + verdict: APPROVED impl_tags: dependencies: [] techniques: + - colorbar - annotations + - custom-legend patterns: - data-generation - iteration-over-groups @@ -281,3 +248,4 @@ impl_tags: styling: - custom-colormap - edge-highlighting + - publication-ready