{"spec_id":"scatter-categorical","library":"echarts","language":"javascript","code":"// anyplot.ai\n// scatter-categorical: Categorical Scatter Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Tiny fixed-seed LCG so both theme renders draw identical points.\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction gaussian(mean, std) {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * std;\n}\n\nconst species = [\n  { name: \"Sepal Iris\", leafLength: 4.4, leafWidth: 1.2, n: 45 },\n  { name: \"Marsh Iris\", leafLength: 5.6, leafWidth: 2.0, n: 45 },\n  { name: \"Alpine Iris\", leafLength: 6.3, leafWidth: 2.4, n: 45 },\n];\n\nconst series = species.map((sp, i) => ({\n  name: sp.name,\n  type: \"scatter\",\n  symbolSize: 18,\n  data: Array.from({ length: sp.n }, () => [\n    Number(gaussian(sp.leafLength, 0.55).toFixed(2)),\n    Number(gaussian(sp.leafWidth, 0.28).toFixed(2)),\n  ]),\n  // Theme-adaptive edge separates overlapping markers in the Marsh/Alpine\n  // overlap band (5.5-6.5 cm); page-bg stroke reads as a halo, not chartjunk.\n  itemStyle: { opacity: 0.72, borderColor: t.pageBg, borderWidth: 1 },\n  emphasis: {\n    focus: \"series\",\n    itemStyle: { opacity: 1, borderColor: t.ink, borderWidth: 1.5 },\n  },\n  // Overlap-zone callout lives on the middle (Marsh Iris) series only.\n  markArea:\n    i === 1\n      ? {\n          silent: true,\n          itemStyle: { color: t.inkSoft, opacity: 0.06 },\n          label: { color: t.inkSoft, fontSize: 12, position: \"insideBottom\" },\n          data: [\n            [\n              { xAxis: 5.5, name: \"Overlap zone\" },\n              { xAxis: 6.5 },\n            ],\n          ],\n        }\n      : undefined,\n}));\n\n// --- Init ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ---------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"scatter-categorical · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    top: 56,\n    textStyle: { color: t.ink, fontSize: 16 },\n    itemWidth: 18,\n    itemHeight: 12,\n  },\n  tooltip: {\n    trigger: \"item\",\n    formatter: (p) =>\n      `${p.seriesName}<br/>Leaf Length: ${p.data[0]} cm<br/>Leaf Width: ${p.data[1]} cm`,\n  },\n  grid: { left: 90, right: 60, top: 130, bottom: 90 },\n  xAxis: {\n    type: \"value\",\n    name: \"Leaf Length (cm)\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Leaf Width (cm)\",\n    nameLocation: \"middle\",\n    nameGap: 55,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series,\n});\n"}