package graph;

import java.awt.*;
import java.applet.*;
import java.util.*;
import java.lang.*;
import java.io.StreamTokenizer;
import java.io.InputStream;
import java.io.IOException;
import java.net.URL;

/*
**************************************************************************
**
**                      Class  graph.Graph2D
**
**************************************************************************
**    Copyright (C) 1995, 1996 Leigh Brookshaw
**
**    This program is free software; you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation; either version 2 of the License, or
**    (at your option) any later version.
**
**    This program is distributed in the hope that it will be useful,
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**    GNU General Public License for more details.
**
**    You should have received a copy of the GNU General Public License
**    along with this program; if not, write to the Free Software
**    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**************************************************************************
**
** class Graph2D extends Canvas 
**
** The main entry point and interface for the 2D graphing package.
** This class keeps track of the DataSets and the Axes.
** It has the main drawing engine that positions axis etc.
**
*************************************************************************/


/**
 * This is the main plotting class. It partitions the canvas to contain the
 * specified axes with the remaining space taken with the plotting region.
 * Axes are packed against the walls of the canvas. The <B>paint</B> and
 * <B>update</B> methods of this class handle all the drawing operations of the
 * graph. This means that independent components like Axis and DataSets must be
 * registered with this class to be incorporated into the plot.
 *
 * @version  $Revision: 1.12 $, $Date: 1996/09/24 05:23:41 $
 * @author   Leigh Brookshaw
 */

public class Graph2D extends Canvas {


/*
** Default Background Color
*/
    private Color DefaultBackground = null;



/*
*********************
**
** Protected Variables
**
*********************/

/**
 *  A vector list of All the axes attached
 *  @see Graph2d#attachAxis()
 */

    protected Vector axis          = new Vector(4);

/**
 *  A vector list of All the DataSets attached
 *  @see Graph2d#attachDataSet()
 *  @see DataSet
 */

    protected Vector dataset       = new Vector(10);

/**
 *  The markers that may have been loaded
 *  @see Graph2D#setMarkers()
 */

    protected Markers markers = null;

/**
 * The blinking "data loading" thread
 * @see Graph2D#startedloading()
 */

    protected LoadMessage load_thread = null;

/**
 * The background color for the data window
 */
    protected Color DataBackground = null;

/*
**********************
**
** Public Variables
**
*********************/

/**
 *  If this is greater than zero it means that
 *  data loading threads are active so the message "loading data"
 *  is flashed on the plot canvas. When it is back to zero the plot
 *  progresses normally
 */
    public int  loadingData  = 0;


/**
 *  The width of the border at the top of the canvas. This allows 
 *  slopover from axis labels, legends etc.
 */
    public int     borderTop          = 20;
/**
 *  The width of the border at the bottom of the canvas. This allows 
 *  slopover from axis labels, legends etc.
 */
    public int     borderBottom       = 20;
/**
 *  The width of the border at the left of the canvas. This allows 
 *  slopover from axis labels, legends etc.
 */
    public int     borderLeft         = 20;
/**
 *  The width of the border at the right of the canvas. This allows 
 *  slopover from axis labels, legends etc.
 */
    public int     borderRight        = 20;

/**
 *  If set <I>true</I> a frame will be drawn around the data window.
 *  Any axes will overlay this frame.
 */
    public boolean frame        = true;
/**
 *  The color of the frame to be drawn
 */
    public Color   framecolor;
/**
 * If set <I>true</I> (the default) a grid will be drawn over the data window.
 * The grid will align with the major tic marks of the Innermost axes.
 */
    public boolean drawgrid     = true; 
/**
 * The color of the grid to be drawn
 */
    public Color   gridcolor    = Color.pink;
/**
 *  If set <I>true</I> (the default) a grid line will be drawn 
 *  across the data window
 *  at the zeros of the innermost axes.
 */
    public boolean drawzero     = true;
/**
 *  The color of the zero grid lines.
 */ 
    public Color   zerocolor    = Color.orange;
/**
 *  The rectangle that the data will be plotted within. This is an output
 *  variable only.
*/
    public Rectangle datarect   = new Rectangle();
/**
 *  If set <I>true</I> (the default) the canvas will be set to the background
 *  color (erasing the plot) when the update method is called.
 *  This would only be changed for special effects.
 */
    public boolean clearAll     = true;
/**
 *  If set <I>true</I> (the default) everything associated with the plot
 *  will be drawn when the update method or paint method are called.
 *  Normally
 *  only modified for special effects
*/
    public boolean paintAll     = true;

/**
 *  Modify the position of the axis and the range of the axis so that
 *  the aspect ratio of the major tick marks are 1 and the plot is square
 *  on the screen
*/
    public boolean square = false;

