Rainbow Knots with P5.js (and Scratch)

I’m going to try something new here, so bear with me as we see how this works. When I’m feeling stressed or overwhelmed I sometimes like to relax with a little creative coding; I just try to get something on the screen and poke around with the code until I find something that soothes me. I’m rarely building towards something specific in these moments, but I do like to explore functions and techniques that I haven’t used before, sometimes just picking a function at random from the documentation and seeing where it gets me. Because these coding sessions are more about the process for me than the actual creation, I don’t end up returning to most of these exploratory sketches. Occasionally, however, I come across something that really resonates with me from a creative perspective or helps me see a different angle to introduce a concept students. In these moments I would think to myself “maybe there’s a lesson hidden in here.”

When I was in the classroom I might throw something together to try with my students the next day, and in fact lots of the projects in my books for born of this process. Sadly, I don’t have a class of students to experiment on these days, and lately I’ve been ignoring the “maybe there’s a lesson hidden in here” instinct, letting potentially great lessons languish. This post is an attempt to put one such “maybe there’s a lesson hidden in here” moment out where it might be useful to someone else. If that someone is you, please let me know how you used it! Maybe it’ll make it into my next book or curriculum.

Context

When making smooth animations it’s often necessary or desirable to have a variable repeatedly increase and decrease smoothly, like a wave. The sin() and cos() functions are perfect for creating these waves, and if you use them together you can even get a circle going.


See inside

This is because sine and cosine (the mathematical functions these are based on) can be used to determine the x and y coordinates of a given angle on a unit circle. Let’s take a quick look at the code that generated this circle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function setup() {
  createCanvas(400, 400);
}

function draw() {
  translate(200, 200);

  let x = cos(frameCount) * 100;
  let y = sin(frameCount) * 100;

  ellipse(x, y, 20, 20);
}

The first bit of note is line 6, which uses the translate() function to shift the origin of my canvas to the center. This makes is a bit easier to use the sin() and cos() functions because I don’t have to manually offset the values they return to center my circle.

Why is that? Well, both sine and cosine produce a value between -1 and 1, with (0, 0) being the center point. In p5.js the (0, 0) sits at the top left corner, so we need to either move that center point (with translate()) or move the circle.

Okay, so with the center point set in the right place, we can move to lines 8 and 9 where the real heavy lifting of this sketch lives. In each of those lines I’m feeding the frameCount variable into the sin() and cos() functions. frameCount is a super useful built-in variable, and it shows up in dang near every p5 sketch I write. As the name suggests, frameCount is a count of the number of frames that have gone by since the sketch started. Every time the draw loop runs, that’s a frame. I could accomplish the same thing with a variable of my own that I increment every tick of the draw loop, but frameCount is already there just begging to be used.

So frameCount is the angle of each point on the circle, and because there’s no call to the background function it just keeps drawing each dot over the last. It’s important to note, however, that both lines 8 and 9 multiply the output of their function by 100. Why? Because as I said earlier, sin() and cos() return a number between -1 and 1, which left alone would make an awfully small circle. Multiplying each by 100 makes a circle with radius 100.

So that’s where I started, with this sine and cosine generated circle, but where I headed next is the fun bit.

Tying Circles into Knots

When the sine and cosine are fed the same number (the same angle on their shared circle), they make a nice even circle, but what if they’re fed different numbers? Let’s see.


See inside

In the above sketch I divided frameCount by a different number for x and y, as you can see below. (I also switched the colorMode() to use hue, another trick I use just about everywhere).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function setup() {
  createCanvas(400, 400);
  colorMode(HSL);
}

function draw() {
  translate(200, 200);
  noStroke();
  let x = cos(frameCount / 40) * 100;
  let y = sin(frameCount / 50) * 100;

  fill(frameCount % 360, 25, 50)
  ellipse(x, y, 20, 20);
}

It looks kinda like a Celtic knot to me, and after discovering this I went on to spend the next hour of so exploring different relationships between the numbers I fed sin() and cos() to see what those relationships produced. In my explorations I found ratios that looped, and those that didn’t. I found a pretzel, a coiled square, and a spring, among other more abstract pieces.

And that’s where the kernel of this lesson idea is, how could you engage students in this exploration of the relationship between sine and cosine in a way that produces great art and drives greater curiosity about the underlying mathematic patterns? What would it look like to add tangent or any of the other numerous math functions into the sketch?

I don’t think this needs to be (or even should be) a deeply Math-focused lesson, but I think there’s something here that could be a real “aha” moment for students who may not otherwise associate math with beauty and joy. As someone who never really grokked trig in school I felt like I was building upon and strengthening my mental model of the underlying mathematics by experimenting here. I asked myself why certain ratios behaved the way they did, and where I might leverage those behaviors in other programs.

Try it out and let me know how it works for you! Feel free to share any of the programs here with your students, including this collection of sketches which include some of my favorites that I came up with in that initial creative coding session. Have fun!

Oh, and you can do all of this in Scratch really easily! Check out the example below.


See inside

See Also