diff --git a/plots/choropleth-basic/implementations/javascript/echarts.js b/plots/choropleth-basic/implementations/javascript/echarts.js new file mode 100644 index 0000000000..d8e1746d09 --- /dev/null +++ b/plots/choropleth-basic/implementations/javascript/echarts.js @@ -0,0 +1,157 @@ +// anyplot.ai +// choropleth-basic: Choropleth Map with Regional Coloring +// Library: echarts 6.1.0 | JavaScript 22.23.2 +// Quality: 89/100 | Created: 2026-09-02 +//# anyplot-orientation: landscape + +const t = window.ANYPLOT_TOKENS; +// ANYPLOT_TOKENS has no "muted" entry — derive it the same way the Python/R +// implementations do (default-style-guide.md "Theme-adaptive Chrome"). +const MUTED = t.theme === "light" ? "#6B6A63" : "#A8A79F"; + +// --- Data: renewable electricity share (%) by U.S. state ------------------- +// No real GeoJSON boundaries are available offline, so states are laid out as +// a tile-grid cartogram (a standard choropleth variant that keeps every state +// equally legible regardless of its real land area). A small fixed-seed LCG +// stands in for Math.random(), which is not reproducible in the browser. +let seed = 42; +function nextRandom() { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + return seed / 0x7fffffff; +} + +// [abbr, full name, grid row (0 = north), grid col (0 = west)] +const STATE_GRID = [ + ["AK", "Alaska", 0, 0], ["ME", "Maine", 0, 12], + ["WA", "Washington", 1, 1], ["ID", "Idaho", 1, 2], ["MT", "Montana", 1, 3], + ["ND", "North Dakota", 1, 4], ["MN", "Minnesota", 1, 5], ["WI", "Wisconsin", 1, 6], + ["MI", "Michigan", 1, 7], ["NY", "New York", 1, 9], ["VT", "Vermont", 1, 10], ["NH", "New Hampshire", 1, 11], + ["OR", "Oregon", 2, 1], ["NV", "Nevada", 2, 2], ["WY", "Wyoming", 2, 3], + ["SD", "South Dakota", 2, 4], ["IA", "Iowa", 2, 5], ["IL", "Illinois", 2, 6], + ["IN", "Indiana", 2, 7], ["OH", "Ohio", 2, 8], ["PA", "Pennsylvania", 2, 9], + ["MA", "Massachusetts", 2, 10], ["RI", "Rhode Island", 2, 11], + ["CA", "California", 3, 1], ["UT", "Utah", 3, 2], ["CO", "Colorado", 3, 3], + ["NE", "Nebraska", 3, 4], ["MO", "Missouri", 3, 5], ["KY", "Kentucky", 3, 6], + ["WV", "West Virginia", 3, 7], ["VA", "Virginia", 3, 8], ["MD", "Maryland", 3, 9], + ["NJ", "New Jersey", 3, 10], ["CT", "Connecticut", 3, 11], + ["AZ", "Arizona", 4, 2], ["NM", "New Mexico", 4, 3], ["KS", "Kansas", 4, 4], + ["AR", "Arkansas", 4, 5], ["TN", "Tennessee", 4, 6], ["NC", "North Carolina", 4, 7], + ["SC", "South Carolina", 4, 8], ["DE", "Delaware", 4, 9], ["DC", "District of Columbia", 4, 10], + ["OK", "Oklahoma", 5, 4], ["MS", "Mississippi", 5, 5], ["AL", "Alabama", 5, 6], ["GA", "Georgia", 5, 7], + ["TX", "Texas", 6, 3], ["LA", "Louisiana", 6, 5], + ["FL", "Florida", 7, 7], ["HI", "Hawaii", 7, 0], +]; + +// A handful of states report no data this cycle -> rendered as muted gray. +const NO_DATA = new Set(["RI", "DE", "WV"]); + +// The south/west "renewable potential" trend now dominates the per-state +// noise (boost spans ~0-46 vs. +/-9 of random jitter), so the tile colors +// visibly cluster warm-toward-blue in the south/west and cool-toward-green +// in the northeast rather than reading as random. +const stateValues = {}; +STATE_GRID.forEach(([abbr, , row, col]) => { + if (NO_DATA.has(abbr)) return; + const southwestBoost = (12 - col) * 1.9 + row * 3.4; // more solar/wind potential toward the south/west + const jitter = (nextRandom() - 0.5) * 18; // +/-9 points of per-state noise + const value = 14 + southwestBoost + jitter; + stateValues[abbr] = Math.round(Math.max(10, Math.min(value, 78)) * 10) / 10; +}); + +// --- Build a tile-grid GeoJSON: one padded square polygon per state -------- +const PAD = 0.08; +const features = STATE_GRID.map(([abbr, , row, col]) => ({ + type: "Feature", + properties: { name: abbr }, + geometry: { + type: "Polygon", + coordinates: [[ + [col + PAD, -row - 1 + PAD], + [col + 1 - PAD, -row - 1 + PAD], + [col + 1 - PAD, -row - PAD], + [col + PAD, -row - PAD], + [col + PAD, -row - 1 + PAD], + ]], + }, +})); +echarts.registerMap("usStateTileGrid", { type: "FeatureCollection", features }); + +const values = Object.values(stateValues); +const minValue = Math.min(...values); +const maxValue = Math.max(...values); +const highestAbbr = Object.keys(stateValues).find((abbr) => stateValues[abbr] === maxValue); +const lowestAbbr = Object.keys(stateValues).find((abbr) => stateValues[abbr] === minValue); + +// --- Init -------------------------------------------------------------------- +const chart = echarts.init(document.getElementById("container")); + +// --- Option -------------------------------------------------------------------- +chart.setOption({ + animation: false, + backgroundColor: "transparent", + title: { + text: "choropleth-basic · javascript · echarts · anyplot.ai", + left: "center", + top: 24, + textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 }, + }, + tooltip: { + trigger: "item", + formatter: (p) => (NO_DATA.has(p.name) ? `${p.name}: no data` : `${p.name}: ${p.value}%`), + }, + graphic: [ + { + type: "text", + right: 70, + top: 74, + style: { + text: `Highest: ${highestAbbr} ${maxValue}%\nLowest: ${lowestAbbr} ${minValue}%`, + fill: t.inkSoft, + fontSize: 15, + lineHeight: 22, + textAlign: "right", + }, + }, + ], + visualMap: { + type: "continuous", + min: minValue, + max: maxValue, + calculable: false, + orient: "horizontal", + left: "center", + bottom: 24, + itemWidth: 18, + itemHeight: 260, + inRange: { color: t.seq }, + text: [`${maxValue}%`, `${minValue}%`], + textStyle: { color: t.inkSoft, fontSize: 14 }, + }, + series: [ + { + type: "map", + map: "usStateTileGrid", + roam: false, + layoutCenter: ["50%", "52%"], + layoutSize: "92%", + // NaN values fall outside visualMap's dimension and paint with this + // fallback instead of the sequential ramp — the "gray" missing-data + // treatment the spec calls for. + itemStyle: { + areaColor: MUTED, + borderColor: t.pageBg, + borderWidth: 3, + }, + label: { + show: true, + color: "#FFFDF6", + fontSize: 13, + fontWeight: "bold", + }, + data: STATE_GRID.map(([abbr]) => ({ + name: abbr, + value: NO_DATA.has(abbr) ? NaN : stateValues[abbr], + })), + }, + ], +}); diff --git a/plots/choropleth-basic/metadata/javascript/echarts.yaml b/plots/choropleth-basic/metadata/javascript/echarts.yaml new file mode 100644 index 0000000000..703fb1589f --- /dev/null +++ b/plots/choropleth-basic/metadata/javascript/echarts.yaml @@ -0,0 +1,239 @@ +library: echarts +language: javascript +specification_id: choropleth-basic +created: '2026-09-02T15:42:04Z' +updated: '2026-09-02T16:02:22Z' +generated_by: claude-sonnet +workflow_run: 33648502665 +issue: 3069 +language_version: 22.23.2 +library_version: 6.1.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/echarts/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/echarts/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/echarts/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/choropleth-basic/javascript/echarts/plot-dark.html +quality_score: 89 +review: + strengths: + - 'Fixed the core attempt-1 weakness: the south/west boost now dominates the random + jitter (spans ~0-46 vs. ±9 noise), so the tile colors show a clear, immediately + readable green(northeast)→blue(southwest) geographic gradient instead of near-random + noise' + - 'New "Highest: TX 57.5% / Lowest: ME 14.4%" callout directly reinforces the geographic + story and adds a concrete data point for the reader' + - layoutSize increase (84%→92%) gives the tile grid more presence on the canvas + - Missing-data states (RI, DE, WV) still render correctly in the theme-adaptive + muted anchor + - Both themes remain fully theme-correct — background, title, legend text, callout, + and tile gutters all adapt while data-driven tile colors stay pixel-identical + between light and dark + - Deterministic fixed-seed LCG, clean idiomatic ECharts code, no regressions from + attempt 1 + weaknesses: + - 'Canvas utilization is still moderate: the tile grid + legend span only ~51% of + the canvas width even after the layoutSize bump; there''s noticeable balanced-but-empty + margin on both sides. Consider whether the tile cartogram could be stretched non-uniformly + (wider tiles, tighter row/col packing) rather than only scaling layoutSize' + - No real geographic boundaries or map projection are used — the spec asks for "an + appropriate map projection (e.g. Albers Equal Area for US)"; the tile-grid cartogram + remains a legitimate, well-documented accommodation for the offline-GeoJSON constraint + (kept per attempt-1 guidance) but this is the one recurring spec gap + - 'LM-02: registerMap + continuous visualMap + graphic overlay is solid idiomatic + usage but still not a deeply distinctive echarts-only capability in the static + PNG' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white, consistent with #FAF8F1. + Chrome: Title "choropleth-basic · javascript · echarts · anyplot.ai" centered top, dark ink, clearly readable. Top-right callout "Highest: TX 57.5% / Lowest: ME 14.4%" in soft ink gray, clearly readable. Bottom colorbar legend labeled "14.4%" / "57.5%" in soft ink gray, clearly readable. Tile gutter borders match the page background. + Data: 50-state + DC tile-grid cartogram, continuous green→blue imprint_seq ramp keyed to renewable-electricity-share (%). Colors visibly cluster green in the northeast and blue in the south/west (clear geographic gradient). Three no-data states (RI, DE, WV) render as solid muted gray. Bold white state-abbreviation labels on every tile. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black, consistent with #1A1A17. + Chrome: Same title now in light ink, fully legible. Same top-right callout and bottom colorbar legend, both light-toned and legible against the dark background. No dark-on-dark failures observed. + Data: Identical tile-grid cartogram and green→blue gradient direction as the light render — data colors confirmed pixel-identical between themes. No-data tiles (RI, DE, WV) render as the theme-adaptive lighter muted gray. Brand green (#009E73) reads clearly on the dark surface. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All font sizes explicit; readable in both themes + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No collisions between tiles, labels, callout, or legend + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Tiles and legend clearly visible and well sized + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Sequential ramp is CVD-safe, good contrast + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: layoutSize raised 84%->92%; still only ~51% canvas width used, balanced + but moderate + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Legend labeled with % units, descriptive title + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: imprint_seq gradient, identical data colors across themes, theme-correct + chrome + design_excellence: + score: 15 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Thoughtful sequential ramp, typographic hierarchy, new callout + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Clean gutters, generous whitespace, unchanged from attempt 1 + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: 'Fixed: south/west boost now dominates jitter, clear geographic gradient + plus highest/lowest callout' + spec_compliance: + score: 13 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 4 + max: 5 + passed: true + comment: Recognized tile-grid cartogram substitute; no real map projection + - id: SC-02 + name: Required Features + score: 3 + max: 4 + passed: true + comment: Legend and missing-data handling present; map projection note unmet + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Region + value mapping correct + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct, legend accurate + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Full value range, no-data states, and highest/lowest callout cover + key features + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Renewable electricity share by state, neutral and realistic + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Values clamped to plausible 10-78% range + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: No functions/classes + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Deterministic fixed-seed LCG + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: No unused imports + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, appropriate complexity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Correct mount-node contract, animation disabled + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: registerMap + continuous visualMap + map series + graphic overlay + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Genuinely echarts-specific but not maximally distinctive + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - colorbar + - annotations + patterns: + - data-generation + dataprep: [] + styling: + - custom-colormap