
本文介绍如何基于正态分布概率密度函数(pdf)生成覆盖整数区间 [0, 10] 的平滑钟形高度序列,支持自由设定峰值位置(如偏移至 x=3)和最大高度(如 100),并提供可交互拖拽调节的 chart.js 实现方案。
在程序化生成地形、UI 动画缓动曲线或数据可视化中,常需构造一条“钟形”高度分布——起始于 0、终止于 10、峰值可控、整体平滑。虽然贝塞尔曲线(如 BezierJS)灵活直观,但若仅需数学上可预测、可参数化、易缩放的单峰曲线,正态分布的概率密度函数(PDF)是更简洁高效的选择。
✅ 核心原理:正态 PDF + 线性归一化
正态 PDF 公式如下(无需积分,仅求点值):
$$ f(x) = \frac{1}{\sigma\sqrt{2\pi}} \cdot e^{-\frac{(x-\mu)^2}{2\sigma^2}} $$
其中:
- μ(mean)控制峰值横坐标(如设为 3 即峰值在 x=3);
- σ(stdDev)控制“宽度”:σ 越小,曲线越陡峭;越大则越扁平;
- 函数天然关于 μ 对称,满足“钟形”要求。
由于 PDF 原始输出最大值不为 100,我们采用线性缩放法:先计算所有采样点中的最大 y 值 maxY,再将每个 y 乘以缩放因子 100 / maxY,确保最终数组严格满足 maxHeight = 100。
? 示例代码(完整可运行)
以下代码生成 [0,1,2,...,10] 对应的 11 个高度值,并用 Chart.js 可视化,集成 chartjs-plugin-dragdata 支持拖拽调节各点:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://chrispahm.github.io/chartjs-plugin-dragdata/assets/chartjs-plugin-dragdata.min.js"></script>
<canvas id="myChart" width="400" height="140"></canvas>
<script>
function calculateNormalPDF(x, mean, stdDev) {
return 1 / (stdDev * Math.sqrt(2 * Math.PI))
* Math.exp(-Math.pow(x - mean, 2) / (2 * Math.pow(stdDev, 2)));
}
const left = 0, right = 10, step = 1, maxHeight = 100;
const mean = 3; // ← 峰值位置:可设为 3、5、7 等任意值
const stdDev = 1.8; // ← 控制“胖瘦”:建议 1.2 ~ 3.0 之间
// 1. 生成原始 PDF 值
const dataPoints = [];
const labels = [];
let maxY = -Infinity;
for (let x = left; x <= right; x += step) {
const y = calculateNormalPDF(x, mean, stdDev);
dataPoints.push(y);
labels.push(x.toFixed(0));
if (y > maxY) maxY = y;
}
// 2. 归一化至 maxHeight = 100
const factor = maxHeight / maxY;
for (let i = 0; i < dataPoints.length; i++) {
dataPoints[i] = parseFloat((dataPoints[i] * factor).toFixed(2));
}
// 3. 渲染可拖拽图表
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
data: dataPoints,
label: 'Bell Curve Heights',
borderColor: '#4e73df',
backgroundColor: 'rgba(78, 115, 223, 0.1)',
tension: 0.4,
pointRadius: 6,
pointBackgroundColor: '#4e73df',
fill: true,
showLine: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: { enabled: false },
dragData: {
round: 0.1,
onDrag: (e, datasetIndex, index, value) => {
// 可在此扩展逻辑:如实时重算 PDF 参数
}
}
},
scales: {
y: { beginAtZero: true, max: maxHeight + 5 },
x: { ticks: { stepSize: 1 } }
}
}
});
</script>⚠️ 注意事项与进阶提示
- 非对称需求? 正态 PDF 天然对称。若需真正左/右偏斜(skewed bell),可考虑 Skew Normal Distribution 或分段二次/三次插值(如使用 BezierJS 构造控制点后 curve.get(t) 采样)。
- 整数点精度: 上述代码以 step = 1 严格对应 [0,1,...,10],若需更高密度(如每 0.5 单位采样),只需调整 step 并对结果取整或插值。
- 性能友好: PDF 计算为 O(n),无迭代或数值积分,适合实时生成(万级点亦无压力)。
-
替代库参考:
- BezierJS:适合手绘拟合 → const curve = new Bezier(p0, p1, p2, p3); curve.get(t);
- mathjs:提供 math.distribution('Normal') 封装;
- d3-shape:d3.line().curve(d3.curveBasis) 用于平滑连接离散点。
✅ 总结:当目标是参数简洁、数学可控、峰值可偏移、结果确定的钟形高度生成时,正态 PDF + 线性归一化是最轻量且专业的方案;配合 Chart.js 插件,即可快速构建带 UI 交互的“迷你 Inkscape 曲线编辑器”。










