{"spec_id":"histogram-returns-distribution","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// histogram-returns-distribution: Returns Distribution Histogram\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-02\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Daily returns (%) for a single equity over one trading year, generated with a\n// fixed-seed LCG. A small mixture of wide-vol days is blended in so the\n// empirical distribution shows the fat tails real markets exhibit versus a\n// pure Gaussian.\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return () => {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rng = makeLcg(42);\nfunction randNormal() {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst N_OBS = 252;\nconst DRIFT = 0.04; // mean daily return, %\nconst VOL = 1.05; // baseline daily volatility, %\nconst returns = [];\nfor (let i = 0; i < N_OBS; i++) {\n  const fatTailDay = rng() < 0.07;\n  const vol = fatTailDay ? VOL * 3.2 : VOL;\n  returns.push(DRIFT + vol * randNormal());\n}\n\n// --- Stats -------------------------------------------------------------------\nconst n = returns.length;\nconst mean = returns.reduce((a, b) => a + b, 0) / n;\nconst variance = returns.reduce((a, b) => a + (b - mean) ** 2, 0) / n;\nconst std = Math.sqrt(variance);\nconst skewness = returns.reduce((a, b) => a + ((b - mean) / std) ** 3, 0) / n;\nconst kurtosis = returns.reduce((a, b) => a + ((b - mean) / std) ** 4, 0) / n - 3;\n\n// --- Histogram (density-normalized) -------------------------------------------\nconst BIN_COUNT = 30;\nconst lo = Math.min(...returns);\nconst hi = Math.max(...returns);\nconst binWidth = (hi - lo) / BIN_COUNT;\nconst counts = new Array(BIN_COUNT).fill(0);\nreturns.forEach((r) => {\n  const idx = Math.min(BIN_COUNT - 1, Math.max(0, Math.floor((r - lo) / binWidth)));\n  counts[idx]++;\n});\nconst density = counts.map((c) => c / (n * binWidth));\nconst binCenters = Array.from({ length: BIN_COUNT }, (_, i) => lo + (i + 0.5) * binWidth);\nconst labels = binCenters.map((c) => `${c.toFixed(1)}%`);\n\n// Fitted normal curve sampled at each bin center, so it overlays the category axis exactly.\nfunction normalPdf(x, mu, sigma) {\n  return Math.exp(-0.5 * ((x - mu) / sigma) ** 2) / (sigma * Math.sqrt(2 * Math.PI));\n}\nconst normalCurve = binCenters.map((c) => normalPdf(c, mean, std));\n\n// Tail bins beyond +/-2 std get the semantic loss/extreme-event color.\nconst tailLow = mean - 2 * std;\nconst tailHigh = mean + 2 * std;\nconst barColors = binCenters.map((c) => (c < tailLow || c > tailHigh ? t.palette[4] : t.palette[0]));\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Stats box plugin ---------------------------------------------------------\nconst statsBoxPlugin = {\n  id: \"statsBox\",\n  afterDraw(chart) {\n    const { ctx, chartArea } = chart;\n    const lines = [\n      `Mean:  ${mean.toFixed(2)}%`,\n      `Std Dev:  ${std.toFixed(2)}%`,\n      `Skewness:  ${skewness.toFixed(2)}`,\n      `Kurtosis:  ${kurtosis.toFixed(2)}`,\n    ];\n    const fontSize = 15;\n    ctx.save();\n    ctx.font = `${fontSize}px sans-serif`;\n    const padding = 16;\n    const lineHeight = fontSize * 1.6;\n    const boxWidth = Math.max(...lines.map((l) => ctx.measureText(l).width)) + padding * 2;\n    const boxHeight = lines.length * lineHeight + padding * 1.2;\n    const boxX = chartArea.right - boxWidth - 24;\n    const boxY = chartArea.top + 16;\n\n    ctx.fillStyle = t.elevatedBg;\n    ctx.strokeStyle = t.grid;\n    ctx.lineWidth = 1;\n    ctx.beginPath();\n    ctx.roundRect(boxX, boxY, boxWidth, boxHeight, 8);\n    ctx.fill();\n    ctx.stroke();\n\n    ctx.fillStyle = t.ink;\n    ctx.textBaseline = \"top\";\n    lines.forEach((line, i) => {\n      ctx.fillText(line, boxX + padding, boxY + padding * 0.6 + i * lineHeight);\n    });\n    ctx.restore();\n  },\n};\n\n// --- Chart ---------------------------------------------------------------------\nnew Chart(canvas, {\n  data: {\n    labels,\n    datasets: [\n      {\n        type: \"bar\",\n        label: \"Daily Returns\",\n        data: density,\n        backgroundColor: barColors,\n        borderWidth: 0,\n        categoryPercentage: 1.0,\n        barPercentage: 0.98,\n        order: 2,\n      },\n      {\n        type: \"line\",\n        label: \"Normal (Fitted)\",\n        data: normalCurve,\n        borderColor: t.palette[2],\n        backgroundColor: \"transparent\",\n        borderWidth: 3.5,\n        pointRadius: 0,\n        tension: 0.35,\n        order: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 8, right: 8 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"histogram-returns-distribution · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 21, weight: \"500\" },\n        padding: { bottom: 8 },\n      },\n      subtitle: {\n        display: true,\n        text: \"Matte-red bars mark returns beyond ±2σ (tail risk)\",\n        color: t.inkSoft,\n        font: { size: 15, style: \"italic\" },\n        padding: { bottom: 20 },\n      },\n      legend: {\n        labels: { color: t.ink, font: { size: 16 }, boxWidth: 24 },\n      },\n    },\n    scales: {\n      x: {\n        ticks: { color: t.inkSoft, font: { size: 14 }, maxTicksLimit: 12, maxRotation: 0 },\n        grid: { display: false },\n        title: { display: true, text: \"Daily Return (%)\", color: t.ink, font: { size: 18 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Density\", color: t.ink, font: { size: 18 } },\n        beginAtZero: true,\n      },\n    },\n  },\n  plugins: [statsBoxPlugin],\n});\n"}