{"spec_id":"strip-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// strip-basic: Basic Strip Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.1\n// Quality: 93/100 | Created: 2026-08-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG + Box-Muller) -----------------------\nfunction makeRng(seed) {\n  let state = seed;\n  return function next() {\n    state = (state * 1103515245 + 12345) & 0x7fffffff;\n    return state / 0x7fffffff;\n  };\n}\n\nfunction gaussian(rng) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst rng = makeRng(42);\n\nconst methods = [\n  { name: \"Lecture-Based\", mean: 72, std: 9, n: 70 },\n  { name: \"Flipped Classroom\", mean: 78, std: 7, n: 70 },\n  { name: \"Project-Based\", mean: 81, std: 11, n: 70 },\n  { name: \"Blended\", mean: 76, std: 8, n: 70 },\n];\n\n// Jitter width: fraction of the category band, spread as a pixel offset so\n// points stay centered on the category tick without perturbing the x value.\nconst JITTER_PX = 90;\n\nconst categoryMeans = [];\n\nconst series = methods.map((method, i) => {\n  const scores = Array.from({ length: method.n }, () => {\n    const raw = method.mean + gaussian(rng) * method.std;\n    return Math.round(Math.min(100, Math.max(0, raw)) * 10) / 10;\n  });\n  categoryMeans.push([method.name, scores.reduce((sum, s) => sum + s, 0) / scores.length]);\n\n  return {\n    name: method.name,\n    type: \"scatter\",\n    data: scores.map((score) => ({\n      value: [method.name, score],\n      symbolOffset: [(rng() - 0.5) * 2 * JITTER_PX, 0],\n    })),\n    symbolSize: 12,\n    itemStyle: { color: t.palette[i], opacity: 0.6, borderColor: t.pageBg, borderWidth: 1 },\n  };\n});\n\n// Group-mean reference line (spec Notes: \"adding horizontal lines for group\n// means... as reference\"). A category axis snaps any fractional coordinate\n// to its nearest integer index, so a markLine confined to one category's\n// band is impossible via axis coords; instead draw it as a `custom` series\n// with a pixel-space offset, exactly like the jitter above.\nconst meanLineSeries = {\n  name: \"Group Mean\",\n  type: \"custom\",\n  silent: true,\n  z: 5,\n  data: categoryMeans,\n  encode: { x: 0, y: 1 },\n  renderItem: (params, api) => {\n    const point = api.coord([api.value(0), api.value(1)]);\n    return {\n      type: \"line\",\n      shape: {\n        x1: point[0] - JITTER_PX,\n        y1: point[1],\n        x2: point[0] + JITTER_PX,\n        y2: point[1],\n      },\n      style: { stroke: t.inkSoft, lineWidth: 2, lineDash: [6, 4] },\n    };\n  },\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: \"strip-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  grid: { left: 100, right: 60, top: 100, bottom: 90 },\n  xAxis: {\n    type: \"category\",\n    data: methods.map((m) => m.name),\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Exam Score (%)\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 30,\n    max: 100,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [...series, meanLineSeries],\n});\n"}