p5js week 07
(photo from https://www.trustedreviews.com/reviews/hotpoint-si9-s8c1-sh-ix-h)
We're halfway through our course. We've talked a lot about toast and JavaScript, so it's a good time to review before we take on some new techniques for improving our coding and our drawing. After we review, we want to try once more to polish up these drawings that we've been working on recently:
- hearts, stars, and smiley faces, 100+ of each
- three types of aliens/monsters, 100+ of each
Let's review first. So what have we studied?
- assigning variables
- calling functions, including using arguments
- making functions, including using arrow functions
- various operators, like plus and minus, and including the remainder operator (%) and the increment operator (++)
- using booleans (true and false)
- structuring data using arrays, and using push, pop, and unshift to add and remove items from those arrays
- for-loops for repeating actions
- using forEach instead of a for-loop
- structuring data using objects
- making classes so that making objects is easier and making those objects more useful
Not only have we studied those things, we've studied a lot about p5js:
- setup and draw functions
- background
- text
- colors
- shapes
- rotate
We've learned how to make other useful tools, too:
- randomInteger
- pick
- buildArray
- a Point class
While doing all of that, we learned about:
- encapsulation
- generalization
In this lesson, after we review these things, we want to continue to study a few things:
- improving our Point class
- making a shape function
- thinking about algorithms
- the map method for arrays
big ideas
(photo from https://www.flickr.com/photos/djwtwo/14033143291)
Let's review the really big ideas first!
- encapsulation
- generalization
When we make a function which contains a procedure, we are using encapsulation.
function makeToast () { ... add code to make toast here ... ... step 1 ... ... step 2 ... ... step 3 ... ... and so on ... }
When we add arguments to our function so that the function can be used in more cases, we are using generalization.
function makeToast (howManyPieces, howBrown) { ... add code to make toast here, using the arguments howManyPieces and howBrown ... step 1 ... ... step 2 ... ... step 3 ... ... and so on ... }
We are also using generalization when we structure our data into a class and use a constructor with arguments.
class Toast { constructor (size, howBrown) { this.size = size; this.browness = howBrown } }
We'll review making functions and classes below.
JavaScript review
(photo from https://upload.wikimedia.org/wikipedia/commons/a/af/DryToast.jpg)
Now we want do the more close-up review of the various JavaScript that we have learned.
assigning variables
(photo from https://www.flickr.com/photos/91261194@N06/51708375114)
Remember, variables are names that we can give to pieces of data, like this:
let numberOfPiecesOfToast = 2;
Later we can use that name instead of the number. Typing the name in the console brings it out.
numberOfPiecesOfToast
We can also put it into a function, such as when we call a function:
console.log(numberOfPiecesOfToast)
making functions, including using arrow functions
We make functions starting with the “function” keyword, followed by the function name, then the arguments, and finally the body of the function inside of curly braces.
Remember, the arguments are the input to our function:
function howMuchToast (number) { console.log("Today, I'll have " + number + " pieces of toast for breakfast!"); return number }
Don't forget the return to get some output from the function!
We can also make functions in the arrow function style. One way is to assign it to a variable:
let howMuchToast2 = number => return "Today, I'll have " + number + " pieces of toast for breakfast!";
If you want to use more than two statements in an arrow function, be sure to use curly braces:
let howMuchToast3 = number => { console.log("Today, I'll have " + number + " pieces of toast for breakfast!"); return number }
calling functions, including using arguments
Try each of those functions with our variable from above as the argument:
howMuchToast(numberOfPiecesOfToast) howMuchToast2(numberOfPiecesOfToast) howMuchToast3(numberOfPiecesOfToast)
All of these functions can tell us how much toast we're having for breakfast.
(photo from https://pxhere.com/en/photo/872146)
various operators
We've practice several operators like all of the ones for arithmetic (+, -, *, / ), and the remainder operator (%) and increment operator (++) as well.
We can use them to do math anywhere, like this:
howMuchToast(numberOfPiecesOfToast + 2)
using booleans (true and false)
One operator that we learned about when we studied for-loops is the > (greater than) operator. There are also the < (less than) operator, the >= (greater than or equal) operator and the ⇐ (less than or equal) operator. There's also the == (equal) operator. Try these:
howMuchToast(numberOfPiecesOfToast + 2) < 5 howMuchToast(numberOfPiecesOfToast + 2) < 5 howMuchToast(numberOfPiecesOfToast + 2) == 4
structuring data using arrays, and using push, pop, and unshift to add and remove items from those arrays
We can collect data and put it into structure. One very important structure is arrays. We use push and unshift to add data to arrays. Let's do that for everyone's breakfast orders:
let everyonesToastOrder = []; let kateToast = howMuchToast(2); let ellaToast = howMuchToast(3); let lynnToast = howMuchToast(1); everyonesToastOrder.push(kateToast); // adds to the end everyonesToastOrder.push(ellaToast); // adds to the end everyonesToastOrder.unshift(lynnToast); // adds to the beginning everyonesToastOrder
Don't forget that you can get items from an array using the indexes. Each position in an array has a number, starting with 0.
everyonesToastOrder[0] everyonesToastOrder[1] everyonesToastOrder[2]
for-loops for repeating actions
We have learned how to make for-loops to do things repeatedly. A for-loop has a first part in parentheses that describe the conditions for the for-loop: what number to start at (a counter), how long to run, and what to do with that counter. A for-loop has a second part in curly braces that says what you want to do on each loop.
We can use one with the things we've made above. Let's find out how much toast we have to make to fill everyone's order. We'll use the for-loop to get the value at each index with the counter i.
function totalToast (toastArray) { let total = 0; for (let i = 0; i < toastArray.length; i++) { total = total + toastArray[i] } return total } <code> We can call that function like this: <code> totalToast (everyonesToastOrder)
using forEach instead of a for-loop
We can rewrite totalToast using forEach instead. Remember that forEach is a method to be used on arrays. Instead of the code inside the for-loop above, we can use an arrow function.
function totalToast2 (toastArray) { let total = 0; toastArray.forEach(x => total = total + x); return total }
structuring data using objects
We have another way to structure toast, not just as arrays. That is objects. Let's make an object for each toast order.
There are properties on the left, each of which has a value on the right. Remember to separate the properties with commas.
let kateToast2 = { name: "Kate", quantity: howMuchToast(2), type: "regular" }
making classes so that making objects is easier and making those objects more useful
Since we want to make a lot of objects like this, we can make a class. A class is like a factory for objects.
class ToastOrder { constructor (name, quantity, type) { this.name = name; this.quantity = howMuchToast(quantity); this.type = type } }
When we want to use the class, we call the constructor with the “new” keyword. We can make all of the objects and then put them in an array like this:
let kateToast3 = new ToastOrder ('kate', 2, 'regular') let ellaToast3 = new ToastOrder ('ella', 3, 'regular'); let lynnToast3 = new ToastOrder ('lynn', 1, 'light'); let toastOrders = [kateToast3, ellaToast3, lynnToast3]
Have a look at one of the ToastOrders that we've made:
kateToast3
Remember, to get the value of a property, we just use its name with a dot after the object name.
kateToast3.quantity; lynnToast3.type
We can then modify totalToast to work on the objects like this:
function totalToast3 (toastArray) { let total = 0; toastArray.forEach(x => total = total + x.quantity); return total }
Now try it out:
totalToast3 (toastOrders)
And that is a quick review of all of the JavaScript that we've learned! Now that we have a class for ToastOrders, we're ready to open a restaurant!
(photo from https://nypl.getarchive.net/media/daily-breakfast-held-by-mansion-at-buffalo-ny-coffee-shop-0a9467)
p5js review
Now that we've reviewed how to code the data of toast, let's review how to program a drawing of that toast.
Here's an example in p5js that uses everything above plus a lot of the things that we've studied before about p5js and some tools we've made. See if you can find each of these things in the code below.
- setup and draw functions
- background
- text
- colors
- shapes
- buildArray
- a Point class
adding some other things to make it cuter
Now we can use some of the other things that we've learned about to make the drawing cuter. Look for these things in this revised version. We'll also reorganize the code just a little so that it makes more sense.
- rotate (including angleMode)
- randomInteger
- pick
Also notice the addition of noLoop to stop the animation.
Now for something new: a shape function
Let's get back to making random shapes, which we might used to make weird slices of toast.
Now we have a Point class. Using that abstraction, let's make another abstraction, a function called shapeFromPoints, that takes an array of Points and draws a shape.
We made a shape-making function in an earlier lesson. Let's change it to accept one argument, an array of Points, and make the function work with any number of Points.
We start a shape. Then we call forEach on the array. We pass each point from the array to the vertex function. The arguments are the x and y values of the point. When all of the vertices have been draw, we end the shape and use the CLOSE argument. Then we have a shape!
Notice how the code becomes shorter and more powerful at the same time!
function shapeFromPoints (pointArray) { beginShape(); pointArray.forEach(p => vertex(p.x,p.y)) endShape(CLOSE); }
Let's test this in p5js now, like this. Notice that this makes use of several functions we've made recently, including randomRange and buildArray.
You can see after you run this a few times that the shapes created aren't always great when the Points are random. You can think about some ways to solve this. Still, this will be easy to use if you specify the Points.
This sometimes makes what is called a self-intersecting polygon.
https://en.wikipedia.org/wiki/Polygon#Convexity_and_non-convexity
They look interesting sometimes, but mostly I think that's not what we want.
algorithms
We want either convex or concave polygons. We can write a set of rules to give us the output we want. We know one word for this: procedure. Another word we can use to call such sets of rules is “algorithm”.
https://simple.wikipedia.org/wiki/Algorithm
Let's try a basic algorithm that goes like this:
- specify a center point
- specify the number of points in our polygon
- calculate a random angle for each point
- since a circle has 360 degrees and we want the points to move around the circle, we should sort the angles from low to high
- for each angle, move a random distance at a random angle from the center point and create a point there
turning our algorithm into an abstraction
We want an abstraction, a function that implements this algorithm, which means to actually put the algorithm into a function so that we can run it.
In this case, we need some components. We'll need to:
- improve our Point class so that we can move according to angle and distance rather than x and y distance.
- make a function that sorts from low to high
Here's a revised version of our Point class. I've added a method called moveByAngle. For now, you don't need to understand how it works (it uses trigonometry to calculate the length of a side of a triangle). You should understand what it does, though. First, here's the code:
class Point { constructor(x,y) { this.x = x; this.y = y; } move(xDistance, yDistance) { return new Point(this.x + xDistance, this.y + yDistance) } moveByAngle (angle, distance) { let r = angle * Math.PI / 180; return new Point(this.x + distance*Math.sin(r), this.y + distance*Math.cos(r)) } }
Here's what it does. Like move, it returns a new Point. Instead of an x distance and a y distance, it has arguments for angle and distance. You know that a circle has 360 degrees. The moveByAngle method moves a point by a distance at the angle which the user specifies. It looks like this:
Test it out so that you can produce the same Point that is shown in the illustration above.
Here's a function that sorts an array of numbers from low to high. It uses the standard JavaScript sort function. You can learn more about that here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
function low2HighSort (inputArray) { return inputArray.sort((a, b) => a - b)}
Try this out on some data to see how it works.
let testArray = [4,6,2,3,1,9,4,6,2] low2HighSort(testArray)
Now we can put these together to make a function that give us a list of points to be used to make a non-intersecting polygon. Notice that it uses the map method for arrays.
function pointsForConvexShape (centerPoint, noOfPoints, minDistance, maxDistance, minRotation, maxRotation) { let angles = low2HighSort( buildArray (noOfPoints,x => randomRange(minRotation,maxRotation))); let distances = buildArray (noOfPoints,x => randomRange(minDistance,maxDistance)); let points = angles.map( (x,i) => { return centerPoint.moveByAngle(x,distances[i]) }); return points } pointsForConvexShape(new Point(200,200),6,50,100,30,330)
map
Like forEach, map is a method for arrays. The big difference is that map applies a function to each item in the array and then returns an array filled with the result of those function calls. Like forEach, it's common to use an arrow function with map. We often use it with a one-argument function. There are other possibilities which we might look at later.
For example, if we have an array of numbers, we can do some math with those numbers and get a new array.
let numberOfSlicesOfToast = [2,2,3,2,1]; let lotsMoreToast = numberOfSlicesOfToast.map(t => t *10)
That's a lot of toast, but it was pretty easy to get those numbers by using map.
You can learn more about map here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
a full example
Then we can use our shapeFromPoints function to draw it. You'll notice that we still sometimes get a self-intersecting polygon, but it's much less common. We could keep working on this algorithm to get one that is perfect, but let's stop here for this lesson.
making your own class for your drawings
(photo from https://www.flickr.com/photos/160866001@N07/44019575945)
Now you're ready to get back to your customized personal toast, uh, drawings…
We've been working on these drawings:
- hearts, stars, and smiley faces, 100+ of each
- three types of aliens/monsters, 100+ of each
Now we have some more useful tools to apply to those drawings.
Make your own class if you haven't already:
- give your class a name to describe what you are drawing; be sure to capitalize the first letter: class Alien, class Heart, etc.
- make a constructor for your class; think about what input you need to give so that the objects can be made
- add a draw method that we can call inside of the p5js draw function in order to draw the objects
Be sure to put your drawing on your page on the wiki and share screen shots in the chat rooms!