{"spec_id":"map-tilegrid","library":"echarts","language":"javascript","code":"// anyplot.ai\n// map-tilegrid: Tile Grid Map for Equal-Area Geographic Comparison\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// European countries on a hand-placed tile grid approximating their real\n// geographic layout, colored by share of electricity from renewable sources.\n// [col, row, renewablePct, abbreviation, countryName]\nconst countries = [\n  [0, 0, 85, \"IS\", \"Iceland\"],\n  [4, 0, 98, \"NO\", \"Norway\"],\n  [5, 0, 65, \"SE\", \"Sweden\"],\n  [6, 0, 47, \"FI\", \"Finland\"],\n  [1, 1, 43, \"GB\", \"United Kingdom\"],\n  [4, 1, 62, \"DK\", \"Denmark\"],\n  [0, 2, 38, \"IE\", \"Ireland\"],\n  [1, 2, 24, \"BE\", \"Belgium\"],\n  [2, 2, 33, \"NL\", \"Netherlands\"],\n  [3, 2, 46, \"DE\", \"Germany\"],\n  [4, 2, 19, \"CZ\", \"Czechia\"],\n  [5, 2, 17, \"PL\", \"Poland\"],\n  [2, 3, 27, \"FR\", \"France\"],\n  [3, 3, 78, \"CH\", \"Switzerland\"],\n  [4, 3, 80, \"AT\", \"Austria\"],\n  [5, 3, 15, \"HU\", \"Hungary\"],\n  [0, 4, 61, \"PT\", \"Portugal\"],\n  [1, 4, 50, \"ES\", \"Spain\"],\n  [3, 4, 40, \"IT\", \"Italy\"],\n  [4, 4, 45, \"GR\", \"Greece\"],\n];\n\nconst colCount = Math.max(...countries.map((c) => c[0])) + 1;\nconst rowCount = Math.max(...countries.map((c) => c[1])) + 1;\nconst values = countries.map((c) => c[2]);\nconst maxValue = Math.max(...values);\nconst minValue = Math.min(...values);\nconst maxCountry = countries.find((c) => c[2] === maxValue);\nconst minCountry = countries.find((c) => c[2] === minValue);\n\n// --- Title --------------------------------------------------------------\nconst title =\n  \"Renewable Electricity Share, Europe · map-tilegrid · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.max(\n  14,\n  Math.round(22 * Math.min(1, 67 / title.length)),\n);\nconst subtitle = `Bold outline marks the highest (${maxCountry[4]}, ${maxValue}%) and lowest (${minCountry[4]}, ${minValue}%) performers`;\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -----------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: title,\n    subtext: subtitle,\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: titleFontSize, fontWeight: 500 },\n    subtextStyle: { color: t.inkSoft, fontSize: 15 },\n  },\n  tooltip: {\n    formatter: (params) => {\n      const c = countries[params.dataIndex];\n      return `${c[4]} (${c[3]}): ${c[2]}% renewable`;\n    },\n  },\n  grid: { left: 60, right: 60, top: 170, bottom: 200, containLabel: false },\n  xAxis: {\n    type: \"category\",\n    data: Array.from({ length: colCount }, (_, i) => i),\n    show: false,\n  },\n  yAxis: {\n    type: \"category\",\n    data: Array.from({ length: rowCount }, (_, i) => i),\n    inverse: true,\n    show: false,\n  },\n  visualMap: {\n    type: \"continuous\",\n    min: minValue,\n    max: maxValue,\n    orient: \"horizontal\",\n    left: \"center\",\n    bottom: 70,\n    itemWidth: 22,\n    itemHeight: 260,\n    text: [`High (${maxValue}%)`, `Low (${minValue}%)`],\n    textGap: 16,\n    textStyle: { color: t.inkSoft, fontSize: 16 },\n    inRange: { color: t.seq },\n    formatter: (value) => `${Math.round(value)}%`,\n  },\n  series: [\n    {\n      // Custom renderItem series (rather than a plain heatmap) draws each\n      // tile as a hand-built rounded rect, giving direct control over the\n      // per-tile emphasis border used to call out the min/max performers.\n      type: \"custom\",\n      coordinateSystem: \"cartesian2d\",\n      encode: { x: 0, y: 1, value: 2 },\n      data: countries.map((c) => [c[0], c[1], c[2]]),\n      renderItem: (params, api) => {\n        const col = api.value(0);\n        const row = api.value(1);\n        const value = api.value(2);\n        const center = api.coord([col, row]);\n        const size = api.size([1, 1]);\n        const width = size[0] * 0.86;\n        const height = size[1] * 0.86;\n        const isExtreme = value === maxValue || value === minValue;\n        const country = countries[params.dataIndex];\n        return {\n          type: \"rect\",\n          shape: {\n            x: center[0] - width / 2,\n            y: center[1] - height / 2,\n            width,\n            height,\n            r: 10,\n          },\n          style: {\n            fill: api.visual(\"color\"),\n            stroke: isExtreme ? t.ink : t.pageBg,\n            lineWidth: isExtreme ? 4 : 8,\n          },\n          textContent: {\n            type: \"text\",\n            style: {\n              text: country[3],\n              fill: \"#FFFFFF\",\n              fontSize: 26,\n              fontWeight: 600,\n              textBorderColor: \"rgba(0,0,0,0.35)\",\n              textBorderWidth: 3,\n            },\n          },\n          textConfig: { position: \"inside\" },\n          emphasis: {\n            style: { stroke: t.ink, lineWidth: 4 },\n          },\n        };\n      },\n    },\n  ],\n});\n"}