It was developed on Sun Microsystems' Java Developers' Kit 1.0.1 running under IBM's OS/2 Warp 4. It ran perfectly on the OS/2 desktop. However, when run in Microsoft Internet Explorer 3.0, the Y-axis line seems to get bombed for no reason which Rob could see at the time.
/** Lissajous Figures demonstrator by Robert J Morton */
/* This version uses a sine table for reason of speed so that
the applet does not take up too much of the host machine's
processing time. Finished 01 October 1997 */
import java.awt.*;
import java.lang.System;
class TabCoord
{
int a = 0; //main phase angle
int d = 1; //main phase angle incrementer
int Q = 0; //quadrant counter
boolean s = true; //indicates the output sign
boolean f = false; //flag to trigger start of wiper
static int[] S = { //Sine Table giving sin(a) every half degree
0, 87,
175, 262, 349, 436, 523, 610, 698, 785, 872, 958,
1045, 1132, 1219, 1305, 1392, 1478, 1564, 1650, 1736, 1822,
1908, 1994, 2079, 2164, 2250, 2334, 2419, 2504, 2588, 2672,
2756, 2840, 2924, 3007, 3090, 3173, 3256, 3338, 3420, 3502,
3584, 3665, 3746, 3827, 3907, 3987, 4067, 4147, 4226, 4305,
4384, 4462, 4540, 4617, 4695, 4772, 4848, 4924, 5000, 5075,
5150, 5225, 5299, 5373, 5446, 5519, 5592, 5664, 5736, 5807,
5878, 5948, 6018, 6088, 6157, 6225, 6293, 6361, 6428, 6494,
6561, 6626, 6691, 6756, 6820, 6884, 6947, 7009, 7071, 7133,
7193, 7254, 7314, 7373, 7431, 7490, 7547, 7604, 7660, 7716,
7771, 7826, 7880, 7934, 7986, 8039, 8090, 8141, 8192, 8241,
8290, 8339, 8387, 8434, 8480, 8526, 8572, 8616, 8660, 8704,
8746, 8788, 8829, 8870, 8910, 8949, 8988, 9026, 9063, 9100,
9135, 9171, 9205, 9239, 9272, 9304, 9336, 9367, 9397, 9426,
9455, 9483, 9511, 9537, 9563, 9588, 9613, 9636, 9659, 9681,
9703, 9724, 9744, 9763, 9781, 9799, 9816, 9833, 9848, 9863,
9877, 9890, 9903, 9914, 9925, 9936, 9945, 9954, 9962, 9969,
9976, 9981, 9986, 9990, 9994, 9997, 9998, 10000, 10000 };
public int Advance(int p, int m, int b) //FINDS VALUE OF CO-ORDINATE CORRESPONDING TO INCREMENTED ANGLE
{
int x = S[a] >> 7; //get x or y co-ord corresponding to given angle
if (s) //if the output should be positive
x = b + x; //add it to the co-ordinate origin bias
else
x = b - x; //take it from the co-ordinate origin bias
a += d << m; //advance the main angle by the appropriate amount (can be > 1)
if (a < 0) { //if as a result we are now at or 'below' the bottom of the table
a = p - a; //wrap back up the table and add this quadrant's phase increment
d = 1; //set main angle to move up the table
Q++; //quadrant counter
s = !s; //reverse sign for the next 2 quadrants
} //alternatively...
else if (a > 180) { //if as a result we are now at or 'above' the top of the table
a = 360 - a - p; //wrap back down the table and subtract this quadrant's phase increment
Q++; //quadrant counter
d = -1; //set to move angle down the table
}
if (!f) //if wiper start flag not set
if (Q > 6) //if 3 quadrants + 80 degrees completed
f = true; //set the wiper start flag
return(x); //return new x or y co-ordinate
}
public void Reset() //RESET THE VALUES IN A CO-ORDIATE OBJECT
{
a = 0; //main phase angle
d = 1; //main phase angle incrementer
Q = 0; //clear the quadrant counter
s = true; //indicates the output sign
f = false; //wiper start flag
}
}
public class Lissajou extends java.applet.Applet
implements Runnable
{
int R = 80; //radius of the trace vector
int B = R + 10; //bias of centre of graph (both x and y the same)
int W = B + B; //width of graphics window
int Bx = B - 4; //bias for the x-axis annotation 'X'
int dBy; //half the height of the letter 'X'
int By = B + 5; //bias for the y-axis annotation 'Y'
int Z = 192; //vertical position of annotations
int q = 4; //number of quadrants to paint on each call to update()
long TF = 200; //total Time Frame for an update cycle
long T; //time at which the next new cycle is due to begin
int m = 1; //x-axis frequency multiplier
boolean wipe = true; //trigger flag to wipe scope trace
Thread ScopeTrace; //declare a thread reference variable
Font font; //and a font reference for annotations
FontMetrics fm; //dimensions of letters etc of chosen font
TabCoord X1; //x co-ordinate of the painter trace
TabCoord Y1; //y co-ordinate of the painter trace
TabCoord X2; //x co-ordinate of the wiper trace
TabCoord Y2; //y co-ordinate of the wiper trace
String S1 = "Y-FREQ ALMOST = X-FREQ"; //single ellipse's annotation string
int B1; //centrallising bias for S1
String S2 = "Y-FREQ ALMOST 2 * X-FREQ"; //double ellipse's annotation string
int B2; //centrallising bias for S2
String s; //current annotation string
int b; //centrallising bias for current annotation string
Color tr; //trace colour
Color ax; //axes colour
public void init()
{
X1 = new TabCoord(); //create co-ordinate objects for painter trace
Y1 = new TabCoord();
X2 = new TabCoord(); //create co-ordinate objects for wiper trace
Y2 = new TabCoord();
tr = new Color( 0, 255, 128); //create special bright green for trace
ax = new Color(64, 192, 0); //create special browny-green for axes
font = new Font("Dialog", Font.PLAIN, 12);
fm = getFontMetrics(font); //get the metrics (pixel dimensions of height, leading etc.) for this font
dBy = fm.getAscent() >> 1; //half the height of the 'X'
B1 = B - (fm.stringWidth(S1) >> 1); //width of first trace's annotation
B2 = B - (fm.stringWidth(S2) >> 1); //width of second trace's annotation
T = System.currentTimeMillis() + TF; //set end time for current update time frame
}
public void paint(Graphics g) //SET UP THE INITIAL 'SCOPE SCREEN'
{
g.setFont(font); //set up the annotation font
g.setColor(Color.black); //set appropriate wiping colour
g.fillRect(0,0,180,200); //clear the applet window
g.setColor(Color.lightGray); //set colour to paint new trace
g.drawString(s, b, Z); //display the graph's annotation
g.drawString("X",Bx + R + 3,By + dBy); //put the 'X' at the end of the X-axis
g.drawString("Y", Bx - 3, By - R - 4); //put the 'Y' at the top of the Y-axis
g.setColor(ax); //set colour to paint new trace
g.drawLine(Bx - R, By, Bx + R, By); //display the x axis and label it
g.drawLine(Bx, By - R, Bx, By + R); //display the y axis and label it
}
public void update(Graphics g) //UPDATE THE 'SCOPE TRACE'
{
int x, y; //temporary co-ordinate variables
if (wipe) { //if it's time to wipe trace
if (m == 0) { //if just finished a double
m = 1; s = S2; b = B2; //start to do a single ellipse
} else { //else
m = 0; s = S1; b = B1; //start a new double
}
X1.Reset(); Y1.Reset(); //reset Coord objects for the painter trace
X2.Reset(); Y2.Reset(); //reset Coord objects for the wiper trace
paint(g); //re-display the blank window with axes and annotations
q = 4; //number of quadrants to update
wipe = false; //cancel the 'wipe' request
}
while(X1.Q < q) { //while current main cycle not yet finished
x = X1.Advance(1, 0, Bx); //get the new x plot
y = Y1.Advance(0, m, By); //get the new y plot
g.setColor(tr); //paint the trace in black
g.drawLine(x, y, x, y); //Draw the next bit of line
if (X1.f) { //If painter has completed at least one cycle
x = X2.Advance(1, 0, Bx); //get the new x plot to be wiped
y = Y2.Advance(0, m, By); //get the new y plot to be wiped
if(x != Bx && y != By) //Provided the plot is not on one of the axes
g.setColor(Color.black); //wipe the trace
else g.setColor(ax); //else repaint it in the axis colour
g.drawLine(x, y, x, y); //and thereby preserve the axial pixels
}
}
q = X1.Q + 4; //reset 'q' to 4 quadrants ahead
}
public void run() //RUN THE ScopeTrace THREAD
{
while(true) { //permanent loop broken by external event
if (X1.Q > 1080) //while 1080 quadrants not yet done
wipe = true; //trigger a scope screen wipe
repaint(); //sets up a call to update()
long s = T //get time remaining in this cycle's time frame
- System.currentTimeMillis();
if (s < 5) s = 5; //in case host PC is too slow for ideal trace speed
try {
Thread.currentThread().sleep(s); //sleep for remaining time
} catch (InterruptedException e) { //allow browser events to break the thread
wipe = true; //triggers a 'reset' if applet is restarted
} //happens if you return to applet's HTML page
T = System.currentTimeMillis() //set finish time of next time frame
+ TF;
}
}
public void start() { //Start program thread by
ScopeTrace = new Thread(this); //creating the thread object
ScopeTrace.start(); //and starting it running
} //[returns a call to run()]
public void stop() { //Stop program thread
ScopeTrace.stop();
}
}