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); */
}
}