How to create and visualize a cardioid using javascript

Eduard Iudinkov - Feb 24 '23 - - Dev Community

Hello everyone! Today we're going to create and visualize a cardioid using javaScript and canvas. Let's get started!

Preparation

A cardioid is a geometric shape that resembles a heart. It is formed by tracing the path of a point on a circle that is rolling around another circle of the same size. The resulting curve has a single cusp at the origin, which gives it its characteristic heart shape. Cardioids are fascinating mathematical shapes that can be used in many different applications, from engineering to art.

In this article, we will explore how to create a cardioid using javascript and the canvas. We will use the canvas to draw the curve of the cardioid and javascript to calculate the coordinates of each point along the curve. We will also discuss the underlying mathematics behind the cardioid and provide a step-by-step guide for creating one. By the end of this article, you will have a deeper understanding of cardioids and how to create them using javascript and canvas.

Theory

We start with a circle of radius r centered at the origin, and a fixed point P on the circle. We will draw a pencil of lines through P and rotate it around the circle.

To draw each line, we calculate the angle between the x-axis and the line using the formula theta = (2π/N) * i, where i is the index of the line.

For any angle theta, the line passing through P and intersecting the circle at angle theta is given by the equation:

xcos(theta) + ysin(theta) = rcos(theta)

To find the intersection points of the line and the circle, we substitute the equation of the line into the equation of the circle and solve for x and y. This gives us a system of equations:

xcos(theta) + ysin(theta) = rcos(theta)
x^2 + y^2 = r^2

By solving this system of equations for x and y, we get:

x = rcos(theta) + rcos(2theta)
y = rsin(theta) + rsin(2theta)

Cardioid as envelope of a pencil of lines

These equations give us the coordinates of the point of intersection of the line passing through P and intersecting the circle at a given angle theta.

To draw the cardioid, we simply plot the points of intersection as we vary the angle theta from 0 to 2π, and connect them with a smooth curve.

Environment setup

Let's define the Cardioid class that will store the state of the cardioid and have two main methods: drawing the cardioid on the screen and getting color of the line:

class Cardioid {
  constructor() {}

  get color() {}

  draw() {}
}
Enter fullscreen mode Exit fullscreen mode

Next, we're going to define a new class called Canvas that will create the canvas element and call the draw method of the Cardioid class:

class Canvas {
  constructor(id) {
    this.canvas = document.createElement("canvas");

    this.canvas.id = id;
    this.canvas.width = window.innerWidth;
    this.canvas.height = window.innerHeight;

    document.body.appendChild(this.canvas);
    this.ctx = this.canvas.getContext("2d");
  }

  draw() {
    const cardioid = new Cardioid(this.ctx);
    const draw = () => {
      this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
      cardioid.draw();
      requestAnimationFrame(draw);
    };
    draw();
  }
}
Enter fullscreen mode Exit fullscreen mode

Consequently, the preparatory phase of the project has been finalized and we can now proceed to the implementation of its principal functionalities.

Cardioid

To generate the most basic form of the cardioid, the initial parameters of the class are defined as the radius of the circle, the quantity of lines and the coordinates of the screen center to set the offset of the points:

class Cardioid {
  constructor(ctx) {
    this.ctx = ctx;
    this.radius = 400;
    this.num_lines = 200;
    this.translate = [window.innerWidth / 2, window.innerHeight / 2];
  }

  get color() {}

  draw() {}
}
Enter fullscreen mode Exit fullscreen mode

The Cardioid class includes a draw method that is responsible for rendering the cardioid on a canvas element. The method does not take any arguments, as the required parameters are specified in the constructor.

To draw the cardioid, the draw method loops over the number of lines defined in the constructor, calculating the two points of intersection for each line using the theory above. Nevertheless, in order to connect two pairs of points with a line, it is necessary to decompose the original equations for x and y into a set of two equations of the following form:

x1 = rcos(theta) + offsetX
y1 = rsin(theta) + offsetY

x2 = rcos(2theta) + offsetX
y2 = rsin(2theta) + offsetY

Thus, the following implementation is obtained:

class Cardioid {
  constructor(ctx) {
    this.ctx = ctx;
    this.radius = 400;
    this.num_lines = 200;
    this.translate = [window.innerWidth / 2, window.innerHeight / 2];
  }

  get color() {}

  draw() {
    this.ctx.lineWidth = 1;

    for (let i = 0; i < this.num_lines; i++) {
      const theta = ((2 * Math.PI) / this.num_lines) * i;
      const x1 = this.radius * Math.cos(theta) + this.translate[0];
      const y1 = this.radius * Math.sin(theta) + this.translate[1];

      const x2 = this.radius * Math.cos(2 * theta) + this.translate[0];
      const y2 = this.radius * Math.sin(2 * theta) + this.translate[1];

      this.ctx.strokeStyle = this.color;
      this.ctx.beginPath();
      this.ctx.moveTo(x1, y1);
      this.ctx.lineTo(x2, y2);
      this.ctx.stroke();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's see how the cardioid looks in real time:

Since the cardioid resembles a heart, we can incorporate a beating effect. This can be accomplished by selecting a sin function to modulate the radius of the cardioid as follows:

y = abs(sin(x) - 0.5)

As a result, we get the following picture:

Next, let's depart from the standard procedure for constructing a cardioid and introduce a variable factorthat depends on time. Additionally, instead of simply doubling the value of the angle theta, we will use this variable to compute the second pair of points:

factor = 1 + 0.0001 * time

Next, let's modify the approach of calculating the time value. To achieve this, we can define the startTime value as performance.now() within the constructor of the Cardioid class and subsequently implement the time getter as the difference between performance.now() and startTime:

get time() {
  return performance.now() - this.startTime;
}
Enter fullscreen mode Exit fullscreen mode

The resulting outcome is presented below:

Conclusion

In conclusion, we have explored the theory of the cardioid as the envelope of a pencil of lines and demonstrated how this concept can be used to create cardioids in javascript using the canvas element. Furthermore, we have showcased various techniques that can be used to add visual effects to the cardioid, such as a beating effect. By leveraging the power of javascript and the canvas element, the possibilities for creating engaging and interactive cardioids are endless and you can try to create your own implementation right now!

. .