A Graphics class for Java.

I have often had the need for a simple graphics package in my programming/scientific career. All you do is send the package an array of floating point or integer numbers, and hey presto! Up comes a nice graph, scaled to fit in the window in which it appears, and with labels along the axis and a grid of fidicual lines that help you see where the data is going. If there is more data than window, a scroll bar is set up to let you scroll along your data. If you have billion of points (this comes to more than around 500 pages of display), the graph will compress your data to fit in 500 pages. This helps keep your computer from overloading with such a large set, and, more importantly, keeps you from doing the same.

Java has no such class, so I enclose the code as an appendix.

I'd like to point out that this is really a one dimensional application. The numbers you send it are assumed to be monatomically increasing. If your X values are (for example) 1, 2, 5, 7.8, 15, you'll achieve some pretty badly morphed results! It's made for 1, 2, 3, 4, 5...

It also only plots in a linear fashion.

What's needed are functions that will add the ability to plot 2 dimensions (by send Graph a pair of arrays) and buttons that display one or both axes in logarithmitic form. If you want to enhance this by adding the requisite buttons and callbacks, please do so, and I'll gladly replace this initial version with your enhanced one.



import java.awt.*;
import java.awt.event.*;
import java.lang.Double;
import javax.swing.*;

 /**
  * Create a 2 panel application.  The top panel is where the data is
  * graphed, and the bottom is where it is controlled.  The entire window has
  * dimensions vDim, and the graph itself has dimensions gDim.  Graph and
  * GraphPanel deal with the display, and GraphData manages the data itself.
  */

class Graph {
   Dimension vDim;   // Viewport dimensions.
   Graphics gx;      // Graphics of graph, where we plot the graph.
   GraphPanel gp;    // Plots the data.

   int dspX,         // Dimensions of the viewport.
       dspY,
       graphY;       // Y dimension of the graph. 

   JFrame f;         // The holding pane for Graph.
   JTextField scale; // The data's Y axis multiplier.
   JScrollPane top;  // The top graphics panel.

   /** Set up a display for an array of floating point data. */
   public Graph(double[] raw) { // The raw floating point data we're to graph.
      getDim(); 
      setupGraphics(raw.length);
      gp.setupData(raw, gx, graphY);
      Dimension gDim = new Dimension(gp.dimX, gp.dimY);
      gp.setPreferredSize(gDim);
      plot();
   }

   /** Set up a display for an array of integerdata. */
   public Graph(int[] raw) { // The raw floating point data we're to graph.
      getDim(); 
      setupGraphics(raw.length);
      gp.setupData(raw, gx, graphY);
      Dimension gDim = new Dimension(gp.dimX, gp.dimY);
      gp.setPreferredSize(gDim);
      plot();
   }

   /** Get the screen dimension, in pixels, of the graph. */
   void getDim() {
      try {
         vDim = Toolkit.getDefaultToolkit().getScreenSize(); 
         dspX = vDim.width - (vDim.width / 8);
         dspY = vDim.height - (vDim.height / 8);
         vDim.setSize(dspX, dspY);
         graphY = dspY - (dspY / 8);
      } catch (Exception e) {
         System.err.println("I couldn't find your screen's size!");
         System.exit(0);
      }
   }

   /** Redraw the graph. */
   void plot() {
     Double f = new Double(scale.getText());
     double factor = f.doubleValue();
     gp.resize(factor);
     gp.paintComponent(gx);
   }

