{"spec_id":"scatter-annotated","library":"echarts","language":"javascript","code":"// anyplot.ai\n// scatter-annotated: Annotated Scatter Plot with Text Labels\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Illustrative city figures: metropolitan area vs. population. Only a subset\n// of points carries a label — annotating all 23 would clutter the chart.\nconst CITIES = [\n  { name: \"Tokyo\", area: 2194, pop: 37.4 },\n  { name: \"Delhi\", area: 1484, pop: 30.3 },\n  { name: \"Shanghai\", area: 6341, pop: 27.1 },\n  { name: \"São Paulo\", area: 1521, pop: 22.0, label: { dx: -95, dy: -34 } },\n  { name: \"Mexico City\", area: 1485, pop: 21.8, label: { dx: 95, dy: 34 } },\n  { name: \"Dhaka\", area: 306, pop: 21.7, label: { dx: -85, dy: -46 } },\n  { name: \"Cairo\", area: 3085, pop: 21.3 },\n  { name: \"Mumbai\", area: 603, pop: 20.7, label: { dx: 75, dy: 42 } },\n  { name: \"Beijing\", area: 16410, pop: 20.5, label: { position: \"top\" } },\n  { name: \"Osaka\", area: 13228, pop: 19.1 },\n  { name: \"New York\", area: 11875, pop: 18.8, label: { dx: -60, dy: 62 } },\n  { name: \"Karachi\", area: 3780, pop: 16.8 },\n  { name: \"Buenos Aires\", area: 4758, pop: 15.4 },\n  { name: \"Istanbul\", area: 5461, pop: 15.2 },\n  { name: \"Kolkata\", area: 1886, pop: 14.9 },\n  { name: \"Kinshasa\", area: 2590, pop: 14.3 },\n  { name: \"Lagos\", area: 1171, pop: 14.4, label: { position: \"bottom\" } },\n  { name: \"Manila\", area: 1620, pop: 13.5 },\n  { name: \"Rio de Janeiro\", area: 4557, pop: 13.5 },\n  { name: \"Guangzhou\", area: 7434, pop: 13.3 },\n  { name: \"Los Angeles\", area: 12562, pop: 12.4, label: { position: \"bottom\" } },\n  { name: \"Moscow\", area: 2511, pop: 12.6 },\n  { name: \"Paris\", area: 2853, pop: 11.0, label: { position: \"bottom\" } },\n];\n\nconst LABEL_STYLE = { color: t.ink, fontSize: 14, fontWeight: \"medium\" };\n\n// Second visual dimension: marker size scales with population density\n// (people per km², sqrt-mapped so encoded area stays perceptually linear).\nconst density = (city) => (city.pop * 1e6) / city.area;\nconst densities = CITIES.map(density);\nconst sqrtMin = Math.sqrt(Math.min(...densities));\nconst sqrtMax = Math.sqrt(Math.max(...densities));\nconst sizeForDensity = (city) => 14 + ((Math.sqrt(density(city)) - sqrtMin) / (sqrtMax - sqrtMin)) * 30;\n\nconst seriesData = CITIES.map((city) => {\n  const item = {\n    name: city.name,\n    value: [city.area, city.pop],\n    symbolSize: sizeForDensity(city),\n  };\n  if (city.label) {\n    item.label = {\n      show: true,\n      formatter: city.name,\n      ...LABEL_STYLE,\n      // Cities with an explicit dx/dy sit in a tight cluster on the chart —\n      // a fixed pixel offset carries the label clear of neighbouring points.\n      // Everything else uses a keyword position at a small fixed distance.\n      ...(city.label.dx !== undefined\n        ? { position: [city.label.dx, city.label.dy] }\n        : { position: city.label.position, distance: 10 }),\n    };\n    // Freeze these labels — the series-level auto-declutter below must not\n    // re-shift a label whose offset already accounts for its connecting line.\n    if (city.label.dx !== undefined) item.labelLayout = { moveOverlap: \"none\" };\n  }\n  return item;\n});\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: \"scatter-annotated · javascript · echarts · anyplot.ai\",\n    subtext: \"Marker size scales with population density\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22 },\n    subtextStyle: { color: t.inkSoft, fontSize: 13 },\n  },\n  grid: { left: 110, right: 70, top: 130, bottom: 100 },\n  tooltip: {\n    trigger: \"item\",\n    formatter: (p) =>\n      `${p.name}<br/>Area: ${p.value[0].toLocaleString()} km²<br/>Population: ${p.value[1]}M` +\n      `<br/>Density: ${Math.round((p.value[1] * 1e6) / p.value[0]).toLocaleString()} /km²`,\n  },\n  xAxis: {\n    type: \"log\",\n    logBase: 10,\n    min: 100,\n    max: 20000,\n    name: \"Metropolitan Area (km²)\",\n    nameLocation: \"middle\",\n    nameGap: 46,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: (v) => v.toLocaleString() },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Population (millions)\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    max: 40,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      type: \"scatter\",\n      data: seriesData,\n      itemStyle: { color: t.palette[0], opacity: 0.75, borderColor: t.pageBg, borderWidth: 1.5 },\n      labelLayout: { hideOverlap: true, moveOverlap: \"shiftY\" },\n    },\n  ],\n});\n\n// --- Connecting lines for offset labels --------------------------------\n// Dhaka/Mumbai/New York and the near-overlapping São Paulo/Mexico City pair\n// sit close to a neighbour, so their labels above use a fixed pixel offset.\n// A short line back to the point makes the association explicit without\n// reaching all the way to the text.\nconst offsetCities = CITIES.filter((city) => city.label && city.label.dx !== undefined);\nconst connectors = offsetCities.map((city) => {\n  const [px, py] = chart.convertToPixel({ xAxisIndex: 0, yAxisIndex: 0 }, [city.area, city.pop]);\n  return {\n    type: \"line\",\n    silent: true,\n    shape: { x1: px, y1: py, x2: px + city.label.dx * 0.55, y2: py + city.label.dy * 0.55 },\n    style: { stroke: t.inkSoft, lineWidth: 1, opacity: 0.5 },\n  };\n});\nchart.setOption({ graphic: connectors });\n"}