This sketch creates a set of random locations, and draws circles there.
Click to reset the set of points.
We would like to figure out the maximum x value (next step). There are many reasons to do this. One is to in order to figure out the range of a set of values, so that we can map the range of data into a range of x or y positions that fits onto the canvas, or onto a set of sizes that is neither too big nor too small.
To find this, we define a function findMaxX, that finds the largest x value in a set of objects that have properties named "x".
It uses result to keep track of the largest x value that it's seen so far. When a new x value is larger than this, it updates result.
The first time through the loop, result does not have a value. (Well, it has the value undefined, which is not a numeric value.) The code therefore can't compare the new value to result. Instead, it treats this condition specially: the first time through the loop, it always updates result.
This works, but the function is complicated, and it is not very reusable. (It only works on x's, and it can't find minima.) The rest of the steps will explore alternative ways to write this.
We can eliminate the firstTime variable by using the other form of the for loop, that steps through the index positions of the array elements instead of the elements themselves.
This adds an extra step (on line 25) to get the array element. In return, the code inside the loop body can use this index to tell whether it is visiting the first element.
An alternative approach is to set the initial value of result to a sentinel value. This is a value that never shows up in the data, that we can test to see whether the variable has been initialized from the data.
We can do better. If we pick the right sentinel value, we can eliminate lines 25–26.
(This step changes the behavior of findMaxX when points is the empty array. Each of the remaining steps also changes its behavior for this special case. Part of the design of a function is the specification of what it should do in this kind of corner case.)
-10000000 is (hypothetically) smaller than any possible x value. The first time through the loop, point.x will always be greater than this negative value, and line 26 will always execute. The remaining times through the loop, line 25 comnpares x to the greatest value that it has seen in the previously-visited array elements.
This works if our hypothesis is true (if no number < -10000000 is present in the data).
This code is simpler (in several technical senses) than the starting code. Eliminating the extra variables and if statements makes it easier to reason about.
The code is complex in another sense. Lines 24-26 are doing two things at once: (1) they are extracting the x values from an array of objects, and (2) they are finding the largest of this extracted set of values.
The next step makes the code more complex in one sense (it adds another for loop), by making it simpler in the other sense (each part of the code does only one thing). This sets the code up to rewrite in other ways.
Lines 23–26 create a new array that contains only the x values from the input array.
Technically, this is a "projection". Think about shining a light onto the circles from the top of the canvas so that all you see are their shadows, on the line that is the bottom of the window. We have lost the y property (and any other properties that might be present in a more sophisticated program), and extracted, or projected, only the value of the x property.
The previous steps use form of the p5.js max function that takes two arguments. There is another form that takes a list of values, and returns the maximum value from that list. That is exactly what lines 28–32 of findMaxX do. We can replace those lines by a single function.
Lines 23–26 make a new array (xs) that is based on an input array (points).
The next step separates this into two parts:
Line 25 of this step have the same effect as line 25 of the previous sketch.
The advantage of writing it this way is that lines 23–26 now do exactly the same thing as the JavaScript Array.map method. (This method is unrelated to the p5.js map function.) Array.map "returns a new array containing the results of calling a function on every element in this array."
projectX is only used once, as an argument to points.map. Instead of defining it (which creates a global variable named projectX), we can simply write it in the place where its value is used.
This is just the same as, instead of defining a variable say `let five = 5` and then using `five`, we can just use the number 5.
We can simplify the arrow function in two ways:
In this case, all of the following are equivalent function expressions:
xxxxxxxxxx
function(point) {
return point.x;
}
xxxxxxxxxx
(point) => {
return point.x;
}
xxxxxxxxxx
point => {
return point.x;
}
xxxxxxxxxx
point => point.x;
How would you modify this to compute xMin? yMax and yMin?
Can you use these to draw the bounding box – the smallest rectangle that contains all the circle centers?
Can you use p5.js map (not Array.map) to stretch out the circle centers so that the leftmost circle is centered on the left edge of the canvas, the rightmost circle is centered on the right edge of the canvas, and the ratio of spaces between circles is maintained?
let points;
function setup() {
createCanvas(windowWidth, windowHeight);
background(100);
makePoints();
}
function draw() {
background(100);
noStroke();
for (let point of points) {
circle(point.x, point.y, 20);
}
}
function makePoints() {
points = [];
for (let i = 0; i < 10; i++) {
let location = {
x: random(width),
y: random(height),
};
points.push(location);
}
}
function mousePressed() {
makePoints();
}