   /** Build the window in which the graph will be displayed. */
   void setupGraphics(int dataCt) { // Number of data points.

      // Create the background frame where the graph and its control buttons
      // will sit.
      f = new JFrame("Graph of " + dataCt + " points");
      BoxLayout box = new BoxLayout(f.getContentPane(), BoxLayout.Y_AXIS);
      f.getContentPane().setLayout(box);
      f.getContentPane().setLayout(box);
      f.setLocation(50, 50);

      // Create the top panel, where the graph will be drawn.
      gp = new GraphPanel(dspX);
      top = new JScrollPane();
      top.setViewportView(gp);
      Dimension d = new Dimension(dataCt, graphY);
      top.setPreferredSize(d);
      f.getContentPane().add(top);

      // Create the bottom panel of control buttons.
      JPanel bottom = new JPanel(new FlowLayout());
      bottom.setBackground(Color.blue);
      bottom.setSize(dspX, (dspY - graphY));

      // Create a Label and text area for control of the scale of the data.
      JLabel lD = new JLabel("Scale factor:");
      lD.setBackground(Color.magenta);
      lD.setForeground(Color.yellow);
      bottom.add(lD);

      scale = new JTextField(" 1   ");
      scale.setBackground(Color.white);
      scale.setForeground(Color.black);
      bottom.add(scale);

      // Create a redraw button.
      JButton reDraw = new JButton("Redraw");
      reDraw.addActionListener(new ActionListener()
      {
         public void actionPerformed(ActionEvent e)
         {
            plot(); 
         }
      });
      reDraw.setBackground(Color.white);
      reDraw.setForeground(Color.green);
      bottom.add(reDraw);

      // Create the exit button.
      JButton exit = new JButton("Exit");
      exit.addActionListener(new ActionListener()
      {
         public void actionPerformed(ActionEvent e)
         {
            goAway();
         }
      });
      exit.setBackground(Color.red);
      exit.setForeground(Color.white);
      bottom.add(exit);

      f.getContentPane().add(bottom);
      f.setSize(vDim);
      f.setVisible(true);
      gx = f.getGraphics();
   }

   /** Shut this display down. */
   void goAway()
   {
      f.setVisible(false);
   }
}

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/** GraphPanel graphs the data. */
public class GraphPanel extends JPanel {

   Color bClr,       // Background color,
         dClr,       // Data color
         fClr,       // Fidicual color;
         lClr;       // label color;

   GraphData gd;

   int dimX = 0,     // Dimensions of the graph.
       dimY = 0,
       vX;           // X dimension of the viewport.

   /** Create the graphic area and scale the data. */
   public GraphPanel(int x) {
      bClr = new Color(0, 0, 100); // Background color, a dark blue.
      dClr = Color.yellow;         // Data color.
      fClr = Color.gray;           // Fidicual color.
      lClr = Color.white;          // Label color.
      vX = x;
   }

   /** Initialize and format the floating point data. */
   public void setupData(double[] fd, // Floating point data.
                         Graphics gx, // Graphic environment.
                         int y) {     // Y dimension of the viewport.
      gd = new GraphData(fd, gx, y);
   }

   /** Initialize and format the integer data. */
   public void setupData(int[] i,     // Integer data.
                         Graphics gx, // Graphic environment.
                         int y) {     // Y dimension of the viewport.
      gd = new GraphData(i, gx, y);
   }

   /** Resize the data.*/
   public void resize(double factor) { gd.resize(factor); } 

   /** Plot the points. */
   public void paintComponent(Graphics g) {
      int i;

      // Don't try and paint anything before the data has been formatted.
      if ((gd == null) || (dimX == 0)) {
         if (gd == null) return;
         if (vX > gd.dimX) {
            dimX = vX;
            dimY = gd.dimY - 20;  // The  - 20 leaves room for the slider.
         } else {
            dimX = gd.dimX;  
            dimY = gd.dimY;
         }
         Dimension d = new Dimension(dimX, dimY);
         setPreferredSize(d);
      }
      // Clear the viewport.
      g.setColor(bClr);
      g.fillRect(0, 0, dimX, dimY);

      // Write and label the fidicuals parallel to the Y axis.
      for (i = 0; i < gd.xm.length; i++)
      {
         g.setColor(lClr); 
         g.drawString(gd.xm[i].label, gd.xm[i].labelLoc,
                      gd.bottomM);
         g.setColor(fClr);
         g.drawLine(gd.xm[i].lineLoc, gd.bottomM, gd.xm[i].lineLoc, gd.topM);
      }

      // Write and label the fidicuals parallel to the X axis.
      for (i = 0; i < gd.ym.length; i++)
      {
         int x = gd.leftM - (gd.ym[i].label.length() * gd.fntSz);
         g.setColor(lClr);
         g.drawString(gd.ym[i].label, x, gd.ym[i].labelLoc);
         g.setColor(fClr);
         g.drawLine(gd.leftM, gd.ym[i].lineLoc, gd.rightM, gd.ym[i].lineLoc);
      }
    
      // Draw a box around the graph.
      g.setColor(lClr);
      g.drawLine(gd.leftM, gd.bottomM, gd.leftM, gd.topM);
      g.drawLine(gd.leftM, gd.topM, gd.rightM, gd.topM);
      g.drawLine(gd.rightM, gd.topM, gd.rightM, gd.bottomM);
      g.drawLine(gd.rightM, gd.bottomM, gd.leftM, gd.bottomM);

      // Plot the data.
      g.setColor(dClr);
      for (i = 0; i < (gd.data.length - 1); i++)
      {
         g.drawLine((i + gd.leftM), gd.data[i],
                    (i + gd.leftM + 1), gd.data[i + 1]);
      }
   }
}

