This JavaScript program demonstrates how to generate and graph a normal distribution. Use the selector to select the bar width on the histogram. The mean is displayed as a red line and three standard deviations are displayed as progressively lighter gray regions.
<!DOCTYPE html> <html> <head> <title>XoaX.net's Javascript</title> <script type="text/javascript" src="GraphingANormalDistribution.js"></script> </head> <body onload="Initialize()"> <div style="width:908px;height:430px;"> <div style="width:108px;height:430px;float:left;"> <input id="idHighY" type="text" size="9" style="width:100px;height:16px;"/> <div style="width:108px;height:356px;"></div> <input id="idLowY" type="text" size="9" style="width:100px;height:16px;"/> </div> <canvas id="idCanvas" width="800" height ="400" style="background-color: #F0F0F0;float:left;"></canvas> <div style="width:800px;height:22px;float:left;"> <input id="idLowX" type="text" size="9" style="width:100px;height:16px;float:left;"/> <div style="width:584px;height:22px;float:left;"> <div style="width:fit-content;margin-left:auto;margin-right:auto;"> <div style="width:fit-content;float:left;">Distribution: <select style="height: 22px;" id="idDistribution" onchange="fnSelectFunction()"> <option value="kGaussian" selected>Gaussian</option> </select> </div> <div style="width:fit-content;float:left;margin-left:5px;">Bar Width: <select style="height: 22px;" id="idBarWidth" onchange="fnSelectFunction()"> <option value="2">2</option> <option value="3">3</option> <option value="5">5</option> <option value="8">8</option> <option value="13">13</option> <option value="21" selected>21</option> </select> </div> </div> </div> <input id="idHighX" type="text" size="9" style="width:100px;height:16px;float:left;"/> </div> <div style="width:108px;height:22px;float:right;"></div> </div> </body> </html>
class CDistribution {
mdaX;
#mdGraphWidth;
#mdMean = null;
#mdStdDev = null;
// The graph width should be calculated automatically from the data or set by the distribution
constructor() {
}
GraphWidth() {
if (this.#mdGraphWidth == null) {
this.#mdGraphWidth = 8*this.StandardDeviation();
}
return this.#mdGraphWidth;
}
Mean() {
if (this.#mdMean == null) {
this.#mdMean = 0.0;
for (let i = 0; i < this.mdaX.length; ++i) {
this.#mdMean += this.mdaX[i]/this.mdaX.length;
}
}
return this.#mdMean;
}
StandardDeviation() {
if (this.#mdStdDev == null) {
let dMean = this.Mean();
let dVariance = 0;
for (let i = 0; i < this.mdaX.length; ++i) {
dVariance += Math.pow(this.mdaX[i] - dMean, 2)/this.mdaX.length;
}
this.#mdStdDev = Math.sqrt(dVariance);
}
return this.#mdStdDev;
}
PixelXToGraphX(dPixelX, iCanvasWidth) {
// Half the size should map to the mean
let dHalfWidth = this.GraphWidth()/2;
let dMean = this.Mean();
let dX = ((dPixelX + .5)/iCanvasWidth)*this.GraphWidth() - dHalfWidth + dMean;
return dX;
}
GraphXToPixelX(dX, iCanvasWidth) {
let dMean = this.Mean();
let dHalfWidth = this.GraphWidth()/2;
// X = ((PX + .5)/CW)GW - HW + M
// PX = (CW(X - M + HW)/GW) - .5
let dPX = iCanvasWidth*(dX - dMean + dHalfWidth)/this.GraphWidth() - .5;
return dPX;
}
Graph(qContext, dBarWidthInPixels) {
// Reset the transformation so that the flip transformations do not build up an cancel each other.
qContext.resetTransform();
// This is the basic transformation to flip the y-axis
qContext.transform(1, 0, 0, -1, 0, qContext.canvas.height);
// The canvas width and height in pixels or pixels per canvas
let iW = qContext.canvas.width;
let iH = qContext.canvas.height;
// Graph the standard deviation regions
qContext.fillStyle = "gray";
qContext.globalAlpha = .25;
for (let i = 0; i < 3; ++i) {
let iStartStdDev = this.GraphXToPixelX(this.Mean() - (i + 1)*this.StandardDeviation(), iW);
let iEndStdDev = this.GraphXToPixelX(this.Mean() + (i + 1)*this.StandardDeviation(), iW);
qContext.fillRect(iStartStdDev, 0, iEndStdDev - iStartStdDev, 400);
}
// Reset the alpha value
qContext.globalAlpha = 1.0;
qContext.fillStyle = "#606060";
// The canvas graph width
let dGraphW = this.GraphWidth();
// Pixel width in graph units = graph width / pixels per canvas width
let dPixW = dGraphW/iW;
// Bar width in pixels / pixels per canvas width
let dBarsPerWidth = iW/dBarWidthInPixels;
// The width of a bar in values
let dBarWidth = dBarWidthInPixels*dPixW;
// The middle bar is centered on the mean
// Find the start of the first bar and continue from there until all of them are drawn
let dStartOfFirstBarPix = iW/2 - dBarWidthInPixels/2 - dBarWidthInPixels*Math.ceil(dBarsPerWidth/2);
let dStartOfCurrBarPix = dStartOfFirstBarPix;
let dStartOfCurrBarValue = this.PixelXToGraphX(dStartOfCurrBarPix, iW);
let iIndexOfNextPoint = 0;
// Get to the first point that is in the range of the first bar.
while (iIndexOfNextPoint < this.mdaX.length && dStartOfCurrBarValue > this.mdaX[iIndexOfNextPoint]) {
++iIndexOfNextPoint;
}
let iaBins = new Array();
while (iIndexOfNextPoint < this.mdaX.length && dStartOfCurrBarPix < iW) {
let dEndOfCurrBarPix = dStartOfCurrBarPix + dBarWidthInPixels;
let dEndOfCurrBarValue = this.PixelXToGraphX(dEndOfCurrBarPix, iW);
let iIndexOfEnd = iIndexOfNextPoint;
while (iIndexOfEnd < this.mdaX.length && dEndOfCurrBarValue > this.mdaX[iIndexOfEnd]) {
++iIndexOfEnd;
}
// The number of points in the bin
iaBins.push(iIndexOfEnd - iIndexOfNextPoint);
// The increment for the bar
dStartOfCurrBarPix = dEndOfCurrBarPix;
// Increment for the points
iIndexOfNextPoint = iIndexOfEnd;
}
// Get the size of the largest bin
let iLargestBinSize = 0;
for (let i = 0; i < iaBins.length; ++i) {
if (iLargestBinSize < iaBins[i]) {
iLargestBinSize = iaBins[i];
}
}
// Make the height of the graph 20% higher than the largest bin size.
let dMaxY = iLargestBinSize*1.1;
let iBinIndex = 0;
dStartOfCurrBarPix = dStartOfFirstBarPix;
// Draw the bars of the graph
while (iBinIndex < iaBins.length && dStartOfCurrBarPix < iW) {
let dEndOfCurrBarPix = dStartOfCurrBarPix + dBarWidthInPixels;
let dRectHeight = iaBins[iBinIndex];
qContext.fillRect(dStartOfCurrBarPix, 0, dBarWidthInPixels, (dRectHeight/dMaxY)*iH);
// The increment for the bar
dStartOfCurrBarPix = dEndOfCurrBarPix;
++iBinIndex;
}
// Draw the mean line
let iMeanBarX = this.GraphXToPixelX(this.Mean(), iW);
qContext.strokeStyle = "red";
qContext.lineWidth = .5;
qContext.beginPath();
qContext.moveTo(iMeanBarX, 0);
qContext.lineTo(iMeanBarX, 400);
qContext.stroke();
// Fill in the bounds
let qLowYElement = document.getElementById("idLowY");
qLowYElement.value = 0;
let qHighYElement = document.getElementById("idHighY");
qHighYElement.value = dMaxY.toFixed(1);
let qLowXElement = document.getElementById("idLowX");
qLowXElement.value = this.PixelXToGraphX(-.5, iW).toFixed(3);
let qHighXElement = document.getElementById("idHighX");
qHighXElement.value = this.PixelXToGraphX(iW-.5, iW).toFixed(3);
}
}
class CGaussianDistribution extends CDistribution {
constructor(iCount) {
super();
// This generates a sorted array of the sample values of the distribution
// This is an approximately gaussian distribution from 0 to 6
this.mdaX = new Array(iCount);
for (let i = 0; i < iCount; ++i) {
// Add the point to the end of the array
this.mdaX[i] = CGaussianDistribution.InversePhi(Math.random());
// Insertion sort each point after it is added
let j = i;
while (j > 0 && this.mdaX[j] < this.mdaX[j - 1]) {
let dSwap = this.mdaX[j];
this.mdaX[j] = this.mdaX[j - 1];
this.mdaX[j - 1] = dSwap;
--j;
}
}
}
// Turn a uniformly chosen random variable into a value chosen from a
// normal distribution with mean 0 and standard deviation 1
static InversePhi(p) {
// Constants for the approximation
const p_low = 0.02425;
const p_high = 1 - p_low;
// Coefficients for the rational approximation
const a1 = -3.969683028665376e+01;
const a2 = 2.209460984245205e+02;
const a3 = -2.759285104469687e+02;
const a4 = 1.383577518672690e+02;
const a5 = -3.066479806614716e+01;
const a6 = 2.506628277459239e+00;
const b1 = -5.447609879822406e+01;
const b2 = 1.615858368580409e+02;
const b3 = -1.556989798598866e+02;
const b4 = 6.680131188771972e+01;
const b5 = -1.328068155288572e+01;
const c1 = -7.784894002430293e-03;
const c2 = -3.223964580411365e-01;
const c3 = -2.400758277161838e+00;
const c4 = -2.549732539343734e+00;
const c5 = 4.374664141464968e+00;
const c6 = 2.938163982698783e+00;
const d1 = 7.784695709041462e-03;
const d2 = 3.224671290700398e-01;
const d3 = 2.445134137142996e+00;
const d4 = 3.754408661907416e+00;
let x = 0.0;
if (0.0 < p && p < 1) {
if (p < p_low) {
let q = Math.sqrt(-2*Math.log(p));
x = (((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
} else if (p_high < p) {
let q = Math.sqrt(-2*Math.log(1-p));
x = -(((((c1*q+c2)*q+c3)*q+c4)*q+c5)*q+c6) / ((((d1*q+d2)*q+d3)*q+d4)*q+1);
} else {
let q = p - 0.5;
let r = q*q;
x = (((((a1*r+a2)*r+a3)*r+a4)*r+a5)*r+a6)*q / (((((b1*r+b2)*r+b3)*r+b4)*r+b5)*r+1);
}
}
return x;
}
}
function Initialize() {
var qCanvas = document.getElementById("idCanvas");
var qContext = qCanvas.getContext("2d");
var qDist = new CGaussianDistribution(50000);
qDist.Graph(qContext, 21);
}
function fnSelectFunction() {
var qCanvas = document.getElementById("idCanvas");
var qContext = qCanvas.getContext("2d");
qContext.clearRect(0, 0, qCanvas.width, qCanvas.height);
// Get the distribution value
var sDistributionValue = document.getElementById("idDistribution").value;
var qDistribution = new CGaussianDistribution(50000);
// Get the bar width
var sBarWidth = document.getElementById("idBarWidth").value;
let iBarWidth = 2;
switch (sBarWidth) {
case "2":
iBarWidth = 2;
break;
case "3":
iBarWidth = 3;
break;
case "5":
iBarWidth = 5;
break;
case "8":
iBarWidth = 8;
break;
case "13":
iBarWidth = 13;
break;
case "21":
iBarWidth = 21;
break;
}
qDistribution.Graph(qContext, iBarWidth);
}
© 20072025 XoaX.net LLC. All rights reserved.