Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

A Spirograph is a toy that draws hypotrochoids and epitrochoids. For this challenge, we'll just focus on the hypotrochoids.

From Wikipedia:

A hypotrochoid is a roulette traced by a point attached to a circle of radius r rolling around the inside of a fixed circle of radius R, where the point is a distance d from the center of the interior circle.

The parametric equations for them can be defined as:

enter image description here

enter image description here

Where θ is the angle formed by the horizontal and the center of the rolling circle.

Your task is to write a program that will draw the path traced by the point defined above. As input, you'll be given R, r, and d, all integers between 1 and 200 inclusive.

You can receive this input from stdin, arguments, or user input, but it cannot be hardcoded into the program. You can accept it in whatever form is most convenient for you; as strings, integers, etc.


  • Input units are given in pixels.
  • R >= r

Output should be a graphical representation of the hypotrochoid defined by the input. No ASCII- or other text-based output is allowed. This image can be saved to a file or displayed on screen. Include a screenshot or image of the output for an input of your choosing.

You can choose any colors you like for the path/background, subject to a contrast restriction. The two colors must have HSV 'Value' component at least half the scale apart. For instance, if you're measuring HSV from [0...1], there should be at least 0.5 difference. Between [0...255] there should be a minimum 128 difference.

This is a code golf, minimum size of source code in bytes wins.

share|improve this question
Can we assume R > r or R ≥ r? (Same for r and d.) – Martin Ender May 23 '14 at 17:09
Congratulations on posting the 2000th question! ;-) – Doorknob May 23 '14 at 17:11
@m.buettner R>=r, but d is not constrained to r, and can be anywhere in the 1-200 range. – Geobits May 23 '14 at 17:12
What kind of resolution are we talking about? – Kyle Kanos May 23 '14 at 17:19
@KyleKanos Since input is in pixels and each has a cap of 200, It shouldn't ever be larger than 798x798, given R=200, r=1, d=200. You can size the image to the input if you want, or keep it at a constant size, as long as it's all visible. – Geobits May 23 '14 at 17:22

12 Answers 12

up vote 8 down vote accepted

Mathematica, 120 bytes


Ungolfed code and example output: enter image description here

If I may include the axes in the plot, I can save another 9 characters.

share|improve this answer

JavaScript (ECMAScript 6) - 312 314 Characters



Example Output


enter image description here

share|improve this answer
I like it, but ikt's broken somehow. Try the examples in R. – edc65 May 24 '14 at 7:38
Last line could be for(;t<R*P;v.lineTo(X(),Y()))t+=P/R – edc65 May 24 '14 at 7:51
@edc65 It's not broken it just wasn't doing enough iterations to do a full rotation in those examples. I've increased the iterations from 9*PI to R*2*PI and it should be better (however, I've left the increment at PI/1000 as otherwise it would break for small values of R). – MT0 May 24 '14 at 9:06

Python: 579


This is not competitive at all given the Mathematica answer, but I decided to post it anyway because the pictures are pretty and it may inspire someone or be useful to someone. Because it is so much bigger, I left it basically ungolfed. The program expects command-line input of R,r,d.


Here are two examples, one for (5,3,5) and one for (10,1,7) example 5-3-5 example 10-1-7


import math
import matplotlib.pyplot as P
from matplotlib.path import Path as H
import matplotlib.patches as S
import sys
while(len(v)<3 or v.count(v[-1])+v.count(v[-2])<3):
share|improve this answer
Can you adjust the ratio? It seems that the image are compressed vertically. – A.L May 24 '14 at 19:04

Perl/Tk - 239 227

use Tk;($R,$r,$d)=@ARGV;$R-=$r;$s=$R+$d;$c=tkinit->Canvas(-width=>2*$s,-height=>2*$s)->pack;map{$a=$x;$b=$y;$x=$s+$R*cos($_/=100)+$d*cos$_*$R/$r;$y=$s+$R*sin($_)-$d*sin$_*$R/$r;$c->createLine($a,$b,$x,$y)if$a}0..628*$s;MainLoop

R=120, r=20, d=40:

R=120, r=20, d=40

R=128, r=90, d=128:

R=128, r=90, d=128

R=179, r=86, d=98:

R=179, r=86, d=98

share|improve this answer

R: 80


However, if one wants 'clean' figures (no axes, no labels etc), then the code will have to be slightly longer (88 characters):


One code example using the longer version of f:

f(R<-179,r<-86,d<-98);title(paste("R=",R,", r=",r," d=",d,sep=""))

Some example outputs:

enter image description here

enter image description here

enter image description here

share|improve this answer
This doesn't take the input sizes in pixels, does it? The first example should be almost three times as large as the second. – Martin Ender May 24 '14 at 9:37
Why all the ,?? – plannapus Jun 5 '14 at 7:52
The commas were used to separate the arguments, many of which were NULL (nothing). Here positional argument matching was used to reduce the length of the code. This of course is bad coding practice. The recommended way would be to use named argument list, such as type="l", xlabel="", etc (and get rid of the redundant commas!). – Feng Jun 11 '14 at 4:53

Processing, 270

