Skip to content

Instantly share code, notes, and snippets.

@yeiichi
Created July 31, 2026 01:59
Show Gist options
  • Select an option

  • Save yeiichi/67aa8ee202e09a35eeb63564caf4bf05 to your computer and use it in GitHub Desktop.

Select an option

Save yeiichi/67aa8ee202e09a35eeb63564caf4bf05 to your computer and use it in GitHub Desktop.
Generate random values from a normal distribution and display the histogram
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gaussian Histogram Generator</title>
<style>
:root {
color-scheme: light;
--bg: #f6f7f9;
--panel: #ffffff;
--text: #1c2430;
--muted: #647184;
--line: #d8dde6;
--accent: #227c70;
--accent-dark: #175f56;
--bar: #4477aa;
--bar-hi: #d4663f;
--shadow: 0 14px 34px rgba(27, 36, 48, 0.08);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
width: min(1180px, calc(100vw - 32px));
margin: 0 auto;
padding: 32px 0;
}
header {
display: flex;
justify-content: space-between;
gap: 24px;
align-items: flex-end;
margin-bottom: 22px;
}
h1 {
margin: 0;
font-size: 32px;
line-height: 1.1;
font-weight: 760;
}
.subtitle {
margin: 8px 0 0;
color: var(--muted);
font-size: 15px;
}
.layout {
display: grid;
grid-template-columns: 320px minmax(0, 1fr);
gap: 18px;
align-items: start;
}
.panel {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: var(--shadow);
}
.controls {
padding: 18px;
}
.field {
display: grid;
gap: 7px;
margin-bottom: 16px;
}
label {
color: #334155;
font-size: 13px;
font-weight: 700;
}
input {
width: 100%;
height: 42px;
border: 1px solid var(--line);
border-radius: 7px;
padding: 0 12px;
color: var(--text);
font: inherit;
background: #fbfcfd;
}
input:focus {
border-color: var(--accent);
outline: 3px solid rgba(34, 124, 112, 0.18);
}
button {
width: 100%;
height: 44px;
border: 0;
border-radius: 7px;
color: #fff;
background: var(--accent);
font: inherit;
font-weight: 760;
cursor: pointer;
}
button:hover {
background: var(--accent-dark);
}
.stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 18px;
}
.stat {
border: 1px solid var(--line);
border-radius: 7px;
padding: 10px;
min-width: 0;
}
.stat span {
display: block;
color: var(--muted);
font-size: 12px;
font-weight: 700;
}
.stat strong {
display: block;
margin-top: 4px;
overflow-wrap: anywhere;
font-size: 18px;
}
.chart-panel {
padding: 16px;
}
.chart-top {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 10px;
color: var(--muted);
font-size: 13px;
font-weight: 700;
}
canvas {
display: block;
width: 100%;
height: 520px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
}
.error {
min-height: 20px;
margin-top: 12px;
color: #a23a2a;
font-size: 13px;
font-weight: 700;
}
@media (max-width: 820px) {
main {
width: min(100vw - 22px, 680px);
padding: 20px 0;
}
header {
display: block;
}
h1 {
font-size: 27px;
}
.layout {
grid-template-columns: 1fr;
}
canvas {
height: 390px;
}
}
</style>
</head>
<body>
<main>
<header>
<div>
<h1>Gaussian Histogram Generator</h1>
<p class="subtitle">Generate random values from a normal distribution and display the histogram.</p>
</div>
</header>
<section class="layout">
<form class="panel controls" id="controls">
<div class="field">
<label for="trials">Trials n</label>
<input id="trials" type="number" min="0" step="1" value="1000" required>
</div>
<div class="field">
<label for="mu">Mu m</label>
<input id="mu" type="number" step="any" value="0" required>
</div>
<div class="field">
<label for="sigma">Sigma s</label>
<input id="sigma" type="number" min="0" step="any" value="1" required>
</div>
<button type="submit">Generate</button>
<div class="error" id="error" aria-live="polite"></div>
<div class="stats" aria-label="Distribution summary">
<div class="stat">
<span>Count</span>
<strong id="count">0</strong>
</div>
<div class="stat">
<span>Mean</span>
<strong id="mean">0</strong>
</div>
<div class="stat">
<span>Std dev</span>
<strong id="stddev">0</strong>
</div>
<div class="stat">
<span>Bins</span>
<strong id="bins">0</strong>
</div>
</div>
</form>
<section class="panel chart-panel">
<div class="chart-top">
<span id="range">Range: -</span>
<span id="peak">Peak: -</span>
</div>
<canvas id="histogram" width="1100" height="620" aria-label="Gaussian histogram"></canvas>
</section>
</section>
</main>
<script>
const form = document.querySelector("#controls");
const canvas = document.querySelector("#histogram");
const ctx = canvas.getContext("2d");
const fields = {
n: document.querySelector("#trials"),
m: document.querySelector("#mu"),
s: document.querySelector("#sigma"),
error: document.querySelector("#error"),
count: document.querySelector("#count"),
mean: document.querySelector("#mean"),
stddev: document.querySelector("#stddev"),
bins: document.querySelector("#bins"),
range: document.querySelector("#range"),
peak: document.querySelector("#peak"),
};
let currentValues = [];
function gaussianRandom(mu, sigma) {
let u = 0;
let v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
return mu + z * sigma;
}
function generateGaussianDistribution(n, m, s) {
if (!Number.isInteger(n) || n < 0) {
throw new Error("n must be a whole number greater than or equal to 0.");
}
if (n > 1000000) {
throw new Error("n must be 1,000,000 or less.");
}
if (!Number.isFinite(m)) {
throw new Error("mu must be a valid number.");
}
if (!Number.isFinite(s) || s < 0) {
throw new Error("sigma must be greater than or equal to 0.");
}
return Array.from({ length: n }, () => gaussianRandom(m, s));
}
function formatNumber(value) {
if (!Number.isFinite(value)) return "-";
return Math.abs(value) >= 1000 || Math.abs(value) < 0.001 && value !== 0
? value.toExponential(3)
: value.toFixed(3);
}
function summarize(values) {
if (values.length === 0) {
return { mean: 0, stddev: 0 };
}
const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / values.length;
return { mean, stddev: Math.sqrt(variance) };
}
function makeBins(values) {
if (values.length === 0) {
return { bins: [], min: 0, max: 0, maxCount: 0 };
}
let min = values[0];
let max = values[0];
for (const value of values) {
if (value < min) min = value;
if (value > max) max = value;
}
const binCount = Math.max(1, Math.min(70, Math.round(Math.sqrt(values.length))));
const bins = Array.from({ length: binCount }, (_, index) => ({
index,
count: 0,
start: min,
end: max,
}));
const span = max - min;
if (span === 0) {
bins[0].count = values.length;
return { bins: [bins[0]], min, max, maxCount: values.length };
}
for (let index = 0; index < binCount; index += 1) {
const start = min + span * index / binCount;
const end = min + span * (index + 1) / binCount;
bins[index].start = start;
bins[index].end = end;
}
for (const value of values) {
const index = Math.min(binCount - 1, Math.floor((value - min) / span * binCount));
bins[index].count += 1;
}
return { bins, min, max, maxCount: Math.max(...bins.map((bin) => bin.count)) };
}
function drawHistogram(values) {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = Math.round(rect.width * dpr);
canvas.height = Math.round(rect.height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const width = rect.width;
const height = rect.height;
const margin = { top: 34, right: 24, bottom: 54, left: 58 };
const plotWidth = width - margin.left - margin.right;
const plotHeight = height - margin.top - margin.bottom;
const histogram = makeBins(values);
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, width, height);
ctx.strokeStyle = "#d8dde6";
ctx.lineWidth = 1;
for (let i = 0; i <= 4; i += 1) {
const y = margin.top + plotHeight * i / 4;
ctx.beginPath();
ctx.moveTo(margin.left, y);
ctx.lineTo(width - margin.right, y);
ctx.stroke();
}
ctx.strokeStyle = "#1c2430";
ctx.beginPath();
ctx.moveTo(margin.left, margin.top);
ctx.lineTo(margin.left, margin.top + plotHeight);
ctx.lineTo(width - margin.right, margin.top + plotHeight);
ctx.stroke();
if (histogram.bins.length === 0) {
ctx.fillStyle = "#647184";
ctx.font = "16px system-ui, sans-serif";
ctx.textAlign = "center";
ctx.fillText("Enter values and generate a histogram", width / 2, height / 2);
fields.range.textContent = "Range: -";
fields.peak.textContent = "Peak: -";
fields.bins.textContent = "0";
return;
}
const barWidth = plotWidth / histogram.bins.length;
histogram.bins.forEach((bin, index) => {
const barHeight = histogram.maxCount === 0 ? 0 : bin.count / histogram.maxCount * plotHeight;
const x = margin.left + index * barWidth + 1;
const y = margin.top + plotHeight - barHeight;
ctx.fillStyle = index === Math.floor(histogram.bins.length / 2) ? "#d4663f" : "#4477aa";
ctx.fillRect(x, y, Math.max(1, barWidth - 2), barHeight);
});
ctx.fillStyle = "#647184";
ctx.font = "12px system-ui, sans-serif";
ctx.textAlign = "right";
ctx.fillText(String(histogram.maxCount), margin.left - 8, margin.top + 4);
ctx.fillText("0", margin.left - 8, margin.top + plotHeight + 4);
ctx.textAlign = "center";
ctx.fillText(formatNumber(histogram.min), margin.left, height - 20);
ctx.fillText(formatNumber(histogram.max), width - margin.right, height - 20);
fields.range.textContent = `Range: ${formatNumber(histogram.min)} to ${formatNumber(histogram.max)}`;
fields.peak.textContent = `Peak: ${histogram.maxCount}`;
fields.bins.textContent = String(histogram.bins.length);
}
function update() {
fields.error.textContent = "";
try {
const n = Number(fields.n.value);
const m = Number(fields.m.value);
const s = Number(fields.s.value);
const values = generateGaussianDistribution(n, m, s);
currentValues = values;
const summary = summarize(values);
fields.count.textContent = String(values.length);
fields.mean.textContent = formatNumber(summary.mean);
fields.stddev.textContent = formatNumber(summary.stddev);
drawHistogram(values);
} catch (error) {
fields.error.textContent = error.message;
}
}
form.addEventListener("submit", (event) => {
event.preventDefault();
update();
});
window.addEventListener("resize", () => drawHistogram(currentValues));
update();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment