Creative Coding Blog

Maths, coding and art

Creating string art cardioid curves in Processing

In this article, we will see how to create this curve in Processing:

This is called a cardioid curve because it is (vaguely) heart shaped

How it works

To draw the curve, we first create a set of points, evenly spaced around the circumference of a circle. We number these points starting at zero. In the image here there are 8 points numbered 0 to 7.

Now for each point n we draw a line connecting it to the point numbered n*2. So point 1 connects to point 2, point 2 connects to point 4, point 3 connects to point 6.

Point 4 should connect to point 8, but there is no point 8. To fix this, we use modulo arithmetic. Instead of n we use n modulo 8 - that is, the remainder when n is divided by 8. When you divide 8 by 8, the remainder is 0, so point 4 connects to point 0.

Point 5 should connect to point 10. 10 modulo 8 is 2, so point 5 actually connects to point 2. And so on.

Unfortunately, we can’t see the full effect with just 8 points. The original image above uses 200 points around the circle, and shows the cardioid curve in all its glory!

The code

We will follow similar scheme to the previous article on parabolic curves:

  • circlePoints creates a circle of equally spaced (x, y) points.
  • joinPoints takes the circles of points and joins each point n to the point n*2.

circlePoints

Here is the code:

PVector[] circlePoints(float cx, float cy, float r, int count)
{
  PVector[] points = new PVector[count];
  for (int i = 0; i < count; i++)
  {
    float x = cx + r*cos(TWO_PI*i/count);
    float y = cy + r*sin(TWO_PI*i/count);
    points[i] = new PVector(x, y);
  }
  return points;
}

This takes four parameters:

  • the centre of the circle (cx, cy)
  • the radius r
  • count - the total number of points

Again we will use the Processing class PVector to represent the points (or pins) in the image. A PVector stores an x and a y value.

The code uses the standard coordinate equations for a circle to calculate points that are evenly distributed at equal angles around the circumference. Since the total angle in a circle is 2 PI radians, the angle if the ith point is given by TWO_PI*i/count.

Finally we return the full array of points.

joinPoints

Here is the code:

void joinPoints(PVector[] p, float mul)
{
  int count = p.length;
  for (int i = 0; i < count; i++)
  {
    int j = (int)((i*mul) % count);
    line(p[i].x, p[i].y, p[j].x, p[j].y);
  }
}

This function accepts a lists of points, p and the multiplication factor mul. The value of mul determines the relationship between the two pints that are connected. In our case, since we are conencting point n to point n*2, we must set mul to 2.

Main draw function

Here is the main draw function:

void draw()
{
    clear();
    background(255);
    noFill();
    strokeWeight(1);
    
    stroke(255, 0, 0);
    PVector[] p = circlePoints(300, 300, 250, 200);
    joinPoints(p, 2);
}

The centre of the image is at (300, 300), and we chose a radius of 250. We set the number of points to 200, which works well for an image of this size.

We create p, a list of points equally spaced around the circle..

Finally we use joinPoints to join the points around the circle. In this case we use the rule that point n connects to point n*2.