import java.awt.*;
import java.awt.event.*;
import java.lang.Double;
import javax.swing.*;

/** 
 * A data class that holds all the information for a graph, along with
 * formatting routines.
 */
public class GraphData {
   boolean type;     // True = integer data, false = floating point data.

   double dataScale, // Scale factor to map the data to the graphic screen.
          dMax,      // Maximum value (in pixels) of the data.
          dMin;      // Minimum value (in pixels) of the data.

   double[] fRaw;    // The raw data, if it's floating point.

   int [] data;      // The formatted data, ready for graphics.
   int [] iRaw;      // The raw data, if it's integer.

   int MAX_POINTS = 1048576, // Maximum number of points Graph will plot.
       MAX_GAP = 100,        // Minimum line spacing.
       MIN_GAP = 25;         // Minimum line spacing.

   int dimX,        // Dimensions of the viewport.
       dimY,
       fntSz,       // Font size.
       iMax,        // Maximum value of the data.
       iMin,        // Minimum value of the data.
       leftM,       // The graph's margins with the drawing area.
       max, min,    // Maximum and minimum values, for integer data.
       pCt,         // Number of points in the graph
       pixPerMark,  // Number of pixels separating adjacent fidicual markers.
       rightM,
       topM,
       bottomM; 

   Marker[] xm;     // Labels for the X axis.
   Marker[] ym;     // Labels for the Y axis.

   /** Construct a floating point instance of GraphData. */
   GraphData(double fp[], // Floating point data.
             Graphics gx, // This graphics environment.
             int y) {     // Y dimension of the viewport.
      dimY = y; 
      fRaw = new double[fp.length];
      for (int i = 0; i < fp.length; i++) {
         fRaw[i] = fp[i];
      }
      formatFloatData(fRaw, gx);
   }

   /** Construct a integer instance of GraphData. */
   GraphData(int i[],     // Integer data.
             Graphics gx, // This graphics environment.
             int y) {     // Y dimension of the viewport.
      dimY = y; 
      iRaw = new int[i.length];
      for (int j = 0; j < i.length; j++) {
         iRaw[j] = i[j];
      }
      formatIntData(iRaw, gx);
   }

