JavaScript Spirograph

I remember using Spirograph as a child being amazed by the patterns that seemed to magically appear as I moved the pen. Math is beautiful isn’t it? We’ll explore the math behind the magic by drawing a Spirograph pattern on a canvas element using JavaScript.

Drawing a Circle

If you remember high school trigonometry, you know the parametric equation to draw a circle. If not, here is it:

x = cx + radius × cos(θ)
y = cy + radius × sin(θ)

Plugging in θ ("theta") values from 0 up to 2π will give you the x and y coordinates for all the points along the circumference of the circle. The values cx and cy represent the center point of our circle. If you’re like me, it’s much easier to understand this with a visual demonstration.

θ = 0
θ = 2π

Multiple Circles

Let’s add a second circle. We’ll center this circle on the circumference of our first circle. After calculating a point on the first circle, we’ll use this as the center point for the next circle.

x = cx + radius1 × cos(θ) + radius2 × cos(θ)
y = cy + radius1 × sin(θ) + radius2 × cos(θ)

The result? A larger circle—not very exciting.

θ = 0
θ = 2π

Give It Some Teeth

In the physical world, Spirograph circles are gears connected by little teeth. The gears have differing number of teeth, which causes them to spin at different rates. This is where the magic comes from. In our JavaScript world, we can simulate this by adjusting theta for the second circle by a ratio.

x = cx + radius1 × cos(θ) + radius2 × cos(θ × ratio)
y = cy + radius1 × sin(θ) + radius2 × cos(θ × ratio)

θ = 0
θ = 2π

Source Code

function drawSpirograph(context, cx, cy, radius1, radius2, ratio) {
  var x, y, theta;

  // Move to starting point (theta = 0)
  context.beginPath();
  context.moveTo(cx + radius1 + radius2, cy);

  // Draw segments from theta = 0 to theta = 2PI
  for (theta = 0; theta <= Math.PI * 2; theta += 0.01) {
    x = cx + radius1 * Math.cos(theta) + radius2 * Math.cos(theta * ratio);
    y = cy + radius1 * Math.sin(theta) + radius2 * Math.sin(theta * ratio);
    context.lineTo(x, y);
  }

  // Apply stroke
  context.stroke();
}

// Get drawing context
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

// Draw spirograph
drawSpirograph(context, canvas.width / 2, canvas.height / 2, 100, 20, 10);