{"spec_id":"histogram-stepwise","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// histogram-stepwise: Step Histogram\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Tiny LCG so the browser (no seeded Math.random) still reproduces the sample.\nfunction lcg(seed) {\n  let state = seed >>> 0;\n  return function () {\n    state = (1664525 * state + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\nfunction randNormal(mean, std) {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * std;\n}\n\nconst sampleSize = 400;\nconst morningScores = Array.from({ length: sampleSize }, () => randNormal(72, 9));\nconst eveningScores = Array.from({ length: sampleSize }, () => randNormal(78, 7));\n\n// Shared bin edges so both step outlines overlay on the same axis.\nconst allScores = morningScores.concat(eveningScores);\nconst dataMin = Math.min(...allScores);\nconst dataMax = Math.max(...allScores);\nconst binCount = 22;\nconst binWidth = (dataMax - dataMin) / binCount;\nconst edges = Array.from({ length: binCount + 1 }, (_, i) => dataMin + i * binWidth);\n\nfunction histogram(values) {\n  const counts = new Array(binCount).fill(0);\n  values.forEach((v) => {\n    let idx = Math.floor((v - dataMin) / binWidth);\n    if (idx < 0) idx = 0;\n    if (idx >= binCount) idx = binCount - 1;\n    counts[idx] += 1;\n  });\n  return counts;\n}\n\n// One (left-edge, count) point per bin plus a leading/trailing zero — with\n// `plotOptions.series.step: \"left\"` Highcharts interpolates the horizontal\n// bin segments and vertical connectors itself, dropping to zero at both ends.\nfunction stepPoints(counts) {\n  const points = [[edges[0], 0]];\n  counts.forEach((count, i) => points.push([edges[i], count]));\n  points.push([edges[edges.length - 1], 0]);\n  return points;\n}\n\nfunction argmax(arr) {\n  return arr.reduce((best, v, i) => (v > arr[best] ? i : best), 0);\n}\n\nconst morningCounts = histogram(morningScores);\nconst eveningCounts = histogram(eveningScores);\n\n// Callout on the modal (tallest) bin across both classes — a small design\n// touch that adds a focal point beyond the plain overlay comparison.\nconst morningPeakIdx = argmax(morningCounts);\nconst eveningPeakIdx = argmax(eveningCounts);\nconst morningIsPeak = morningCounts[morningPeakIdx] >= eveningCounts[eveningPeakIdx];\nconst peakIdx = morningIsPeak ? morningPeakIdx : eveningPeakIdx;\nconst peakCount = morningIsPeak ? morningCounts[morningPeakIdx] : eveningCounts[eveningPeakIdx];\nconst peakSeriesName = morningIsPeak ? \"Morning Class\" : \"Evening Class\";\nconst peakColor = morningIsPeak ? t.palette[0] : t.palette[1];\nconst peakX = (edges[peakIdx] + edges[peakIdx + 1]) / 2;\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"line\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      load() {\n        const chart = this;\n        const px = chart.xAxis[0].toPixels(peakX);\n        const py = Math.max(chart.yAxis[0].toPixels(peakCount), chart.plotTop + 14);\n        const labelOnRight = px < chart.plotLeft + chart.plotWidth / 2;\n        chart.renderer\n          .circle(px, py, 5)\n          .attr({ fill: peakColor, stroke: t.pageBg, \"stroke-width\": 2, zIndex: 6 })\n          .add();\n        chart.renderer\n          .text(`Modal bin · ${peakSeriesName}: ${peakCount} students`, px + (labelOnRight ? 10 : -10), py - 12)\n          .css({ color: t.ink, fontSize: \"13px\", fontWeight: \"600\" })\n          .attr({ align: labelOnRight ? \"left\" : \"right\" })\n          .add();\n      },\n    },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"histogram-stepwise · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: { text: \"Exam Score\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: edges[0],\n    max: edges[edges.length - 1],\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: { text: \"Number of Students\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: 0,\n    gridLineColor: t.grid,\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: \"Score {point.x:.1f}<br/>Count: <b>{point.y}</b>\",\n  },\n  plotOptions: {\n    series: {\n      animation: false,\n      marker: { enabled: false },\n      lineWidth: 3,\n      step: \"left\",\n      states: { hover: { lineWidthPlus: 0 } },\n    },\n  },\n  series: [\n    { name: \"Morning Class\", data: stepPoints(morningCounts) },\n    { name: \"Evening Class\", data: stepPoints(eveningCounts) },\n  ],\n});\n"}