Canvas JavaScript

Graphing a Selected Distribution

This JavaScript program demonstrates how to generate and graph a selected distribution. Use the selector to select a distribution on the bar width on the histogram.

GraphingASelectedDistribution.html

<!DOCTYPE html>
<html>
	<head>
		<title>XoaX.net's Javascript</title>
		<script type="text/javascript" src="GraphingASelectedDistribution.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="kUniform">Uniform</option>
								<option value="kGaussian" selected>Gaussian</option>
								<option value="kExponential">Exponential</option>
								<option value="kTriangular">Triangular</option>
								<option value="kBreitWigner">Breit-Wigner</option>
								<option value="kSemicircular">Semicircular</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>

GraphingASelectedDistribution.js

class CDistribution {
	mdaX;
	#mdGraphWidth;
	#mdMean = null;
	// The graph width should be calculated automatically from the data or set by the distribution
	constructor(dGraphWidth) {
		this.#mdGraphWidth = dGraphWidth;
	}
	Mean() {
		let dMean = 0;
		for (let i = 0; i < this.mdaX.length; ++i) {
			dMean += this.mdaX[i]/this.mdaX.length;
		}
		return dMean;
	}
	StandardDeviation() {
		return Math.sqrt(this.Variance());
	}
	Variance() {
		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;
		}
		return dVariance;
	}
	PixelXToGraphX(dPixelX, iCanvasWidth) {
		// Half the size should map to the mean
		let dHalfWidth = this.#mdGraphWidth/2;
		if (null == this.#mdMean) {
			this.#mdMean = this.Mean();
		}
		let dT = ((dPixelX - .5)/iCanvasWidth)*this.#mdGraphWidth - dHalfWidth + this.#mdMean;
		return dT;
	}
	Graph(qContext, dBarWidthInPixels) {
		qContext.fillStyle = "#606060";
		// Outline properties
		qContext.strokeStyle = "#F0F0F0";
		qContext.lineWidth = "1";
		// 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;
		// The canvas graph width
		let dGraphW = this.#mdGraphWidth;
		// 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 zero
		// 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);
			// Draw the half pixel width outline in the background color to frame the bars
			qContext.strokeRect(dStartOfCurrBarPix, 0, dBarWidthInPixels, (dRectHeight/dMaxY)*iH);
			// The increment for the bar
			dStartOfCurrBarPix = dEndOfCurrBarPix;
			++iBinIndex;
		}
		// 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 CUniformDistribution extends CDistribution {
	constructor(iCount) {
		// The base class constructor must be called before using this.
		super(1.2);
		// This generates a sorted array of the sample values of the distribution
		// This is a uniform distribution from 0 to 1
		this.mdaX = new Array(iCount);
		for (let i = 0; i < iCount; ++i) {
			this.mdaX[i] = 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;
			}
		}
	}
}

class CGaussianDistribution extends CDistribution {
	constructor(iCount) {
		super(6.1);
		// 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) {
			let dSum = 0.0;
			for (let j = 0; j < 6; ++j) {
				dSum += Math.random();
			}
			// Add the point to the end of the array
			this.mdaX[i] = dSum;
			// 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;
			}
		}
	}
}

class CExponentialDistribution extends CDistribution {
	constructor(iCount) {
		// The base class constructor must be called before using this.
		super(32.2);
		// This generates a sorted array of the sample values of the distribution
		// This is a uniform distribution from 0 to 1
		this.mdaX = new Array(iCount);
		for (let i = 0; i < iCount; ++i) {
			let dMean = 12.0;
			this.mdaX[i] = -dMean*Math.log(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;
			}
		}
	}
}

class CTriangularDistribution extends CDistribution {
	constructor(iCount) {
		// The base class constructor must be called before using this.
		super(1.2);
		// This generates a sorted array of the sample values of the distribution
		// This is a uniform distribution from 0 to 1
		this.mdaX = new Array(iCount);
		for (let i = 0; i < iCount; ++i) {
			let dA = -0.5;
			let dB = 0.5;
			let dC = 0.0;
			let dX = Math.random();
			if (dX < (dC - dA)/(dB - dA)) {
				this.mdaX[i] = dA + Math.sqrt((dB - dA)*(dC - dA)*dX);
			} else {
				this.mdaX[i] = dB - Math.sqrt((dB - dA)*(dB - dC)*(1.0 - dX));
			}
			// 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;
			}
		}
	}
}

class CBreitWignerDistribution extends CDistribution {
	constructor(iCount) {
		// The base class constructor must be called before using this.
		super(10.2);
		// This generates a sorted array of the sample values of the distribution
		// This is a uniform distribution from 0 to 1
		this.mdaX = new Array(iCount);
		for (let i = 0; i < iCount; ++i) {
			let dGamma = 1.0;
			let dA = 0.5;
			let dX = Math.random();
			this.mdaX[i] = dA + (dGamma/2.0)*Math.tan(Math.PI*(dX - .5));
			// 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;
			}
		}
	}
}

class CSemicircleDistribution extends CDistribution {
	constructor(iCount) {
		// The base class constructor must be called before using this.
		super(2.2);
		// This generates a sorted array of the sample values of the distribution
		// This is a uniform distribution from 0 to 1
		this.mdaX = new Array(iCount);
		for (let i = 0; i < iCount; ++i) {
			let dX = 2.0*Math.random() - 1.0;
			let dY = Math.random();
			while (dX*dX + dY*dY > 1.0) {
				dX = 2.0*Math.random() - 1.0;
				dY = Math.random();
			}
			this.mdaX[i] = dX;
			// 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;
			}
		}
	}
}

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 = null;
	switch (sDistributionValue) {
		case "kUniform":
			qDistribution = new CUniformDistribution(50000);
			break;
		case "kGaussian":
			qDistribution = new CGaussianDistribution(50000);
			break;
		case "kExponential":
			qDistribution = new CExponentialDistribution(50000);
			break;
		case "kTriangular":
			qDistribution = new CTriangularDistribution(50000);
			break;
		case "kBreitWigner":
			qDistribution = new CBreitWignerDistribution(50000);
			break;
		case "kSemicircular":
			qDistribution = new CSemicircleDistribution(50000);
			break;
	}
	// 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);
}
 

Output

 
 

© 2007–2025 XoaX.net LLC. All rights reserved.