// PlotLines.java  -  line-plotting some X-Y data

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Vector;
import java.text.DecimalFormat;

/**
 * A simple line-plot routine <BR>
 * This example is an expansion of the Plot.java example.
 * 
 * @author Bary W Pollack
 * @version Feb. 25, 2001
 */

public class PlotLines extends Frame {

    // the paint method will (re-)display the line-plot
    private static boolean bFlag = true;

    public void paint(Graphics g) {
        // black background
        g.setColor(Color.black);
        g.fillRect(BORDER, BORDER + OFFSET - 1, WIDTH - 2*BORDER + 2, HEIGHT - 2*BORDER - OFFSET + 3);
        // a double-width border
        g.setColor(BORDERCOLOR);
        g.drawRect(BORDER-1, BORDER + OFFSET - 1, WIDTH - 2*BORDER + 3, HEIGHT - 2*BORDER - OFFSET + 3);
        g.drawRect(BORDER-2, BORDER + OFFSET - 2, WIDTH - 2*BORDER + 5, HEIGHT - 2*BORDER - OFFSET + 5);
        // plot the lines
        g.setColor(PLOTCOLOR);
        int[] nXa = new int[x.length];
        int[] nYa = new int[x.length];
        for (int i = 0; i < x.length; i++) {
            nXa[i] = scale(x[i], WIDTH, 0);
            nYa[i] = scale(y[i], HEIGHT, OFFSET);
        }
        if (bFlag) {
            g.drawPolyline(nXa, nYa, nXa.length);
        } else {
            // sort the data by X-value
            for (int i = 0; i < nXa.length - 1; i++) {
                for (int j = i + 1; j < nXa.length; j++) {
                    if (nXa[i] > nXa[j]) {
                        int nT = nXa[i];
                        nXa[i] = nXa[j];
                        nXa[j] = nT;
                        nT = nYa[i];
                        nYa[i] = nYa[j];
                        nYa[j] = nT;
                    }
                }
            }
            g.drawPolyline(nXa, nYa, nXa.length);
        }
        bFlag = !bFlag;
    }

    // scale and translate so that there's a small border
    public int scale(double dVal, int nRange, int nOffset) {
        return (int) (dVal * (nRange - 2 * BORDER - nOffset)) + BORDER + nOffset;
    }

    // this constructor does the real work of the application
    public PlotLines() {
        // put title into the frame's title area
        setTitle(" Line-plotting " + NUMLINES + " random lines...");
        // detect the "close box" click -- to close the application
        this.addWindowListener (new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                t.stop();
                System.exit(0);
            }
        });
        // set the background
        setBackground(BACKGROUND);
        // set up the Timer
        t = new Timer(this);
        t.start();
    }
    
    public static void main(String[] args) {
        System.out.println("Line-plotting " + NUMLINES + " random lines...");
        System.out.println();
        new MakeData(NUMLINES, FILENAME);
        ReadData rd = new ReadData(FILENAME);
        x = rd.getX();
        y = rd.getY();
        PlotLines p = new PlotLines();
        p.setSize(WIDTH, HEIGHT);
        p.setVisible(true);
        System.out.println();
    }

    // variables used by the application
    static double[] x;                                // the X values
    static double[] y;                                // the Y values
    static Timer t;

    // constants used by the application
    final static int WIDTH           = 400;            // width and height
    final static int HEIGHT        = 400;            // of the display
    final static int BORDER           = 40;
    final static int OFFSET           = 20;
    final static Color PLOTCOLOR   = Color.white;
    final static Color BORDERCOLOR = Color.cyan;
    final static Color BACKGROUND  = Color.gray;
    final static int DELAY           = 1500;            // in milliseconds
    final static int NUMLINES       = 200;            // only used by MakeData
    final static String FILENAME   = "xydata.txt";    // name of the data file
    final static boolean DEBUG       = false;            // true for debug output
}