  /**
   * Text to be painted Last onto the Graph Canvas.
   */
    public TextLine lastText = null;
/*
*******************
**
**  Public Methods
**
*******************/


/**
 *  Load and Attach a DataSet from a File. 
 *  The method loads the data into a DataSet class
 *  and attaches the class to the graph for plotting.
 *
 *  The data is assumed to consist
 *  (at this stage) 2 ASCII columns of numbers x, y. As always blank lines
 *  are ignored and everything following # is ignored as a comment.
 *
 *  @param file    The URL of the data file to read.
 *  @return        The DataSet constructed containing the data read.
 */
    public DataSet loadFile( URL file) {
           byte b[] = new byte[50];
           int nbytes = 0;
           int max  = 100;
           int inc  = 100;
           int n    = 0;
           double data[] = new double[max];
           InputStream is = null;
           boolean comment    = false;
           int c;

           try {
                 is = file.openStream();

                 while( (c=is.read()) > -1 ) {
                     
                     switch (c) {
                     
                         case '#':
                                    comment = true;
                                    break;
                         case '\r': case '\n':
                                    comment = false;
                         case ' ': case '\t':
                                if( nbytes > 0 ) {
                                   String s = new String(b,0,0,nbytes);
                                   data[n] = Double.valueOf(s).doubleValue();
                                   n++;
                                   if( n >= max ) {
                                       max += inc;
                                       double d[] = new double[max];
                                       System.arraycopy(data, 0, d, 0, n);
                                       data = d;
                                   }

                                   nbytes = 0;
                                }
                                break;
                         default:
                                   if( !comment ) {
                                        b[nbytes] = (byte)c;
                                        nbytes++;
                                   }
                                   break;
                      }

                  }

                if (is != null) is.close();
        } catch(Exception e) {
          System.out.println("Failed to load Data set from file ");
          e.printStackTrace();
          if (is != null) try { is.close(); } catch (Exception ev) { }
          return null;
        }

        return loadDataSet(data,n/2);
        
    }

/**
 *  Load and Attach a DataSet from an array. 
 *  The method loads the data into a DataSet class
 *  and attaches the class to the graph for plotting.
 *
 *  The data is assumed to be stored
 *  in the form  x,y,x,y,x,y.... A local copy of the data is made.
 *
 *  @param  data   The data to be loaded in the form x,y,x,y,...
 *  @param  n      The number of (x,y) data points. This means that the
 *                 minimum length of the data array is 2*n.
 *  @return        The DataSet constructed containing the data read.
 */
    public DataSet loadDataSet( double data[], int n ) {
       DataSet d;
       try { 
             d =  new DataSet(data, n);
             dataset.addElement( d );
             d.g2d = this;
            }
       catch (Exception e) { 
         System.out.println("Failed to load Data set ");
         e.printStackTrace();
         return null;
       }
       return d;
    }

/**
 *   Attach a DataSet to the graph. By attaching the data set the class
 *   can draw the data through its paint method.
 */

    public void attachDataSet( DataSet d ) {

       if( d != null) {
             dataset.addElement( d );
             d.g2d = this;
       }
    }
/**
 *    Detach the DataSet from the class. Data associated with the DataSet
 *    will nolonger be plotted.
 *
 *    @param d    The DataSet to detach.
 */

