Search IntMath
Close

450+ Math Lessons written by Math Professors and Teachers

5 Million+ Students Helped Each Year

1200+ Articles Written by Math Educators and Enthusiasts

Simplifying and Teaching Math for Over 23 Years

#### Tips, tricks, lessons, and tutoring to help reduce test anxiety and move to the top of the class.

Home | Math art in code | Animated Lissajous figures

Page by Murray Bourne, IntMath.com. Last updated: 05 Dec 2019

# Animated Lissajous figures

As a child, I spent many hours drawing curves with my Spirograph:

The Spirograph allows you to draw complex spirals [Image source]

By moving the smaller disks within a larger cogged circle, you were effectively combining the ratios between the two circles, and the distance from the center of the smaller circle, resulting in quite complex shapes.

The following interactive graph involving Lissajous curves works on a similar principle. We combine two parametric trigonometric curves, one given by the x-expression, and the other by the y-expression. The animations show how the curve grows, as the angle t increases over time.

## General form of Lissajous curves

For the following interactive graph, we are using the following general form of Lissajous figures, a pair of parametric tigonometric equations:

x = cos(t/a + delta)

y = sin(t/b)

The value δ in the expression for x produces a phase shift, which you can vary below using a slider.

## Things to do

You can:

1. Try some values of a or b (using the sliders below the graph) and see the effect on the resulting curve.
2. Change the phase shift (using the "ps" slider) for the given values of a and b to explore what happens.
3. Select Show circles (using the check box) to see how Lissajous curves are the result of combining two signals.

Show circles

a =
b =
ps =

Resulting parametric equations, with phase shift:

See the section on Lissajous Figures in the chapter on trigonometric graphs.

## The complete javascript

try {
var reducer = 1;

function createBrd() { // Create board
removeEle("asvg0SVG");
boardID = "asvg0";
if(brdType === 0) {
xMin=-1; xMax=1; yMin=-1;  yMax=1;
} else {
xMin=-3; xMax=1; yMin=-3;  yMax=1;
}
boardWidthToHeight = 1;
initBoard(boardID, xMin, xMax, yMin);
doGrids = 0;
//axes(2,2,"labels",2,2); // for testing
stroke = corpColor;

if(brdType === 1) {
strokewidth = 1;
strokeopacity = 0.4;
circle([-2,0],reducer,"circ0");
circle([0,-2],reducer,"circ1");

ASdot([-2+reducer,0], 2, corpColor, corpColor, "ptOnCirc0");
ASdot([reducer,-2], 2, corpColor, corpColor, "ptOnCirc1");

segment([-2-reducer,0], [reducer,0], "seg0");
segment([reducer,-2-reducer], [reducer,reducer], "seg1");
}
strokewidth = 2;
strokeopacity = 0.4;
}

brdType = 0;
createBrd();

// Fractions cancelling
function reduce(n, d) {
// Based on: http://tiny.cc/facCancel
var gcd = function(a, b) {
return b ? gcd(b, a % b) : a
}
gcd = gcd(n, d)
N = n/gcd;
D = d/gcd;
return [N, D]
}

// Variables
var p, n0, d0, N, D, reduceArr, inc, anim0, t, domFactor, raf0;
var j=0, del=0, rot = false;
var completed = false;
var frameTime0 = 100000,
lastLoop0 = new Date(),
thisLoop0;
var filterStrength = 20;
var ox = brdPropsArr[brdID]["ox"], oy = brdPropsArr[brdID]["oy"];

// Initial values
function init() {
window.cancelAnimationFrame(raf0);
for(k=0;k<j+1;k++){
removeEle("greenCurve"+k);
}
removeEle("ptOnCurv");
removeEle("greenCurve");
p = [[reducer*cos(del),0]];
n0 = 1*slider0.noUiSlider.get();
d0 = 1*slider1.noUiSlider.get();

// Remove strange negative on 0.00
if( Math.sign(1*slider2.noUiSlider.get() ) === -0) {
gebi( "slider2" ).getElementsByClassName("noUi-tooltip")[0].innerHTML = "0.00";
}

if(del < 0) {
plusSgn = "";
} else {
plusSgn = "+";
}
plusSgn = ( del < 0 ? "" : "+" );
gebi("theFn").innerHTML = "x = cos(t/"+n0+ plusSgn +del.toFixed(2)+"),~ ~ ~ y = sin(t/"+d0+")";
AMfunc(true);

reduceArr = reduce(n0,d0);
N = reduceArr[0];
D = reduceArr[1];
//inc = (N + D)/100;
inc = (n0 + d0)/100;
anim0 = true;
j=0;
t=0;
minn0d0 = Math.min(n0,d0);
domFactor = ( (N == n0 && D == d0) ? 1 : minn0d0);
}

function doAnim() {
if( t > 2*N*D*pi * domFactor ) {
t=0;
curveLength=0;
curveLength1=0;
anim0 = false;
ptOnCurv.setAttribute("cx", origin[0] + xunitlength*reducer*cos(del));
ptOnCurv.setAttribute("cy", (boardHeight - origin[1]) );
if(brdType === 1 ) {
ptOnCirc1.setAttribute( "transform", "rotate("+ -del*180/pi+", " + (origin[0]).toFixed(1)  + ","+(boardHeight - (origin[1] - 2*yunitlength)).toFixed(1)+")");
seg1.setAttribute("transform", "translate("+ -xunitlength*reducer*(1-cos(del))+")");
}
window.cancelAnimationFrame(raf0);
completed = true;
gebi("pause").innerHTML = "&#9658; start";
fps0Span.innerText = 0;

// Split curve after every pi, so repaint area is small

} else if(t > (j+1)*pi) {
j++;
p = [[reducer*cos(t/(n0) + del), reducer*sin(t/(d0))]];
raf0 = window.requestAnimationFrame(doAnim);
} else {
if (anim0) {
t += inc;
if( typeof(ptOnCurv) == "undefined") {
ASdot([reducer*cos(t/(n0) + del), reducer*sin(t/(d0))], 2, corpColor, corpColor, "ptOnCurv");
gebi("ptOnCurv");
}
ptOnCurv.setAttribute("cx", origin[0] + xunitlength*reducer*cos(t/(n0) + del) );
ptOnCurv.setAttribute("cy", (boardHeight - origin[1]) - yunitlength*reducer*sin(t/(d0) ));

if(brdType === 1 ) {
ptOnCirc0.setAttribute( "transform", "rotate(" + -(t*180/(d0*pi)).toFixed(1) + ", " + (origin[0] - 2*xunitlength).toFixed(1)  + ","+(boardHeight - origin[1]).toFixed(1)+")");

ptOnCirc1.setAttribute( "transform", "rotate(" + -(( t/n0+del)*180/pi ).toFixed(1) + ", " + (origin[0]).toFixed(1)  + ","+(boardHeight - (origin[1] - 2*yunitlength)).toFixed(1) + ")" );

seg0.setAttribute("transform", "translate(0 "+(- yunitlength*reducer*(sin(t/(d0))))+")");
seg1.setAttribute("transform", "translate("+(-xunitlength*reducer*(1-cos(t/(n0)+del)))+")");
}
p.push([reducer*cos(t/(n0) + del), reducer*sin(t/(d0))]);
path(p, "greenCurve"+j);
raf0 = window.requestAnimationFrame(doAnim);
}
}
// Frames per second
if (anim0) {
var thisFrameTime0=(thisLoop0=new Date())-lastLoop0;
frameTime0+=(thisFrameTime0-frameTime0)/filterStrength;
lastLoop0=thisLoop0;
if(10*t.toFixed(1) % 10 == 0)  {
fps0 = (1000/frameTime0).toFixed(1);
fps0Span.innerText = fps0;
}
}
}

function doRot() {
removeEle("greenCurve");
for (t = 0; 2*N*D*pi*domFactor + 2*inc > t; t += inc){
p.push([reducer*cos(t/N + del), reducer*sin(t/D)]);
}
// Plot the array
path(p, "greenCurve");
rot = true;
}

if(slider0.noUiSlider) {
slider0.noUiSlider.destroy();
}
noUiSlider.create(slider0, {
start: 4,
step: 1,
range: {
"min": 1,
"max": 20
},
tooltips: [true]
});
slider0.noUiSlider.on("slide", function(values, handle){
var leftBy = (1*values[0] < 10 ? 200 : -100 );
document.getElementsByClassName( "noUi-tooltip")[0].style.left = leftBy+"%";
plusSgn = ( 1*values[0] < 0 ? "" : "+" );
gebi("theFn").innerHTML = "x = cos(t/"+Number(values[0]).toFixed(0)+ plusSgn + Math.abs(del).toFixed(2)+"),~ ~ ~ y = sin(t/"+d0+")";
AMfunc(true);
anim0 = false;
});

slider0.noUiSlider.on("set", function(values, handle){
init();
doAnim();
});

if(slider1.noUiSlider) {
slider1.noUiSlider.destroy();
}
noUiSlider.create(slider1, {
start: 3,
step: 1,
range: {
"min": 1,
"max": 20
},
tooltips: [true]
});
slider1.noUiSlider.on("slide", function(values, handle){
var leftBy = (1*values[0] < 10 ? 200 : -100 );
document.getElementsByClassName( "noUi-tooltip")[1].style.left = leftBy+"%";
plusSgn = ( del < 0 ? "" : "+" );
gebi("theFn").innerHTML = "x = cos(t/"+Number(values[0]).toFixed(0)+ plusSgn +del.toFixed(2)+"),~ ~ ~ y = sin(t/"+d0+")";
AMfunc(true);
anim0 = false;
});

slider1.noUiSlider.on("set", function(values, handle){
init();
doAnim();
});

if(slider2.noUiSlider) {
slider2.noUiSlider.destroy();
}

noUiSlider.create(slider2, {
start: 0.001,
step: 0.01,
range: {
"min": -pi,
"max": pi
},
tooltips: [true]
});

slider2.noUiSlider.on("slide", function(values, handle){
del = 1*values[0];
var leftBy = (del < 0 ? 200 : -100 );
document.getElementsByClassName( "noUi-tooltip")[2].style.left = leftBy+"%";
gebi("pause").innerHTML = "&#9658; start";
init();
anim0 = false;
fps0Span.innerText = 0;
doRot();
});

// Call these after defining sliders
init();
doAnim();

if(anim0 == true) {
anim0 = false;
this.innerHTML = "&#9658; resume";
} else {
anim0 = true;
if(rot) {
init();
rot = false;
}
if(completed) {
init();
completed = false;
}
doAnim();
this.innerHTML = "<small>&#9612;&#9612;</small> pause";
}
});

actualResizeHandler = function() {
createBrd();
init();
doAnim();
}

brdType = ( brdType == 0 ? 1 : 0 );
reducer = ( brdType == 0 ? 1 : 0.9 );

t = 0;
createBrd();
setBoardParams("asvg0");
init();
doAnim();
});

} catch(err) {
gebi("err").innerHTML = err.message;
}


## Credit

Based loosely on a script by Jase Smith.