{"spec_id":"ridgeline-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// ridgeline-basic: Basic Ridgeline Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-25\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: monthly temperature distributions (seasonal pattern) ------------\n// deterministic LCG (Numerical Recipes constants) — no seeded RNG in the browser\nlet seed = 42;\nfunction nextUniform() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\nfunction nextNormal() {\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\nconst months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n                \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\nconst monthlyMeanC = [2, 3, 7, 12, 17, 21, 24, 23, 19, 13, 7, 3];\nconst monthlyStdC  = [4.2, 4.0, 3.6, 3.2, 2.8, 2.5, 2.3, 2.4, 2.7, 3.1, 3.6, 4.1];\nconst samplesPerMonth = 200;\n\nconst monthlySamples = months.map((_, i) =>\n  Array.from({ length: samplesPerMonth },\n    () => monthlyMeanC[i] + monthlyStdC[i] * nextNormal())\n);\n\n// --- Gaussian KDE, evaluated on a shared temperature grid -------------------\nfunction gaussianKDE(samples, xGrid, bandwidth) {\n  const norm = 1 / (samples.length * bandwidth * Math.sqrt(2 * Math.PI));\n  return xGrid.map((x) => {\n    let sum = 0;\n    for (const s of samples) {\n      const z = (x - s) / bandwidth;\n      sum += Math.exp(-0.5 * z * z);\n    }\n    return sum * norm;\n  });\n}\n\nconst allSamples = monthlySamples.flat();\nconst xMin = Math.floor(Math.min(...allSamples) - 3);\nconst xMax = Math.ceil(Math.max(...allSamples) + 3);\nconst gridPoints = 140;\nconst xGrid = Array.from({ length: gridPoints },\n  (_, i) => xMin + (i / (gridPoints - 1)) * (xMax - xMin));\n\n// Silverman's rule of thumb per group keeps each ridge appropriately smooth\nconst bandwidths = monthlyStdC.map((std) => 1.06 * std * Math.pow(samplesPerMonth, -0.2));\n\n// --- Stack ridges: one baseline offset per month, peaks scaled to overlap --\nconst rowStep = 1;\nconst ridgeHeight = 1.7; // ~65-70% overlap into the row(s) above\nconst offsets = months.map((_, i) => i * rowStep);\n\nconst ridgeColorFrom = Highcharts.color(t.seq[0]);\nconst ridgeColors = months.map((_, i) =>\n  ridgeColorFrom.tweenTo(Highcharts.color(t.seq[1]), i / (months.length - 1))\n);\n\nconst series = months.map((month, i) => {\n  const density = gaussianKDE(monthlySamples[i], xGrid, bandwidths[i]);\n  const peak = Math.max(...density);\n  const offset = offsets[i];\n  return {\n    name: month,\n    color: ridgeColors[i],\n    fillColor: ridgeColors[i],\n    fillOpacity: 0.92,\n    lineWidth: 2,\n    threshold: offset,\n    data: xGrid.map((x, j) => [x, offset - (density[j] / peak) * ridgeHeight]),\n  };\n});\n\n// Render back-to-front (Dec first, Jan last) so each ridge's peak tucks\n// behind the ridge in the row above it, matching top-down chronological order.\nconst orderedSeries = series.slice().reverse();\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"area\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  title: {\n    text: \"ridgeline-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: { text: \"Temperature (°C)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: xMin,\n    max: xMax,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    gridLineWidth: 1,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: { text: null },\n    reversed: true,\n    min: -ridgeHeight,\n    max: offsets[offsets.length - 1] + rowStep,\n    tickPositions: offsets,\n    gridLineWidth: 0,\n    lineWidth: 0,\n    labels: {\n      style: { color: t.inkSoft, fontSize: \"14px\" },\n      formatter() {\n        return months[Math.round(this.value / rowStep)];\n      },\n    },\n  },\n  legend: { enabled: false },\n  plotOptions: {\n    series: {\n      animation: false,\n      marker: { enabled: false, states: { hover: { enabled: false } } },\n      enableMouseTracking: false,\n    },\n    area: { threshold: undefined },\n  },\n  series: orderedSeries,\n});\n"}