// Plot.java  -  scatter-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 scatter-plot routine
 * 
 * @author Bary W Pollack
 * @version Feb. 25, 2001
 */

public class Plot extends Frame {

    // the paint method will (re-)display the scatter-plot
    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 points
        g.setColor(PLOTCOLOR);
        for (int i = 0; i < x.length; i++) {
            int nX = scale(x[i], WIDTH, 0);
            int nY = scale(y[i], HEIGHT, OFFSET);
            g.fillRect(nX, nY, 2, 2);
        }
    }

    // scale and translate so that there's a small border
    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 Plot() {
        // put title into the frame's title area
        setTitle(" Scatter-plotting " + NUMPOINTS + " random points...");
        // detect the "close box" click -- to close the application
        this.addWindowListener (new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        // set the background
        setBackground(BACKGROUND);
    }
    
    public static void main(String[] args) {
        System.out.println("Scatter-plotting " + NUMPOINTS + " random points...");
        System.out.println();
        new MakeData(NUMPOINTS, FILENAME);
        ReadData rd = new ReadData(FILENAME);
        x = rd.getX();
        y = rd.getY();
        Plot p = new Plot();
        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

    // 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 NUMPOINTS       = 2500;            // 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 nNumPoints, String sFileName) {
        System.out.println("Creating " + nNumPoints + " tuples in file " + sFileName);
        try {
            PrintWriter pw = new PrintWriter(
                                 new BufferedWriter(
                                     new FileWriter(sFileName)));
            // output nNumPoints tab-separated tuples; with 6 digit precision
            DecimalFormat decFmt = new DecimalFormat("0.######");
            for (int i = 0; i < nNumPoints; 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 nNumPoints = 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 (Plot.DEBUG)
                            System.out.println("  Data:  " + tokens.nval);
                        v.addElement(new Double(tokens.nval));
                        ++nNumPoints;
                        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 = nNumPoints / 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 (Plot.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

}
