Program Flow
This tutorial outlines some various techniques for controlling the sequence and timing of events in your code, which is known as program flow.
Branching
We can use conditional statements to control the program flow. Conditional statements perform different actions based on tests for different conditions. JavaScript has the following conditional statements:
- Use if to specify a block of code to be executed, if a specified condition is true
- Use else to specify a block of code to be executed, if the same condition is false
- Use else if to specify a new condition to test, if the first condition is false
In the following example, change the value for variable i to change the color of the rectangle. If i equals to 0, the condition for the if statement is satisfied, and the filling color is red. In this case, the program continues to draw the rectangle skipping the else if and else statements. If i equals to 1, the condition for the if statement is not satisfied, and the program moves on to check the condition for the else if statement. Since else if condition is satisfied, the filling color is green. If neither the if nor the else if conditions are satisfied, the program runs the else statement, and the filling color is blue.
Loops
Loops can execute a block of code repeatedly. p5 supports several different kinds of loops in JavaScript:
- for - loops through a block of code a specified number of times
- for/in - loops through the properties of an object
- while - loops through a block of code while a specified condition is true
- do/while - also loops through a block of code while a specified condition is true
The for loop sets up a variable (usually i or x) that is then incrementally changed for each loop. It has the following structure:
for (statement 1; statement 2; statement 3) {
code block to be executed
}
- Statement 1 is executed (one time) before the execution of the code block. It sets the starting value for the variable
- Statement 2 defines the condition that must be true for the code block to be executed.
- Statement 3 is executed every time after the code block has finished running if statement 2 evaluated to be true.
In this example, variable i is initially set to 0. Every time the for loop runs, i is displayed on the screen and 1 is added to i. Note the for loop will only run until i equals to 4 because after this the condition that i be less than 5 will be false.
The for/in statement loops through the properties of an object:
In this example, as the for loop cycles through each property of the person object, the property value is added to myText string.
The while loop cycles through a block of code as long as its specified condition is true.
This example gives the same result as the for loop example above. Sometimes while loops and for loops can be used interchangeably.
The do/while loop is a variant of the while loop. This loop will execute the code block once, before checking if the condition is true, it will then repeat the loop as long as the condition is true.
noLoop(), loop() and redraw()
The draw() function in p5 runs as a loop. The code inside the draw() function runs continuously from top to bottom until the program is stopped. The draw() loop may be stopped by calling noLoop(), and can then be resumed with loop(). If using noLoop() in setup(), it should be the last line inside the block.
In this example, noLoop() is called in setup(), so the code within draw() will only run once at the start of the program. Since loop() is placed in mousePressed(), the draw() block will resume looping when mouse is pressed. When mouse is released, noLoop() is called again and hence the draw() loop stops.
The function redraw() executes the code within draw() one time. This functions allows the program to update the display window only when necessary, such as when an event registered by mousePressed() or keyPressed() occurs. In structuring a program, it only makes sense to call redraw() within events such as mousePressed() outside of the draw() loop. The redraw() function does not work properly when called inside draw(). In addition, you can set the number of loops through draw by adding a single argument (an integer) to the redraw() function.
This example is similar to the previous one, where noLoop() is called in setup() and the code within draw() will only run once at the start of the program. However, when mouse is pressed, redraw() is called and draw() will only loop once. To make smooth animations, it is easier to work with noLoop() and loop().
Asynchronicity in p5.js
In JavaScript, events may occur concurrently with the main program flow. This is considered as asynchronicity in programming. In p5, for example, when we use loadImage() in setup(), the browser begins the process of loading the image but skip onto the next line before it is finised loading. The following example demonstrates such asynchronicity.
let img;
function setup(){
createCanvas(100, 100);
img = loadImage("/assets/learn/program-flow/images/clouds.jpg");
noLoop();
}
function draw(){
background(200);
image(img,0,0);
}
When you run this program, you'll notice that the drawing canvas is grey with no image displayed. This is because loadImage() begins to load the image, but does not have time to finish this task before the program continues on through the rest of setup() and on to draw(). Even with the noLoop() function that stops p5.js from continuously executing the code within draw(). The image() function is unable to display the image as it is not properly loaded.
Introduction to Preload
To help with this issue of asynchronicity, p5.js has the preload() function. Unlike setup(), preload() forces the program to wait until everything has loaded before moving on. It is best to only make load calls in preload(), and do all other setup in setup().
let img;
function preload(){
img = loadImage("/assets/learn/program-flow/images/clouds.jpg");
}
function setup(){
createCanvas(100, 100);
noLoop();
}
function draw(){
background(200);
image(img,0,0);
}
preload() ensures that the image has been loaded before running the other code.
Loading with a Callback
An alternative to preload() is to use a callback function. A callback function is a function that is passed as an argument to a second function, and that runs after the second function has completed. The following example illustrates this technique.
function setup(){
createCanvas(100, 100);
loadImage("/assets/learn/program-flow/images/clouds.jpg", drawImage);
noLoop();
}
function draw(){
background(200);
}
function drawImage(img){
image(img, 0, 0);
}
In this example, the second argument in loadImage() is the function we want to run after the load is complete. Once the image has loaded, this callback function, drawImage(), is automatically called. It has one argument which contains the image that was just loaded. There is no need to create a global variable to hold the image. The image is passed directly into the callback function, as the parameter name chosen in the function definition.
Loading JSON & APIs
The JSON (JavaScript Object Notation) format is a common system for storing data. Like HTML and XML formats, the elements have labels associated with them. One way to load JSON file is to use loadJSON() function in preload().
The following request at https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson returns data of recent earthquakes in the world from USGS.
Alternatively, loadJSON() can also take a callback. To use data from an API, you may need a callback function as, like with an image, the data takes time to load. API (Application Programming Interface) requests are commands that request data from a service. A lot of APIs will return data in JSON format. Some need you to authenticate with the API to use it (e.g. register as a developer and get keys). You can’t always use preload() when getting data from APIs because the data might change while you sketch is running and you will want your program to respond accordingly.
loadJSON() can be used in a few ways:
- loadJSON(path)
- loadJSON(path, callback)
- loadJSON(path, callback, datatype)
- loadJSON(path, callback, errorCallback)
- loadJSON(path, datatype, callback, errorCallback)
- loadJSON(path, jsonpOptions, datatype, callback, errorCallback)
where:
- path - String: name of the file or url to load
- jsonpOptions - Object: options object for jsonp related settings
- datatype - String: "json" or "jsonp"
- callback - Function: function to be executed after loadJSON() completes, data is passed in as first argument
- errorCallback - Function: function to be executed if there is an error, response is passed in as first argument
In this example, the loadJSON() function is placed in setup() and takes a custom callback function showEarthquake(). This means when the program finishes loading the JSON file from the USGS earthquakes API, the function showEarthQuake() is called. The place and magnitude of the most recent earthquake listed by the API is stored in local variables within showEarthquake and are then displayed on the screen.
Sometimes we use setInterval() to control the frequency of requests made to the API. setInterval() can also take a callback function. If you call setInterval() in setup(), it will run repeatedly for the duration of the program at the interval set.
In this example, the earthquake data is grabbed from the API every 5 seconds and is displayed on the screen.
More Callback Functions
In addition to the loadImage(), loadJSON() and setInterval(), there are other functions in p5 that accept callbacks. Typically, functions that involve loading data of some kind accept callbacks, or should be put in preload(). For example:
DOM functionality makes it easy to interact with other HTML5 objects, including text, hyperlink, image, input, video, audio, and webcam. Some DOM creation methods also accept callbacks:
Interactivity and Event Listeners
Callback functions are functions that can be passed as an argument into another function and be executed after the first function is complete. An event listener or handler is a type of callback. It is called whenever an event occurs such as when the mouse is pressed, or a key is pressed etc.
Mouse functions like mousePressed(), mouseClicked(), mouseReleased(), mouseMoved(), etc. can be used as event listeners. They can be attached to certain elements in a sketch.
- mousePressed() - Code inside this block is run one time when a mouse button is pressed
- mouseReleased() - Code inside this block is run one time when a mouse button is released
- mouseClicked() - Code inside this block is run once after a mouse button is pressed and released over the element
- doubleClicked() - Code inside this block is run once after a mouse button is pressed and released over the element twice
- mouseWheel() - Code inside this block is run once when mouse wheel is scrolled over the element
- mouseMoved() - Code inside this block is run one time when the mouse is moved
- mouseOver() - Code inside this block is run once after every time a mouse moves onto the element.
- mouseOut() - Code inside this block is run once after every time a mouse moves off the element
In this example, a canvas element is created and an event listener mousePressed() is attached. Function changeGrey() will only run when the mouse is pressed over the canvas, and will will change the background color to a random grey. If the mouse is pressed anywhere, even outside of the canvas, the diameter of the ellipse will increase by 10 pixels. The custom function changeGray(), in this instance, is placed within the mousePressed() function and is to be triggered when mouse is pressed over the canvas element. If the mouse is not pressed, false is passed and changeGrey() will not run.
The above mouse functions can be attached to an element like the canvas or can be used without specifying an element. The keyboard functions keyPressed(), keyReleased(), keyTyped(), and mouse function mouseDragged() cannot be attached to a specific element.
- mouseDragged() - Code inside this block runs once when the mouse is moved and the mouse button is pressed
- keyPressed() - Code inside this block runs once when any key is pressed
- keyTyped() - Code inside this block is runs once when a key is pressed, but action keys such as Ctrl, Shift, and Alt are ignored. The most recent key pressed will be stored in the key variable.
- keyReleased() - Code inside this block is runs once when any key is released