Distributions

Created May 13, 2025 (Today)Updated May 13, 2025 (Today)

Uniform

Simplest form of random - every value has an equal probability of being selected. In 1D, this creates an even spread of points along a line. In 2D, it creates an even spread of points across a plane.

Uniform distributions are useful as a baseline for comparison and for generating truly random positions. However, they often look artificial since natural phenomena rarely follow perfectly uniform patterns.

/**
 * Generates a random number between the specified minimum and maximum values.
 * If only one argument is provided, it is assumed to be the maximum value and the minimum value is set to 0.
 *
 * @param {number} [min=0] - The minimum value (inclusive). Defaults to 0 if not provided.
 * @param {number} [max=1] - The maximum value (exclusive). Defaults to 1 if not provided.
 * @returns {number} - A random number between min and max.
 */
function uniform(min = 0, max = 1) {
    if (arguments.length === 1) {
        // assume the user passed in a max value
        max = min
        min = 0
    }
    // Return a random number between min and max
    return Math.random() * (max - min) + min
}

// example: create 1000 uniformly random points
// const pts = Array.from({ length: 1000 }, () => [uniform(), uniform()])

Gaussian

Gaussian distribution - mean: 0.5, stdDev: 0.15

Also known as the normal distribution or "bell curve", Gaussian distributions cluster values around a mean (center) value, with decreasing probability as you move away from the mean. The spread of values is controlled by the standard deviation.

Gaussian distributions are extremely common in nature and statistics. They're useful for creating natural-looking clusters, organic patterns, and modeling real-world variation. The central limit theorem states that many random processes tend toward a Gaussian distribution.

In a Gaussian distribution, about 68% of values lie within one standard deviation of the mean, 95% within two, and 99.7% within three — a property known as the empirical rule.

// Function to generate a normally distributed random number
// Note: this throws away the 2nd value and can be optimized through caching
function boxMullerTransform() {
    let u = 0,
        v = 0
    while (u === 0) u = Math.random() // Converting [0,1) to (0,1)
    while (v === 0) v = Math.random()
    let z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v)
    return z
}

/**
 * Calculates a random number from a Gaussian distribution.
 *
 * @param {number} mean - The mean value of the distribution.
 * @param {number} stdDev - The standard deviation of the distribution.
 * @returns {number} The random number from the Gaussian distribution.
 */
function gaussian(mean, stdDev) {
    return mean + stdDev * boxMullerTransform()
}

Pareto

Noise

Should this be included?

Creative Coding Examples

Urban Density

import { Rectangle } from 'root/geo'
import { gaussian, pareto, weightedRandom } from 'root/random'
import { range } from 'root/array'

/* https://www.tylerxhobbs.com/words/probability-distributions-for-algorithmic-artists */
function urbanDensity(cmd, palette) {
    const { background, primary, secondary, accent, dark, neutral } = palette
    const colors = [primary, secondary, accent, neutral]

    // draw random rect (width, heights) according to pareto distribution
    const rects = range(2000).map(() => {
        const width = pareto(0.008, 1.0)
        const height = pareto(0.005, 1.0)
        const color = weightedRandom(colors, [3, 3, 2, 1])
        return new Rectangle([gaussian(-0.2, 0.4), gaussian(-0.2, 0.5)], [width, height], { fill: color + 'AA' })
    })

    cmd.clear(background)
    cmd.draw(rects)
}
urbanDensity.title = 'Urban Density'
export { urbanDensity }