Variation

Vary a drawing with randomness, waves, and noise.

Choose the character of the variation

Scrub one input through three patterns.

Fixed random samples Irregular, with abrupt changes between samples.
Sine / cosine Smooth changes with a recognizable period.
Perlin noise Smooth changes with an irregular rhythm.

The horizontal axis is input; height is the resulting value, from 0 to 1. The circles use that value for size.

Explore scale, amount, and cosine

Input scale changes how much of the pattern fits across the row. Variation amount changes how far the values push the marks, without changing the pattern itself. Cosine is sine shifted by a quarter cycle.

Try it: . The curves still differ; every mark becomes the same size.

p5.js code: the three rows

Comments identify code that belongs outside the sketch functions, in setup(), or in draw(). Canvas creation, background clearing, styling, and function wrappers are omitted. Sine and cosine use p5.js’s default radians. The snippets draw the marks and reproduce the behavior, not the exact samples of these canvases; axes, curves, and the cursor are omitted.

Fixed random samples

// Outside setup() and draw():
let samples;

// In setup():
randomSeed(42);
samples = Array.from({ length: 49 }, () => random());

// In draw():
const inputScale = 1;
const amount = 0.8;
for (let i = 0; i < 25; i++) {
  const u = (i / 24) * 12 * inputScale;
  const value = samples[floor(u * 4)];
  const diameter = 14 + (value - 0.5) * 22 * amount;
  circle(map(i, 0, 24, 24, width - 24), 40, diameter);
}

Sine / cosine

// In draw():
const inputScale = 1;
const amount = 0.8;
for (let i = 0; i < 25; i++) {
  const u = (i / 24) * 12 * inputScale;
  const value = (1 + sin(u)) / 2;
  const diameter = 14 + (value - 0.5) * 22 * amount;
  circle(map(i, 0, 24, 24, width - 24), 40, diameter);
}
// Replace sin(u) with cos(u) for a quarter-cycle shift.

Perlin noise

// In setup():
noiseSeed(42);

// In draw():
const inputScale = 1;
const amount = 0.8;
for (let i = 0; i < 25; i++) {
  const u = (i / 24) * 12 * inputScale;
  const value = noise(u);
  const diameter = 14 + (value - 0.5) * 22 * amount;
  circle(map(i, 0, 24, 24, width - 24), 40, diameter);
}

Keep irregularity without flicker

These grids begin with irregular sizes. Step forward one frame to see which sizes change. Play to compare abrupt changes with a smoothly evolving field.

Frame 0 · 0.00 s

Try it: play, then restart and play again. Restart returns to frame 0 with the same sequence, so the same seed repeats the same flicker, frame for frame.

Fresh random values New samples on every frame.
Retained random values Sample once; reuse on every frame.
Moving through noise Advance the time coordinate.

Playback runs at 30 steps per second and pauses after 10 seconds.

p5.js code: Fresh random values

Comments identify code that belongs outside the sketch functions, in setup(), or in draw(). Canvas creation, styling, and function wrappers are omitted.

// In setup():
randomSeed(42);

// In draw():
for (let row = 0; row < 7; row++) {
  for (let column = 0; column < 6; column++) {
    const value = random();
    circle(20 + column * 40, 20 + row * 30, 6 + value * 20);
  }
}
p5.js code: Retained random values
// Outside setup() and draw():
let values;

// In setup():
randomSeed(42);
values = Array.from({ length: 42 }, () => random());

// In draw():
for (let row = 0; row < 7; row++) {
  for (let column = 0; column < 6; column++) {
    const value = values[row * 6 + column];
    circle(20 + column * 40, 20 + row * 30, 6 + value * 20);
  }
}
p5.js code: Moving through noise
// In setup():
noiseSeed(42);

// In draw():
const seconds = millis() / 1000;
for (let row = 0; row < 7; row++) {
  for (let column = 0; column < 6; column++) {
    const value = noise(column * 0.6, row * 0.6, seconds);
    circle(20 + column * 40, 20 + row * 30, 6 + value * 20);
  }
}

Retained random values are useful for persistent differences between objects. Waves suit deliberate pulses and cycles. Noise suits related variation across a surface or through time. None is universally more natural: choose the kind of regularity the drawing needs.

Make one mark change with time

Scrub time to change the circle’s size, then press Play. The noise function and size mapping stay fixed.

