/*
 * @(#) PlotViewport.java 1.0 2000/07/10 Larry Rodrigues
 * Copyright (c) 2000 Larry Rodrigues
 */

package vis.beans.plotter;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import vis.util.*;
import vis.beans.events.*;
import vis.beans.customizers.*;

import java.awt.print.*;
import java.awt.image.*;
import java.beans.*;
import java.beans.beancontext.*;
import java.io.*;
import com.sun.image.codec.jpeg.*;
import java.awt.geom.*;
/** PlotViewport is the base class for
  * all the types of plots. It defines common properties and methods.
  * Except for pie chart, a plot has two axes: X and Y. An axis has a title and a number of
  * big ticks. A big tick can have a label.
  * Some plots can have small ticks two etween two big ticks.
  * The inputs ( X and Y axis values) for the drawing methods are of type Object
  * which can be an array of Java primitive data types.
  * Only one plot can be drawn at a time on the plot viewport. A graph
  * is defined to be a trace of Y values Vs X values. A plot can have many
  * graphs. There is only one title for a plot. When there are more that one
  * graph, the plot viewport can turn on the legend. Each legend displays
  * the corresponding graph title.
  * @version 1.0  10 July 2000
  * @author Larry Rodrigues
  **/
abstract public class PlotViewport extends Viewport implements Printable{
    static int MAX_INDEX =32;
    static final int MAX_BARS = 10;
    static final int  MAX_XTICKS = 16;
    static final int  MAX_YTICKS = 16;
    static final int  MAX_SMALL_XTICKS = 8;
    static final int  MAX_SMALL_YTICKS = 8;

    static final int  TICK_LEN = 6;
    static final int  SMALL_TICK_LEN = 3;
    static final int ROW_GAP =15;
    static final int COL_GAP = 10;
    static final Point BORDER = new Point(10,15);
    static final int HEADER_HT = 25;
    static final int LEGEND_BOX_SIZE = 5;
    static final int LEGEND_STR_WID  = 8;
    static final int MAX_GRAPHS  = 4;

    static final int MIN_FONT_SIZE = 5;
    public static final int AUTO =0;
    protected Point border = new Point(BORDER.x, BORDER.y);
    /** Origin of the graph
      */
    protected Point orig;
     //Relative coordinates of the graphs origin.
    static Point relOrig = new Point(60,60);

    /** Position of the title.
      */
    protected Point headerPt = new Point(20,30);

    /** Hieght of the header.
      */
    protected int headerHt;

    /** X axis scale factor.
      */
    protected double xScale= 1.0;

    /** Y axis scale factor.
      */
    protected double yScale= 1.0;

    /**  X axis range of values i.e.; max - min.
      */
    protected double xRange =1.0;

    /** Y axis range.
      */
    protected double yRange = 1.0;

    /** Size of the legend box.
      */
    protected int legBoxSize;

    /**  Absolute legend position, i.e.; legendPos+incr.
      */
    protected Point legPt;

    /** Object that holds an array of X axis values. The array type can be of any Java primitive
      * type or String.
      */
    protected Object x;

    /**  Object that holds an array of Y axis values. The array type can be of any Java primitive
      * type.
      */
    protected Object y;

    /**  Flag that indicates whether to redraw the plot.
      */
    protected boolean enableDraw= false;

    /** Gap between X axis ticks in pixels.
      */
    protected double tickGap;

    /** Array that holds the values corresponding to the X axis ticks(big).
      */
    protected int xTickPt[];

    //---------------- Properties-----------------------------------//

     /**  Plot title.
       */
     protected String titleString = "Plot Title";

     /**  X axis title.
       */
     protected String xLabelString = "X Label ";

     /**  Y axis title.
       */
     protected String yLabelString = "Y Label";

     /** Position of the X axis title.
       */
     protected  Point xLabel;

     /** Position of the Y axis title.
       */
     protected  Point yLabel;

     /** Maximum number of graphs.
      */

    protected int maxGraphs = MAX_GRAPHS;

    /**  Current graph color.
      */
    protected Color graphColor = Color.blue;

    /** Range of X values as an array of int.
      */
    protected int[] xMinmax = null;

    /** Range of Y values as an array of int.
      */
    protected int[] yMinmax = null;

    /**
      * Range of X values as an array of any Java primitive data type or String.
      */

    Object xMinmaxObj =null;

    /**
      * Range of Y values as an array of any Java primitive data type or String.
      */
    Object yMinmaxObj = null;


    /** Font.
      */
    protected Font font = new Font("Helevetica", Font.BOLD, 9);