  /**
   * Prepare the floating point dataset we're ploting so it fits on the graph,
   * and the dimensions of the graph itself.
   */ 
   void formatFloatData(double[] fRaw, // The original data.
                        Graphics gx) { // Current Graphics environment.
      int i, j;                        // Scratchpad.

      dMax = Double.MIN_VALUE;         // Maximum value of the data.
      dMin = Double.MAX_VALUE;         // Minimum value of the data.
      type = false;
      Font fnt = gx.getFont();
      fntSz = fnt.getSize();

      for (i = 0; i < fRaw.length; i++) {
         // What are the extremes? 
         if (fRaw[i] > dMax) dMax = fRaw[i];
         if (fRaw[i] < dMin) dMin = fRaw[i];
      }

      // Determine the number of pixels per fidicual mark.
      String s = Double.toString(dMax);
      pixPerMark = s.length() * fntSz;
      if (pixPerMark < MIN_GAP) pixPerMark = MIN_GAP;
      if (pixPerMark > MAX_GAP) pixPerMark = MAX_GAP;

      // Graph only supports up to MAX_POINTS data points.
      double scale = (double) MAX_POINTS / (double) fRaw.length;
      if (fRaw.length > MAX_POINTS) {
         fRaw = new double[MAX_POINTS];
         // Remove points in the data so it fits into MAX_POINTS.
         j = 0;
         for (i = 0; i < fRaw.length; i++) {
            fRaw[j] = fRaw[i];
            j = (int) ((double) i * scale);
         }
         pCt = MAX_POINTS;
      } else { 
         pCt = fRaw.length;
      }

      // Initialize the data array.
      data = new int[pCt];

      // Determine the top and bottom margins.
      bottomM = dimY - pixPerMark;
      topM = pixPerMark;
 
      // Map the data's extremes to the window's extremes.
      dataScale = (double) (bottomM - topM) / (double) (dMax - dMin);
      iMax = Integer.MIN_VALUE;
      iMin = Integer.MAX_VALUE;
      for (i = 0; i < pCt; i++) {
         data[i] = bottomM - (int) ((double) (fRaw[i] - dMin) * dataScale);
         if (data[i] > iMax) iMax = data[i];
         if (data[i] < iMin) iMin = data[i];
      }

      // Y coordinate labels and tic marks.
      int gap = fntSz * 2;
      if (gap < MIN_GAP) gap = MIN_GAP;

      // Number of increments along the Y axis.
      int yIncCt = (bottomM - topM) / gap;

      // Size of each increment, in terms of the data.
      double yInc = (dMax - dMin) / (double) yIncCt;

      // Create the labels for the Y axis data.
      ym = new Marker[yIncCt + 1];

      // Calculate the first, lowest value.
      ym[0] = new Marker();
      ym[0].label = String.format("%9.3g", dMin);
      ym[0].lineLoc = ym[0].labelLoc = bottomM;
      double val = dMin;
      int maxLen = 0;          // Maximum length of the Y axis data strings.
      for (i = 1; i < (yIncCt + 1); i++) {
         ym[i] = new Marker();
         ym[i].label = String.format("%9.3g", (val + ((double) i * yInc)));
         if (ym[i].label.length() > maxLen) maxLen = ym[i].label.length();
         ym[i].lineLoc = ym[i].labelLoc = bottomM - (i * gap);
      }
 
      // Determine the left and right margins, and the size of the X axis.
      leftM = (maxLen + 1) * fntSz;
      rightM = pCt + leftM; 
      dimX = rightM + (maxLen* fntSz); 

      // Set up the X axis fidicuals
      // X coordinate labels and tic marks.
      int xTicCt = (pCt / pixPerMark);  
      int xGap = (rightM - leftM) / (xTicCt + 1);
      xm = new Marker[xTicCt + 2];

      for (i = 0; i <= xTicCt; i++) {
         j = i * xGap;
         s = Integer.toString(i * pixPerMark); 
         xm[i] = new Marker();
         xm[i].label = s;
         xm[i].labelLoc = (j + leftM) - (s.length() / 2);
         xm[i].lineLoc = j + leftM;
      }
    
      // The last point is on the right axis. 
      s = Integer.toString(pCt); 
      xm[xTicCt + 1] = new Marker();
      xm[xTicCt + 1].label = s;
      xm[xTicCt + 1].labelLoc = rightM; 
      xm[xTicCt + 1].lineLoc = rightM;
   }