Time → noise → size.

Playback starts paused and stops at 20 seconds; pressing Play at the end starts over. Scrubbing pauses playback and redraws at that time.

Give each mark its own input

Both grids use the same noise function and size mapping. Only the inputs differ.

Time alone Every mark receives the same input and changes together.
Position + time Each mark samples a different place in the field; sizes differ and evolve together.

Try it: play, then pause. Which grid still has different sizes?

Explore the combinations

Choose an input source, a variation function, and a shape property. Try mouse or touch instead of time, sine or fixed random instead of noise, or position instead of size. To yoke a property to an input is to make it follow that input.

Input source
Variation function
Shape property
Position gives each mark its own input. Nearby marks sample nearby coordinates.
Connection diagram

Click a source, function, or output box to select it; keyboard users can focus a box and press Enter or Space. “Position + time” connects those inputs together.

Inputs connected through variation to a shape property Column and row pass through input scale and Perlin noise, then variation amount, to size. column row millis() ÷ 1000 mouseX ÷ width × 10 mouseY ÷ height × 10 Input scale × 0.60 Fixed random coordinate cell Sine (1 + sin(sum)) / 2 Perlin noise noise(u, v, w) Variation amount center: value − 0.5 × 0.90 × 34 px x position base x + offset y position base y + offset Size 20 + offset Position + time
Column and row → input scale → Perlin noise → variation amount → size.
  1. Source Position
  2. Scale × 0.60
  3. Function Perlin noise
  4. Amount × 0.90
  5. Property size

Try it: compare Time with Position + time during playback. Which makes all marks change together?

Scale multiplies each active input. Noise and fixed random use separate coordinates; sine adds them. Position modes show faint anchor marks at the unshifted grid locations.

p5.js code: selected method and input

The code follows the controls. Drag an underlined number horizontally, or focus it and use arrow keys, to change the matching slider. Shift + arrow takes larger steps. Comments identify code that belongs outside the sketch functions, in setup(), or in draw(); canvas creation, background clearing, styling, and function wrappers are omitted. In a sketch, millis() supplies time and mouseX/mouseY supply pixel coordinates; this page normalizes the pointer to run from 0 to 1 across the canvas and multiplies by 10 to explore a larger input range.

// In setup():
noiseSeed(42);

// In draw():
const inputScale = 0.6;
const amount = 0.9;
for (let row = 0; row < 8; row++) {
  for (let column = 0; column < 18; column++) {
    const u = column * inputScale;
    const v = row * inputScale;
    const value = noise(u, v);
    const diameter = 20 + (value - 0.5) * 34 * amount;
    circle(20 + column * 40, 30 + row * 40, diameter);
  }
}

The expressions use normalized values: wave(u) = (1 + sin(u)) / 2. fixedRandom(x, y, z) retrieves a seeded random value for a coordinate cell; it is a helper in this demonstration, not a p5.js function. Its cache keeps one sample per cell for the life of the sketch, so with the same seed, visiting cells in the same order reproduces the samples. Run initialization once, not on every frame. The noise demonstration uses single-octave gradient Perlin noise; p5.js noise() has different implementation details, but the same useful nearby-input behavior. The snippet reproduces the behavior, not the exact noise or random samples of this canvas.

Going beyond: other inputs

Time, position, and the mouse are starting points. In creative coding and physical computing, the same connection can begin with any value your program can read:

Interaction
Keyboard keys, touch gestures, game-controller buttons and sticks.
Physical sensors
Light, distance, pressure, temperature, or motion from an accelerometer.
Sound
Microphone level, estimated pitch, or detected beats. Extract a useful feature from the audio before mapping it to a property.
Messages and data
MIDI notes and controls, OSC or other network messages, weather observations, or values read from a dataset.
The program itself
A counter, a simulation variable, or another object's position.

The pattern stays the same: read an input → transform its range → drive a visible property. A distance sensor could control spacing between marks; microphone level could control their size. Neither needs to pass through a random, sine, or noise function first.

Working with sensors. Check the units and calibrate the useful range, then normalize it, for example to 0–1. Keep the most recent reading between updates rather than assuming a new sample arrives on every drawing frame. Smooth noisy continuous readings when useful, accepting the added lag; for switches or button presses, detecting a change may be more useful than smoothing.

Use these choices in a sketch