{"spec_id":"ridgeline-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// ridgeline-basic: Basic Ridgeline Plot\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 92/100 | Created: 2026-07-25\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { useXScale, useYScale, useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst FONT = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\";\n\n// --- Data: daily high temperatures by month, temperate continental climate -\n// Deterministic LCG so the sampled distributions (and their KDEs) are stable\n// across renders — the browser has no seeded Math.random().\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return function next() {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\nfunction gaussianSample() {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst MONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\n// Monthly mean/std of daily highs (°C) — wider spread in winter, tighter in summer\nconst MEAN_HIGH = [-2, 1, 7, 14, 20, 26, 29, 28, 23, 15, 7, -1];\nconst STD_HIGH = [4.5, 4.5, 4.0, 3.5, 3.0, 2.5, 2.3, 2.3, 2.8, 3.3, 4.0, 4.5];\nconst SAMPLE_COUNT = 200;\n\nconst monthlySamples = MEAN_HIGH.map((mean, i) =>\n  Array.from({ length: SAMPLE_COUNT }, () => mean + STD_HIGH[i] * gaussianSample())\n);\n\nconst allTemps = monthlySamples.flat();\nconst dataMin = Math.min(...allTemps);\nconst dataMax = Math.max(...allTemps);\nconst xPad = (dataMax - dataMin) * 0.08;\nconst X_MIN = dataMin - xPad;\nconst X_MAX = dataMax + xPad;\n\nconst GRID_N = 160;\nconst grid = Array.from({ length: GRID_N }, (_, k) => X_MIN + (k * (X_MAX - X_MIN)) / (GRID_N - 1));\n\n// Gaussian KDE per month, bandwidth via Silverman's rule of thumb, normalized\n// to a common peak of 1 so ridge height compares shape, not raw sample count.\nfunction stdOf(samples) {\n  const m = samples.reduce((a, b) => a + b, 0) / samples.length;\n  const variance = samples.reduce((a, b) => a + (b - m) ** 2, 0) / (samples.length - 1);\n  return Math.sqrt(variance);\n}\nfunction kde(samples) {\n  const n = samples.length;\n  const bandwidth = 0.9 * stdOf(samples) * Math.pow(n, -0.2);\n  const raw = grid.map((gx) => samples.reduce((sum, s) => sum + Math.exp(-0.5 * ((gx - s) / bandwidth) ** 2), 0));\n  const peak = Math.max(...raw);\n  return raw.map((v) => v / peak);\n}\nconst monthlyDensity = monthlySamples.map((samples) => kde(samples));\n\n// --- Ridge layout ------------------------------------------------------------\n// Row 0 (Jan) at the top, row N-1 (Dec) at the bottom; ~60% peak-to-row-gap\n// overlap per the spec's guidance. Rendered in Jan→Dec order so each lower\n// ridge paints over the tail of the ridge above it (painter's algorithm).\nconst N = MONTHS.length;\nconst ROW_GAP = 1;\nconst PEAK_HEIGHT = 1.6;\nconst baselineOf = (i) => (N - 1 - i) * ROW_GAP;\nconst Y_MIN = -0.25;\nconst Y_MAX = baselineOf(0) + PEAK_HEIGHT + 0.15;\n\n// Interpolate between the two imprint_seq stops by a 0..1 fraction — color\n// tracks month order (Jan = brand green, Dec = blue), a redundant encoding\n// alongside vertical position.\nfunction lerpColor(hexA, hexB, frac) {\n  const a = parseInt(hexA.slice(1), 16);\n  const b = parseInt(hexB.slice(1), 16);\n  const ar = (a >> 16) & 255, ag = (a >> 8) & 255, ab = a & 255;\n  const br = (b >> 16) & 255, bg = (b >> 8) & 255, bb = b & 255;\n  const rr = Math.round(ar + (br - ar) * frac);\n  const rg = Math.round(ag + (bg - ag) * frac);\n  const rb = Math.round(ab + (bb - ab) * frac);\n  return `rgb(${rr}, ${rg}, ${rb})`;\n}\nconst ridgeColor = (i) => lerpColor(t.seq[0], t.seq[1], i / (N - 1));\n\n// --- Ridges — density curves drawn on the MUI X coordinate system -----------\nfunction Ridges() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n\n  return (\n    <g strokeLinejoin=\"round\">\n      {MONTHS.map((name, i) => {\n        const baseline = baselineOf(i);\n        const baselineY = yScale(baseline);\n        const topPoints = grid.map((gx, k) => `${xScale(gx)},${yScale(baseline + monthlyDensity[i][k] * PEAK_HEIGHT)}`);\n        const path = `M${xScale(grid[0])},${baselineY} L${topPoints.join(\" L\")} L${xScale(grid[GRID_N - 1])},${baselineY} Z`;\n        return <path key={name} d={path} fill={ridgeColor(i)} fillOpacity={0.92} stroke={t.pageBg} strokeWidth={2} />;\n      })}\n    </g>\n  );\n}\n\n// Group labels replace the numeric y-axis, one per ridge baseline.\nfunction RidgeLabels() {\n  const yScale = useYScale();\n  const { left } = useDrawingArea();\n\n  return (\n    <g>\n      {MONTHS.map((name, i) => (\n        <text\n          key={name}\n          x={left - 14}\n          y={yScale(baselineOf(i))}\n          textAnchor=\"end\"\n          dominantBaseline=\"middle\"\n          fontSize={15}\n          fill={t.inkSoft}\n          fontFamily={FONT}\n        >\n          {name}\n        </text>\n      ))}\n    </g>\n  );\n}\n\nfunction ChartTitle({ text, fontSize }) {\n  const { left, top, width: drawW } = useDrawingArea();\n  return (\n    <text x={left + drawW / 2} y={top - 46} textAnchor=\"middle\" fontSize={fontSize} fontWeight={600} fill={t.ink} fontFamily={FONT}>\n      {text}\n    </text>\n  );\n}\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  const title = \"ridgeline-basic · javascript · muix · anyplot.ai\";\n  const titleSize = title.length > 67 ? Math.round(22 * (67 / title.length)) : 22;\n\n  return (\n    <ChartContainer\n      width={W}\n      height={H}\n      skipAnimation\n      series={[]}\n      xAxis={[\n        {\n          scaleType: \"linear\",\n          min: X_MIN,\n          max: X_MAX,\n          label: \"Daily High Temperature (°C)\",\n          labelStyle: { fontSize: 16 },\n          tickLabelStyle: { fontSize: 14 },\n        },\n      ]}\n      yAxis={[{ scaleType: \"linear\", min: Y_MIN, max: Y_MAX }]}\n      margin={{ top: 110, right: 60, bottom: 90, left: 90 }}\n    >\n      <ChartTitle text={title} fontSize={titleSize} />\n      <Ridges />\n      <RidgeLabels />\n      <ChartsXAxis position=\"bottom\" />\n    </ChartContainer>\n  );\n}\n"}