Graphing P5 Noise

The slider controls how big a step the graph takes along the noise space with each move to the right along the x-axis (moving the slider to the right makes the steps bigger so the line looks noisier).

What Is This About?

The plot is a visualization of the p5.js noise function. I got the idea for it from the Coding Train but the author of that site explains it by talking about it in a video so I thought I'd re-do it so I can understand what the code is doing a little better. The p5 documentation and the Coding Train both describe the noise function as producing Perlin noise, and Dan Shiffman (the Coding Train author) even goes on to point out that it's the original version of perlin noise from 1983 and in a later video he shows how to implement Simplex Noise, an improved version created by Ken Perlin, the author of the original Perlin Noise. On the github site for Processing, however, there are two issues that claim that processing's noise isn't really perlin noise but rather a form of Value Noise.

I don't know enough to know what's right, so I'll just call it noise or p5's noise and not worry about which it is.

Setup The Sketch

Now on to the code. This first section isn't really about plotting the graph but rather about setting up the P5 sketch in the HTML.

Name The Parent DIV

We're making a div to stick the sketch into the page so here's the ID we'll use for that div so the code can refer to it.

const NOISE_GRAPH_DIV = "noise-graph";

The Instance Container Function

To keep all the variables for the sketch contained in its scope we're going to use a function (noise_graph) to act as a closure.

