/** Lissajous Figures demonstrator by Robert John Morton */ /* This version uses a sine table for reason of speed so that the program does not take up too much of the host machine's processing time. Finished 01 October 1997 */ import java.awt.*; import javax.swing.*; import java.awt.image.BufferedImage; public class lgraph extends JPanel implements Runnable { private int XE, // Horizontal extent of window and JFrame. YE, // Vertical extent of window and JFrame. m = 1, // x-axis frequency multiplier R = 80, // radius of the trace vector B = R + 10, // bias of centre of graph (both x and y the same) W = B + B, // width of graphics window Bx = B - 4, // bias for the x-axis annotation 'X' dBy, // half the height of the letter 'X' By = B + 5, // bias for the y-axis annotation 'Y' Z = 192, // vertical position of annotations q = 4, /* Number of quadrants to paint on each call to repaint() */ B1, // centrallising bias for S1 [see below] B2, // centrallising bias for S2 [see below] b, // centrallising bias for current annotation string delay = 15; // delay the run process this number of cycles private long tf = 70, // total Time Frame for an update cycle [milliseconds] t; // time at which the next new cycle is due to begin private boolean wipe = true; // trigger flag to wipe scope trace private TabCoord X1 = new TabCoord(), // x co-ordinate of the painter trace Y1 = new TabCoord(), // y co-ordinate of the painter trace X2 = new TabCoord(), // x co-ordinate of the wiper trace Y2 = new TabCoord(); // y co-ordinate of the wiper trace private String // Annotation strings for single and double ellipses S1 = " Y-FREQ ALMOST = X-FREQ ", S2 = "Y-FREQ ALMOST TWICE X-FREQ", s = S2; // current annotation string private Color tr = new Color(0,255,128), // bright green for trace ax = new Color(64,192,0); // special browny-green for axes private BufferedImage I; // off-screen image on which to plot the graph private Graphics2D gi; // graphics reference for off-screen image private Thread T; // declare a run-thread reference variable private Font // font in which to print the annotations font = new Font("Dialog",Font.PLAIN,12); private FontMetrics fm; // dimensions of letters etc of chosen font public lgraph(int XE, int YE) { this.XE = XE; // horizontal [X] dimension of the JPanel this.YE = YE; // vertical [Y] dimension of the JPanel setBounds(0,0,XE,YE); // the JPanel fills the whole of the JFrame area setLayout(null); // JPanel has free-form layout [for graph] /* Create a Buffered Image "I" on which to plot Lissajou's Figures off- screen and get its graphics context "gi" for realising the plots. */ I = new BufferedImage(XE,YE,BufferedImage.TYPE_INT_RGB); gi = I.createGraphics(); // get metrics (pixel dimensions of height, leading etc.) for this font. fm = getFontMetrics(font); dBy = fm.getAscent() >> 1; // half the height of the letter '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 = new Thread(this); // create a new program thread T.start(); // start the run() loop } public void paint(Graphics g) { g.drawImage(I,30,20,this); // display the buffered image } private void initialPlot() { // SET UP THE INITIAL 'SCOPE SCREEN' gi.setFont(font); // set up the annotation font gi.setColor(Color.black); // set appropriate wiping colour gi.fillRect(0,0,200,200); // clear the plotting area gi.setColor(Color.lightGray); // set colour to paint new trace gi.drawString(s, b, Z); // display the graph's annotation /* Place the letter 'X' at the end of the X-axis and the letter 'Y' at the end of the Y-axis. */ gi.drawString("X",Bx + R + 3,By + dBy); gi.drawString("Y",Bx - 3,By - R - 4); gi.setColor(ax); // set colour to paint new trace gi.drawLine(Bx-R,By,Bx+R,By); // display the x axis and label it gi.drawLine(Bx,By-R,Bx,By+R); // display the y axis and label it } private void currentPlot() { // 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 initialPlot(); // initial display of the annotated axes 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 gi.setColor(tr); // paint the trace in black gi.drawLine(x,y,x,y); // Draw the next bit of line if (X1.f) { // If painter 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 /* Provided the plot is not located on one of the axes, wipe the trace; otherwise, repaint it in the axis colour. This makes sure the axes are preserved in the right colour. */ if(x != Bx && y != By) gi.setColor(Color.black); // wipe the trace else gi.setColor(ax); // repaint it in the axis colour gi.drawLine(x,y,x,y); } } q = X1.Q + 4; // reset 'q' to 4 quadrants ahead } public void run() { // RUN THE T THREAD // Set the system time at which the first plotting cycle should end t = System.currentTimeMillis() + tf; while(true) { // permanent loop broken by external event if (X1.Q > 2160) // while 2160 quadrants not yet done wipe = true; // trigger a scope screen wipe currentPlot(); // update the graph repaint(); // sets up a call to paint() // Get time remaining in this cycle's time frame. long s = t - System.currentTimeMillis(); if (s < 5) s = 5; // in case host PC is too slow for ideal trace speed try{T.sleep(s); // sleep for remaining time /* Allow external events to break the thread. Triggers a 'reset' if program is restarted. This happens, for example, if, having left it, you return to an applet's HTML page. */ } catch(InterruptedException e){wipe = true;} // Set the finishing time for next time frame t = System.currentTimeMillis() + tf; } } }