    public void detachDataSet( DataSet d ) {
       if(d != null) {
                      if(d.xaxis != null) d.xaxis.detachDataSet(d);
                      if(d.yaxis != null) d.yaxis.detachDataSet(d);
                      dataset.removeElement(d);
       }
    }
/**
 *    Detach All the DataSets from the class.
*/

    public void detachDataSets() {
           DataSet d;
           int i;

           if(dataset == null | dataset.isEmpty() ) return;

           for (i=0; i<dataset.size(); i++) {
             d = ((DataSet)dataset.elementAt(i));
             if(d.xaxis != null) d.xaxis.detachDataSet(d);
             if(d.yaxis != null) d.yaxis.detachDataSet(d);
           }

           dataset.removeAllElements();
    }


/**
 *    Create and attach an Axis to the graph. The position of the axis
 *    is one of Axis.TOP, Axis.BOTTOM, Axis.LEFT or Axis.RIGHT.
 *
 *    @param position   Position of the axis in the drawing window.
 *
*/
    public Axis createAxis( int position ) {
       Axis a;

       try { 
             a =  new Axis(position);
             a.g2d = this;
             axis.addElement( a );
            }
       catch (Exception e) { 
         System.out.println("Failed to create Axis");
         e.printStackTrace();
         return null;
       }

       return a;
    }
/**
 *  Attach a previously created Axis. Only Axes that have been attached will
 *  be drawn 
 *
 *    @param the Axis to attach.
*/
    public void attachAxis( Axis a ) {

       if(a == null) return;
       
       try { 
             axis.addElement( a );
             a.g2d = this;
            }
       catch (Exception e) { 
         System.out.println("Failed to attach Axis");
         e.printStackTrace();
       }

    }

/**
 * Detach a previously attached Axis.
 *    @param the Axis to dettach.
*/
    public void detachAxis( Axis a ) {
       
       if(a != null) {
                   a.detachAll();
                   a.g2d = null;
                   axis.removeElement(a);
       }
    }
/**
 * Detach All attached Axes.
*/
    public void detachAxes() {
       int i;

       if(axis == null | axis.isEmpty() ) return;

       for (i=0; i<axis.size(); i++) {
            ((Axis)axis.elementAt(i)).detachAll();
            ((Axis)axis.elementAt(i)).g2d = null;
       }

       axis.removeAllElements();
    }

/**
 * Get the Maximum X value of all attached DataSets.
 *  @return  The maximum value
*/
     public double getXmax() {
         DataSet d;
         double max=0.0;

         if(dataset == null | dataset.isEmpty() ) return max;
         for (int i=0; i<dataset.size(); i++) {
             d = ((DataSet)dataset.elementAt(i));
             if(i==0) max = d.getXmax();
             else     max = Math.max(max,d.getXmax());
	 }

         return max;
     }
/**
 * Get the Maximum Y value of all attached DataSets.
 *  @return  The maximum value
*/
     public double getYmax() {
         DataSet d;
         double max=0.0;

         if(dataset == null | dataset.isEmpty() ) return max;
         for (int i=0; i<dataset.size(); i++) {
             d = ((DataSet)dataset.elementAt(i));
             if(i==0) max = d.getYmax();
             else     max = Math.max(max,d.getYmax());
	 }

         return max;
     }

/**
 * Get the Minimum X value of all attached DataSets.
 *  @return  The minimum value
*/

     public double getXmin() {
         DataSet d;
         double min = 0.0;

         if(dataset == null | dataset.isEmpty() ) return min;
         for (int i=0; i<dataset.size(); i++) {
             d = ((DataSet)dataset.elementAt(i));
             if(i==0) min = d.getXmin();
             else     min = Math.min(min,d.getXmin());
	 }

         return min;
     }

/**
 * Get the Minimum Y value of all attached DataSets.
 *  @return  The minimum value
*/

