{"spec_id":"polar-scatter","library":"echarts","language":"javascript","code":"// anyplot.ai\n// polar-scatter: Polar Scatter Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 85/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Wind observations: direction (degrees), speed (m/s), time of day.\n// A tiny LCG stands in for a seeded RNG (Math.random() is not reproducible).\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1103515245 + 12345) % 2147483648;\n  return seed / 2147483648;\n}\n\nconst TIME_OF_DAY = [\"morning\", \"afternoon\", \"evening\"];\nconst PREVAILING_DIRECTION = [260, 90, 300]; // morning / afternoon / evening prevailing bearing, degrees\nconst SPREAD = 35; // degrees of scatter around the prevailing bearing\nconst POINTS_PER_PERIOD = 42;\n\nconst observations = [];\nTIME_OF_DAY.forEach((period, i) => {\n  for (let j = 0; j < POINTS_PER_PERIOD; j++) {\n    const jitter = (lcg() - 0.5) * 2 * SPREAD;\n    const angle = (PREVAILING_DIRECTION[i] + jitter + 360) % 360;\n    const speed = 4 + lcg() * 6 + (lcg() - 0.5) * 3; // m/s, roughly 2.5-11.5\n    observations.push({\n      name: period,\n      value: [Math.max(0.5, speed), angle],\n    });\n  }\n});\n\n// Circular mean bearing + mean speed per period, drawn as a dashed radial\n// spoke from the center — a distinctive touch beyond a stock polar scatter,\n// calling out the prevailing wind direction per time of day at a glance.\nconst meanByPeriod = TIME_OF_DAY.map((period) => {\n  const pts = observations.filter((o) => o.name === period).map((o) => o.value);\n  let sumX = 0;\n  let sumY = 0;\n  let sumR = 0;\n  pts.forEach(([r, deg]) => {\n    const rad = (deg * Math.PI) / 180;\n    sumX += Math.cos(rad);\n    sumY += Math.sin(rad);\n    sumR += r;\n  });\n  return {\n    period,\n    meanAngle: ((Math.atan2(sumY, sumX) * 180) / Math.PI + 360) % 360,\n    meanRadius: sumR / pts.length,\n  };\n});\n\nconst scatterSeries = TIME_OF_DAY.map((period, i) => ({\n  name: period,\n  type: \"scatter\",\n  coordinateSystem: \"polar\",\n  symbolSize: 20,\n  itemStyle: { color: t.palette[i], opacity: 0.75 },\n  data: observations.filter((o) => o.name === period).map((o) => o.value),\n}));\n\nconst meanSpokes = meanByPeriod.map(({ period, meanAngle, meanRadius }, i) => ({\n  name: `${period}-mean`,\n  type: \"line\",\n  coordinateSystem: \"polar\",\n  symbol: (_value, params) => (params.dataIndex === 1 ? \"diamond\" : \"none\"),\n  symbolSize: (_value, params) => (params.dataIndex === 1 ? 16 : 0),\n  lineStyle: { color: t.palette[i], width: 2, type: \"dashed\", opacity: 0.9 },\n  itemStyle: { color: t.palette[i] },\n  data: [\n    [0, meanAngle],\n    [meanRadius, meanAngle],\n  ],\n  silent: true,\n  z: 3,\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: \"Wind Observations · polar-scatter · javascript · echarts · anyplot.ai\",\n    subtext:\n      \"Radius = wind speed (m/s) · Angle = wind direction (°) · dashed spoke = mean bearing\",\n    left: \"center\",\n    top: 26,\n    textStyle: { color: t.ink, fontSize: 20, fontWeight: 500 },\n    subtextStyle: { color: t.inkSoft, fontSize: 15 },\n  },\n  legend: {\n    data: TIME_OF_DAY,\n    bottom: 20,\n    textStyle: { color: t.inkSoft, fontSize: 15 },\n    itemWidth: 16,\n    itemHeight: 16,\n  },\n  polar: {\n    center: [\"50%\", \"55%\"],\n    radius: \"70%\",\n  },\n  angleAxis: {\n    type: \"value\",\n    min: 0,\n    max: 360,\n    interval: 45,\n    startAngle: 90,\n    clockwise: false,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      margin: 12,\n      formatter: \"{value}°\",\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  radiusAxis: {\n    type: \"value\",\n    min: 0,\n    name: \"Wind speed (m/s)\",\n    nameGap: 42,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: { color: t.inkSoft, fontSize: 13 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n    splitArea: {\n      show: true,\n      areaStyle: { color: [t.grid, \"transparent\"] },\n    },\n  },\n  series: [...scatterSeries, ...meanSpokes],\n});\n"}