{"spec_id":"cartogram-area-distortion","library":"echarts","language":"javascript","code":"// anyplot.ai\n// cartogram-area-distortion: Cartogram with Area Distortion by Data Value\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 85/100 | Created: 2026-06-08\n\nconst t = window.ANYPLOT_TOKENS;\n\n// European GDP 2023 estimates: [lon, lat, gdp_B_USD, gdppc_k_USD, code, name]\n// Positions manually adjusted (Dorling-style) to reduce circle overlap\nconst countries = [\n  [-1.5, 53.5, 3131,  46.1, \"GB\", \"United Kingdom\"],\n  [-3.7, 40.4, 1623,  34.3, \"ES\", \"Spain\"],\n  [-8.2, 53.3,  550, 107.8, \"IE\", \"Ireland\"],\n  [-8.2, 39.5,  255,  24.8, \"PT\", \"Portugal\"],\n  [ 2.2, 46.2, 3131,  46.0, \"FR\", \"France\"],\n  [ 3.0, 49.5,  627,  54.1, \"BE\", \"Belgium\"],\n  [ 5.3, 52.4, 1118,  63.9, \"NL\", \"Netherlands\"],\n  [ 8.2, 47.4,  905, 103.1, \"CH\", \"Switzerland\"],\n  [10.0, 56.3,  406,  68.5, \"DK\", \"Denmark\"],\n  [10.5, 51.2, 4456,  53.1, \"DE\", \"Germany\"],\n  [10.7, 60.5,  548, 101.5, \"NO\", \"Norway\"],\n  [12.6, 42.8, 2130,  36.3, \"IT\", \"Italy\"],\n  [14.6, 47.7,  526,  57.8, \"AT\", \"Austria\"],\n  [15.5, 49.8,  330,  30.3, \"CZ\", \"Czechia\"],\n  [17.1, 62.2,  627,  59.7, \"SE\", \"Sweden\"],\n  [19.5, 52.1,  748,  19.7, \"PL\", \"Poland\"],\n  [21.8, 39.1,  222,  21.3, \"GR\", \"Greece\"],\n  [27.0, 64.5,  305,  55.5, \"FI\", \"Finland\"],\n];\n\nconst maxGdp   = Math.max(...countries.map(c => c[2]));\nconst gdppcMin = Math.min(...countries.map(c => c[3]));\nconst gdppcMax = Math.max(...countries.map(c => c[3]));\n\n// Dorling scatter: area ∝ GDP, minimum size floor for legibility\nconst scatterData = countries.map(c => ({\n  value: [c[0], c[1], c[2], c[3], c[4], c[5]],\n  symbolSize: Math.max(16, Math.sqrt(c[2] / maxGdp) * 80),\n}));\n\n// Reference inset: true geographic positions, uniform dot size for comparison\nconst refData = countries.map(c => ({\n  value: [c[0], c[1], c[2], c[3], c[4], c[5]],\n  symbolSize: 7,\n  itemStyle: { color: t.inkSoft, opacity: 0.75 },\n}));\n\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nconst titleStr = \"cartogram-area-distortion · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / titleStr.length));\n\n// Size legend: reference circles at 55% scale for display in legend area\n// radii ≈ [6.5, 13.1, 22.0]\nconst legGdps = [500, 2000, 4456];\nconst legLabels = [\"$500B\", \"$2T\", \"$4.5T\"];\nconst legR = legGdps.map(g => (Math.max(16, Math.sqrt(g / maxGdp) * 80) * 0.55) / 2);\n\nconst legBaseY = 828;  // y-baseline (bottom of circles)\nconst legTitleX = 395;\nconst graphicEls = [\n  // Reference inset label (above secondary grid)\n  {\n    type: \"text\",\n    left: 78,\n    top: 695,\n    style: {\n      text: \"Geographic reference (equal area)\",\n      fill: t.inkSoft,\n      fontSize: 11,\n      fontStyle: \"italic\",\n    },\n  },\n  // Size legend title\n  {\n    type: \"text\",\n    left: legTitleX,\n    top: 695,\n    style: {\n      text: \"Circle area ∝ GDP\",\n      fill: t.inkSoft,\n      fontSize: 12,\n      fontWeight: \"bold\",\n    },\n  },\n];\n\n// Build size legend circles and labels\nlet circCx = 420;\nlegGdps.forEach((gdp, i) => {\n  const r = legR[i];\n  graphicEls.push(\n    {\n      type: \"circle\",\n      shape: { cx: circCx, cy: legBaseY - r, r },\n      style: { fill: \"none\", stroke: t.inkSoft, lineWidth: 1.5 },\n    },\n    {\n      type: \"text\",\n      left: circCx - 18,\n      top: legBaseY + 5,\n      style: { text: legLabels[i], fill: t.inkSoft, fontSize: 11 },\n    }\n  );\n  if (i < legGdps.length - 1) {\n    circCx += r + 20 + legR[i + 1];\n  }\n});\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n\n  title: {\n    text: titleStr,\n    subtext: \"Dorling cartogram — circle area ∝ total GDP · color ∝ GDP per capita · Europe 2023\",\n    subtextStyle: { color: t.inkSoft, fontSize: 13 },\n    left: \"center\",\n    top: 18,\n    textStyle: { color: t.ink, fontSize: titleFontSize, fontWeight: \"normal\" },\n  },\n\n  tooltip: {\n    trigger: \"item\",\n    backgroundColor: t.elevatedBg,\n    borderWidth: 0,\n    padding: [8, 12],\n    textStyle: { color: t.ink, fontSize: 13 },\n    formatter: (params) => {\n      if (params.seriesIndex !== 0) return undefined;\n      const v = params.value;\n      return (\n        `<b>${v[5]} (${v[4]})</b><br/>` +\n        `GDP: $${v[2].toLocaleString()}B<br/>` +\n        `Per capita: $${v[3].toFixed(1)}k`\n      );\n    },\n  },\n\n  visualMap: {\n    type: \"continuous\",\n    dimension: 3,\n    min: gdppcMin,\n    max: gdppcMax,\n    inRange: { color: t.seq },\n    text: [\"$108k\", \"$20k\"],\n    textStyle: { color: t.inkSoft, fontSize: 12 },\n    itemWidth: 16,\n    itemHeight: 180,\n    right: 24,\n    top: \"middle\",\n    orient: \"vertical\",\n    hoverLink: false,\n    seriesIndex: 0,\n  },\n\n  grid: [\n    { left: 72, right: 180, top: 100, bottom: 240 },\n    { left: 78, top: 715, width: 228, height: 108 },\n  ],\n\n  xAxis: [\n    {\n      gridIndex: 0,\n      type: \"value\",\n      name: \"Longitude\",\n      nameLocation: \"middle\",\n      nameGap: 36,\n      nameTextStyle: { color: t.inkSoft, fontSize: 12 },\n      min: -15,\n      max: 38,\n      interval: 10,\n      axisLabel: { color: t.inkSoft, fontSize: 11, formatter: (v) => `${v}\\xb0` },\n      axisLine: { show: false },\n      axisTick: { show: false },\n      splitLine: { lineStyle: { color: t.grid } },\n    },\n    {\n      gridIndex: 1,\n      type: \"value\",\n      min: -15,\n      max: 38,\n      show: false,\n    },\n  ],\n\n  yAxis: [\n    {\n      gridIndex: 0,\n      type: \"value\",\n      name: \"Latitude\",\n      nameLocation: \"middle\",\n      nameGap: 44,\n      nameTextStyle: { color: t.inkSoft, fontSize: 12 },\n      min: 34,\n      max: 72,\n      interval: 10,\n      axisLabel: { color: t.inkSoft, fontSize: 11, formatter: (v) => `${v}\\xb0` },\n      axisLine: { show: false },\n      axisTick: { show: false },\n      splitLine: { lineStyle: { color: t.grid } },\n    },\n    {\n      gridIndex: 1,\n      type: \"value\",\n      min: 34,\n      max: 72,\n      show: false,\n    },\n  ],\n\n  series: [\n    {\n      type: \"scatter\",\n      xAxisIndex: 0,\n      yAxisIndex: 0,\n      data: scatterData,\n      label: {\n        show: true,\n        formatter: (params) => params.value[4],\n        fontSize: 11,\n        fontWeight: \"bold\",\n        color: \"#ffffff\",\n      },\n      emphasis: { focus: \"self\", scale: true },\n    },\n    {\n      type: \"scatter\",\n      xAxisIndex: 1,\n      yAxisIndex: 1,\n      data: refData,\n      label: { show: false },\n      emphasis: { disabled: true },\n    },\n  ],\n\n  graphic: graphicEls,\n});\n"}