Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions plots/choropleth-basic/implementations/javascript/echarts.js
Original file line number Diff line number Diff line change
@@ -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],
})),
},
],
});
239 changes: 239 additions & 0 deletions plots/choropleth-basic/metadata/javascript/echarts.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading