{"spec_id":"ridgeline-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// ridgeline-basic: Basic Ridgeline Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-07-25\n\nconst t = window.ANYPLOT_TOKENS;\nconst size = window.ANYPLOT_SIZE;\n\n// --- Deterministic PRNG (LCG + Box-Muller, no seeded RNG in the browser) ----\nlet seed = 42;\nfunction nextUniform() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction nextGaussian() {\n  const u1 = Math.max(nextUniform(), 1e-9);\n  const u2 = nextUniform();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// --- Data: call-center wait times (minutes) across 8 regional support hubs -\n// Ordered by mean wait time, best to worst, top to bottom.\nconst REGIONS = [\n  { label: \"North\", mean: 4.5, std: 1.4 },\n  { label: \"East\", mean: 5.2, std: 1.3 },\n  { label: \"Pacific\", mean: 5.8, std: 1.6 },\n  { label: \"South\", mean: 6.0, std: 1.8 },\n  { label: \"Atlantic\", mean: 6.8, std: 1.7 },\n  { label: \"Central\", mean: 7.0, std: 1.9 },\n  { label: \"West\", mean: 8.5, std: 2.4 },\n  { label: \"Mountain\", mean: 9.5, std: 2.6 },\n];\nconst SAMPLES_PER_GROUP = 300;\n\nconst groupSamples = REGIONS.map((region) => {\n  const samples = [];\n  for (let i = 0; i < SAMPLES_PER_GROUP; i++) {\n    samples.push(Math.max(0.2, region.mean + region.std * nextGaussian()));\n  }\n  return samples;\n});\n\n// --- Gaussian KDE over a shared support --------------------------------------\nconst X_MIN = 0;\nconst X_MAX = 18; // multiple of the 3-unit tick interval so ticks land evenly\nconst GRID_POINTS = 120;\nconst xGrid = Array.from(\n  { length: GRID_POINTS },\n  (_, i) => X_MIN + (i * (X_MAX - X_MIN)) / (GRID_POINTS - 1)\n);\n\nfunction kde(samples, xValues) {\n  const n = samples.length;\n  const mean = samples.reduce((a, b) => a + b, 0) / n;\n  const variance = samples.reduce((a, b) => a + (b - mean) ** 2, 0) / n;\n  const std = Math.sqrt(variance);\n  const bandwidth = 1.06 * std * Math.pow(n, -0.2); // Silverman's rule of thumb\n  const norm = 1 / (n * bandwidth * Math.sqrt(2 * Math.PI));\n  return xValues.map((xi) =>\n    norm * samples.reduce((sum, s) => sum + Math.exp(-0.5 * ((xi - s) / bandwidth) ** 2), 0)\n  );\n}\n\nconst densities = groupSamples.map((samples) => kde(samples, xGrid));\nconst maxDensity = Math.max(...densities.flat());\n\n// --- Layout: N overlapping grids, one per ridge -----------------------------\nconst N = REGIONS.length;\nconst LEFT = 130;\nconst RIGHT = 50;\nconst TOP = 150;\nconst BOTTOM = 90;\nconst OVERLAP = 2.0; // ridgeHeight / bandStep -> ~50% vertical overlap\nconst plotW = size.width - LEFT - RIGHT;\nconst plotH = size.height - TOP - BOTTOM;\nconst bandStep = plotH / N;\nconst ridgeHeight = bandStep * OVERLAP;\n\nconst grids = [];\nconst xAxes = [];\nconst yAxes = [];\nconst series = [];\n\nREGIONS.forEach((region, i) => {\n  const baseline = TOP + bandStep * (i + 1);\n\n  grids.push({\n    left: LEFT,\n    width: plotW,\n    top: baseline - ridgeHeight,\n    height: ridgeHeight,\n  });\n\n  xAxes.push({\n    gridIndex: i,\n    type: \"value\",\n    min: X_MIN,\n    max: X_MAX,\n    show: i === N - 1,\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    axisLabel: { color: t.inkSoft, fontSize: 15 },\n    splitLine: {\n      show: i === N - 1,\n      lineStyle: { color: t.grid, opacity: 0.5 },\n    },\n    name: i === N - 1 ? \"Wait Time (minutes)\" : undefined,\n    nameLocation: \"middle\",\n    nameGap: 38,\n    nameTextStyle: { color: t.ink, fontSize: 17 },\n  });\n\n  yAxes.push({\n    gridIndex: i,\n    type: \"value\",\n    min: 0,\n    max: maxDensity * 1.05,\n    show: true,\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { show: false },\n    axisLabel: {\n      formatter: (_value, idx) => (idx === 0 ? region.label : \"\"),\n      color: t.ink,\n      fontSize: 16,\n      fontWeight: 500,\n      margin: 14,\n    },\n  });\n\n  series.push({\n    type: \"line\",\n    xAxisIndex: i,\n    yAxisIndex: i,\n    data: xGrid.map((x, j) => [x, densities[i][j]]),\n    smooth: true,\n    symbol: \"none\",\n    lineStyle: { width: 2, color: t.palette[i] },\n    areaStyle: { color: t.palette[i], opacity: 0.68 },\n    z: i + 2,\n  });\n});\n\n// --- Title (fontsize scales with length, see plot-generator.md) -------------\nconst TITLE = \"Call Center Wait Times · ridgeline-basic · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.max(15, Math.round(22 * Math.min(1, 67 / TITLE.length)));\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: TITLE,\n    left: \"center\",\n    top: 28,\n    textStyle: { color: t.ink, fontSize: titleFontSize, fontWeight: 500 },\n  },\n  grid: grids,\n  xAxis: xAxes,\n  yAxis: yAxes,\n  series: series,\n});\n"}