{"spec_id":"histogram-returns-distribution","library":"d3","language":"javascript","code":"// anyplot.ai\n// histogram-returns-distribution: Returns Distribution Histogram\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 87/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 60, bottom: 90, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data: 252 daily returns (%) from a fixed-seed mixture -----------------\n// 92% \"calm\" days ~ N(0.05, 1.05); 8% \"shock\" days ~ N(-2.1, 2.6) — the\n// mixture produces the negative skew and fat left tail typical of equity\n// return series without relying on an unseeded RNG.\nlet lcgState = 20260902;\nfunction uniform() {\n  lcgState = (1103515245 * lcgState + 12345) % 2147483648;\n  return lcgState / 2147483648;\n}\nfunction gaussian() {\n  const u1 = Math.max(uniform(), 1e-9);\n  const u2 = uniform();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst n = 252;\nconst returns = [];\nfor (let i = 0; i < n; i++) {\n  const shock = uniform() < 0.08;\n  const value = shock ? -2.1 + gaussian() * 2.6 : 0.05 + gaussian() * 1.05;\n  returns.push(value);\n}\n\n// --- Fitted statistics --------------------------------------------------\nconst meanVal = d3.mean(returns);\nconst stdVal = d3.deviation(returns);\nconst skew =\n  d3.sum(returns, (d) => Math.pow((d - meanVal) / stdVal, 3)) / n;\nconst kurt =\n  d3.sum(returns, (d) => Math.pow((d - meanVal) / stdVal, 4)) / n - 3;\n\n// --- Histogram (density-normalized, equal-width bins) -----------------------\n// Binned manually (rather than d3.bin()'s default \"nice\" thresholds) so every\n// bin has the exact same width — d3.bin()'s rounded edge thresholds can leave\n// the first/last bin narrower than the rest, which would distort a lone\n// tail observation into a misleadingly tall density spike.\nconst [dataMin, dataMax] = d3.extent(returns);\nconst numBins = 28;\nconst binWidth = (dataMax - dataMin) / numBins;\nconst density = d3.range(numBins).map((i) => {\n  const x0 = dataMin + i * binWidth;\n  const x1 = x0 + binWidth;\n  const count = returns.filter((v) => v >= x0 && (v < x1 || (i === numBins - 1 && v <= x1))).length;\n  return { x0, x1, y: count / (n * binWidth) };\n});\n\n// --- Normal curve fitted to mean/std ----------------------------------------\nfunction normalPdf(x) {\n  return (\n    Math.exp(-0.5 * Math.pow((x - meanVal) / stdVal, 2)) /\n    (stdVal * Math.sqrt(2 * Math.PI))\n  );\n}\nconst curveX = d3.range(dataMin, dataMax, (dataMax - dataMin) / 200);\nconst curvePoints = curveX.map((x) => ({ x, y: normalPdf(x) }));\n\n// --- Scales -------------------------------------------------------------\nconst x = d3.scaleLinear().domain([dataMin, dataMax]).nice().range([0, iw]);\nconst yMax = Math.max(d3.max(density, (d) => d.y), d3.max(curvePoints, (d) => d.y)) * 1.1;\nconst y = d3.scaleLinear().domain([0, yMax]).nice().range([ih, 0]);\n\n// --- SVG mount ------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Axes -----------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).tickFormat((d) => `${d}%`).ticks(10));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6));\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.grid);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// y-axis gridlines (subtle, matches \"both axes for continuous/scatter-like\" density plot)\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).ticks(6).tickSize(-iw).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\ng.select(\".grid .domain\").remove();\n\n// --- Tail boundary reference lines (±2σ) ------------------------------------\nconst tailLo = meanVal - 2 * stdVal;\nconst tailHi = meanVal + 2 * stdVal;\nfor (const bound of [tailLo, tailHi]) {\n  g.append(\"line\")\n    .attr(\"x1\", x(bound)).attr(\"x2\", x(bound))\n    .attr(\"y1\", 0).attr(\"y2\", ih)\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 1.5)\n    .attr(\"stroke-dasharray\", \"6,5\")\n    .attr(\"opacity\", 0.6);\n}\n\n// --- Histogram bars: green for the bulk, matte red beyond ±2σ --------------\ng.selectAll(\"rect.bar\")\n  .data(density)\n  .join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", (d) => x(d.x0) + 1)\n  .attr(\"y\", (d) => y(d.y))\n  .attr(\"width\", (d) => Math.max(x(d.x1) - x(d.x0) - 2, 0))\n  .attr(\"height\", (d) => ih - y(d.y))\n  .attr(\"fill\", (d) => {\n    const center = (d.x0 + d.x1) / 2;\n    return center < tailLo || center > tailHi ? t.palette[4] : t.palette[0];\n  })\n  .attr(\"opacity\", 0.9);\n\n// --- Fitted normal curve ----------------------------------------------------\nconst line = d3.line().x((d) => x(d.x)).y((d) => y(d.y)).curve(d3.curveBasis);\ng.append(\"path\")\n  .datum(curvePoints)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[2])\n  .attr(\"stroke-width\", 3)\n  .attr(\"d\", line);\n\n// --- Axis titles --------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2).attr(\"y\", ih + 60)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"17px\")\n  .text(\"Daily Return (%)\");\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2).attr(\"y\", -62)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"17px\")\n  .text(\"Density\");\n\n// --- Legend -----------------------------------------------------------------\nconst legend = [\n  { label: \"Daily returns\", swatch: t.palette[0] },\n  { label: \"|z| > 2σ (tail)\", swatch: t.palette[4] },\n  { label: \"Normal fit\", swatch: t.palette[2], line: true },\n];\nconst legendG = g.append(\"g\").attr(\"transform\", `translate(${iw - 230}, 6)`);\nlegend.forEach((item, i) => {\n  const row = legendG.append(\"g\").attr(\"transform\", `translate(0, ${i * 26})`);\n  if (item.line) {\n    row.append(\"line\").attr(\"x1\", 0).attr(\"x2\", 18).attr(\"y1\", 8).attr(\"y2\", 8)\n      .attr(\"stroke\", item.swatch).attr(\"stroke-width\", 3);\n  } else {\n    row.append(\"rect\").attr(\"width\", 18).attr(\"height\", 14).attr(\"y\", 1).attr(\"fill\", item.swatch);\n  }\n  row.append(\"text\").attr(\"x\", 26).attr(\"y\", 12)\n    .attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\")\n    .text(item.label);\n});\n\n// --- Statistics callout box --------------------------------------------------\nconst statLines = [\n  `mean  ${meanVal >= 0 ? \"+\" : \"\"}${meanVal.toFixed(2)}%`,\n  `std       ${stdVal.toFixed(2)}%`,\n  `skew    ${skew.toFixed(2)}`,\n  `kurt     ${kurt.toFixed(2)}`,\n];\nconst boxW = 210;\nconst boxH = 24 * statLines.length + 24;\nconst boxG = g.append(\"g\").attr(\"transform\", `translate(12, 12)`);\nboxG.append(\"rect\")\n  .attr(\"width\", boxW).attr(\"height\", boxH)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"stroke\", t.grid)\n  .attr(\"rx\", 6);\nboxG.append(\"text\")\n  .attr(\"x\", 16).attr(\"y\", 26)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"15px\").style(\"font-weight\", \"600\")\n  .text(\"Fitted statistics\");\nstatLines.forEach((line, i) => {\n  boxG.append(\"text\")\n    .attr(\"x\", 16).attr(\"y\", 52 + i * 22)\n    .attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\")\n    .style(\"font-family\", \"monospace\")\n    .text(line);\n});\n\n// --- Title ------------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 52)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"histogram-returns-distribution · javascript · d3 · anyplot.ai\");\n"}