Swing Components Java

/*
 * The contents of this file are subject to the Sapient Public License
 * Version 1.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * http://carbon.sf.net/License.html.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is The Carbon Component Framework.
 *
 * The Initial Developer of the Original Code is Sapient Corporation
 *
 * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
 */
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** 

This class provides a consistent space within to graph real
 * numbers. It provides features such as auto-centering and
 * real-time scaling. A user of this graph provides data by creating
 * one or more tracks and then adding real points to those. Calling
 * translate periodically allows you to create a scrolling graph as
 * well.


 *
 * 

This graph will also maintain tick marks that resize and
 * can be stuck to the sides of the screen so they are always
 * visible even if the origin is off-screen.


 *
 * Copyright 2001 Sapient
 * @author Greg Hinkle
 * @version $Revision: 1.4 $ ($Author: dvoet $ / $Date: 2003/05/05 21:21:26 $)
 */
public class GraphCanvas extends JPanel {
    /** A list of  Track's that are a part of this graph */
    private Map tracks = new HashMap(11);
    /** The current graph bounds that are visible */
    protected Rectangle2D graphBounds;
    /** The portion of the entire height that should be researved as
     * a border, above and below the highest and lowest track points */
    private static final double BORDER_PERCENT = 0.1d;
    /** The background color for this graph */
    protected Color backgroundColor = new Color(204,204,204);
    protected static NumberFormat labelFormat = null;
    protected static NumberFormat bigNumberLabelFormat = null;
    /**
     * Instantiates a graph canvas
     */
    public GraphCanvas() {
        super();
        setBackground(Color.blue);
        this.graphBounds = new Rectangle2D.Double(-5,0,150,2);
        this.labelFormat = NumberFormat.getNumberInstance();
        this.labelFormat.setMaximumFractionDigits(2);
        this.bigNumberLabelFormat = NumberFormat.getNumberInstance();
        this.bigNumberLabelFormat.setMaximumFractionDigits(0);
        System.out.println("GraphCanvas:: - New GraphCanvas created.");
    }
    /**
     * 

Sets the background color of this graph
     *
     * @param color the Color to set the background to
     */
    public void setBackgroundColor(Color color) {
        this.backgroundColor = color;
    }
    /** Gets the bounds of the graphing space that are currently showing
     * on the screen.
     * @return Rectangle2D The bounds of the currently visible graph
     */
    public Rectangle2D getGraphBounds() {
        return this.graphBounds;
    }
    /**
     * Sets the bounds that this graph is displaying
     *
     * @param rect the Rectangle2D of the desired graph points
     */
    public void setGraphBounds(Rectangle2D rect) {
        this.graphBounds = rect;
    }
    public AffineTransform getTransform() {
        AffineTransform affineT =
        new AffineTransform(1d,0d,0d,-1d,0d,super.getParent().getHeight());
        // scale to current scale
        affineT.concatenate(
        AffineTransform.getScaleInstance(
        this.getBounds().getWidth() / this.graphBounds.getWidth(),
        this.getBounds().getHeight() / this.graphBounds.getHeight()));
        // translate to the current origin
        affineT.concatenate(
        AffineTransform.getTranslateInstance(
        -this.graphBounds.getX(),
        -this.graphBounds.getY()));
        return affineT;
    }
    // CLEAR ALL CURVES FROM PLOT
    public void clear() {
    }
    public void addTrack(String trackName) {
        this.tracks.put(trackName, new Track(trackName));
    }
    public void addTrack(String trackName,Color color) {
        this.tracks.put(trackName, new Track(trackName,color));
    }
    // ADD CURVE TO STORAGE (DOESN'T GRAPH UNTIL REPAINT()).
    public void addPoint(String track, Point2D point) {
        ((Track)this.tracks.get(track)).addPoint(point);
    }
    public Track getTrack(String trackName) {
        return (Track) this.tracks.get(trackName);
    }
    public void clearAll() {
        this.getGraphics().clearRect(
        (int)getBounds().getX(),
        (int)getBounds().getY(),
        (int)getBounds().getWidth(),
        (int)getBounds().getHeight());
    }
    public void paint(Graphics gg) {
        Graphics2D g = (Graphics2D) gg;
        g.setBackground(this.backgroundColor);
        // What is the current graph to panel transform
        AffineTransform newTrans = getTransform();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
        // Erase this entire graph so that we can redraw it
        g.clearRect(
            (int)getBounds().getX(),
            (int)getBounds().getY(),
            (int)getBounds().getWidth(),
            (int)getBounds().getHeight());
        // This draws the tick marks and the tick values
        drawLines(g,newTrans);
        // This drawes the axeses
        drawAxes(g,newTrans);
        // This draws each of tracks in this graph
        drawTracks(g,newTrans);
        // This draws the keys for each graph
        drawKey(g,newTrans);
    }
    /**
     * 

Draw the key to the tracks by calling thier toString


     *
     * @param graphics2D the Graphics2D to draw to
     * @param transform the Affine Transform to use to determine how to draw
     */
    protected void drawKey(Graphics2D g, AffineTransform transform) {
        int start = 20;
        Iterator trackIterator = this.tracks.values().iterator();
        while(trackIterator.hasNext()) {
            Track track = (Track) trackIterator.next();
            String info = track.toString();
            // Will draw the key in the same color as it's line
            g.setColor(track.getColor());
            g.drawString(info,50, start+=25);
        }
    }
    protected void drawTracks(Graphics2D g, AffineTransform transform) {
        // Store original transform to restore it later
        // until I figure out how to track differences only
        AffineTransform origTrans = g.getTransform();
        // Transform for local drawing
        g.transform(transform);
        g.setColor(Color.orange);
        // Using a small stroke will minimize the width to a single output
        // level pixel up to a reasonable scaling size
        g.setStroke(new BasicStroke(0.001f));
        // Draw the tracks
        Iterator trackIterator = this.tracks.values().iterator();
        while (trackIterator.hasNext()) {
            Track track = (Track) trackIterator.next();
            g.setColor(track.getColor());
            GeneralPath path = track.getPath();
            g.draw(path);
        }
        // Reset transformation
        g.setTransform(origTrans);
    }
    /**
     * This draws the axes
     */
    protected void drawAxes(Graphics2D g,AffineTransform transform) {
        g.setColor(Color.white);
        Point2D origin = transform.transform(new Point2D.Double(0,0),null);
        // If you want to have rubber banding axes (always visible)
        Rectangle2D axesRect = new Rectangle2D.Double(5,5,this.bounds().getWidth()-10,this.bounds().getHeight());
        origin = floorPoint(origin,axesRect);
        Line2D x = new Line2D.Double(
        getBounds().getMinX(), origin.getY(),
        getBounds().getMaxX(), origin.getY());
        Line2D y = new Line2D.Double(
        origin.getX(), getBounds().getMinY(),
        origin.getX(), getBounds().getMaxY());
        g.draw(x);
        g.draw(y);
    }
    /**
     * 

This finds the closest point on a rectangle's edge to a point outside
     * the rectangle or if that point is within the rectangle it is returned.
     * 


     *
     * @param point The point to rectangularly floor
     * @param rect The rectangle to floor within
     */
    public static Point2D floorPoint(Point2D point, Rectangle2D rect) {
        double x = point.getX();
        double y = point.getY();
        if (x < rect.getMinX())
            x = rect.getMinX();
        if (x > rect.getMaxX())
            x = rect.getMaxX();
        if (y < rect.getMinY())
            y = rect.getMinY();
        if (y > rect.getMaxY())
            y = rect.getMaxY();
        return new Point2D.Double(x,y);
    }
    /**
     * 

This draws the tick marks in the graph
     *
     */
    protected void drawLines(Graphics2D g, AffineTransform transform) {
        g.setColor(Color.white);
        int REAL_TICK_SPACE = 40;
        int REAL_TICK_HEIGHT = 10;
        double graphTickSpaceX = (REAL_TICK_SPACE / transform.getScaleX());
        double graphTickSpaceY = (REAL_TICK_SPACE / Math.abs(transform.getScaleY()));
        Point2D origin = transform.transform(new Point2D.Float(0,0),null);
        // If you want to have rubber banding axes (always visible)
        Rectangle2D axesRect = new Rectangle2D.Double(5,5,this.bounds().getWidth()-10,this.bounds().getHeight());
        Point2D falseOrigin = floorPoint(origin,axesRect);
        double firstX = this.graphBounds.getMinX();
        Point2D pt = new Point2D.Float();
        for (double x = firstX; x <= (this.graphBounds.getMaxX()+graphTickSpaceX); x += graphTickSpaceX) {
            double tx = (Math.floor(x/graphTickSpaceX)) * graphTickSpaceX;
            pt.setLocation(tx,0);
            transform.transform(pt,pt);
            g.drawLine((int)pt.getX(),(int)falseOrigin.getY() - 5 ,(int)pt.getX(),(int)falseOrigin.getY() + 5);
            String label;
            if (tx > 10)
                label = this.bigNumberLabelFormat.format(tx);
            else
                label = this.labelFormat.format(tx);
            g.drawString(label,
                (float)pt.getX(),(float)falseOrigin.getY()-9);
        }
        double firstY = this.graphBounds.getMinY();
        for (double y = firstY; y <= (this.graphBounds.getMaxY()+graphTickSpaceY); y += graphTickSpaceY) {
            double ty = (Math.floor(y/graphTickSpaceY)) * graphTickSpaceY;
            pt.setLocation(0,ty);
            transform.transform(pt,pt);
            g.drawLine((int)falseOrigin.getX() - 5,(int)pt.getY() ,(int)falseOrigin.getX() + 5,(int)pt.getY());
            String label;
            if (ty > 10)
                label = this.bigNumberLabelFormat.format(ty);
            else
                label = this.labelFormat.format(ty);
            g.drawString(label,
                (float)falseOrigin.getX()+7,(float)pt.getY());
        }
    }
    public static class Track {
        protected String name;
        protected Color color = Color.black;  //Default to black
        protected GeneralPath path = new GeneralPath();
        protected boolean started = false;
        protected NumberFormat keyFormat;
        public Track(String name) {
            super();
            this.name = name;
            this.keyFormat = NumberFormat.getNumberInstance();
            this.keyFormat.setMaximumFractionDigits(2);
        }
        public Track(String name, Color color) {
            this(name);
            this.color = color;
        }
        public void setPath(GeneralPath path) {
            this.path = path;
        }
        public GeneralPath getPath() {
            return this.path;
        }
        public void addPoint(Point2D point) {
            if (path.getCurrentPoint() == null) {
                this.path.moveTo((float)point.getX(),(float)point.getY());
                this.started = true;
            } else {
                this.path.lineTo((float)point.getX(),(float)point.getY());
            }
        }
        public Color getColor() {
            return this.color;
        }
        public void setColor(Color color) {
            this.color = color;
        }
        public String toString() {
            String value = null;
            if (this.path.getCurrentPoint() != null) {
                double val = this.path.getCurrentPoint().getY();
                //NumberFormat nf = NumberFormat.getNumberInstance();
                value = this.keyFormat.format(val);
            }
            return this.name + ": " + value;
        }
    }
    /**
     * 

Bounds the graph to the limits of the tracks verticaly providing a
     * useful scaling. A more intelligent implementation could have minimum
     * bounds to limit the bouncyness to startup.


     */
    public void verticalBound() {
        Rectangle2D rect = null;
        Rectangle2D orig = getGraphBounds();
        Iterator trackIterator = this.tracks.values().iterator();
        while(trackIterator.hasNext()) {
            Track track = (Track) trackIterator.next();
            GeneralPath path = track.getPath();
            if (rect == null)
                rect = path.getBounds2D();
            else
                Rectangle.union(rect,path.getBounds2D(),rect);
        }
        Rectangle.union(rect,new Rectangle2D.Double(orig.getX(),0,1,1),rect);
        double border = rect.getHeight() * BORDER_PERCENT;
        setGraphBounds(new Rectangle2D.Double(
            orig.getMinX(),
            rect.getMinY()-border,
            orig.getWidth(),
            rect.getHeight()+(2d*border)));
    }
    public void clipOld() {
        Rectangle2D rect = getGraphBounds();
        //Rectangle2D orig = AffineTransform.getScaleInstance(1.5,1.5).createTransformedShape(getGraphBounds()).getBounds();
        Iterator trackIterator = this.tracks.values().iterator();
        double[] cs = new double[6];
        while(trackIterator.hasNext()) {
            Track track = (Track) trackIterator.next();
            GeneralPath path = track.getPath();
            GeneralPath newPath = new GeneralPath();
            PathIterator pIter = path.getPathIterator(new AffineTransform());
            while (!pIter.isDone()) {
                pIter.currentSegment(cs);
                //Point2D pt = new Point2D.Double(cs[0],cs[1]);
                if (cs[0] > rect.getMinX()) {
                    if (newPath.getCurrentPoint() == null)
                        newPath.moveTo((float)cs[0],(float)cs[1]);
                    else
                        newPath.lineTo((float)cs[0],(float)cs[1]);
                }
                /*
                System.out.println("Current Segment: " +
                cs[0] + ", " +
                cs[1] + ", " +
                cs[2] + ", " +
                cs[3] + ", " +
                cs[4] + ", " +
                cs[5]);
                 **/
                pIter.next();
            }
            track.setPath(newPath);
        }
    }
    /**
     * 

Translates the main graph rect by x and y, horizontally and vertically
     * respectively.


     */
    public void translate(double x, double y) {
        Rectangle2D rect = getGraphBounds();
        setGraphBounds(
        new Rectangle2D.Double(rect.getMinX()+x,
        rect.getMinY()+y,rect.getWidth(),rect.getHeight()));
    }
    public static void main(String[] args) throws Exception {
        GraphCanvas gc = new GraphCanvas();
        gc.show();
        JFrame frame = new JFrame("Memory Graph");
        frame.getContentPane().add(gc);
        frame.setSize(600,200);
        // TODO: Add window exit listener
        frame.show();
        gc.repaint();
        gc.paint((Graphics2D)gc.getGraphics());
        long start = System.currentTimeMillis();
        gc.addTrack("test", Color.cyan);
        gc.addTrack("test2", Color.blue);
        gc.addTrack("test3", Color.red);
        gc.addTrack("test4", Color.yellow);
        gc.addTrack("test5", Color.green);
        gc.addTrack("test6", Color.orange);
        gc.addTrack("test7", Color.pink);
        int i=0;
        while (true) {
            i++;
            Point2D pt = new Point2D.Float(i,((float)Math.cos(i/20f) + (float)Math.sin(i/40f)) * 3f);
            gc.addPoint("test",pt);
            Point2D pt2 = new Point2D.Float(i,(float)Math.cos(i/25.0f)*10f);
            gc.addPoint("test2",pt2);
            Point2D pt3 = new Point2D.Float(i,Math.min((float)Math.cos(Math.sin(i/4f))*13f - (float)Math.cos(i/80f)*20f,400f));
            gc.addPoint("test3",pt3);
            Point2D pt4 = new Point2D.Float(i,
                (float) Math.sin(.31*i)*2f +
                ((float)2f*(float)Math.cos(.07f*i))*8f);
            gc.addPoint("test4",pt4);
            Point2D pt5 = new Point2D.Float(i,
                (float) Math.cos(.66*i)*1f +
                ((float)2f*(float)Math.cos(.07f*i))*3f);
            gc.addPoint("test5",pt5);
            Point2D pt6 = new Point2D.Float(i,
                (float) Math.sin(.31*i)*2f +
                ((float)2f*(float)Math.cos(.07f*Math.tan(i)))*5f);
            gc.addPoint("test6",pt6);
            Point2D pt7 = new Point2D.Float(i,
                (float) Math.sin(i)*2f +
                ((float)2f*(float)Math.sin(.25f*i))*0.5f);
            gc.addPoint("test7",pt7);
            if (i > 150)
                gc.translate(1,0);
            gc.verticalBound();
            //if(i%100 == 0) {
                gc.clipOld();
            //}
            gc.repaint();
            Thread.sleep(10);
            if (i % 100 == 0) {
                System.out.println("Framewrate: " +
                    (100d / ((System.currentTimeMillis()-start)/1000d)));
                start = System.currentTimeMillis();
            }
        }
    }
}