  /**
   * Prepare the integer dataset we're ploting so it fits on the graph,
   * and the dimensions of the graph itself.
   */ 
   void formatIntData(int[] iRaw,    // The original data.
                      Graphics gx) { // Current Graphics environment.
      int i, j;                      // Scratchpad.

      max = Integer.MIN_VALUE;       // Maximum value of the data.
      min = Integer.MAX_VALUE;       // Minimum value of the data.
      type = true;
      Font fnt = gx.getFont();
      fntSz = fnt.getSize();

      // What are the extremes? 
      for (i = 0; i < iRaw.length; i++) {
         if (iRaw[i] > max) max = iRaw[i];
         if (iRaw[i] < min) min = iRaw[i];
      }

      // Determine the number of pixels per fidicual mark.
      String s = Double.toString(iMax);
      pixPerMark = s.length() * fntSz;
      if (pixPerMark < MIN_GAP) pixPerMark = MIN_GAP;
      if (pixPerMark > MAX_GAP) pixPerMark = MAX_GAP;

      // Graph only supports up to MAX_POINTS data points.
      double scale = (double) MAX_POINTS / (double) iRaw.length;
      if (iRaw.length > MAX_POINTS) {
         iRaw = new int[MAX_POINTS];
         // Remove points in the data so it fits into MAX_POINTS.
         j = 0;
         for (i = 0; i < iRaw.length; i++) {
            iRaw[j] = iRaw[i];
            j = (int) ((double) i * scale);
         }
         pCt = MAX_POINTS;
      } else { 
         pCt = iRaw.length;
      }

      // Initialize the data array.
      data = new int[pCt];

      // Determine the top and bottom margins.
      bottomM = dimY - pixPerMark;
      topM = pixPerMark;
 
      // Map the data's extremes to the window's extremes.
      dataScale = (double) (bottomM - topM) / (double) (max - min);

      iMax = Integer.MIN_VALUE;
      iMin = Integer.MAX_VALUE;
      for (i = 0; i < pCt; i++) {
         data[i] = bottomM - (int) ((double) (iRaw[i] - min) * dataScale);
         if (data[i] > iMax) iMax = data[i];
         if (data[i] < iMin) iMin = data[i];
      }

      // Y coordinate labels and tic marks.
      int gap = fntSz * 2;
      if (gap < MIN_GAP) gap = MIN_GAP;

      // Number of increments along the Y axis.
      int yIncCt = (bottomM - topM) / gap;

      // Size of each increment, in terms of the data.
      double yInc = (max - min) / (double) yIncCt;

      // Create the labels for the Y axis data.
      ym = new Marker[yIncCt + 1];

      // Calculate the first, lowest value.
      ym[0] = new Marker();
      ym[0].label = Integer.toString(min);
      ym[0].lineLoc = ym[0].labelLoc = bottomM;
      int val = min;
      int maxLen = 0;          // Maximum length of the Y axis data strings.
      for (i = 1; i < (yIncCt + 1); i++) {
         ym[i] = new Marker();
         ym[i].label = Integer.toString(val + (int) ((double) i * yInc));
         if (ym[i].label.length() > maxLen) maxLen = ym[i].label.length();
         ym[i].lineLoc = ym[i].labelLoc = bottomM - (i * gap);
      }
 
      // Determine the left and right margins, and the size of the X axis.
      leftM = (maxLen + 1) * fntSz;
      rightM = pCt + leftM; 
      dimX = rightM + (maxLen * fntSz); 

      // Set up the X axis fidicuals
      // X coordinate labels and tic marks.
      int xTicCt = (pCt / pixPerMark);  
      int xGap = (rightM - leftM) / (xTicCt + 1);
      xm = new Marker[xTicCt + 2];

      for (i = 0; i <= xTicCt; i++) {
         j = i * xGap;
         s = Integer.toString(i * pixPerMark); 
         xm[i] = new Marker();
         xm[i].label = s;
         xm[i].labelLoc = (j + leftM) - (s.length() / 2);
         xm[i].lineLoc = j + leftM;
      }
    
      // The last point is on the right axis. 
      s = Integer.toString(pCt); 
      xm[xTicCt + 1] = new Marker();
      xm[xTicCt + 1].label = s;
      xm[xTicCt + 1].labelLoc = rightM; 
      xm[xTicCt + 1].lineLoc = rightM;
   }

   /** Resize the data by a factor of scale. */ 
   public void resize(double scale) {
      if (type) resizeInt(scale);
      else resizeFloat(scale);
   } 

   /** Resize the floating point data by a factor of scale. */ 
   public void resizeFloat(double scale) {
      for (int i = 0; i < pCt; i++) {
         data[i] = bottomM -
                   (int) ((double) (fRaw[i] - dMin) * dataScale * scale);
         if (data[i] > iMax) data[i] = iMax;
	 if (data[i] < iMin) data[i] = iMin;
      }
   }

   /** Resize the integer data by a factor of scale. */ 
   public void resizeInt(double scale) {
      for (int i = 0; i < pCt; i++) {
         data[i] = bottomM -
                   (int) ((double) (iRaw[i] - min) * dataScale * scale);
         if (data[i] > iMax) data[i] = iMax;
	 if (data[i] < iMin) data[i] = iMin;
      }
   }
}