{"spec_id":"bubble-map-geographic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// bubble-map-geographic: Bubble Map with Sized Geographic Markers\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-01\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// World metro-area population by city (millions, approximate), grouped by\n// continent for color + legend. [longitude, latitude, population].\nconst REGIONS = [\n  {\n    name: \"Asia\",\n    color: t.palette[0],\n    cities: [\n      { name: \"Tokyo\", lon: 139.65, lat: 35.68, pop: 37.4 },\n      { name: \"Delhi\", lon: 77.21, lat: 28.61, pop: 32.9 },\n      { name: \"Shanghai\", lon: 121.47, lat: 31.23, pop: 29.2 },\n      { name: \"Dhaka\", lon: 90.41, lat: 23.81, pop: 22.5 },\n      { name: \"Beijing\", lon: 116.41, lat: 39.9, pop: 21.9 },\n      { name: \"Mumbai\", lon: 72.88, lat: 19.08, pop: 21.3 },\n      { name: \"Osaka\", lon: 135.5, lat: 34.69, pop: 19.1 },\n      { name: \"Karachi\", lon: 67.01, lat: 24.86, pop: 16.8 },\n    ],\n  },\n  {\n    name: \"Africa\",\n    color: t.palette[1],\n    cities: [\n      { name: \"Cairo\", lon: 31.24, lat: 30.04, pop: 22.2 },\n      { name: \"Kinshasa\", lon: 15.27, lat: -4.44, pop: 15.6 },\n      { name: \"Lagos\", lon: 3.38, lat: 6.52, pop: 15.4 },\n    ],\n  },\n  {\n    name: \"Europe\",\n    color: t.palette[2],\n    cities: [\n      { name: \"Istanbul\", lon: 28.97, lat: 41.01, pop: 15.6 },\n      { name: \"Moscow\", lon: 37.62, lat: 55.75, pop: 12.6 },\n      { name: \"Paris\", lon: 2.35, lat: 48.85, pop: 11.1 },\n      { name: \"London\", lon: -0.13, lat: 51.51, pop: 9.6 },\n    ],\n  },\n  {\n    name: \"N. America\",\n    color: t.palette[3],\n    cities: [\n      { name: \"Mexico City\", lon: -99.13, lat: 19.43, pop: 22.3 },\n      { name: \"New York\", lon: -74.01, lat: 40.71, pop: 18.9 },\n      { name: \"Los Angeles\", lon: -118.24, lat: 34.05, pop: 12.5 },\n    ],\n  },\n  {\n    name: \"S. America\",\n    color: t.palette[4],\n    cities: [\n      { name: \"Sao Paulo\", lon: -46.63, lat: -23.55, pop: 22.6 },\n      { name: \"Buenos Aires\", lon: -58.38, lat: -34.6, pop: 15.4 },\n      { name: \"Rio de Janeiro\", lon: -43.17, lat: -22.91, pop: 13.7 },\n    ],\n  },\n  {\n    name: \"Oceania\",\n    color: t.palette[5],\n    cities: [\n      { name: \"Sydney\", lon: 151.21, lat: -33.87, pop: 5.3 },\n      { name: \"Melbourne\", lon: 144.96, lat: -37.81, pop: 5.2 },\n    ],\n  },\n];\n\n// --- Basemap: simplified world continent outlines (rough, low-vertex hand\n// trace -- geographic context only, not a precise survey boundary) ----------\nconst NORTH_AMERICA = [\n  [-165, 68], [-150, 60], [-130, 55], [-125, 48], [-124, 40], [-118, 34],\n  [-108, 23], [-97, 16], [-90, 14], [-84, 9], [-77, 8], [-82, 22], [-80, 31],\n  [-75, 35], [-71, 41], [-66, 45], [-60, 48], [-55, 52], [-65, 58], [-80, 62],\n  [-95, 68], [-110, 72], [-130, 71], [-150, 71], [-165, 68],\n];\nconst SOUTH_AMERICA = [\n  [-77, 8], [-79, 1], [-81, -5], [-81, -15], [-75, -20], [-71, -30],\n  [-71, -40], [-73, -50], [-68, -55], [-65, -52], [-62, -42], [-58, -34],\n  [-48, -25], [-40, -12], [-50, 0], [-60, 5], [-65, 8], [-77, 8],\n];\nconst AFRICA = [\n  [-17, 15], [-16, 21], [-10, 30], [0, 37], [10, 37], [20, 32], [32, 31],\n  [35, 27], [43, 13], [51, 12], [45, 0], [40, -12], [35, -20], [33, -27],\n  [27, -33], [18, -34], [14, -23], [12, -6], [9, 4], [-5, 5], [-11, 7],\n  [-17, 15],\n];\nconst EURASIA = [\n  [-9, 37], [-9, 44], [-5, 48], [3, 51], [10, 60], [25, 70], [40, 68],\n  [60, 72], [90, 75], [130, 75], [170, 66], [160, 55], [140, 45], [130, 35],\n  [122, 30], [108, 10], [100, 5], [95, 15], [88, 22], [80, 10], [77, 8],\n  [70, 20], [65, 25], [55, 27], [50, 15], [35, 15], [35, 32], [28, 36],\n  [23, 37], [12, 38], [-5, 36], [-9, 37],\n];\nconst AUSTRALIA = [\n  [113, -22], [114, -30], [121, -34], [130, -32], [137, -35], [141, -38],\n  [147, -38], [150, -37], [153, -28], [145, -17], [142, -11], [136, -12],\n  [130, -12], [124, -15], [113, -22],\n];\nconst CONTINENTS = [NORTH_AMERICA, SOUTH_AMERICA, AFRICA, EURASIA, AUSTRALIA];\n\n// Fills+outlines one continent polygon; coordinates are projected through the\n// chart's own lon/lat value axes via api.coord (same cartesian2d technique\n// used for the coastline/ocean layer in hexbin-map-geographic).\nfunction makeLandRenderer(coords) {\n  return function renderLand(params, api) {\n    const points = coords.map(([lon, lat]) => api.coord([lon, lat]));\n    return {\n      type: \"polygon\",\n      shape: { points },\n      style: { fill: t.grid, stroke: t.inkSoft, lineWidth: 1, opacity: 0.55 },\n      silent: true,\n    };\n  };\n}\n\nconst ALL_POPS = REGIONS.flatMap((r) => r.cities.map((c) => c.pop));\nconst POP_MIN = Math.min(...ALL_POPS);\nconst POP_MAX = Math.max(...ALL_POPS);\nconst SIZE_MIN = 24;\nconst SIZE_MAX = 78;\n\n// Bubble diameter scales with sqrt(value) so bubble AREA — not radius — is\n// proportional to population, per anyplot's bubble-map perception rule.\nfunction bubbleSize(pop) {\n  const s0 = Math.sqrt(POP_MIN);\n  const s1 = Math.sqrt(POP_MAX);\n  const ratio = Math.max(0, Math.min(1, (Math.sqrt(pop) - s0) / (s1 - s0)));\n  return SIZE_MIN + ratio * (SIZE_MAX - SIZE_MIN);\n}\n\n// --- Init ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Size-key legend (fixed screen-space graphic, stacked top-to-bottom) ---\nconst LEGEND_VALUES = [37, 15, 5];\nconst LEGEND_X = 1480;\nconst legendGraphics = [\n  {\n    type: \"text\",\n    left: LEGEND_X - 30,\n    top: 232,\n    style: { text: \"Population (M)\", fill: t.inkSoft, fontSize: 13, fontWeight: 500 },\n  },\n];\nlet cy = 260;\nfor (const v of LEGEND_VALUES) {\n  const r = bubbleSize(v) / 2;\n  cy += r;\n  legendGraphics.push({\n    type: \"circle\",\n    shape: { cx: LEGEND_X, cy, r },\n    style: { fill: \"none\", stroke: t.inkSoft, lineWidth: 1.5 },\n  });\n  legendGraphics.push({\n    type: \"text\",\n    left: LEGEND_X + r + 14,\n    top: cy - 7,\n    style: { text: `${v}M`, fill: t.inkSoft, fontSize: 13 },\n  });\n  cy += r + 24;\n}\n\n// --- Option -----------------------------------------------------------------\nconst title = \"World City Populations · bubble-map-geographic · javascript · echarts · anyplot.ai\";\n\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: title,\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 18, fontWeight: 500 },\n  },\n  legend: {\n    top: 74,\n    data: REGIONS.map((r) => r.name),\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 16,\n    itemHeight: 12,\n  },\n  tooltip: {\n    trigger: \"item\",\n    formatter: (p) => `${p.data.name}<br/>${p.data.value[2]}M metro population`,\n  },\n  grid: { left: 90, top: 150, width: 1300, height: 560 },\n  xAxis: {\n    type: \"value\",\n    name: \"Longitude\",\n    nameLocation: \"middle\",\n    nameGap: 34,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    min: -180,\n    max: 180,\n    interval: 30,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 12,\n      formatter: (v) => `${Math.abs(v)}°${v < 0 ? \"W\" : v > 0 ? \"E\" : \"\"}`,\n    },\n    axisLine: { onZero: false, lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Latitude\",\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    min: -60,\n    max: 80,\n    interval: 30,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 12,\n      formatter: (v) => `${Math.abs(v)}°${v < 0 ? \"S\" : v > 0 ? \"N\" : \"\"}`,\n    },\n    axisLine: { onZero: false, lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  graphic: legendGraphics,\n  series: [\n    ...CONTINENTS.map((coords, i) => ({\n      name: `Basemap ${i}`,\n      type: \"custom\",\n      coordinateSystem: \"cartesian2d\",\n      renderItem: makeLandRenderer(coords),\n      data: [[0, 0]],\n      silent: true,\n      tooltip: { show: false },\n      z: 0,\n    })),\n    ...REGIONS.map((region, i) => ({\n      name: region.name,\n      type: \"scatter\",\n      data: region.cities.map((c) => ({ name: c.name, value: [c.lon, c.lat, c.pop] })),\n      symbolSize: (val) => bubbleSize(val[2]),\n      itemStyle: { color: region.color, opacity: 0.58, borderColor: t.pageBg, borderWidth: 1 },\n      emphasis: { itemStyle: { opacity: 0.9 } },\n      z: 2,\n      ...(i === 0\n        ? {\n            markLine: {\n              silent: true,\n              symbol: \"none\",\n              lineStyle: { color: t.ink, opacity: 0.2, type: \"dashed\", width: 1 },\n              label: { show: false },\n              data: [{ xAxis: 0 }, { yAxis: 0 }],\n            },\n          }\n        : {}),\n    })),\n  ],\n});\n"}