     public double getYmin() {
         DataSet d;
         double min=0.0;

         if(dataset == null | dataset.isEmpty() ) return min;
         for (int i=0; i<dataset.size(); i++) {
             d = ((DataSet)dataset.elementAt(i));
             if(i==0) min = d.getYmin();
             else     min = Math.min(min,d.getYmin());
	 }

         return min;
     }



/**
 *  Set the markers for the plot.
 *  @param m  Marker class containing the defined markers
 *  @see Markers
 */
    public void setMarkers(Markers m) {

        markers = m;

    }
/**
 *  Get the markers
 *  @return defined Marker class
 *  @see Markers
 */   
    public Markers getMarkers() {

        return markers;

    }
/**
 * Set the background color for the entire canvas.
 * @params c The color to set the canvas
 */
    public void setGraphBackground(Color c) {
        if(c == null) return;

        setBackground(c);

    }

/**
 * Set the background color for the data window.
 * @params c The color to set the data window. 
 */
    public void setDataBackground(Color c) {
        if(c == null) return;

        DataBackground = c;

    }



/**
 *  This paints the entire plot. It calls the draw methods of all the
 *  attached axis and data sets.
 *  The order of drawing is - Axis first, data legends next, data last.
 *  @params g Graphics state.
 */
    public void paint(Graphics g) {
        int i;
        Graphics lg  = g.create();
        Rectangle r = bounds();

        /* The r.x and r.y returned from bounds is relative to the
	** parents space so set them equal to zero.
        */
        r.x = 0;
        r.y = 0;

        if(DefaultBackground == null) DefaultBackground=this.getBackground();
        if(DataBackground == null)    DataBackground=this.getBackground();

//        System.out.println("Graph2D paint method called!");

        if( !paintAll ) return;

        r.x      += borderLeft;
        r.y      += borderTop;
        r.width  -= borderLeft+borderRight;
        r.height -= borderBottom+borderTop;


        paintFirst(lg,r);

        if( !axis.isEmpty() ) r = drawAxis(lg, r);
        else  {
               if(clearAll ) {
                 Color c = g.getColor();
	         g.setColor(DataBackground);
                 g.fillRect(r.x,r.y,r.width,r.height);
                 g.setColor(c);
               }
               drawFrame(lg,r.x,r.y,r.width,r.height);
        }

        paintBeforeData(lg,r);

        if( !dataset.isEmpty() ) {

           datarect.x      = r.x;
           datarect.y      = r.y;
           datarect.width  = r.width;
           datarect.height = r.height;

           for (i=0; i<dataset.size(); i++) {
             ((DataSet)dataset.elementAt(i)).draw_data(lg,r);
           }
	}

        paintLast(lg,r);

        lg.dispose();


    }

/**
 *  A hook into the Graph2D.paint method. This is called before
 *  anything is plotted. The rectangle passed is the dimension of
 *  the canvas minus the border dimensions.
 *  @params g Graphics state
 *  @params r Rectangle containing the graph
 */
    public void paintFirst( Graphics g, Rectangle r) {
    }

/**
 *  A hook into the Graph2D.paint method. This is called before
 *  the data is drawn but after the axis. 
 *  The rectangle passed is the dimension of
 *  the data window.
 *  @params g Graphics state
 *  @params r Rectangle containing the data
 */
    public void paintBeforeData( Graphics g, Rectangle r) {
    }

/**
 *  A hook into the Graph2D.paint method. This is called after
 *  everything has been drawn. 
 *  The rectangle passed is the dimension of
 *  the data window.
 *  @params g Graphics state
 *  @params r Rectangle containing the data
 */
    public void paintLast( Graphics g, Rectangle r) {
      if( lastText != null ) {
         lastText.draw(g,r.width/2, r.height/2, TextLine.CENTER);
      }
    }

/**
 * This method is called via the Graph2D.repaint() method.
 * All it does is blank the canvas (with the background color)
 * before calling paint.
 */
    public void update(Graphics g) {

//          System.out.println("Graph2d update method called");
        if( clearAll ) {
            Color c = g.getColor();
	    /* The r.x and r.y returned from bounds is relative to the
	    ** parents space so set them equal to zero
            */
            Rectangle r = bounds();

            r.x = 0;
            r.y = 0;


            g.setColor(getBackground());
            g.fillRect(r.x,r.y,r.width,r.height);
            g.setColor(c);
          }

          if( paintAll ) paint(g);
    }
/**
 * Handle  keyDown events. Only one event is handled the pressing
 * of the key 'r' - this will repaint the canvas.
 */
   public boolean keyDown(Event e, int key) {

        if( key == 'r' ) {
                              repaint();
                              return true;
        }  else {
                              return false;
        }
    }                                               
   
    
    
/**
 *  Calling this method pauses the plot and displays a flashing
 *  message on the screen. Mainly used when data is being loaded across the
 *  net. Everytime this routine is called a counter is incremented
 *  the method Graph2D.finishedloading() decrements the counter. When the
 *  counter is back to zero the plotting resumes.
 *  @see Graph2D#finishedloading()
 *  @see Graph2D#loadmessage()
 *  @see LoadMessage
*/
    public void startedloading() {

           loadingData++;


           if(loadingData != 1) return;

           if(load_thread == null) load_thread = new LoadMessage(this);
           load_thread.setFont(new Font("Helvetica", Font.PLAIN, 25));

           load_thread.begin();

    }
/**
 * Decrement the loading Data counter by one. When it is zero resume
 * plotting.
 *  @see Graph2D#startedloading()
 *  @see Graph2D#loadmessage()
 *  @see LoadMessage
*/
    public void finishedloading() {

           loadingData--;

           if(loadingData > 0) return;

           if(load_thread != null) load_thread.end();
           load_thread = null;

    }
/**
 * Change the message to be flashed on the canvas
 * @param s String contining the new message.
 * @see Graph2D#startedloading()
 * @see Graph2D#finishedloading()
 * @see LoadMessage
*/
    public void loadmessage(String s) {
           if(load_thread == null) load_thread = new LoadMessage(this);
           load_thread.setMessage(s);
    }                


/*
*******************
**
** Protected Methods
**
*******************/

/**
 *  Force the plot to have an aspect ratio of 1 by forcing the
 *  axes to have the same range. If the range of the axes
 *  are very different some extremely odd things can occur. All axes are
 *  forced to have the same range, so more than 2 axis is pointless.
 */
        protected Rectangle ForceSquare(Graphics g, Rectangle r) {
            Axis a;
            Rectangle dr;
            int x      = r.x;
            int y      = r.y;
            int width  = r.width;
            int height = r.height;

            double xmin;
            double xmax;
            double ymin;
            double ymax;

            double xrange = 0.0;
            double yrange = 0.0;
            double range;

            double aspect;

            if( dataset == null | dataset.isEmpty() ) return r; 
                
/*
**          Force all the axis to have the same range. This of course
**          means that anything other than one xaxis and one yaxis
**          is a bit pointless.
*/
            for (int i=0; i<axis.size(); i++) {
               a = (Axis)axis.elementAt(i);
               range = a.maximum - a.minimum;
               if(a.isVertical()) {
                                    yrange = Math.max(range, yrange);
	       } else {
                                    xrange = Math.max(range, xrange);
               }
            }

            if(xrange <= 0 | yrange <= 0 )      return r; 


            if( xrange > yrange )      range = xrange;
            else                       range = yrange;

            for (int i=0; i<axis.size(); i++) {
               a = (Axis)axis.elementAt(i);
               a.maximum = a.minimum + range;
            }
/*
**          Get the new data rectangle
*/
            dr = getDataRectangle(g, r);
/*
**          Modify the data rectangle so that it is square.
*/
	    if(dr.width > dr.height) {
                    x += (dr.width-dr.height)/2.0;
                    width -= dr.width-dr.height;
            } else {
                    y += (dr.height-dr.width)/2.0;
                    height -= dr.height-dr.width;
            }


            return new Rectangle(x,y,width,height);


	}
/**
 *  Calculate the rectangle occupied by the data
 */

