package graph; import java.awt.*; import java.applet.*; import java.util.*; import java.lang.*; /* ************************************************************************** ** ** Class DataSet ** ************************************************************************** ** Modified from Leigh Brookshaw's graph classes by Michael Cross ** Copyright (C) 1995, 1996 Leigh Brookshaw ** ** This program 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. ** ** This program 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 this program; if not, write to the Free Software ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ************************************************************************** ** ** This class is designed to be used in conjunction with ** the Graph2D class and Axis class for plotting 2D graphs. ** *************************************************************************/ /** * This class is designed to hold the data to be plotted. * It is to be used in conjunction with the Graph2D class and Axis * class for plotting 2D graphs.
* * @version LB1.15 modified MCC 3/19/97 * @author Leigh Brookshaw, modified by Michael Cross */ public class DataSet extends Object { /* *************************** ** Public Static Values **************************/ /** * A constant value flag used to specify no straight line segment * is to join the data points */ public final static int NOLINE = 0; /** * A constant value flag used to specify that a straight line segment * is to join the data points. */ public final static int LINE = 1; /* *********************** ** Public Variables **********************/ /** * The Graphics canvas that is driving the whole show. * @see graph.Graph2D */ public Graph2D g2d; /** * The linestyle to employ when joining the data points with * straight line segments. Currently only solid and no line * are supported. */ public int linestyle = LINE; /** * The color of the straight line segments */ public Color linecolor = null; /** * The index of the marker to use at the data points. * @see graph.Markers */ public int marker = 0; /** * The marker color */ public Color markercolor = null; /** * The scaling factor for the marker. Default value is 1. */ public double markerscale = 1.0; /** * The Axis object the X data is attached to. From the Axis object * the scaling for the data can be derived. * @see graph.Axis */ public Axis xaxis; /** * The Axis object the Y data is attached to. * @see graph.Axis */ public Axis yaxis; /** * The current plottable X maximum of the data. * This can be very different from * true data X maximum. The data is clipped when plotted. */ public double xmax; /** * The current plottable X minimum of the data. * This can be very different from * true data X minimum. The data is clipped when plotted. */ public double xmin; /** * The current plottable Y maximum of the data. * This can be very different from * true data Y maximum. The data is clipped when plotted. */ public double ymax; /** * The current plottable Y minimum of the data. * This can be very different from * true data Y minimum. The data is clipped when plotted. */ public double ymin; /** * Boolean to control clipping of the data window. * Default value is true, clip the data window. */ public boolean clipping = true; /* ********************* ** Protected Variables **********************/ /** * The data X maximum. * Once the data is loaded this will never change. */ protected double dxmax; /** * The data X minimum. * Once the data is loaded this will never change. */ protected double dxmin; /** * The data Y maximum. * Once the data is loaded this will never change. */ protected double dymax; /** * The data Y minimum. * Once the data is loaded this will never change. */ protected double dymin; /** * The array containing the actual data */ protected double data[]; /** * The number of data points stored in the data array */ protected int length; /** * The X range of the clipped data */ protected double xrange; /** * The Y range of the clipped data */ protected double yrange; /** * The length of the example line in the data legend. */ protected int legend_length = 20; /** * The legend text */ protected TextLine legend_text = null; /** * The X pixel position of the data legend */ protected int legend_ix; /** * The Y pixel position of the data legend */ protected int legend_iy; /** * The X data position of the data legend */ protected double legend_dx; /** * The Y data position of the data legend */ protected double legend_dy; /** * The amount to increment the data array when the append method is being * used. */ protected int increment = 100; // MCC 3/19/97 last point plotted protected int previousPointLines = -1; protected int previousPointMarkers = -1; /** * The stride of the data. For data pairs (x,y) the stride is 2 */ protected int stride = 2; /* ********************* ** Constructors ********************/ /** * Instantiate an empty data set. */ public DataSet ( ) { length = 0; range(stride); } /** * Instantiate an empty data set. * @param stride the stride of the data set. The default stride is 2. */ public DataSet (int stride ) throws Exception { if( stride < 2 ) throw new Exception("Invalid stride parameter!"); this.stride = stride; length = 0; range(stride); } /** * Instantiate a DataSet with the parsed data. Default stride is 2. * The double array contains the data. The X data is expected in * the even indices, the y data in the odd. The integer n is the * number of data Points. This means that the length of the data * array is 2*n. * @param d Array containing the (x,y) data pairs. * @param n Number of (x,y) data pairs in the array. * @exception Exception * A Generic exception if it fails to load the * parsed array into the class. */ public DataSet ( double d[], int n ) throws Exception { int i; int k = 0; length = 0; if ( d == null || d.length == 0 || n <= 0 ) { throw new Exception("DataSet: Error in parsed data!"); } // Copy the data locally. data = new double[n*stride]; length = n*stride; System.arraycopy(d, 0, data, 0, length); // Calculate the data range. range(stride); } /** * Instantiate a DataSet with the parsed data. * The double array contains the data. The X data is expected to be in * indices i*stride where i=0,1,... The Y data is expected to be found * in indices i*stride+1 where i=0,1,2... * The integer n is the * number of data Points. This means that the length of the data * array is 2*stride. * @param d Array containing the (x,y) data pairs. * @param n Number of (x,y) data pairs in the array. * @param s The stride of the data. * @exception Exception * A Generic exception if it fails to load the * parsed array into the class. */ public DataSet ( double d[], int n, int s ) throws Exception { if( s < 2 ) throw new Exception("Invalid stride parameter!"); int i; int k = 0; length = 0; if ( d == null || d.length == 0 || n <= 0 ) { throw new Exception("DataSet: Error in parsed data!"); } this.stride = s; // Copy the data locally. data = new double[n*stride]; length = n*stride; System.arraycopy(d, 0, data, 0, length); // Calculate the data range. range(stride); } /* ******************* ** Public Methods ******************/ /** * Append data to the data set. * @param d Array containing (x,y) pairs to append * @param n Number of (x,y) data pairs in the array. * @exception Exception * A generic exception if it fails to load the * parsed array into the class. */ public synchronized void append( double d[], int n ) throws Exception { int i; int k = 0; double tmp[]; int ln = n*stride; if ( d == null || d.length == 0 || n <= 0 ) { throw new Exception("DataSet: Error in append data!"); } if(data == null) data = new double[increment]; // Copy the data locally. if( ln+length < data.length ) { System.arraycopy(d, 0, data, length, ln); length += ln; } else { tmp = new double[ln+length+increment]; if( length != 0 ) { System.arraycopy(data, 0, tmp, 0, length); } System.arraycopy(d, 0, tmp, length, ln); length += ln; data = tmp; } // Calculate the data range. range(stride); // Update the range on Axis that this data is attached to if(xaxis != null) xaxis.resetRange(); if(yaxis != null) yaxis.resetRange(); } /** * Append single data point to the data set. Added by MCC 3/19/97. Useful * for animated plotting since reranging etc can be calculated more easily. * @param x x-coord of point to be added * @param y y-coord of point to be added */ public synchronized void appendPoint( double x, double y ) { double[] d = new double[2]; d[0]=x; d[1]=y; int i; int k = 0; double tmp[]; if(data == null) data = new double[increment]; // Copy the data locally. if( 2+length < data.length ) { System.arraycopy(d, 0, data, length, 2); length += 2; } else { tmp = new double[2+length+increment]; if( length != 0 ) { System.arraycopy(data, 0, tmp, 0, length); } System.arraycopy(d, 0, tmp, length, 2); length += 2; data = tmp; } /* Old version changed 3/19/97 // Calculate the data range. if(x < dxmin) dxmin = x; if(x > dxmax) dxmax = x; if(y < dymin) dymin = y; if(y > dymax) dymax = y; // Update the range on Axis that this data is attached to if(xaxis != null) xaxis.resetRange(); if(yaxis != null) yaxis.resetRange(); */ // MCC: added 0.05 terms 9/19/97 to reduce number of times graph must be rescaled if(x < dxmin) { dxmin = x; if(xaxis != null) xaxis.resetRange(); } if(x > dxmax) { dxmax = x; if(xaxis != null) xaxis.resetRange(); } if(y < dymin) { dymin = y; if(yaxis != null) yaxis.resetRange(); } if(y > dymax) { dymax = y; if(yaxis != null) yaxis.resetRange(); } } /** * Delete data from the data set (start and end are inclusive). * The first (x,y) pair in the data set start at index 0. * @param start The start (x,y) pair index. * @param end The end (x,y) pair index. */ public synchronized void delete( int start, int end ) { int End = stride*end; int Start = stride*start; if(length <= 0) return; if( End < Start ) return; if( Start < 0 ) Start = 0; if( End > length-stride ) End = length-stride; if( End < length-stride) { System.arraycopy(data, End+stride, data, Start, length - End - stride); } length -= End+stride-Start; // Calculate the data range. range(stride); } /** * Delete all the data from the data set. */ public synchronized void deleteData( ) { length = 0; data = null; range(stride); } /** * Draw the straight line segments and/or the markers at the * data points. * If this data has been attached to an Axis then scale the data * based on the axis maximum/minimum otherwise scale using * the data's maximum/minimum * @param g Graphics state * @param bounds The data window to draw into */ public synchronized void draw_data(Graphics g, Rectangle bounds) { Color c; if ( xaxis != null ) { xmax = xaxis.maximum; xmin = xaxis.minimum; } if ( yaxis != null ) { ymax = yaxis.maximum; ymin = yaxis.minimum; } xrange = xmax - xmin; yrange = ymax - ymin; /* ** draw the legend before we clip the data window */ draw_legend(g,bounds); /* ** Clip the data window */ if(clipping) g.clipRect(bounds.x, bounds.y, bounds.width, bounds.height); c = g.getColor(); if( linestyle != DataSet.NOLINE ) { if ( linecolor != null) g.setColor(linecolor); else g.setColor(c); draw_lines(g,bounds); } if( marker > 0 ) { if(markercolor != null) g.setColor(markercolor); else g.setColor(c); draw_markers(g,bounds); } g.setColor(c); } /** * return the data X maximum. */ public double getXmax() { return dxmax; } /** * return the data X minimum. */ public double getXmin() { return dxmin; } /** * return the data Y maximum. */ public double getYmax() { return dymax; } /** * return the data Y minimum. */ public double getYmin() { return dymin; } /** * Define a data legend in the graph window * @param x pixel position of the legend. * @param y pixel position of the legend. * @param text text to display in the legend */ public void legend(int x, int y, String text) { if(text == null) { legend_text = null; return; } if(legend_text == null) legend_text = new TextLine(text); else legend_text.setText(text); legend_text.setJustification(TextLine.LEFT); legend_ix = x; legend_iy = y; legend_dx = 0.0; legend_dy = 0.0; } /** * Define a data legend in the graph window * @param x data position of the legend. * @param y data position of the legend. * @param text text to display in the legend */ public void legend(double x, double y, String text) { if(text == null) { legend_text = null; return; } if(legend_text == null) legend_text = new TextLine(text); else legend_text.setText(text); legend_text.setJustification(TextLine.LEFT); legend_dx = x; legend_dy = y; legend_ix = 0; legend_iy = 0; } /** * Set the font to be used in the legend * @param f font */ public void legendFont(Font f) { if(f == null) return; if(legend_text == null) legend_text = new TextLine(); legend_text.setFont(f); } /** * Set the color for the legend text * @param c color */ public void legendColor(Color c) { if(c == null) return; if(legend_text == null) legend_text = new TextLine(); legend_text.setColor(c); } /** * Return the number of data points in the DataSet * @return number of (x,y0 points. */ public int dataPoints() { return length/stride; } /** * get the data point at the parsed index. The first (x,y) pair * is at index 0. * @param index Data point index * @return array containing the (x,y) pair. */ public double[] getPoint(int index) { double point[] = new double[stride]; int i = index*stride; if( index < 0 || i > length-stride ) return null; for(int j=0; j 0) { firstPoint = previousPointLines -1; } else firstPoint = 0; // Is the first point inside the drawing region ? if( (inside0 = inside(data[firstPoint], data[firstPoint+1])) ) { x0 = (int)(w.x + ((data[firstPoint]-xmin)/xrange)*w.width); y0 = (int)(w.y + (1.0 - (data[firstPoint+1]-ymin)/yrange)*w.height); if( x0 < xcmin || x0 > xcmax || y0 < ycmin || y0 > ycmax) inside0 = false; } int newLength = length; for(i=firstPoint+2; i xcmax || y1 < ycmin || y1 > ycmax) inside1 = false; } // If the second point is inside calculate the first point if it // was outside if ( !inside0 && inside1 ) { x0 = (int)(w.x + ((data[i-2]-xmin)/xrange)*w.width); y0 = (int)(w.y + (1.0 - (data[i-1]-ymin)/yrange)*w.height); } // If either point is inside draw the segment if ( inside0 || inside1 ) { g.drawLine(x0,y0,x1,y1); } /* ** The reason for the convolution above is to avoid calculating ** the points over and over. Now just copy the second point to the ** first and grab the next point */ inside0 = inside1; x0 = x1; y0 = y1; } previousPointLines = newLength - 1; } /** * Return true if the point (x,y) is inside the allowed data range. */ protected boolean inside(double x, double y) { if( x >= xmin && x <= xmax && y >= ymin && y <= ymax ) return true; return false; } /** * Draw the markers. (Modified by MCC 3/19/97) * Only markers inside the specified range will be drawn. Also markers * close the edge of the clipping region will be clipped. * @param g Graphics context * @param w data window * @see graph.Markers */ protected void draw_markers(Graphics g, Rectangle w) { int x1,y1; int i; // Calculate the clipping rectangle Rectangle clip = g.getClipRect(); int xcmin = clip.x; int xcmax = clip.x + clip.width; int ycmin = clip.y; int ycmax = clip.y + clip.height; /* ** Load the marker specified for this data */ Markers m = g2d.getMarkers(); if( m == null) return; // System.out.println("Drawing Data Markers!"); int firstPoint; //MCC 7/14/96: add first point if(previousPointMarkers > 0) { firstPoint = previousPointMarkers -1; } else firstPoint = 0; int newLength = length; // MCC 7/14/96: for thread safe for(i=firstPoint; i= xcmin && x1 <= xcmax && y1 >= ycmin && y1 <= ycmax ) m.draw(g, marker, markerscale, x1, y1); } } previousPointMarkers = newLength -1; // MCC 7/14/96 } /** * Draw a legend for this data set * @param g Graphics context * @param w Data Window */ protected void draw_legend(Graphics g, Rectangle w) { Color c = g.getColor(); Markers m = null; if( legend_text == null) return; if( legend_text.isNull() ) return; if( legend_ix == 0 && legend_iy == 0 ) { legend_ix = (int)(w.x + ((legend_dx-xmin)/xrange)*w.width); legend_iy = (int)(w.y + (1.0 - (legend_dy-ymin)/yrange)*w.height); } if( linestyle != DataSet.NOLINE ) { if ( linecolor != null) g.setColor(linecolor); g.drawLine(legend_ix,legend_iy,legend_ix+legend_length,legend_iy); } if( marker > 0 ) { m = g2d.getMarkers(); if( m != null) { if(markercolor != null) g.setColor(markercolor); else g.setColor(c); m.draw(g,marker,1.0, legend_ix+legend_length/2, legend_iy); } } legend_text.draw( g, legend_ix+legend_length+legend_text.charWidth(g,' '), legend_iy+legend_text.getAscent(g)/3); g.setColor(c); } /** * Calculate the range of the data. This modifies dxmin,dxmax,dymin,dymax * and xmin,xmax,ymin,ymax */ protected void range(int stride) { int i; if( length >= stride ) { dxmax = data[0]; dymax = data[1]; dxmin = dxmax; dymin = dymax; } else { dxmin = 0.0; dxmax = 0.0; dymin = 0.0; dymax = 0.0; } for(i=stride; i data[i] ) { dxmin = data[i]; } if( dymax < data[i+1] ) { dymax = data[i+1]; } else if( dymin > data[i+1] ) { dymin = data[i+1]; } } if( xaxis == null) { xmin = dxmin; xmax = dxmax; } if( yaxis == null) { ymin = dymin; ymax = dymax; } /* MCC: added 9/19/97 to reduce number of times graph must be rescaled dxmin=dxmin-0.05*(dxmax-dxmin); dxmax=dxmax+0.05*(dxmax-dxmin); dymin=dymin-0.05*(dymax-dymin); dymax=dymax+0.05*(dymax-dymin); */ } }