function noise_graph(p5js){

Constants and Variables

Now we're into the p5 sketch code (contained within noise_graph). Before defining the setup and draw methods let's define some variables that we can use within them.

The Noise Space

The inputs to the noise function are what we'll call the X_NOISE_COORDINATE (or variations on that name, anyway). Every time the canvas gets re-drawn we'll re-draw the graph by putting in a sequence of x-values starting at STARTING_X_NOISE_COORDINATE and incrementing it by X_NOISE_COORDINATE_STEP_SIZE as we move along the x-axis of the canvas.

let X_NOISE_COORDINATE_START = 0;
let X_NOISE_COORDINATE_STEP_SIZE = 0.01;

The X_NOISE_COORDINATE_STEP_SIZE is sort of a re-mapping or re-proportioning of the x-value we use to draw the points for the noise function. The p5 canvas uses integers for the pixel coordinates so as we move across the canvas the x-coordinates are non-negative integers - \(0, 1, 2, \ldots, width - 1\). The noise function, on the other hand, works best with much smaller changes. If you use increments of 1 the output will look pretty much random, while our default of 0.01 will make our plot appear smoother and also show that each point falls reasonably close to the point that just precedes it. So as the x input to the vertex function goes up \(0, 1, 2, \ldots\) we'll increment the input to the noise function as \(0 \times 0.01 = 0, 1 \times 0.01 = 0.01, 2 \times 0.01 = 0.02, \ldots\).

But the noise function is, well, a function. If you keep passing in the same value to it you'll get the same value out of it so if we just use a scaled value of the x-coordinate (\(x \times x_{noise-coordinate-step-size}\)) as our input then every time p5 re-draws the sketch it will draw the same graph, producing what looks like a static image. In order to get an animated image we'll add an initial value to the noise input (NOISE-COORDINATE-START) that we'll increment every time we re-start the plot so that the noise function starts with a different value each time we re-draw the graph. So instead of \(x \times x_{noise-coordinate-step-size}\) we use \(x_{noise-coordinate-start} + x \times x_{noise-coordinate-step-size}\) as the input to the noise function.

The Slider

These are some variables so we can add a slider to allow the user to change the step-size to increment the noise input. The slider step size of 0 means that there will be a continuous change in the value as the slider is moved, as opposed to giving it discrete values to jump to.

const MINIMUM_NOISE_COORDINATE_STEP_SIZE = 0;
const MAXIMUM_NOISE_COORDINATE_STEP_SIZE = 1;
const SLIDER_WIDTH = "500px";

let slider;
let DEFAULT_NOISE_COORDINATE_STEP_SIZE = X_NOISE_COORDINATE_STEP_SIZE;
let SLIDER_STEP_SIZE = 0;

Colors

These are the color settings for the plot.

let BLACK = 0;
let WHITE = 255;
let OPACITY = 20;
let LINE_COLOR = BLACK;
let BACKGROUND_COLOR = WHITE;

Setup

Now we'll monkey-patch the setup function that gets called once at the start of the animation.

p5js.setup = function() {

Canvas and Line

The only possibly not-so-obvious thing here should be the document.getElementById(NOISE_GRAPH_DIV).offsetWidth argument to the createCanvas method which grabs the width of the container in which the plot is being put (not the width of the entire window).

let canvas = p5js.createCanvas(
  document.getElementById(NOISE_GRAPH_DIV).offsetWidth, 400);
p5js.stroke(LINE_COLOR);
p5js.noFill();

Slider

Now we'll create the slider to let the user play with the step-size for the noise input.

slider = p5js.createSlider(
  MINIMUM_NOISE_COORDINATE_STEP_SIZE,
  MAXIMUM_NOISE_COORDINATE_STEP_SIZE,
  DEFAULT_NOISE_COORDINATE_STEP_SIZE,
  SLIDER_STEP_SIZE);
slider.style("width", SLIDER_WIDTH);

Draw

Here's where we define the draw function that gets called repeatedly to animate our sketch.

The Draw Function

Monkey patch the draw method on the p5 object.

p5js.draw = function() {

Setup The Next Frame

We'll paint the canvas with a semi-opaque white on every refresh so that you can sort of see how the graph changes with each loop. We also create some variables:

  • noise_step_size is the amount that the x_noise-coordinate changes as the plot moves from left to right along the x-axis of the graph
  • y will be the y-coordinate for the points in our plot
p5js.background(BACKGROUND_COLOR, OPACITY);
p5js.noFill();
p5js.stroke(LINE_COLOR);
let noise_step_size = slider.value();
let y;

Plot Next Frame

Here's where we plot the graph. We're going to draw the graph using connected line segments so before the loop starts we'll tell p5 to start the shape.

p5js.beginShape();

To draw the graph we'll traverse the canvas from left to right with a for-loop. The x variable in the for-loop corresponds to the x-coordinate in the canvas where we're going to put the next point in our line.

for (let x = 0; x < p5js.width; x++) {

Now we'll get the y-coordinate for the point. Since the noise function's output is a float from 0 to 1 we can use it to set the y-coordinate to a fraction of the canvas' height by multiplying \(noise \times height\).

y = p5js.noise(X_NOISE_COORDINATE_START + x * noise_step_size)
  * p5js.height;

Now that we have the x and y coordinates we can draw the next segment by adding a vertex to the shape.

p5js.vertex(x, y);

That's the end of the for-loop. Now, outside of the loop we call endShape to stop drawing our graph (otherwise it'd draw a line back to the start of the graph the next time we went through the loop).

p5js.endShape();

Move the Noise Input

Now we'll move the input for the noise function at the start of the graph a little. If we didn't the input to the noise function as we went through the loop would always be the same so our plot would just draw the same thing over and over again (well, if we move the slider to change the noise_step_size it wouldn't be exactly the same, but the starting point would always be the same).

X_NOISE_COORDINATE_START += noise_step_size;

I'm not showing the closing braces (}) to end the functions but at this point we close the draw and noise_graph functions, ending our definitions for the sketch.

Slider Label

p5js.textSize(32);
p5js.textAlign(p5js.CENTER);
p5js.fill("white");
p5js.noStroke()
p5js.rect(p5js.width/2 + 20, p5js.height - 35 , 250, 30);
p5js.fill("black");
p5js.text(`Noise Change: ${slider.value().toFixed(3)}`,
            p5js.width/2 , p5js.height - 10);

Once Again But As a Picture

for-loop-diagram

Make The Sketch Object

Next we pass our sketch definition to p5 to build it and attach it to our HTML div.

new p5(noise_graph, NOISE_GRAPH_DIV);

And That's It, Then

At this point we should have a graph that helps to visualize how the noise function changes over one-dimension. Noise to the aside, it's also a useful template for plotting any kind of graph, you'd just have to change the setting of the y value to whatever other function you want to visualize.

Sources

P5 Reference:

Wikipedia on Noise:

The original javascript came from Daniel Shiffman's Coding Train:

Bugs on the (now deprecated) github processing Issues page pointing out that noise isn't really perlin noise: