Lissajous figures

logo

Made with p5js

This applet draws lissajous figures. The x and y simple harmonic motions are shown by the grey circles on the top and the side and the motion of a particle subjected to both these motions simultaneously is shown by the black circle.

So let's say that the x motion is given by x=asin(ωt)x = a\sin(\omega t) and the y motion by y=asin(ωt+ϕ)y = a\sin(\omega t + \phi). A phase difference ϕ\phi has been added to indicate that these motions do not have to be in step. These two equations are just the parametric equations of a lissajous figure which is what is being plotted.

Here is the code:

let _canvas;
let _freq_1_lbl, _freq_2_lbl;
let _freq_1_sldr, _freq_2_sldr;
let _phase_diff_sldr;
let _phase_diff_lbl;
let _sldr_lbl_map;
let _pg;
let _cur_indx;
let _cur_x, _cur_y;
let _rad;
let _freq_1, _freq_2;
let _canvas_div;
const _ellipse_rad = 16;
const _canvas_height = 300;

function setup() {
_canvas_div = document.getElementById('canvas_div');
const cw = _canvas_div.clientWidth;
_canvas = createCanvas(cw, _canvas_height);
_canvas.parent('canvas_div');
_rad = (min(cw, _canvas_height) - 50) / 2 - 5;
_pg = createGraphics(cw - 50, _canvas_height - 50);

_freq_1_lbl = createElement('output', 'x frequency: 2');
_freq_1_lbl.parent('top_labels');

_freq_2_lbl = createElement('output', 'y frequency: 5');
_freq_2_lbl.parent('top_labels');

_freq_1_sldr = createSlider(1, 7, 2);
_freq_1_sldr.parent('top_sliders');
_freq_1_sldr.id('freq_1_sldr');
_freq_1_sldr.elt.addEventListener('change', onSldrMoved);

_freq_2_sldr = createSlider(1, 7, 5);
_freq_2_sldr.parent('top_sliders');
_freq_2_sldr.id('freq_2_sldr');
_freq_2_sldr.elt.addEventListener('change', onSldrMoved);

_phase_diff_sldr = createSlider(0, 360, 60);
_phase_diff_sldr.attribute('step', '15');
_phase_diff_sldr.parent('botm_slider');
_phase_diff_sldr.id('phase_diff_sldr');
_phase_diff_sldr.elt.addEventListener('change', onSldrMoved);

_phase_diff_lbl = createElement('output', 'Phase difference: 60');
_phase_diff_lbl.parent('botm_label');

_sldr_lbl_map = Object.create(null);
_sldr_lbl_map[_freq_1_sldr.elt.id] = {
caption: 'x frequency: ',
lbl: _freq_1_lbl,
};
_sldr_lbl_map[_freq_2_sldr.elt.id] = {
caption: 'y frequency: ',
lbl: _freq_2_lbl,
};
_sldr_lbl_map[_phase_diff_sldr.elt.id] = {
caption: 'Phase difference: ',
lbl: _phase_diff_lbl,
};

_pg.stroke(0);
noStroke();
setFreqs();
resetDrawing();
frameRate(30);
}

function windowResized() {
const cw = _canvas_div.clientWidth;
resizeCanvas(cw, _canvas_height);
_rad = (min(cw, _canvas_height) - 50) / 2 - 5;
_pg = createGraphics(cw - 50, _canvas_height - 50);
resetDrawing();
}

function onSldrMoved(change_event) {
const slider = change_event.target;
const val = slider.value;
const slider_id = slider.id;
_sldr_lbl_map[slider_id].lbl.value(_sldr_lbl_map[slider_id].caption + val);
setFreqs();
resetDrawing();
}

function draw() {
const cur_angle = radians(_cur_indx * 0.5);
const x = _rad * cos(_freq_1 * cur_angle);
const y = _rad * cos(_freq_2 * cur_angle + radians(_phase_diff_sldr.value()));
_pg.line(
_pg.width / 2 + _cur_x,
_pg.height / 2 + _cur_y,
_pg.width / 2 + x,
_pg.height / 2 + y
);
background(255);
translate(width / 2, height / 2);
image(_pg, -_pg.width / 2, -_pg.height / 2);
fill(0);
ellipse(x, y, _ellipse_rad, _ellipse_rad);
fill(220);
ellipse(x, -height / 2 + _ellipse_rad / 2, _ellipse_rad, _ellipse_rad);
ellipse(-width / 2 + _ellipse_rad / 2, y, _ellipse_rad, _ellipse_rad);
_cur_x = x;
_cur_y = y;
if (_cur_indx === 720) {
resetDrawing();
} else ++_cur_indx;
}

function resetDrawing() {
_cur_indx = 0;
_pg.background(255);
_cur_x = _rad;
_cur_y = _rad * cos(radians(_phase_diff_sldr.value()));
}

function hcf(i, j) {
let divdnd, divsr, rem;
divsr = min(i, j);
divdnd = max(i, j);
do {
rem = divdnd % divsr;
divdnd = divsr;
divsr = rem;
} while (rem !== 0);
return divdnd;
}

function setFreqs() {
_freq_1 = _freq_1_sldr.value();
_freq_2 = _freq_2_sldr.value();
const hcf_freqs = hcf(_freq_1, _freq_2);
_freq_1 /= hcf_freqs;
_freq_2 /= hcf_freqs;
}