       protected Rectangle getDataRectangle(Graphics g, Rectangle r) {
            Axis a;
            int waxis;
            int x     = r.x;
            int y     = r.y;
            int width = r.width;
            int height = r.height;

            for (int i=0; i<axis.size(); i++) {
               a = ((Axis)axis.elementAt(i));
 
               waxis = a.getAxisWidth(g);

               switch (a.getAxisPos()) {
               case Axis.LEFT:
                          x += waxis;
                          width -= waxis;
                          break;
               case Axis.RIGHT:
                          width -= waxis;
                          break;
               case Axis.TOP:
                          y += waxis;
                          height -= waxis;
                          break;
               case Axis.BOTTOM:
                          height -= waxis;
                          break;
               }
            }

             return new Rectangle(x, y, width, height);
            }

/**
 *
 *  Draw the Axis. As each axis is drawn and aligned less of the canvas
 *  is avaliable to plot the data. The returned Rectangle is the canvas
 *  area that the data is plotted in.
 */
        protected Rectangle drawAxis(Graphics g, Rectangle r) {
            Axis a;
            int waxis;
            Rectangle dr;
            int x;
            int y;
            int width;
            int height;


            if(square) r = ForceSquare(g,r);

            dr = getDataRectangle(g,r);

            x      = dr.x;
            y      = dr.y;
            width  = dr.width;
            height = dr.height;

            if(clearAll ) {
               Color c = g.getColor();
	       g.setColor(DataBackground);
               g.fillRect(x,y,width,height);
               g.setColor(c);
            }

// Draw a frame around the data area (If requested)
            if(frame) drawFrame(g,x,y,width,height);

// Now draw the axis in the order specified aligning them with the final
// data area.
            for (int i=0; i<axis.size(); i++) {
               a = ((Axis)axis.elementAt(i));

               a.data_window = new Dimension(width,height);

               switch (a.getAxisPos()) {
               case Axis.LEFT:
                          r.x += a.width;
                          r.width -= a.width;
                          a.positionAxis(r.x,r.x,y,y+height);
                          if(r.x == x ) {
                             a.gridcolor = gridcolor;
                             a.drawgrid  = drawgrid;
                             a.zerocolor = zerocolor;
                             a.drawzero  = drawzero;
			   }

                           a.drawAxis(g);

                           a.drawgrid  = false;
                           a.drawzero  = false;

                          break;
               case Axis.RIGHT:
                          r.width -= a.width;
                          a.positionAxis(r.x+r.width,r.x+r.width,y,y+height);
                          if(r.x+r.width == x+width ) {
                             a.gridcolor = gridcolor;
                             a.drawgrid  = drawgrid;
                             a.zerocolor = zerocolor;
                             a.drawzero  = drawzero;
                          }

                          a.drawAxis(g);

                          a.drawgrid  = false;
                          a.drawzero  = false;

                           break;
               case Axis.TOP:
                          r.y += a.width;
                          r.height -= a.width;
                          a.positionAxis(x,x+width,r.y,r.y);
                          if(r.y == y) {
                             a.gridcolor = gridcolor;
                             a.drawgrid  = drawgrid;
                             a.zerocolor = zerocolor;
                             a.drawzero  = drawzero;
                          }

                          a.drawAxis(g);

                          a.drawgrid  = false;
                          a.drawzero  = false;


                          break;
               case Axis.BOTTOM:
                          r.height -= a.width;
                          a.positionAxis(x,x+width,r.y+r.height,r.y+r.height);
                          if(r.y +r.height == y+height ) {
                             a.gridcolor = gridcolor;
                             a.drawgrid  = drawgrid;
                             a.zerocolor = zerocolor;
                             a.drawzero  = drawzero;
                          }

                          a.drawAxis(g);

                          a.drawgrid  = false;
                          a.drawzero  = false;


                          break;
               }
            }

           return r;
      }
/*
 *  Draws a frame around the data area.
 */
      protected void drawFrame(Graphics g, int x, int y, int width, int height) {
        Color c = g.getColor();

        if( framecolor != null ) g.setColor(framecolor);

        g.drawRect(x,y,width,height);

        g.setColor(c);


     }



}