/**
 * Create the random data to be plotted. <BR>
 * Place into a text file as floating point tuples. <BR>
 * This class creates nNumPoint pairs of random double values <BR>
 * in the half-open range [0,1).
 */

class MakeData {

    MakeData(int nNUMLINES, String sFileName) {
        System.out.println("Creating " + nNUMLINES + " tuples in file " + sFileName);
        try {
            PrintWriter pw = new PrintWriter(
                                 new BufferedWriter(
                                     new FileWriter(sFileName)));
            // output nNUMLINES tab-separated tuples; with 6 digit precision
            DecimalFormat decFmt = new DecimalFormat("0.######");
            for (int i = 0; i < nNUMLINES; i++)
                pw.println(decFmt.format(Math.random()) + "\t" + decFmt.format(Math.random()));
            pw.close();
        } catch (IOException e) {
            System.out.println("ERROR in MakeData -- can't create file: " + sFileName);
            System.out.println(e);
            System.exit(1);
        }
        System.out.println(sFileName + " created");
    }

}

/**
 * Read the data file, creating the X-Y data arrays
 */

class ReadData {

    ReadData(String sFileName) {
        System.out.println("Reading X-Y tuples from " + sFileName);
        int nNUMLINES = 0;
        // we'll store the values in a Vector; later convert to a pair of arrays
        Vector v = new Vector();
        try {
            FileReader reader = new FileReader(sFileName);
            StreamTokenizer tokens = new StreamTokenizer(
                                         new BufferedReader(reader));
            // tell the tokenizer that we want to parse numbers "properly"
            tokens.parseNumbers();

            int next = 0;
            while ((next = tokens.nextToken()) != StreamTokenizer.TT_EOF) {
                switch (next) {
                    case StreamTokenizer.TT_NUMBER:
                        if (PlotLines.DEBUG)
                            System.out.println("  Data:  " + tokens.nval);
                        v.addElement(new Double(tokens.nval));
                        ++nNUMLINES;
                        break;
                    default:
                        System.out.print("ERROR in ReadData -- file: " + sFileName + ".  ");
                        System.out.println("BAD DATA ENCOUNTERED!  (" + tokens.sval + ")");
                        System.out.println("It will be ignored");
                        break;
                }
            }

            reader.close();
        } catch (IOException e) {
            System.out.println("ERROR in ReadData -- file: " + sFileName);
            System.out.println(e);
            System.exit(1);
        }
        int nTuples = nNUMLINES / 2;
        System.out.println(nTuples + " tuples read from " + sFileName);

        // now, create the x and y arrays of double values
        x = new double[nTuples];
        y = new double[nTuples];
        for (int i = 0; i < nTuples; i++) {
            int j = 2 * i;
            x[i] = ((Double) v.elementAt(j)).doubleValue();
            y[i] = ((Double) v.elementAt(j + 1)).doubleValue();
        }

        if (PlotLines.DEBUG)
            for (int i = 0; i < x.length; i++)
                System.out.println(i + "\t" + x[i] + "\t" + y[i]);
    }

    public double[] getX() { return x; }
    public double[] getY() { return y; }

    private Vector v;            // data stored here when read in
    private double[] x;            // X-values
    private double[] y;            // Y-values

}

/**
 * Create a timer that goes off periodically, <BR>
 * causing a screen repaint.
 */
 
class Timer implements Runnable {

    Timer(Frame f) {
        this.f = f;
    }

    public void start() {
        if (timerThread == null) {
            timerThread = new Thread(this, "Clock");
            timerThread.start();
        }
    }

    public void run() {
        Thread myThread = Thread.currentThread();
        while (timerThread == myThread) {
            f.repaint();
            sleep(PlotLines.DELAY);
        }
    }

    public void stop() {
        timerThread = null;
    }

    // sleep for a given number of milliseconds
    private void sleep(int nMilliseconds) {
        try {
            Thread.sleep(nMilliseconds);
        } catch (InterruptedException e) {
        }
    }

    private Frame f;
    private Thread timerThread = null;
}
