I think I just made up a thing. Usually when I think that, it just means fewer than a few hundred people have thought about it before me, so who knows.

Let’s start with cardioids. A cardioid is a heart-shaped curve. One way to create a cardiod is to roll a circle around another circle of the same size, tracing the path of a single point on the moving circle. Like so:

I discovered another neat way to create a cardioid while checking out the math art challenge.

#MathArtChallenge Day 7: Cardioids!

In this method, you divide a circle into an arbitrary number of points around the radius. Then, for each point `n`

, you draw a line from point `n`

to point `n*2`

. Point 1 to 2, point 2 to 4, point 3 to 6, etc.

Here’s some JavaScript/Canvas code showing this in action:

context.translate(400, 400);

const radius = 350;

const res = 100;

const slice = Math.PI * 2 / res;

const mult = 2;

context.beginPath();

for(let i = 1; i < res; i++) {

let a1 = slice * i;

let a2 = slice * i * mult;

context.moveTo(Math.cos(a1) * radius, Math.sin(a1) * radius);

context.lineTo(Math.cos(a2) * radius, Math.sin(a2) * radius);

}

context.stroke();

And here’s what that gives you:

You’ll notice in the code that I’ve divided the circle into 100 points, which is is rather low-res. If I up that to 360, we get something nicer:

So I’m calculating the points by getting two angles, `a1`

and `a2`

, calculating those with `slice * i`

and `slice * i * mult`

as described above. `slice`

being a full circle divided by `res`

and `mult`

equals 2 for now.

let a1 = slice * i;

let a2 = slice * i * mult;

What if I change that to `mult = 3`

instead?

Or, `mult = 4`

?

You see that for each multiplier, `m`

, you get `m-1`

nodes in the cardioid. Let’s just go crazy and see what happens when we set `res`

to 1440 points and `mult`

to 25:

Here’s the code for those of you following along at home:

context.translate(400, 400);

const radius = 350;

const res = 1440;

const slice = Math.PI * 2 / res;

const mult = 25;

context.lineWidth = 0.25;

context.beginPath();

for(let i = 1; i < res; i++) {

let a1 = slice * i;

let a2 = slice * i * mult;

context.moveTo(Math.cos(a1) * radius, Math.sin(a1) * radius);

context.lineTo(Math.cos(a2) * radius, Math.sin(a2) * radius);

}

context.stroke();

All very interesting, but I wanted to start changing things up even more. I decided that rather than using a simple circle, what if I varied the radius of the circle with a sine wave? Here’s the code I came up with:

context.translate(400, 400);

const radius = 300;

const res = 1440;

const slice = Math.PI * 2 / res;

const mult =5;const waves = 6;

context.lineWidth = 0.25;

context.beginPath();

for(let i = 1; i < res; i++) {

let a1 = slice * i;

let a2 = slice * i * mult;let r1 = radius + Math.sin(a1 * waves) * 100;context.moveTo(Math.cos(a1) *

let r2 = radius + Math.sin(a2 * waves) * 100;r1, Math.sin(a1) *r1);

context.lineTo(Math.cos(a2) *r2, Math.sin(a2) *r2);

}

context.stroke();

First I created a `waves`

constant that controls how many sine waves will go around the circle. Then an `r1`

variable that is based on radius, multiplied by the sine of `a1 * waves`

times 100. And an `r2`

variable based on `a2 * waves`

. So for each point, it’s radius will get larger and smaller as they progress around the circle. The result (setting `mult`

back to 5):

You can get all kinds of interesting shapes by varying how many nodes and how many waves and the size of the waves and the resolution.

Of course, I had to have a go at animating these. The first idea was to vary the height of that radial wave. Here, it’s going back and forth from -80 to +80:

I’m not going to give the source for the animation examples, because it was written in another system entirely, but if you’ve followed along so far, you’ll be able to figure it out.

Next, I thought about varying the phase of that radial wave, so that the wave itself seemed to be animating around in a circle. This produced some really striking animations. I’ll close the article by posting animations for 2, 3, 4, 5 and 6 wave animated sinosoidal cardiodids. Enjoy!