    /** Font metrics.
      */
    protected FontMetrics  pFont;

    /** Font size.
      */
    protected int fontSize =9;

    /** X axis size ratio, i.e.; (X axis width in pixels)/256.
     */
    protected double xSizeRatio =1.0;

    /** Y axis size ratio, i.e.; (Y axis width in pixels)/256.
     */
    protected double ySizeRatio =1.0;

    /**
     * Width and height of the graph.
     */
    protected int graphWid, graphHt;

    /** Current graph number.
     */
      protected int graphNum =0;

    /** Number of x ticks.
     */
    protected int numXTicks=5;

    /** Number of y ticks.
     */
    protected int numYTicks=4;

   /** Number of small x ticks.
     */
    protected int numSmallXTicks=1;

    /**
     * Number of small y ticks.
     */
    protected int numSmallYTicks=1;

    /**
      * Tick length.
      */
    protected int tickLen;

    /**
      * Graph title.
      */
    protected String graphTitle= "Graph Title";

    /**  X axis string values.
      */
    protected String xTextValues[] = {"Jan", "Feb", "Mar", "Apr", "May", "June"};

    /**
      * Legend on or off. The default value is false.
      */
    protected boolean legendOn =false;

    /**
      * Interval between two X axis labels in terms of nummer of big X ticks.
      * The default value is set to 1.
      */
    protected int xLabelsPerTick=1;

    /**
      * Interval between two Y axis labels in terms of nummer of big Y ticks.
      * The default value is set to 1.
      */
    protected int yLabelsPerTick=1;

    /**
      * Upper left hand corner coordinate of the border.
      */
    protected int[] borderULHC = {border.x,border.y};

    /**
      * Legend position.
      */
    protected int[] legendPos = {0,0};

    /** Legend position increment in X direction.
      */
    protected int legendPosXIncr =0;

    /** Legend position increment in Y direction.
      */
    protected int legendPosYIncr =0;

    /**
      * The legend gap.
      */
    protected int legendGap =12;

    /** Legend gap increment.
      */
    protected int legendGapIncr =0;

    protected Vector legendColor = new Vector();
    protected Vector legendString = new Vector();

    /** Origin increment in X direction.
      */
    protected int originXIncr =0;

    /** Origin increment in Y direction.
      */
    protected int originYIncr =0;

    protected Class customizerClass = null;
    boolean printOn = false;

    public Color graphColors[] = {Color.blue, Color.red, Color.green,
                                  Color.yellow, Color.cyan, Color.darkGray,
                                  Color.magenta, Color.white, Color.orange};

