{"spec_id":"histogram-kde","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// histogram-kde: Histogram with KDE Overlay\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-08-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Tiny LCG PRNG + Box-Muller — the browser has no seeded RNG.\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return function () {\n    state = (1103515245 * state + 12345) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\nfunction randNormal() {\n  let u1 = 0;\n  while (u1 === 0) u1 = rand();\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// Marketing analytics scenario: right-skewed customer session durations.\nconst sampleSize = 600;\nconst sessionDurations = [];\nfor (let i = 0; i < sampleSize; i++) {\n  sessionDurations.push(Math.exp(2.85 + 0.5 * randNormal()));\n}\n\n// --- Histogram (density-scaled, not counts) ---------------------------------\nconst dataMin = Math.min(...sessionDurations);\nconst dataMax = Math.max(...sessionDurations);\nconst binCount = 28;\nconst binWidth = (dataMax - dataMin) / binCount;\nconst counts = new Array(binCount).fill(0);\nsessionDurations.forEach((v) => {\n  const idx = Math.min(binCount - 1, Math.floor((v - dataMin) / binWidth));\n  counts[idx]++;\n});\nconst histogramData = counts.map((count, i) => [\n  dataMin + (i + 0.5) * binWidth,\n  count / (sampleSize * binWidth),\n]);\n\n// --- Kernel density estimate (Gaussian kernel, Silverman's rule bandwidth) --\nconst mean = sessionDurations.reduce((a, b) => a + b, 0) / sampleSize;\nconst variance =\n  sessionDurations.reduce((a, b) => a + (b - mean) ** 2, 0) / (sampleSize - 1);\nconst std = Math.sqrt(variance);\nconst sorted = [...sessionDurations].sort((a, b) => a - b);\nfunction quantile(arr, q) {\n  const pos = (arr.length - 1) * q;\n  const base = Math.floor(pos);\n  const rest = pos - base;\n  return arr[base + 1] !== undefined\n    ? arr[base] + rest * (arr[base + 1] - arr[base])\n    : arr[base];\n}\nconst iqr = quantile(sorted, 0.75) - quantile(sorted, 0.25);\nconst bandwidth =\n  0.9 * Math.min(std, iqr / 1.34) * Math.pow(sampleSize, -0.2);\n\nconst gridPoints = 200;\nconst kdeMin = Math.max(0, dataMin - 3 * bandwidth);\nconst kdeMax = dataMax + 3 * bandwidth;\nconst kdeData = [];\nlet peakIdx = 0;\nfor (let i = 0; i < gridPoints; i++) {\n  const x = kdeMin + ((kdeMax - kdeMin) * i) / (gridPoints - 1);\n  let sum = 0;\n  for (let j = 0; j < sampleSize; j++) {\n    const u = (x - sessionDurations[j]) / bandwidth;\n    sum += Math.exp(-0.5 * u * u);\n  }\n  kdeData.push([x, sum / (sampleSize * bandwidth * Math.sqrt(2 * Math.PI))]);\n  if (kdeData[i][1] > kdeData[peakIdx][1]) peakIdx = i;\n}\nconst peakX = kdeData[peakIdx][0];\n\n// --- Chart -------------------------------------------------------------------\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"column\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"histogram-kde · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"26px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    type: \"linear\",\n    title: {\n      text: \"Session Duration (minutes)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      {\n        value: peakX,\n        color: t.inkSoft,\n        dashStyle: \"Dash\",\n        width: 1,\n        zIndex: 5,\n        label: {\n          text: `Peak: ${peakX.toFixed(1)} min`,\n          style: { color: t.inkSoft, fontSize: \"12px\" },\n          y: -8,\n        },\n      },\n    ],\n  },\n  yAxis: {\n    title: {\n      text: \"Density\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    gridLineColor: t.grid,\n    lineColor: t.inkSoft,\n    tickAmount: 6,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: {\n    headerFormat: \"\",\n    pointFormat: \"{series.name}: <b>{point.y:.3f}</b>\",\n  },\n  plotOptions: {\n    series: { animation: false },\n    column: { pointPadding: 0, groupPadding: 0, borderWidth: 0 },\n  },\n  series: [\n    {\n      name: \"Histogram\",\n      type: \"column\",\n      data: histogramData,\n      pointRange: binWidth,\n      color: hexToRgba(t.palette[0], 0.5),\n    },\n    {\n      name: \"KDE\",\n      type: \"spline\",\n      data: kdeData,\n      color: t.palette[1],\n      lineWidth: 2.5,\n      marker: { enabled: false },\n    },\n  ],\n});\n"}