/** * Copyright (c) 2005 - Bob Lang (http://www.cems.uwe.ac.uk/~lrlang/) * * http://www.frinika.com * * This file is part of Frinika. * * Frinika 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. * Frinika 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 Frinika; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.frinika.contrib.boblang; //import uwejava.*; import java.text.*; import java.awt.*; import java.awt.event.*; // import javax.swing.*; /** Graph is a simple class that provides facilities for <ul> <li>storing points <li>plotting them as a dot or line graph on the screen <li>printing multiple graphs . </ul> <p>Several graphs can be printed in one window. The axes labels are worked out from the points themselves. @author: Bob Lang */ public class Graph extends Frame { /** * */ private static final long serialVersionUID = 1L; public static final Color WHITE = Color.white, LIGHT_GRAY = Color.lightGray, GRAY = Color.gray, DARK_GRAY = Color.darkGray, BLACK = Color.black, RED = Color.red, PINK = Color.pink, ORANGE = Color.orange, YELLOW = Color.yellow, GREEN = Color.green, MAGENTA = Color.magenta, CYAN = Color.cyan, BLUE = Color.blue; // Fixed constants for visibility options private static final boolean VISIBLE = true, // Visible line to current point INVISIBLE = false; // No line to the current point // Fixed constant for the number of trace titles public static final int MAX_TRACE_TITLE = 4; // Title strings private String xAxisTitle, // Label for x axis yAxisTitle, // Label for y axis graphTitle, // Title at the top of graph subTitle; // Main sub title // Titles for individual plots (instead of a sub title) private String [] traceTitles = new String [MAX_TRACE_TITLE]; // Colour for each individual plot title private Color [] traceTitleColours = new Color [MAX_TRACE_TITLE]; // Current colour of the points being plotted private Color colour; // Various flags private boolean newGraph, exitWhenClosed; // Vector storing the points to be plotted private GraphPointVector points; // Maximum and minimum ranges of x-y co-ordinates private double xMax, yMax, xMin, yMin; /** Preferred constructor which provides labels for graph, x and y axes. Use empty strings if not all labels are applicable. */ public Graph (String inGraphTitle, String inXTitle, String inYTitle) { // Clear the window and set up the listener clearGraph (inGraphTitle, inXTitle, inYTitle); setupWindowListener (); // Default window size setSize (640, 480); } // Graph (String,String,String) /** Constructor which does not provide any labels */ public Graph () { this ("", "", ""); } // Graph () /** Method which clears out a graph so that the window can be used again. */ public void clearGraph (String inGraphTitle, String inXTitle, String inYTitle) { // Set or change the title of the graph setGraphTitle (inGraphTitle); // Save the titles for later use graphTitle = inGraphTitle; xAxisTitle = inXTitle; yAxisTitle = inYTitle; // Clear out sub title and individual plot titles subTitle = ""; for (int i = 0; i < MAX_TRACE_TITLE; i++) { traceTitles [i] = ""; } // for // Other initialisation points = new GraphPointVector (); colour = BLACK; newGraph = true; // Max and min values xMax = 0.0; yMax = 0.0; xMin = 0.0; yMin = 0.0; } // clearGraph /** Start a new graph using the same axes and titles so that several graphs can be drawn on the same frame (presumably in different colours) */ public void nextGraph () { colour = BLACK; newGraph = true; } // nextGraph () /** Put a subtitle underneath the plotted graph */ public void setSubTitle (String inSubTitle) { // Save the subtitle subTitle = inSubTitle; // Clear out any trace titles for (int i = 0; i < MAX_TRACE_TITLE; i++) { traceTitles [i] = ""; } // for } // setSubTitle () /** Put a title for the specified trace */ public void setTraceTitle (int traceNumber, Color inColour, String inTitle) { // Clear out any subtitle subTitle = ""; // Clear out any trace titles if (traceNumber < MAX_TRACE_TITLE) { traceTitleColours [traceNumber] = inColour; traceTitles [traceNumber] = inTitle; } // if } // setTraceTitle () /** Set the colour of the subsequent points plotted on the graph */ public void setColor (Color c) { colour = c; } // setColor (Color) /** setColor for English spellers! */ public void setColour (Color c) { setColor (c); } // setColour (int) /** Add a new point to the current graph. The graph is not actually shown until showGraph () is called. */ public void add (double x, double y) { // System.out.println ("Adding " +x + " " + y); // Don't draw a line to the first point boolean visible = !newGraph; newGraph = false; // Put the point in the vector points.addPoint (new GraphPoint (colour, visible, x, y)); // Find maximum and minimum values if (x > xMax) xMax = x; if (x < xMin) xMin = x; if (y > yMax) yMax = y; if (y < yMin) yMin = y; } // add (double, double) /** Integer version of add (x,y) */ public void add (int x, int y) { double dx = x, dy = y; add (dx, dy); } // add (int, int) /** Skip to a different point. Just place an "invisible" point in the list. */ public void skipTo (double x, double y) { // Put the point in the vector points.addPoint (new GraphPoint (colour, INVISIBLE, x, y)); newGraph = false; } // skipTo () /** Integer version of skipTo() */ public void skipTo (int x, int y) { double dx = x, dy = y; skipTo (dx, dy); } // skipTo () /** Compulsory request to show the current graph(s) in a frame. This version will always close the program when the user closes the frame */ public void showGraph () { //$ System.out.println ("showGraph"); exitWhenClosed = true; repaint (); show (); } // showGraph /** Alternative version of showGraph which allows the programmer to control whether the program exits when the frame is closed. */ public void showGraph (boolean inExitWhenClosed) { //$ System.out.println ("showGraph (b)"); exitWhenClosed = inExitWhenClosed; repaint (); show (); } // showGraph (boolean) // **** GRAPH PAINT METHOD ******************************************** // Attributes used for plotting the graphs private int xOffset, // X and Y offsets for printing multiple graphs yOffset; // (for screen use, these should be 0,0) private double xSpread, // Range of values to be plotted on the X axis ySpread; // Range of values to be plotted on the Y axis private int xAxisLength, // Lengths of X and Y axes in pixels yAxisLength, xOrigin, // Position of X and Y origin (0,0) yOrigin; private int xBorder, // Border for writing text and labels yBorder; private int frameWidth, // Size of the frame in pixels frameHeight; private boolean printGraph; // This graph is to be printed on paper /** Standard paint method which draws the entire graph. Although marked public this method should not be called by the user program, only by the Java's windowing system. */ public synchronized void paint (Graphics g) { //$ System.out.println ("Calling paint"); // Not to be printed printGraph = false; // Set the offset at 0,0 relative to the start of the frame xOffset = 0; yOffset = 0; // calculate length of axes from window size minus a suitable border xBorder = 80; yBorder = 80; // Set up the frame width and height Dimension d = this.getSize (); frameWidth = d.width; frameHeight = d.height; // Draw the axes and plot the graphs setUpAxes (); drawAxes (g); plotGraphs (g); } // paint (Graphics) /** Similar to paint () method, but used to print the graph to a printer. The graphics object must have been previously set up by creating a PrintJob object. The x and y offsets, width and height potentially allow multiple graphs to be plotted on the same piece of paper */ public synchronized void print (int inXOffset, int inYOffset, int printWidth, int printHeight, Graphics g) { // Print this graph on paper printGraph = true; // Set the offset at 0,0 relative to the start of the frame xOffset = inXOffset; yOffset = inYOffset; // calculate length of axes from window size minus a suitable border xBorder = 50; yBorder = 60; // Set up the frame width and height frameWidth = printWidth; frameHeight = printHeight; // Draw a border to surround the graph g.setColor (BLUE); g.drawRect (xOffset, yOffset, frameWidth, frameHeight); // Draw the axes and plot the graphs setUpAxes (); drawAxes (g); plotGraphs (g); } // print (Graphics) // **** PRIVATE METHODS ************************************************** /** Set the title of the graph in the title bare */ private void setGraphTitle (String inGraphTitle) { // Set the title and the default size of the graph graphTitle = "Graph"; if (inGraphTitle != null) { if (inGraphTitle.length () > 1) { graphTitle = inGraphTitle; } // if } // if setTitle (graphTitle); } // setGraphTitle () /** Method which sets up the window listener for this graph. */ private void setupWindowListener () { addWindowListener (new WindowAdapter () { public void windowClosing (WindowEvent e) { dispose (); if (exitWhenClosed) System.exit (0); } // WindowClosing () }); // addWindowListener () } // setupWindowListener () /** Calculate the X scaling factor */ private int scaleX (double x) { return (int) ((x - xMin)/xSpread*xAxisLength) + xBorder; } // scaleX (double) /** Calculate the Y scaling factor */ private int scaleY (double y) { return (int) (yAxisLength - ((y - yMin)/ySpread*yAxisLength)) + yBorder; } // scaleY (double) /** Set up the X and Y axes and origin */ private void setUpAxes () { xAxisLength = (int) frameWidth - 2*xBorder; yAxisLength = (int) frameHeight - 2*yBorder; // Special case when asked to plot an empty graph if (xMin > xMax) { xMin = -1; xMax = 1; } // if else if (xMin == xMax) { xMin = xMin - 1; xMax = xMax + 1; } // if // Similar processing for Y axis if (yMin > yMax) { yMin = -1; yMax = 1; } // if else if (yMin == yMax) { yMin = yMin - 1; yMax = yMax + 1; } // if // calculate value spreads from mins and maxs which have // been recorded as we go xSpread = xMax - xMin; ySpread = yMax - yMin; // Calculate the origin positions if (xMin > 0) xOrigin = scaleX (xMin); else xOrigin = scaleX (0); if (yMin > 0) yOrigin = scaleY (yMin); else yOrigin = scaleY (0); } // setUpAxes () /** Draw the X-Y axes onto the current frame and write in appropriate titles/subtitles/trace titles */ private void drawAxes (Graphics g) { int xPos, yPos; g.setColor (BLACK); Font plain = g.getFont (); Font small = new Font (plain.getFamily (), Font.PLAIN, 10); Font bold = new Font (plain.getFamily (), Font.BOLD, 14); // Draw the X and Y axes g.drawLine (xBorder - 5 + xOffset, yOrigin + yOffset, xAxisLength + xBorder + 5 + xOffset, yOrigin + yOffset); g.drawLine (xOrigin + xOffset, yBorder - 5 + yOffset, xOrigin + xOffset, yAxisLength + yBorder + 5 + yOffset); // Put the titles on the X and Y axes xPos = frameWidth - g.getFontMetrics ().stringWidth (xAxisTitle) - xBorder/2; g.drawString (xAxisTitle, xPos + xOffset, yOrigin - 5 + yOffset); xPos = xOrigin - g.getFontMetrics ().stringWidth (yAxisTitle)/2; g.drawString (yAxisTitle, xPos + xOffset, yBorder - 8 + yOffset); // Draw the horizontal and vertical ticks g.setFont (small); drawHorizontalTicks (g); drawVerticalTicks (g); // Main title at top of the graph g.setFont (bold); xPos = (frameWidth - g.getFontMetrics ().stringWidth (graphTitle))/2; g.drawString (graphTitle, xPos + xOffset, yBorder/2 + yOffset); // Sub title, or titles for each trace if (subTitle.length () > 0) { g.setFont (plain); xPos = (frameWidth - g.getFontMetrics ().stringWidth (subTitle))/2; yPos = frameHeight - yBorder/2; g.drawString (subTitle, xPos + xOffset, yPos + yOffset); } // if else { // Use small font for the plot titles g.setFont (small); // Draw title for each individual trace (max 4) for (int t = 0; t < MAX_TRACE_TITLE; t++) { if (traceTitles [t].length () > 0) { // Calculate position to draw title xPos = t*(frameWidth - 2*xBorder)/MAX_TRACE_TITLE + xBorder; yPos = frameHeight - yBorder/2; g.setColor (traceTitleColours [t]); g.drawString (traceTitles [t], xPos + xOffset, yPos + yOffset); } // if } // for } // else // Revert to default font g.setFont (plain); } // drawAxes (Graphics) /** Draw and label the horizontal ticks */ private void drawHorizontalTicks (Graphics g) { // Find the interval for each tick.. 1, 5, 10, 50, 100, 500 etc boolean byTwo = false; int tickInterval = 1; int pixTick = scaleX (tickInterval) - scaleX (0); //$ System.out.println ("First pix = " + pixTick); while (pixTick < 20) { //$ System.out.println ("tick " + tickInterval + " pix = " + pixTick); // Alternately multiply by five and two. if (byTwo) { tickInterval = tickInterval*2; } else { tickInterval = tickInterval*5; } byTwo = !byTwo; pixTick = scaleX (tickInterval) - scaleX (0); } // Find the position of the first and last ticks int firstTick = ((int) xMin)/tickInterval; int lastTick = ((int) xMax)/tickInterval + 1; //$ System.out.print ("Interval = " + tickInterval); //$ System.out.println (" First = " + firstTick + " Last = " + lastTick); // Draw the horizontal ticks // for (int tick= firstTick; tick <= lastTick; tick+=tickInterval) { for (int tick = firstTick; tick < lastTick; tick++) { // Calculate the position of the point and draw the tick double tickPoint = tick*tickInterval; int xTick = scaleX (tickPoint); g.drawLine (xTick + xOffset, yOrigin + yOffset, xTick + xOffset, yOrigin + 5 + yOffset); // Label every second tick but not 0.0 if (((int) tickPoint%(tickInterval*2) == 0) && (tickPoint != 0.0)) { g.drawString (Convert.doubleToString (tickPoint, 1, 0), xTick - 10 + xOffset, yOrigin + 15 + yOffset); } // if } // for } // drawHorizontalTicks (Graphics g) /** Draw and label the vertical ticks */ private void drawVerticalTicks (Graphics g) { // Set up the minimum pixels between ticks int tickPixels = 20; if (printGraph) { tickPixels = 10; } // if // Get the font metrics for the current font FontMetrics fm = g.getFontMetrics (); // Find the interval for each tick.. 1, 5, 10, 50, 100, 500 etc boolean byTwo = false; int tickInterval = 1; int pixTick = scaleY (0) - scaleY (tickInterval); //$ System.out.println ("First pix = " + pixTick); while (pixTick < tickPixels) { //$ System.out.println ("tick " + tickInterval + " pix = " + pixTick); // Alternately multiply by five and two. if (byTwo) { tickInterval = tickInterval*2; } else { tickInterval = tickInterval*5; } byTwo = !byTwo; pixTick = scaleY (0) - scaleY (tickInterval); } // Find the position of the first and last ticks int firstTick = ((int) yMin)/tickInterval; int lastTick = ((int) yMax)/tickInterval + 1; //$ System.out.print ("Interval = " + tickInterval); //$ System.out.println (" First = " + firstTick + " Last = " + lastTick); // Draw the horizontal ticks // for (int tick= firstTick; tick <= lastTick; tick+=tickInterval) { for (int tick = firstTick; tick < lastTick; tick++) { // Calculate the position of the point and draw the tick double tickPoint = tick*tickInterval; int yTick = scaleY (tickPoint); g.drawLine (xOrigin + xOffset, yTick + yOffset, xOrigin - 5 + xOffset, yTick + yOffset); // Label every tick but not 0.0 if (tickPoint != 0.0) { String tickString; int offset; // Determine if engineering notation required if (ySpread >= 1E5) { DecimalFormat df = new DecimalFormat ("0.0E0"); tickString = df.format (tickPoint); } else { tickString = Convert.doubleToString (tickPoint, 1, 0); } //$ System.out.println ("<" + tickString + ">"); g.drawString (tickString, xOrigin - fm.stringWidth (tickString) - 5 + xOffset, yTick + 5 + yOffset); } // if } // for } // drawVerticalTicks (Graphics g) /** Method to plot all the points stored in the points vector */ private void plotGraphs (Graphics g) { int x1, y1, x2, y2; Color plotColour; // Set to invalid colour plotColour = null; // Set the default first point x1 = 0; y1 = 0; // Loop through all the points recorded GraphPoint point = points.getFirstPoint (); while (point != null) { // System.out.println ("Recalling " + point.x + " " + point.y); x2 = scaleX (point.x); y2 = scaleY (point.y); // See if a change of colour required if (point.colour != plotColour) { g.setColor (point.colour); plotColour = point.colour; } // plot the line and/or point if (point.visible) { g.drawLine (x1 + xOffset, y1 + yOffset, x2 + xOffset, y2 + yOffset); } // if // Save coordinates for next time round the loop x1 = x2; y1 = y2; // Get the next point to be plotted point = points.getNextPoint (); } // while } // plotGraphs () } // Graph