{"spec_id":"histogram-kde","library":"echarts","language":"javascript","code":"// anyplot.ai\n// histogram-kde: Histogram with KDE Overlay\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 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  const u1 = Math.max(rand(), 1e-12);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// Customer session durations (minutes) — right-skewed, lognormal-shaped.\nconst n = 600;\nconst mu = Math.log(8);\nconst sigma = 0.55;\nconst durations = [];\nfor (let i = 0; i < n; i++) {\n  durations.push(Math.exp(mu + sigma * randNormal()));\n}\n\n// --- Histogram (density-scaled) ---------------------------------------------\nconst binCount = 26;\nconst dataMin = Math.min(...durations);\nconst dataMax = Math.max(...durations);\nconst binWidth = (dataMax - dataMin) / binCount;\nconst counts = new Array(binCount).fill(0);\ndurations.forEach((v) => {\n  const idx = Math.min(binCount - 1, Math.floor((v - dataMin) / binWidth));\n  counts[idx]++;\n});\nconst histData = counts.map((count, i) => [\n  dataMin + (i + 0.5) * binWidth,\n  count / (n * binWidth),\n]);\n\n// --- KDE (Gaussian kernel, Silverman-style bandwidth) -----------------------\nconst mean = durations.reduce((a, b) => a + b, 0) / n;\nconst variance = durations.reduce((a, b) => a + (b - mean) ** 2, 0) / (n - 1);\nconst std = Math.sqrt(variance);\nconst bandwidth = 1.06 * std * Math.pow(n, -0.2);\n\nconst kdePoints = 200;\nconst xMin = Math.max(0, dataMin - 2 * bandwidth);\nconst xMax = dataMax + 2 * bandwidth;\nconst kdeData = [];\nfor (let i = 0; i < kdePoints; i++) {\n  const x = xMin + ((xMax - xMin) * i) / (kdePoints - 1);\n  let density = 0;\n  for (let j = 0; j < n; j++) {\n    const u = (x - durations[j]) / bandwidth;\n    density += Math.exp(-0.5 * u * u);\n  }\n  density /= n * bandwidth * Math.sqrt(2 * Math.PI);\n  kdeData.push([x, density]);\n}\n\n// --- Custom renderItem: pixel-perfect histogram bars -------------------------\nfunction renderHistogramBar(params, api) {\n  const yValue = api.value(1);\n  const start = api.coord([api.value(0) - binWidth / 2, yValue]);\n  const end = api.coord([api.value(0) + binWidth / 2, 0]);\n  const rectShape = echarts.graphic.clipRectByRect(\n    {\n      x: start[0],\n      y: start[1],\n      width: end[0] - start[0],\n      height: end[1] - start[1],\n    },\n    {\n      x: params.coordSys.x,\n      y: params.coordSys.y,\n      width: params.coordSys.width,\n      height: params.coordSys.height,\n    },\n  );\n  return (\n    rectShape && {\n      type: \"rect\",\n      shape: rectShape,\n      style: api.style(),\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[0], t.palette[1]],\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"histogram-kde · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  legend: {\n    data: [\"Histogram\", \"KDE\"],\n    top: 100,\n    right: 80,\n    textStyle: { color: t.inkSoft, fontSize: 16 },\n    itemWidth: 22,\n    itemHeight: 14,\n  },\n  grid: { left: 110, right: 70, top: 160, bottom: 110 },\n  xAxis: {\n    type: \"value\",\n    name: \"Session Duration (min)\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    max: Math.ceil(xMax),\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Density\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 0,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      type: \"custom\",\n      name: \"Histogram\",\n      coordinateSystem: \"cartesian2d\",\n      renderItem: renderHistogramBar,\n      data: histData,\n      itemStyle: { color: t.palette[0], opacity: 0.5 },\n      z: 1,\n    },\n    {\n      type: \"line\",\n      name: \"KDE\",\n      data: kdeData,\n      smooth: true,\n      symbol: \"none\",\n      lineStyle: { width: 3.5, color: t.palette[1] },\n      itemStyle: { color: t.palette[1] },\n      z: 2,\n      markLine: {\n        symbol: \"none\",\n        silent: true,\n        animation: false,\n        label: {\n          formatter: (p) => `Mean: ${p.value.toFixed(1)} min`,\n          position: \"insideEndTop\",\n          color: t.inkSoft,\n          fontSize: 13,\n        },\n        lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5 },\n        data: [{ xAxis: mean }],\n      },\n    },\n  ],\n});\n"}