import java.util.Scanner;
void setup(){size(500, 500);}
Scanner s=new Scanner(;
int R=s.nextInt(),r=s.nextInt(),d=s.nextInt();
void draw(){
  int t=width/2,q=(R-r);
  for(float i=0;i<R*PI;i+=PI/2e4)

The input is entered via console, one number per line.

Screenshot for R=65, r=15, d=24: enter image description here

share|improve this answer

GeoGebra, 87

That is, if you consider GeoGebra a valid language.


Accepts input from the GeoGebra input bar, in the format <variable>=<value>, e.g. R=1000.

Note that you may need to manually change the zoom size to view the whole image.


(The thing at the bottom of the window is the input bar that I was talking about)

Try it online here.

share|improve this answer
I suppose this has the same limitation as Kyle Kanos's submission, that you can't specify the size in pixels? – Martin Ender May 23 '14 at 22:31
@m.buettner Yes you're right... missed that – ace May 23 '14 at 23:34

HTML + Javascript 256 286 303

Edit Removed 1st call to moveTo, it works anyway. Could save more cutting beginPath, but then it works only the first time

Edit2 30 bytes saved thx @ӍѲꝆΛҐӍΛПҒЦꝆ

<canvas id=c></canvas>R,r,d:<input oninput="n=400;c.width=c.height=t=n+n;v=c.getContext('2d');s=this.value.split(',');r=s[1],d=s[2],R=s[0]-r;v.beginPath();for(C=Math.cos,S=Math.sin;t>0;v.lineTo(n+R*C(t)+d*C(R/r*t),n+R*S(t)-d*S(R/r*t)),t-=.02);v.stroke()">


Put input in the text box (comma separated) then press tab

R,r,d:<input onchange="n=400;c.width=c.height=t=n+n;v=c.getContext('2d');s=this.value.split(',');r=s[1],d=s[2],R=s[0]-r;v.beginPath();for(C=Math.cos,S=Math.sin;t>0;v.lineTo(n+R*C(t)+d*C(R/r*t),n+R*S(t)-d*S(R/r*t)),t-=.02);v.stroke()"><canvas id=c></canvas>

share|improve this answer
Couldn't you just add an id to the canvas and use that id globally instead of having to use querySelector! – Mama Fun Roll Feb 1 at 20:25
@ӍѲꝆΛҐӍΛПҒЦꝆ yeeeeees I could. It's something I was not aware of in may 2014 – edc65 Feb 1 at 20:41
Wow that was way more bytes saved than I thought. – Mama Fun Roll Feb 1 at 21:21

C# 813, was 999

Needs some work to reduce byte count. I managed to reduce it a little. It accepts three space separated integers from the Console.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
class P:Form
int R,r,d;
P(int x,int y,int z) {R=x;r=y;d=z;}
protected override void OnPaint(PaintEventArgs e)
Graphics g=e.Graphics;
int w=(int)this.Width/2;
int h=(int)this.Height/2;
List<PointF> z= new List<PointF>();
PointF pt;
double t,x,y;
double pi=Math.PI;
for (t=0;t<2*pi;t+=0.001F)
pt=new PointF((float)x,(float)y);
static void Main()
char[] d={' '};
string[] e = Console.ReadLine().Split(d);
Application.Run(new P(Int32.Parse(e[0]),Int32.Parse(e[1]),Int32.Parse(e[2])));

Output sample:


share|improve this answer

shell script + gnuplot (153)

Most of the effort is to remove the axes and tics, set the size and range, and increase the precision. Thankfully, gnuplot is natural for golfing, so most of the commands can be abbreviated. To save characters, the output must be redirected to an image file manually.

se t pngc si 800,800
se pa
se sa 1e4
uns bor
uns tic
p[0:2*pi][-b:b][-b:b]a*cos($2*t)+$3*cos(a*t),a*sin($2*t)-$3*sin(a*t) not

Calling the script with 175 35 25>i.png gives enter image description here

share|improve this answer

R, 169 characters



    png(w=2*R,h=2*R) #Creates a png device of 2*R pixels by 2*R pixels
    par(mar=rep(0,4)) #Get rid of default blank margin
    t=seq(0,R*pi,.01) #theta
    plot(x,y,t="l",xaxs="i",yaxs="i") #Plot spirograph is a plot that fits tightly to it (i. e. 2*R by 2*R) #Close the png device.


> f(65,15,24)

enter image description here

> f(120,20,40)

enter image description here

> f(175,35,25)

enter image description here

share|improve this answer

wxMaxima: 110


This is called in the interactive session via f(#,#,#). As a sample, consider f(3,2,1):

enter image description here

share|improve this answer
While I like the pretty output, I'm not sure how this follows "integers between 1 and 200" or "given as pixels". – Geobits May 23 '14 at 18:47
Input can be integers or floats, wxMaxima will convert to float to do its work anyway, I'll update an image using integers. I'll have to think more about input as pixels too. – Kyle Kanos May 23 '14 at 18:56
Yeah, I figured it would convert them internally, and that's not a problem. The integer constraint on input was mainly to get closed loops easier (they just look better imo). – Geobits May 23 '14 at 18:58

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.