p5js week 06

(picture from https://pxhere.com/en/photo/1621020)

We've talked a lot about toast, but somehow I think we haven't been able to describe our toast really well in code. It would be cool if we could describe the properties of toast, like its size, how well-toasted it is, where it is, and so on. You know, it might be something like this:

My Toast
  - size: 9cm x 9 cm
  - toasted: true
  - degreeOfToastiness: light
  - color: lightBrown

That's not real JavaScript, but you can imagine how it might be useful to describe toast like that, right?

Let's learn how to do it the JavaScript way. Then we can use that to draw our toast, too!

(photo from https://commons.wikimedia.org/wiki/File:French_toast_making.jpg)

We need many tools to make and serve good toast: toaster, knife, plate, and so on. Then think about everything that's needed for making French toast! Like that, we're still building up all of our JavaScript tools so that we can make good digital toast.

That means there are a lot of things to talk about:

  1. structuring data using objects
  2. how objects work
  3. making objects using classes
  4. using objects and classes in p5js
  5. making a very useful class for p5js: the Point class
  6. learning about the forEach method for arrays
  7. … and finally your drawings using objects, Points, and forEach!

We have talked about organizing data before; one way that we have learned is grouping data into lists called arrays. JavaScript gives us another way to organize data (another way to say this is “structure data”). We can make an object by using the curly braces and separating the items with commas, just like we do with arrays. Objects contain things called properties. We make objects and properties to represent things and their characteristics.

For example, we can make an object which represents a student by giving it properties for the student's name, the student's age, and the student's favorite color. It looks like this:

student1 = {
  name: "Emma Jones",
  age: 13,
  favoriteColor: "green"
}

The object is assigned to the variable called student1. It has three properties: name, age, and favoriteColor. Each property has a value. For the property of name, the value is “Emma Jones”. For the property of age, the value is 13. For favoriteColor, the value is “green”.

Later, when we want to know some data inside an object, we can ask for it using something called dot notation. You have seen dot notation before; we use it when we want to know the length of an array. Length is a property of arrays.

Let's try asking for the values of the object we made before. We write the name of the object, then a dot, then the name of the property we want to access. Try each of these and see what happens.

student1.name
student1.age
student1.favoriteColor

(photo from https://pixabay.com/photos/breakfast-morning-toast-food-3836731/)

We can do that with toast, too! Here's the example from the beginning of the lesson in real JavaScript. This object, which is assigned to the variable myToast, has four properties.

let myToast = {
      size: [9,9],
      toasted: true,
      degreeOfToastiness: "light",
      color: "lightBrown"
      }

Try it out! You can get the values of each property like this:

myToast.size
myToast.toasted
myToast.degreeOfToastiness
myToast.color

When we have structured our data, it's easier to use:

  1. easier to pass around
  2. easier to get things from inside
  3. easier to make more things like them

Objects (and classes) help us to do that. Objects also have functions that only work on them called methods, which we'll look at more later in this lesson.

So remember, start empty objects with curly braces:

{}

Then you can add properties with undefined values (if you don't know the values yet). Separate the properties with commas:

{
  property1: undefined,
  property2: undefined
}

Then you can give values to the properties.

{
  property1: "hi",
  property2: "y'all"
}

If you assign this object to a variable, you can then get the value of a property by using dot notation like this:

let testObject = 
  {
    property1: "hi",
    property2: "y'all"
  }
testObject.property1
testObject.property2

You can make a function that produces objects:

function newObject (word1, word2) {
    let outputObject = {property1: word1, property2: word2}
    return outputObject
}

Then we can test it like this:

let testObject2 = newObject ("hi", "again")

Look at what is stored in testObject2:

testObject2
testObject2.property2

(photo from https://world.openfoodfacts.org/product/5025820001834/melba-toast-aleyna)

Let's try using objects in p5js, and along the way we'll learn more about making them. We'll use it to make toast, of course!

Normally when we want to make objects, we define a class. A class is the definition of a type of object. It is a tool for making a particular type of objects and collecting functions that only work on that type of objects. You know the word “class” like this class for creative coding that we're in right now. However, “class” has another meaning which is kind of like “kind” or “type”. When we make a class, we're making a kind of thing, but we're not making the things themselves yet.

Each class starts with a special function called a constructor. We're going to use the constructor later to construct (or make) objects which belong to the class.

We usually start variables, function names, and arguments with lowercase letters. However, we use uppercase letters to start class names.

Let's start a new class.

It starts like this example. Notice the special constructor keyword used inside; and this added to the beginning of each property. Also notice that you need to use equals and semicolons:

class Toast {
  constructor (size, tColor, x, y)
  { this.toastSize = size;
    this.toastColor = tColor;
    this.xPosition = x;
    this.yPosition = y;
  }
}

Do you see the parentheses after the word “constructor”? Those are the arguments for the constructor function. That's where we input information to use when making our objects.

We can then use this class and the constructor to make objects which come from this class. We use it a little differently from other functions:

let toast1 = new Toast (100, "brown", 150, 150)

Notice that you have to use the keyword new to make a new Toast object. Now you can check what has been stored:

toast1

So far our class just keeps some information about toast, but it doesn't tell p5js how to draw our toast.

We can design functions that work specifically on objects from a class, and the class will make it more convenient to call those functions. Another name for these functions is methods.

Let's make a method which draws the toast we make. Notice how we use the keyword “this” in the draw method.

class Toast {
  constructor (size, tColor, x, y)
  { this.toastSize = size;
    this.toastColor = tColor;
    this.xPosition = x;
    this.yPosition = y;
  }
  draw () {
    fill(color(this.toastColor))
    rect(this.xPosition,this.yPosition,this.toastSize,this.toastSize)
  }
}

We can use the constructor as we did above, and then we can call the method to draw the toast. When you call a method, you have to write the object name, then a dot, then the method name, then parentheses. If the method has arguments, put them inside the parentheses. It's like this:

myToast.draw()

Call it inside the draw function in the p5js editor. It works like this:

One of the things that our drawings often need to use is coordinates or points on a grid. Think about it… we have the x and y values for the position of a rect or ellipse, for starters. Some people have used the triangle function before, and it requires several arguments which are all x and y positions. Using beginShape and endShape, you can describe much more complicated shapes using x and y coordinates. Because they are so common, it would be useful to have some abstractions (encapsulated and generalized procedures in functions) that make working with points easier. Let's do that! You can try out all of the code below in the browser console.

We make classes when we have some data that we want to group together. In our class, that would clearly be x and y. You might think that you could just use arrays, but let's see why that might be confusing. Start with the point (0,0). As an array in JavaScript, we might write:

let myPoint = [0,0]

That seems easy enough, but the first possibly confusing thing to remember is that when we want the x coordinate or y coordinate, we have to get it the same way that we always get things from arrays, like this:

let x = myPoint[0];
let y = myPoint[1];

It's not terrible, but then imagine that we have an array of points describing the corners of a square, like this:

let myPoints = [[0,0],[20,0],[20,20],[0,20]]

If we want the x coordinate of the third point, we have to get it like this:

let thirdx = myPoints[2][0]

That's not easy to read, and it seems like it'd be easy to make a mistake if we have to do a lot of those or things get complicated. How would we do all of that if we had class for Points?

We'll start our class like this:

class Point {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
}

If we do the same things we did above but using our class, it looks like this:

let myPoint2 = new Point (0,0);
let x2 = myPoint2.x;
let y2 = myPoint2.y;

That's easier to read. How about in the case of the square?

let myPoints2 = [new Point(0,0), new Point(20,0), new Point(20,20), new Point(0,20)]

Then when we want the x of the third Point, we write this:

let thirdx = myPoints2[2].x

That's a little better, I think.

We can change our Toast class to use the Point class, then we can make the objects using buildArray. Finally, we can use a for-loop to draw all of the Toast objects.

Notice how we use the “new” keyword, which means the constructor, in the arrow function inside of the calls to the buildArray function.

When we want to draw the objects, we call the draw method inside the draw function at the end.

It works like this:

We often need a series of points, and sometimes that means moving from a previous point. In the square above, the second point is moved by 20 on the x axis and 0 on the y axis. We can make a function that does that. Let's include it as a method in the class. By the way, if you are trying this class in the console, you may need to refresh the page so that you can declare the new version of this class.

class Point {
  constructor(x,y) {
    this.x = x;
    this.y = y;
  }
  move(xDistance, yDistance) {
    return new Point(this.x + xDistance, this.y + yDistance)
  }
}

Now we can move to a new Point from the original Point like this:

let myPoint2 = new Point (0,0);
myPoint2
let myPoint3 = myPoint2.move(20,0)
myPoint3
let myPoint4 = myPoint3.move(20,0)
myPoint4

Check the output to see that this returns a new Point object.

You could make a function that makes an array of points using the move method and buildArray. To make this even easier, let's use an arrow function in the second argument of buildArray. Remember, it requires a function as the second argument.

function lineOfPoints (startingPoint, numberOfPoints, xDistance) {
  return buildArray (numberOfPoints, i => startingPoint.move(xDistance*i,0))
}

Now if we want a line of 10 Points, each 20 apart from one another, we can call that function like this:

lineOfPoints(myPoint2,10,20)

Check the output to see that this returns an array of new Point objects.

Then you can use forEach to draw something at each of the points in the resulting array. I'll let you try to figure that out.

Arrays have other useful methods. We want to learn one called forEach today. It takes one argument. The special thing about this argument is that it's not a number or a string; it's a function. That means we can use it as a replacement for a for-loop. For-loops are easy to make mistakes with because they require more code, so this is really useful. Let's look at the version with a for-loop first:

function printAll (inputArray) {
  for (let i = 0; i < inputArray.length; i++){
    console.log(inputArray[i])
  }
}

We can call it like this:

printAll (testArray1)

That doesn't look like so much code, but look at what we can do with forEach:

testArray1.forEach(console.log)

It's really that short. We'll talk about the output you just saw later. Notice that the argument for forEach is a function. We just include the name of the function, without calling it. forEach calls the function on each item of the array, just like a for-loop does with a counter used to get the value of an item at an index. JavaScript programmers have a special name for this kind of function-as-an-argument. It's called a callback.

There are three types of functions we can use in forEach. The first type is a function which requires a single argument; we'll talk about the other two types another day. In today's case, forEach will call the function once per item in the array, with each item as an argument.

Let's do a test. First, we need a function which takes one argument. We'll use this function with another variable, an array to collect results in.

let testArray2 = [];
function add1 (x) {testArray2.push(x + 1)}

We're going to use forEach and add1 to build the array called testArray2. When we use add1 as the callback, forEach is going to take each item from the array and pass it to add1. Let's try it:

testArray1.forEach(add1)

Using forEach makes it much easier to draw our toast. Look at line 42 in the code below. We just have to call forEach on our someToast array. We use an arrow function to call the draw method for each item in the array.

Notice that using forEach makes our code shorter and easier to read!

Try to use the Point class and the ideas discussed above to make a simple p5js drawing that is rects at random Points in the canvas. You can make a function that produces a random point, then you can make an array of the random Points. Then you can use forEach to draw a rect at each of the random points. Please share your code and your screenshot!

Whew! That was a lot of stuff to learn. Don't worry if you think you didn't understand it all after reading it one time. It's going to require some practice. Let's practice those things with the drawings that you started last time. Remember, you were making:

  1. hearts, stars, and smiley faces, 100+ of each
  2. three types of aliens/monsters, 100+ of each

See if you can use the techniques that you learned today to improve the drawings that you started last time.

When I'm working, I might have several versions of a drawing so that I can compare and see which version I like best. Why don't you make two or three different versions and share them with us? Be sure to save each one with a different name!

  • p5js-week-06.txt
  • Last modified: 4 weeks ago
  • by renick