// Class   : SeriesGraph
// Author  : Russ Ethington
// Version : 1/28/96 for Java 1.0
// Notice  : Copyright (C) 1995, 1996 Russ Ethington

import java.awt.*;
import java.lang.*;
import java.util.*;

class MarathonGraph extends Panel 
{
   Vector      dateSets = null;
   Vector      dataSets = null;
   Vector      seriesNames = null;
   Font        font = null;
   String      title = null;
   double      maximum = 0.;
   double      minimum = 0.;
   int         totalPoints = 0;
   Color       colors[] = { Color.blue, Color.red, Color.green, 
                            Color.orange, Color.cyan, Color.magenta };
   
   public final Color BACKGROUND = new Color(225, 225, 225);
   public final int   VERTICALLY = 0;
   public final int   HORIZONTALLY = 1;
   public final int   LEFT = 0;
   public final int   BELOW = 1;

   public MarathonGraph()
   {
      dataSets = new Vector();
      font = new Font("Helvetica", Font.PLAIN, 10);
      title = "Series Graph Sample";   
      setBackground(Color.white);
   }

   public void setTitle(String theTitle)
   {
      title = theTitle;
   }

   public void updateView(Vector names, Vector dates, Vector values)
   {
      totalPoints = 0;
      seriesNames = names;
      dateSets = dates;

      dataSets.removeAllElements();

      for (int i = 0; i < values.size(); i++)
      {
         // Find maximum length vector.
         int size = ((Vector) values.elementAt(i)).size();
         if (size > totalPoints) 
            totalPoints = size;
      }
      minimum = Double.MAX_VALUE;      
      maximum = Double.MIN_VALUE;

      // Construct vectors of doubles, finding min/max along the way.
      for (int i = 0; i < names.size(); i++)
      {
         Vector value = (Vector) values.elementAt(i);
         Vector newSet = new Vector();

         dataSets.addElement(newSet);

         double lastV = 0.;

         for (int j = 0; j < value.size(); j++)
         {
            double v;
            String s = (String) value.elementAt(j);

            try {
               v = Double.valueOf(s).doubleValue(); }
            catch (NumberFormatException e) {
               v = lastV; }
            lastV = v;

            newSet.addElement(new Double(v));

            if (v > maximum)
               maximum = v;
            if (v < minimum)
               minimum = v;
         }        
      }
   }

   public void paint(Graphics g) 
   {
      int     inset = 35;
      int     width = bounds().width - 2 * inset;
      int     height = bounds().height - 2 * inset;

      // Put an outer border on the entire graph area.
      g.setPaintMode();
      g.setColor(Color.gray);
      g.draw3DRect(1, 1, bounds().width - 2, bounds().height - 2, true);

      // Fill the paint area, leaving a border.
      g.setColor(BACKGROUND);
      g.translate(inset, inset);
      g.fillRect(0, 0, width, height);
      
      // Draw X and Y axes.
      g.setColor(Color.black);
      g.drawLine(0, 0, 0, height);
      g.drawLine(0, height, width, height);

      // Draw shading.
      g.setColor(Color.gray);
      g.drawLine(0, 0, width, 0);
      g.drawLine(width, 0, width, height);

      inset = 1;
      width -= (2 * inset);
      height -= (2 * inset);
      g.translate(inset, inset);

      drawTitle(g, width, height);
      drawLegendMarker(g, width, height);
      drawTicksAndLabels(g, width, height);
      drawPoints(g, width, height);
   }

   private void drawTitle(Graphics g, int width, int height) 
   {
      Font titleFont = new Font("Helvetica", Font.PLAIN, 14);
      FontMetrics metrics = getFontMetrics(titleFont);
      char characters[] = title.toCharArray();
      int theLength = title.length();
      Color  save = g.getColor();

      int newx = (width - metrics.charsWidth(characters, 0, theLength)) / 2;
      int newy = -10;

      g.setFont(titleFont);
      g.setColor(Color.black);
      g.drawChars(characters, 0, theLength, newx, newy);
      g.setColor(save);
   }

   private void drawLegendMarker(Graphics g, int width, int height)
   {
      Font legendFont = new Font("Helvetica", Font.ITALIC, 10);
      FontMetrics metrics = getFontMetrics(legendFont);
      String text = "Legend";
      char characters[] = text.toCharArray();
      int theLength = text.length();
      Color  save = g.getColor();

      int textW = metrics.charsWidth(characters, 0, theLength);
      int textH = metrics.getHeight();
      int newx = (width + 30) - textW;
      int newy = height + 30;

      g.setFont(legendFont);
      g.setColor(Color.blue);
      g.drawLine(newx, newy + 1, newx + textW, newy + 1);
      g.drawChars(characters, 0, theLength, newx, newy);
      g.setColor(save);
   }

   private void drawTicksAndLabels(Graphics g, int width, int height)
   {
      int hPadding = 45;
      int vPadding = 15;
      Vector points = (Vector) dataSets.elementAt(0);
      Vector dates = (Vector) dateSets.elementAt(0);

      // Draw the horizontal ticks.
      double hScale = width / (double) (points.size() - 1);
      int hInterval = (int) Math.ceil(hPadding / hScale);

      for (int i = 1; i < points.size() - 1; i++) 
      {
         int x = (int) Math.round(i * hScale);
         int y = height;

         if (i % hInterval == 0) 
         {
            String hLabel = (String) dates.elementAt(i);;            
            drawTick(g, x, y, width, height, VERTICALLY);
            drawLabel(g, x, y, hLabel, BELOW);
         }
      }

      // Draw the vertical ticks.
      double vScale = height / (maximum - minimum);
      int vInterval = (int) Math.ceil(vPadding / vScale);

      for (int i = (int) minimum + 1; i < (int) maximum - 1; i++)
      {
         int x = 0;
         int y = (int) (height - Math.round((i - minimum) * vScale));

         if (i % vInterval == 0) 
         {
            String vLabel = new String(new Double(i).toString());
            drawTick(g, x, y, width, height, HORIZONTALLY);
            drawLabel(g, x, y, vLabel, LEFT);
         }                                      
      }
   }

   private void drawTick(Graphics g, int x, int y, int width, int height, 
                         int orientation)
   {
      int tickSize = 3;
      Color save = g.getColor();

      g.setColor(Color.black);      

      // Draw the tick mark, gridline, and shading.      
      if (orientation == VERTICALLY)
      {
         g.drawLine(x , y - tickSize, x, y);
         g.setColor(Color.gray);
         g.drawLine(x , y - (tickSize + 1), x, 0);
         g.setColor(Color.white);
         g.drawLine(x + 1 , y , x + 1, 0);
      }
      else // HORIZONTALLY
      {
         g.drawLine(x, y , x + tickSize, y);
         g.setColor(Color.gray);
         g.drawLine(x + (tickSize + 1), y , width, y);
         g.setColor(Color.white);
         g.drawLine(x, y + 1, width, y + 1);
      }
      g.setColor(save);
   }

   private void drawLabel(Graphics g, int x, int y, String label, int where)
   {
      Color save = g.getColor();
      char characters[] = label.toCharArray();
      int theLength = label.length();
      FontMetrics metrics = getFontMetrics(font);
      int stringWidth = metrics.charsWidth(characters, 0, theLength);

      g.setFont(font);
      g.setColor(Color.black);

      if (where == LEFT)
      {
         int newx = x - (stringWidth + 3);
         int newy = y;
         g.drawChars(characters, 0, theLength, newx, newy);
      }
      else // BELOW
      {
         int newx = x - (stringWidth / 2);
         int newy = y + metrics.getHeight() + 3;
         g.drawChars(characters, 0, theLength, newx, newy);
      }
      g.setColor(save);
   }

   private void drawPoints(Graphics g, int width, int height)
   {
      boolean nubs = (width / totalPoints > 10) ? true : false;

      for (int i = 0; i < dataSets.size(); i++)
      {
         int oldx = 0;
         int oldy = height;

         Vector points = (Vector) dataSets.elementAt(i);

         double vScale = height / (maximum - minimum);
         double hScale = width / (double) (points.size() - 1);

         g.setColor(colors[i % colors.length]);

         for (int j = 0; j < points.size(); j++) 
         {
            double v = ((Double) points.elementAt(j)).doubleValue();
            int x = (int) Math.round(j * hScale);
            int y = (int) (height - Math.round((v - minimum) * vScale));

            if (j == 0)
               g.drawLine(x, y, x, y);
            else
               g.drawLine(oldx, oldy, x, y);
            
            if (nubs) drawNub(g, x, y);

            oldx = x;
            oldy = y;
         }
      }
   }

   private void drawNub(Graphics g, int x, int y) 
   {
      Color save = g.getColor();

      g.setColor(Color.black);
      g.drawLine(x - 1 , y - 1, x + 1, y - 1);
      g.drawLine(x - 1 , y - 1, x - 1, y + 1);

      g.drawLine(x + 1 , y + 1, x + 1, y - 1);
      g.drawLine(x + 1 , y + 1, x - 1, y + 1);
      g.setColor(save);
   }
}  