/**
 * This should be thrown if any of the packages fileloaders 
 * encounter a format error
*/
class FileFormatException extends Exception {
    public FileFormatException(String s) {
        super(s);
    }
}



/**
 *   This is a separate thread that flashes a message
 *   on the Graph2D canvas that data is loading
 */

class LoadMessage extends Thread {
        Graph2D  g2d;
        String   message    = "Loading Data ... Please Wait!";
        String   newmessage = null;
        long     visible    = 500;
        long     invisible  = 200;
        Color    foreground = Color.red;
        Graphics lg = null;
        Font     f = null;
        
/**
 *    Instantiate the class
 * @param g2d The Graph2D canvas to draw message on
 *
 */            
        public LoadMessage(Graph2D g2d) {
           this.g2d = g2d;

        }

/**
 *  Instantiate the class
 * @param g2d The Graph2D canvas to draw message on
 * @param s   The string to flash on the canvas
 */            
        public LoadMessage(Graph2D g2d, String s) {

           this(g2d);
           this.message = s;

        }
/**
 *  Instantiate the class
 * @param g2d The Graph2D canvas to draw message on
 * @param s   The string to flash on the canvas
 * @param visible Number of milliseconds the message is visible
 * @param invisible Number of milliseconds the message is invisible
 */            

        public LoadMessage(Graph2D g, String s, long visible, long invisible) {
            this(g,s);
            this.visible = visible;
            this.invisible = invisible;
        }

/**
 *   begin displaying the message
 */
        public void begin() {
 
            g2d.clearAll = false;
            g2d.paintAll = false;

            super.start();

	  }
/**
 *   end displaying message and force a graph repaint
 */
        public void end() {
             
            super.stop();

            g2d.clearAll = true;
            g2d.paintAll = true;

            if(lg != null) lg.dispose();

            g2d.repaint();

	  }
/**
 *   The method to call when the thread starts
 */
        public void run() {
           boolean draw = true;
           FontMetrics fm;
           Rectangle r;
           int sw = 0;
           int sa = 0;
           int x  = 0;
           int y  = 0;

           setPriority(Thread.MIN_PRIORITY);

           
           while(true) {

                if( newmessage != null && draw) {
                    message = newmessage;
                    newmessage = null;
		}

                if(lg == null) {           
                  lg = g2d.getGraphics();
                  if(lg != null) lg = lg.create();
	        }

                if( lg != null) {
                   if(f != null) lg.setFont(f);
                   fm = lg.getFontMetrics(lg.getFont());
                   sw = fm.stringWidth(message);
                   sa = fm.getAscent();
		} else {
                   draw = false;
		 }

                if( draw ) {
                      lg.setColor(foreground);
                      r = g2d.bounds();
                      x = r.x + (r.width-sw)/2;
                      y = r.y + (r.height+sa)/2;
                      lg.drawString(message, x, y);
                
                      g2d.repaint();

                      try { sleep(visible); }
                      catch(Exception e) { }
	        } else {
                      if(lg != null) {
                         lg.setColor(g2d.getBackground());
                         lg.drawString(message, x, y);

                         g2d.repaint();
		       }

                       try { sleep(invisible); }
                       catch(Exception e) { }

		 }

                draw = !draw;

	      }
	 }

  /**
   * Set the font the message will be displayed in
   * @param f the font
   */
          public void setFont(Font f) {
              this.f = f;
          }
  /**
   *  The foreground color for the message
   * @param c the foreground color
   */

          public void setForeground(Color c) {
              if(c == null) return;
              this.foreground = c;
          }
  /**
   *   Set the message to be displayed
   * @param s the message
   */

          public void setMessage(String s) {
              if(s==null) return;
              newmessage = s;
          }


}