    public PlotViewport(){
       graphNum = 0;
       super.setBackground(Color.black);
       super.setForeground(Color.white);
       //Enable mouse events
       enableEvents(AWTEvent.MOUSE_EVENT_MASK|AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }

    /** If this class is used as a class library, you can use this constructor.
      * @param wd the width.
      * @param hgt the height.
      */
    public PlotViewport(int wd, int hgt){
       super(wd, hgt);
       width = wd;
       height = hgt;
       setWidHt(width, height);
       setSize(width, height);
    }

    /**Sets width and height.
     */
    public void setWidHt(int wd, int hgt){
       width = wd;
       height = hgt;
       offScrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
       setOffScrGc();
    }

    /** Sets the drawing parameters.
      */
    protected void setParams(){
      if(width <200) fontSize = MIN_FONT_SIZE;
      else if((width <=256) && (width > 200)) fontSize = MIN_FONT_SIZE+2;
           else if((width > 256 )&& (width <512)) fontSize = MIN_FONT_SIZE+ +4;
                else fontSize = MIN_FONT_SIZE+ 6;
      xSizeRatio = ((double)width)/256.0;
      ySizeRatio = ((double)height)/256.0;

      border.x = (int)(borderULHC[0]*xSizeRatio);
      border.y = (int)(borderULHC[1]*ySizeRatio);
      headerHt = (int)(HEADER_HT * ySizeRatio);
      headerPt.x = width/2;
      headerPt.y = border.y + fontSize;

      tickLen = (int)(TICK_LEN*xSizeRatio);
      if((legendPos[0] ==0)&& (legendPos[1]==0)) {
         legendPos[0] = width-border.x- LEGEND_STR_WID*fontSize;
         legendPos[1] = border.y+ headerHt+ 30;
      }
      legPt = new Point(legendPos[0]+legendPosXIncr, legendPos[1]+legendPosYIncr);
      legBoxSize= (int)(LEGEND_BOX_SIZE*xSizeRatio);
    }

   /** Creates the plot template. This includes drawing the border and the
    * plot title.
    */
     public void createPlotTemplate(){
        Rectangle rect = this.getBounds();
        width = (rect.width > 0 )? rect.width:400;
        height = (rect.height > 0 )? rect.height:300;
        setParams();
        if(offScrGc == null) {
           //paranoid check
           offScrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
           setOffScrGc();
         }
         offScrGc.setColor(getBackground());
         offScrGc.fillRect(0,0, width,height);
         offScrGc.setColor(getForeground());
         if(font == null) font = new Font("Helevetia", Font.BOLD,fontSize);
         else font = new Font(font.getName(),Font.BOLD,fontSize);
         offScrGc.setFont(font);
         // Draw the border
         offScrGc.drawRect(border.x,
                           border.y+ headerHt,
                           width- 2*border.x,
                           height-2*border.y-headerHt);
        // Draw the plot title
        offScrGc.setFont(new Font("Helevetica", Font.PLAIN,fontSize+1));
        pFont = offScrGc.getFontMetrics();
        if(titleString == null) titleString = new String("Title");
        int strWid = pFont.stringWidth(titleString);
        offScrGc.drawString(titleString, headerPt.x -(int)(strWid/2), headerPt.y );
     }

     /**
     * Sets the background color.
     * @param clr the color.
     */
     public void setBackground(Color clr){
        super.setBackground(clr);
        draw();
     }
     /**
     * Sets the foreground color.
     * @param clr the color.
     */
     public void setForeground(Color clr){
        super.setForeground(clr);
        draw();
     }

     /**
       * Sets the plot title.
       * @param title the plot title.
       */
     public void setTitleString(String title){
        titleString = title;
        draw();
     }

     /**
       * @return the plot title.
       */
     public String getTitleString(){
        return titleString;
     }

     /**
       * Sets the X axis title.
       * @param xlab the X axis title.
       */
     public void setXLabelString(String xlab){
        xLabelString = xlab;
        draw();
     }

     /**
       * @return the X axis title.
       */
     public String getXLabelString(){
        return xLabelString;
     }

     /**
       * Sets Y axis title.
       * @param y axis title.
       */
     public void setYLabelString(String ylab){
        yLabelString = ylab;
        draw();
     }

     /**
       * @return the Y axis title.
       */
     public String getYLabelString(){
        return yLabelString;
     }

     /**
       * Sets the number of big Y axis ticks.
       * @param num the number of big Y axis ticks.
       */
     public void setNumYTicks(int num){
        if((num ==0) || (num > MAX_YTICKS)) return;
        numYTicks = num;
        draw();
     }

     /** @return the number of big Y axis ticks.
       * */
     public int getNumYTicks(){
        return numYTicks;
     }

     /**
     * Sets the number of small Y ticks between two big ticks.
     * @param num number of small y ticks.
     *
     */
     public void setNumSmallYTicks(int num){
        if((num == 0) || (num > MAX_SMALL_YTICKS)) return;
        numSmallYTicks = num;
        draw();
     }

     /**
       * @return the number of small Y axis ticks.
       */
     public int getNumSmallYTicks(){
        return numSmallYTicks;
     }

     /**
       * Sets the number of big X axis ticks.
       * @param num the number of big X axis ticks.
       */
     public void setNumXTicks(int num){
        if((num ==0) || (num >MAX_XTICKS)) return;
        numXTicks = num;
        draw();
     }

     /** @return the number of big X axis ticks.
       */
     public int getNumXTicks(){
        return numXTicks;
     }

     /** Sets the number of small X axis ticks.
       * @param num the number of small x axis ticks between two big ticks.
       */
     public void setNumSmallXTicks(int num){
        if((num ==0) || (num >MAX_SMALL_XTICKS)) return;
        numSmallXTicks = num;
        draw();
     }

     /**
       * @return the number of small X ticks.
       */
     public int getNumSmallXTicks(){
        return numSmallXTicks;
     }

     /**
       * Sets the graph title.
       * @param grTitle the graph title.
       */
     public void setGraphTitle(String grTitle){
        graphTitle = grTitle;
        draw();
     }
     /**
       * @return the graph title.
       */
     public String getGraphTitle(){
        return graphTitle;
     }
     /**
       * Sets the legend visibility on or of.
       * @param onOff the legend is on or off.
       */
     public void setLegendOn(boolean onOff){
        legendOn = onOff;
        draw();
     }
     /**
       * @return the legend on property.
       */
     public boolean getLegendOn(){
        return legendOn;
     }

     /** Sets the font.
       * @param fnt the font.
       */
     public void setFont(Font fnt){
        super.setFont(fnt);
        font = fnt;
        draw();
     }

     /** Sets the max number of graphs
      * @param num the maximum number of graphs.
      */
     public void setMaxGraphs(int num){
        maxGraphs = num;
        draw();
     }

     /**
      * @return the maximum number of graphs.
      */
     public int  getMaxGraphs(){
        return maxGraphs;
     }

     /**
       * Sets the graph color.
       * @param col the graph color.
       */
     public void  setGraphColor(Color col){
        graphColor = col;
        draw();
     }

     /**
       * @return the graph color.
       */
     public Color getGraphColor(){
        return graphColor;
     }

     /**
      * Sets the range of X axis values.
      * @param minmax an array of size 2. minmax[0] holds minimum value.
      * minmax[1] holds maximum value.
      */
     public void setXMinmax(int minmax[]){
        xMinmax = minmax;
     }

     /**
       * @return the range of x axis values.
       */
     public int[] getXMinmax(){
        return xMinmax;
     }

     /**
       * Sets the Y range.
       * @param minmax an array of size 2. minmax[0] holds minimum value.
       * minmax[1] holds maximum value.
       */
     public void setYMinmax(int minmax[]){
        yMinmax = minmax;
     }

     /**
       * @return the range of Y axis values.
       */
     public int[] getYMinmax(){
        return yMinmax;
     }

     /**
       * Sets the X axis min and max values.
       * @param xmin an array of of size two that holds
       * X axis min and max values. The type of these
       * values can be any of the Java primitive data type or String.
       */
     public void setXMinmaxObj(Object xmin){
        xMinmaxObj = xmin;
     }

     /**
       * @return an array of X axis min and max values.
       */
     public Object getXMinmaxObj(){
        return xMinmaxObj;
     }

     /**
       * Sets the Y axis min and max values.
       * @param xmin an array of of size two that holds
       * Y axis min and max values. The type of these
       * values can be any of the Java primitive data type or String.
       */
     public void setYMinmaxObj(Object ymin){
        yMinmaxObj = ymin;
     }

     /**
       * @return an array of Y axis min and max values.
       */
     public Object getYMinmaxObj(){
        return yMinmaxObj;
     }

     /**
       * Sets the number of big X axis ticks between labels.
       * If the tick lables overlap or too far apart, users can
       * modify this property to display labels at certain intervals.
       * @param num the number of big ticks between labels.
       */
     public void setXLabelsPerTick(int num){
        if(num ==0) return;
        xLabelsPerTick = num;
        draw();
     }

     /**
       * @return the big X axis ticks between labels.
       */
     public int getXLabelsPerTick(){
       return xLabelsPerTick;
     }

     /** Sets the number of big Y axis ticks between labels.
       * If the tick lables overlap or too far apart, users can
       * modify this property to display labels at certain intervals.
       * @param num the number of big ticks between labels.
       */
     public void setYLabelsPerTick(int num){
        if(num ==0) return;
        yLabelsPerTick = num;
        draw();
     }

     /** @return the big Y axis ticks between labels.
       */
     public int getYLabelsPerTick(){
        return yLabelsPerTick;
     }

     /**
       * Sets the upper left hand corner coordinate(ULHC) of the plot border.
       * This is an indexed property.
       * @param br an array of size two that holds
       * the x and y coordinates of upper left hand corner of the border.
       */
     public void setBorderULHC(int[] br){
        borderULHC = br;
        draw();
     }

     /**
       * @return the upper left hand coordinate of the plot border.
       */
     public int[] getBorderULHC(){
        return borderULHC;
     }

     /**
       * Sets one of the border ULHC coordinate which is an indexed property.
       * @param index the index of the borderULHC index property.
       * @param xy the value corresponding to the index.
       */
     public void setBorderULHC(int index, int xy){
        borderULHC[index]= xy;
        draw();
     }
     /**
       * @return the value at a given index.
       * @param index the index of the borderULHC property. It can be 0 or 1.
       */
     public int getBorderULHC(int index){
        return borderULHC[index];
     }

     /**
       * Sets the legend position which is an indexed property.
       * @param br an array of size two that holds
       * the x and y coordinates of upper left hand corner of the border.
       */
     public void setLegendPos(int[] pt){
        legendPos = pt;
        draw();
     }

     /**
       * Sets the legend position which is an indexed property.
       * @param index the index of the borderULHC index property.
       * @param xy the value corresponding to the index.
       */
     public void setLegendPos(int index, int xy){
        legendPos[index]= xy;
        draw();
     }

     /**
       * @return an array of int which is hold the X and Y coordinates of the
       * legend position.
       */
     public int[] getLegendPos(){
        return legendPos;
     }

     /**
       * @return an element of the legend position index property.
       * @param index the index of the array.
       */
     public int getLegendPos(int index){
        return legendPos[index];
     }

     /** Sets the legend position increment in the X direction.
       * @param incr the increment.
       */
     public void setLegendPosXIncr(int incr){
        legendPosXIncr = incr;
        draw();
     }

     /** @return the legend position x increment.
       */
     public int getLegendPosXIncr(){
        return legendPosXIncr;
     }

     /** Sets the legend position increment in the Y direction.
       * @param incr the increment.
       */
     public void setLegendPosYIncr(int num){
        legendPosYIncr = num;
        draw();
     }

     /**  @return the legend position y increment.
       */
     public int getLegendPosYIncr(){
        return legendPosYIncr;
     }

     /** Sets the origin increment in the X direction.
       * @param incr the increment.
       */
     public void setOriginXIncr(int incr){
        originXIncr = incr;
        draw();
     }

     /** @return the origin x increment.
       */
     public int getOriginXIncr(){
        return originXIncr;
     }

     /** Sets the origin increment in the Y direction.
       * @param incr the increment.
       */
     public void setOriginYIncr(int num){
        originYIncr = num;
        draw();
     }

     /**  @return the origin y increment.
       */
     public int getOriginYIncr(){
        return originYIncr;
     }
     /**
       * Sets the legend gap increment.
       * @param incr the increment.
       */
     public void setLegendGapIncr(int incr){
        legendGapIncr = incr;
        draw();
     }

     /**
       * @return the legend gap increment.
       */
     public int getLegendGapIncr(){
        return legendGapIncr;
     }

    public Dimension getMinimumSize(){
       return new Dimension(300,200);
    }

     /**
       * @return the customizer class.
       */
    public Class getCustomizerClass(){
       return customizerClass;
    }

     /** Sets the customizer class.
       * @param the customizer class.
       */
    public void setCustomizerClass(Class cl){
       customizerClass = cl;
    }

    public void setGraphColors(Color col, int i){
       if(i>= graphColors.length) return;
       graphColors[i] = col;
    }

    public Color getGraphColors(int i){
       if(i>= graphColors.length) return graphColors[i%graphColors.length];
       return graphColors[i];
    }
    /** This method adds a graph.
      * @param x the x axis values as an array of primitive data types or String.
      * @param y the y axis values as an array of primitive data types.
      */
    abstract protected void addGraph(Object x, Object y);

    /** Paints all the graphs.  This method is called  by draw() to paint the actual plots.
      **/
    abstract public void paintGraphs();

    /**
      * Draws the plot. Currently, only the Auto mode is implemented. This
      * method computes the minimum and maximum values from the x and y inputs.
      * Once these are computed, it calls draw(). Before calling draw(), it sets
      * the enableDraw to true. The enableDraw flag makes sure that the draw()
      * method is called only after the executing drawPlot(). The draw() method
      * is called by many property setter methods to reflect the change.
      * @param x the x axis values as an array of primitive data types or String.
      * @param y the y axis values as an array of primitive data types.
      */
    public void drawPlot(Object x, Object y){
       this.x =x; this.y =y;
       if((x ==null) || (y==null)) {
           System.out.println(" x or y null values");
           return;
       }
       if(xMinmaxObj == null){
          xMinmaxObj = Util.getMinMax(x);
       }

       if(yMinmaxObj == null){
          yMinmaxObj = Util.getMinMax(y);
       }
       enableDraw=true;
       draw();
    }

    /** This method does the actual drawing. In addition to being called by
      * drawPlot(), this method is called by many setter methods so as to
      * reflect a change in property.
      * draw() calls methods calls methods to create a new plot temeplate,
      * draw X and Y axes, ticks and lables.
      * Some of these methods are ovverriden by sub classes.
      */
    public void draw(){
       if(!enableDraw) return;
       //Dispose the graphics context and create a new one.
       setOffScrGc();
       createPlotTemplate();
       yRange = Util.getRange(yMinmaxObj);
       String yMaxStr = Util.maxString(yMinmaxObj);
       if(yMaxStr == null) yMaxStr = "         ";
       xRange = Util.getRange(xMinmaxObj);
       String xMaxStr = Util.maxString(xMinmaxObj);
       if(xMaxStr == null) xMaxStr = "         ";
       drawAxis(xMaxStr,yMaxStr);
       drawXLabel();
       drawYLabel(yMaxStr);

       //Scale the data
       xScale= (double)(graphWid/xRange);
       yScale= (double)(graphHt/yRange);

       drawYTicks(yMinmaxObj);
       drawXTicks(xMinmaxObj);
       //Draw within the plot rectangle
       offScrGc.clipRect(orig.x, orig.y-graphHt, graphWid, graphHt);
       graphNum =0;
       paintGraphs();
       repaint();
    }

    /**
     * Draws the X and Y axis. The origin is adjusted such that there is enough
     * room for the widest Y axis tick label and last X axis tick label.
     * @param  xMaxStr the X axis tick label on extreme right.
     * @param  yMaxStr the longest Y axis tick lable.
     *
     */
    protected void drawAxis(String xMaxStr, String yMaxStr){

      int yLabelGap = pFont.stringWidth(yMaxStr)+
                         pFont.getMaxAdvance()+4*fontSize+border.x+originXIncr;
      int xLabelGap = tickLen + (2*fontSize) +(2*fontSize)+border.y+originYIncr;
      orig = new Point(yLabelGap, height-xLabelGap);
      offScrGc.setColor(Color.cyan);

      graphWid =  width - (border.x+10+ pFont.stringWidth(xMaxStr)) - orig.x;
      graphHt = orig.y - (border.y+fontSize+5)- headerHt;
      // X Axis
      offScrGc.drawLine( orig.x, orig.y, orig.x+graphWid +5, orig.y);
      //Y Axis
      offScrGc.drawLine( orig.x, orig.y, orig.x, orig.y-graphHt-5);
    }

    /**
      * Draws the X axis title.  The title is centered w.r.t the X axis.
      * If the title string width is longer than the X axis, it is truncated.
      */
    protected void drawXLabel(){
        // Draw X Label
        if(xLabelString == null) xLabelString = new  String(" X Values");

        int xlen =  pFont.stringWidth(xLabelString);
        int xLabY = orig.y +tickLen+pFont.getHeight()+fontSize+5;
        int xLabX;
        if(xlen < graphWid){
           xLabX = orig.x+ (graphWid/2 - xlen/2);
        }
         else{
            xLabX = orig.x+ 2*fontSize;
        }
        xLabel = new Point(xLabX, xLabY);
        offScrGc.drawString(xLabelString,xLabel.x,xLabel.y);
   }

   /**
     * Draws the Y axis title. The letters are drawn one below the other.
     * If the lenght exceeds the Y axis, the title is trucated.
     */
   protected void drawYLabel(String yMaxStr){

       if(yLabelString == null) yLabelString = new String ("Y Values");
         char c[] = new char[1];
         int ylen =  fontSize* yLabelString.length();
         int yLabX = orig.x - (tickLen+ pFont.stringWidth(yMaxStr)+(2*fontSize));
         int yLabY, yLabLen;

         if(ylen < graphHt){
            yLabY = (orig.y - graphHt/2 + ylen/2);
            yLabLen = yLabelString.length();
         }
         else {
            yLabY = border.y + 2*fontSize;
            yLabLen =(int)((double)(graphHt)/(double)fontSize);
         }

         yLabel = new Point(yLabX, yLabY);
         AffineTransform origTx = offScrGc.getTransform();
         AffineTransform ninetyDegTx = new AffineTransform();
         ninetyDegTx.rotate(-Math.PI/2, (double)yLabX, (double)yLabY);
         offScrGc.transform(ninetyDegTx);
         offScrGc.drawString(yLabelString, yLabX, yLabY);
         offScrGc.setTransform(origTx);
    }

   /**
     * Draws X ticks.
     * @param minmax an array of size two that holds the X axis minimun
     * maximum value.
     */
   protected void drawXTicks(Object minmax){
        String xstr=null;
        int xwid=0;
        int xTick;
        int  totalTicks =  numXTicks*numSmallXTicks;

        double valPerTick = xRange/totalTicks;
        double valPerTickPix= ((double) graphWid)/totalTicks;
        tickGap = valPerTickPix;
        xTickPt = new int[numXTicks+1];
        int j=0;
        for(int i=0;i<= totalTicks;i++){
            xTick = orig.x + (int)(valPerTickPix*i);
            offScrGc.setColor(Color.cyan);
            if(i%numSmallXTicks ==0){
               xTickPt[j++] = xTick;
               offScrGc.drawLine(xTick, orig.y, xTick, orig.y + tickLen);
               if((i%xLabelsPerTick) ==0){
                   offScrGc.setColor(Color.green);
                   xstr =  Util.getInBetString(minmax,  (i*valPerTick) );
                   if(pFont != null) xwid = pFont.stringWidth(xstr);
                   if(xstr !=null) offScrGc.drawString(xstr, xTick+1, orig.y + tickLen + fontSize+1);
               }
            }else{
             offScrGc.drawLine(xTick, orig.y, xTick, orig.y + tickLen/2);
            }
         }
    }
   /** Draws Y ticks.
     * @param minmax an array of size two that holds the Y axis minimun
     * maximum value.
     */
    protected void drawYTicks(Object minmax){
        String str = null;
        int yTick;
         // Draw Y ticks
         offScrGc.setColor(Color.red);
        int  totalTicks =  numYTicks*numSmallYTicks;

        double valPerTick = yRange/totalTicks;
        double valPerTickPix= ((double)graphHt)/totalTicks;

         for(int i =0; i<= totalTicks;i++){
             yTick = orig.y - (int)(valPerTickPix*i);
             offScrGc.setColor(Color.cyan);
             if(i%numSmallYTicks ==0){
                offScrGc.drawLine(orig.x, yTick, orig.x-tickLen, yTick  );

                if((i%yLabelsPerTick) ==0){
                    offScrGc.setColor(Color.green);
                    str =  Util.getInBetString(minmax,  (i*valPerTick) );
                    if(str != null) offScrGc.drawString(str, orig.x- (pFont.stringWidth(str)+tickLen/2+1),
                        yTick-2 );
                }
             }
             else offScrGc.drawLine(orig.x, yTick, orig.x-tickLen/2, yTick  );

         }
    }

   /** Adds a legend.
     * @param col the color of the legend box.
     * @param str the legend string.
     */
   public void addLegend(Color col, String str){
      setGraphColors(col, graphNum-1);
      legendColor.addElement(col);
      legendString.addElement(str);
      //drawLegends();
   }
   public void drawLegends(){
      if( offScrGc == null)  return;
      int grNum =0;
      Enumeration s = legendString.elements();
      int strwidMax = 0;
      while(s.hasMoreElements()) {
            String str = (String)s.nextElement();
            int strwid = pFont.stringWidth(str);
            if(strwid > strwidMax) strwidMax = strwid;
      }
      legendPos[0] = width- 2*border.x- strwidMax-10;
      legendPos[1] = border.y+ headerHt+ 30;
      legPt = new Point(legendPos[0]+legendPosXIncr, legendPos[1]+legendPosYIncr);
      s = legendString.elements();
      Enumeration e = legendColor.elements();
      while( e.hasMoreElements()) {
           Color col = (Color)e.nextElement();
           offScrGc.setColor(col);
           String str = (String)s.nextElement();
           int gap = legendGap+legendGapIncr;
           offScrGc.fillRect(legPt.x, legPt.y+ grNum*gap, legBoxSize, legBoxSize);
           offScrGc.drawString(str, legPt.x + 2*legBoxSize,
                          legPt.y+ legBoxSize+(grNum*gap));
          grNum++;
      }
   }

   /** Resets the plot viewport.
     */
   public void resetPlotViewport(){
      graphNum =0;
      enableDraw = false;
      //numPlots =0;
      legendColor = new Vector();
      legendString = new Vector();
   }

   protected void processMouseEvent(MouseEvent e){
      int x = e.getX();
      int y = e.getY();
      switch(e.getID()) {
        case MouseEvent.MOUSE_CLICKED :
           if(SwingUtilities.isRightMouseButton(e)){
              popupMenu(x,y);
           }
           break;
        default: break;
      }
   }

   public int print(Graphics g, PageFormat f, int pageIndex){
      if(pageIndex >= 1) return Printable.NO_SUCH_PAGE;
      Graphics2D g2d = (Graphics2D) g;
      g2d.translate(f.getImageableX(), f.getImageableY());
      draw();
      paint(g2d);
      return Printable.PAGE_EXISTS;
   }

   protected void popupMenu(int x, int y){
      final int xx =x;
      final int yy =y;
      JPopupMenu jp = new JPopupMenu("");
      jp.setLightWeightPopupEnabled(true);
      add(jp);
      JMenuItem custitem = new JMenuItem("Customize");
      custitem.addActionListener(
         new ActionListener(){
           public void actionPerformed(ActionEvent e){
              try{
                Class cust = getCustomizerClass();
                if(cust != null) launchCustomizer(cust);
              } catch(Exception e1) {}
           }
         }
      );
      JMenuItem printitem = new JMenuItem("Print");
      printitem.addActionListener(
         new ActionListener(){
           public void actionPerformed(ActionEvent e){
              if(plBeanContext == null){
                 printPlot();
                 return;
              }
              BeanContextServices bcs = (BeanContextServices)plBeanContext;
              if(bcs.hasService(java.awt.print.PrinterJob.class)) {
                 try{
                     if(invokePrinterService()== null){
                        printPlot();
                     }
                   } catch(Exception e1) {
                       printPlot();
                   }
              } else {
                  printPlot();
             }
          }
        }
      );
      JMenuItem saveitem = new JMenuItem("Save");
      saveitem.addActionListener(
         new ActionListener(){
           public void actionPerformed(ActionEvent e){
              if(plBeanContext == null) {
                 saveAsJPEG(offScrImage);
                 return;
              }
              BeanContextServices bcs = (BeanContextServices)plBeanContext;
              if (bcs.hasService(com.vistech.util.SaveAsJPEGService.class)) {
                  try{
                    if(invokeSaveImageService()== null) saveAsJPEG(offScrImage);
                  } catch(Exception e1) {
                     e1.printStackTrace();
                     saveAsJPEG(offScrImage);
                  }
              } else saveAsJPEG(offScrImage);
           }
         }
      );
      jp.add(custitem);
      jp.add(printitem);
      jp.add(saveitem);
      jp.show(this,xx,yy);
   }

   protected void printPlot() {
      try{
         PrinterJob pj = PrinterJob.getPrinterJob();
         //PageFormat pm = pj.pageDialog(pj.defaultPage());
         pj.printDialog();
         pj.setPrintable(this);
         pj.print();
       }catch(Exception e1){e1.printStackTrace();}
   }

   public static String saveAsJPEG(BufferedImage bi) {
       try {
         String filename = launchFileDialog();
         ByteArrayOutputStream boutstream = new ByteArrayOutputStream();
         JPEGImageEncoder enc = JPEGCodec.createJPEGEncoder(boutstream);
         enc.encode(bi);
         FileOutputStream fimage = new FileOutputStream(new File(filename));
         boutstream.writeTo(fimage);
         fimage.close();
         return filename;
         } catch (Exception e) {
         System.out.println(e);
         return null;
       }
   }

   public static String launchFileDialog(){
        Frame fr = new Frame();
        FileDialog fd = new FileDialog(fr,"", FileDialog.SAVE);
        fd.show();
        String dir = fd.getDirectory();
        String file = fd.getFile();
        if((dir == null) || (file == null)) return null;
        String path = dir+file;
        int lastindex = path.lastIndexOf(".");
        if(lastindex <0) path += ".jpg";
        else {
          String sb = path.substring(lastindex);
          if(sb.equals(".jpg") || sb.equals(".jpeg")) return path;
          else {
            System.out.println(path + " enter extension .jpg or .jpeg");
          }
        }
        return path;
   }


   protected void launchCustomizer(Class cl)
                throws InstantiationException,  IllegalAccessException {
      final Object bean = this;
      Component cmp = (Component)cl.newInstance();
      if(!(cmp instanceof java.beans.Customizer)) return;
      Customizer customizer = ((java.beans.Customizer)cmp);
      customizer.setObject(bean);
      customizer.addPropertyChangeListener(
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
               Object newVal = e.getNewValue();
               String propName = e.getPropertyName();
               CustomizerUtil.setProperty(bean, propName, newVal);
           }
          }
      );
      JButton jb = new JButton("OK");
      jb.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e ){
              JButton bt = (JButton) e.getSource();
              Object jf = bt.getParent();
              if(jf instanceof JFrame)((JFrame)jf).dispose();

          }
        }

      );
      cmp.setVisible(true);
      Dimension dm = cmp.getPreferredSize();
      int wid =400, ht=300;
      if(dm.width > 0) {
         wid = dm.width+15;
         ht = dm.height+15;
      }
      cmp.repaint();
      JFrame fr = new JFrame();
      fr.getContentPane().add("North",cmp);
      fr.getContentPane().add("South",jb);
      fr.pack();
      Dimension d = cmp.getSize();
      fr.setSize(new Dimension(wid,ht+20));
      fr.show();
      fr.repaint();
   }
}
