package graph;
import java.awt.*;
import java.applet.*;
import java.util.Vector;
import java.util.Enumeration;
import java.lang.*;
/*
**************************************************************************
**
** Class Axis
**
**************************************************************************
** 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 DataSet class for plotting 2D graphs.
**
*************************************************************************/
/**
* This class controls the look and feel of axes.
* It is designed to be used in conjunction with
* the Graph2D class and DataSet class for plotting 2D graphs.
*
* To work with the other classes a system of registration is used.
* The axes have to be attached to the controlling Graph2D class
* and the DataSet's have to be attached to both the Graph2D class
* and the Axis class.
*
* This way the 3 main classes Graph2D, Axis and DataSet know of each
* others existence.
*
* This does not mean the classes cannot be used independently, they can
* but in this mode nothing is automated, the user must code everything
* manually
* MCC 6/9/97 Changed SpecialFunction.log10 to internal log10
*
* @version LB1.12 MCC 3/19/97
* @author Leigh Brookshaw modified by Michael Cross
*/
public class Axis extends Object {
/*
***************************
** Public Static Values
**************************/
/**
* Constant flagging Horizontal Axis
*/
static final int HORIZONTAL = 0;
/**
* Constant flagging Vertical Axis
*/
static final int VERTICAL = 1;
/**
* Constant flagging Axis position on the graph.
* Left side => Vertical
*/
public static final int LEFT = 2;
/**
* Constant flagging Axis position on the graph.
* Right side => Vertical
*/
public static final int RIGHT = 3;
/**
* Constant flagging Axis position on the graph.
* Top side => Horizontal
*/
public static final int TOP = 4;
/**
* Constant flagging Axis position on the graph.
* Bottom side => Horizontal
*/
public static final int BOTTOM = 5;
/**
* The first guess on the number of Labeled Major tick marks.
*/
static final int NUMBER_OF_TICS = 4;
/*
***********************
** Public Variables
**********************/
/**
* If true draw a grid positioned on major ticks over the graph
*/
public boolean drawgrid = false;
/**
* If true draw a line positioned on the Zero label tick mark.
*/
public boolean drawzero = false;
/**
* Color of the grid
*/
public Color gridcolor = null;
/**
* Color of the line at the Zero label
*/
public Color zerocolor = null;
/**
* Default value true. Normally never changed. If set false
* the Axis draw method exits without drawing the axis.
* @see Axis#drawAxis()
*/
public boolean redraw = true;
/**
* Rescale the axis so that labels fall at the end of the Axis. Default
* value false.
*/
public boolean force_end_labels = false;
/**
* True if axes have changed. Added by MCC.
*/
public boolean changed = true; //MCC 7/9/96
/**
* Size in pixels of the major tick marks
*/
public int major_tic_size = 10;
/**
* Size in pixels of the minor tick marks
*/
public int minor_tic_size = 5;
/**
* Number of minor tick marks between major tick marks
*/
public int minor_tic_count = 1;
/**
* Color of the Axis.
*/
public Color axiscolor;
/**
* Minimum data value of the axis. This is the value used to scale
* data into the data window. This is the value to alter to force
* a rescaling of the data window.
*/
public double minimum;
/**
* Maximum data value of the axis. This is the value used to scale
* data into the data window. This is the value to alter to force
* a rescaling of the data window.
*/
public double maximum;
/**
* Before the Axis can be positioned correctly and drawn the data window
* needs to be calculated and passed to the Axis.
*/
public Dimension data_window = new Dimension(0,0);
/**
* The graph canvas this axis is attached to (if it is attached to any)
* @see graph.Graph2D
*/
public Graph2D g2d = null;
/*
***********************
** Protected Variables
**********************/
/**
* The position in pixels of the minimum point of the axis line
*/
protected Point amin;
/**
* The position in pixels of the maximum point of the axis line
*/
protected Point amax;
/**
* The orientation of the axis. Either Axis.HORIZONTAL or
* Axis.VERTICAL
*/
protected int orientation;
/**
* The position of the axis. Either Axis.LEFT, Axis.RIGHT, Axis.TOP, or
* Axis.BOTTOM
*/
protected int position;
/**
* The width of the Axis. Where width for a horizontal axis is really
* the height
*/
protected int width = 0;
/**
* Textline class to contain the title of the axis.
*/
protected RTextLine title = new RTextLine();
/**
* Textline class to hold the labels before printing.
*/
protected RTextLine label = new RTextLine("0");
/**
* Textline class to hold the label's exponent (if it has one).
*/
protected RTextLine exponent = new RTextLine();
/**
* The width of the maximum label. Used to position a Vertical Axis.
*/
protected int max_label_width = 0;
/**
* Vector containing a list of attached DataSets
*/
protected Vector dataset = new Vector();
/**
* String to contain the labels.
*/
protected String label_string[] = null;
/**
* The actual values of the axis labels
*/
protected float label_value[] = null;
/**
* The starting value of the labels
*/
protected double label_start = 0.0;
/**
* The increment between labels
*/
protected double label_step = 0.0;
/**
* The label exponent
*/
protected int label_exponent = 0;
/**
* The number of labels required
*/
protected int label_count = 0;
/**
* Initial guess for the number of labels required
*/
protected int guess_label_number = 4;
/**
* If true the axis range must be manually set by setting the
* Axis.minimum and Axis.maximum variables. The default is false.
* The default action is for the axis range to be calculated everytime
* a dataset is attached.
*/
protected boolean manualRange = false;
/*
*********************
** Constructors
********************/
/**
* Instantiate the class. The defalt type is a Horizontal axis
* positioned at the bottom of the graph.
*/
public Axis() {
orientation = HORIZONTAL;
position = BOTTOM;
}
/**
* Instantiate the class. Setting the position.
* @param p Set the axis position. Must be one of Axis.BOTTOM,
* Axis.TOP, Axis.LEFT, Axis.RIGHT, Axis.HORIZONTAL or Axis.VERTICAL.
* If one of the latter two are used then Axis.BOTTOM or
* Axis.LEFT is assumed.
*/
public Axis(int p) {
setPosition(p);
switch (position) {
case LEFT: case VERTICAL:
title.setRotation(90);
break;
case RIGHT:
title.setRotation(-90);
break;
default:
title.setRotation(0);
break;
}
}
/*
*******************
** Public Methods
******************/
/**
* Set the axis position.
* @param p Must be one of Axis.BOTTOM,
* Axis.TOP, Axis.LEFT, Axis.RIGHT, Axis.HORIZONTAL or Axis.VERTICAL.
* If one of the latter two are used then Axis.BOTTOM or
* Axis.LEFT is assumed.
*/
public void setPosition(int p) {
position = p;
switch (position) {
case LEFT:
orientation = VERTICAL;
break;
case RIGHT:
orientation = VERTICAL;
break;
case TOP:
orientation = HORIZONTAL;
break;
case BOTTOM:
orientation = HORIZONTAL;
break;
case HORIZONTAL:
orientation = HORIZONTAL;
position = BOTTOM;
break;
case VERTICAL:
orientation = VERTICAL;
position = LEFT;
break;
default:
orientation = HORIZONTAL;
position = BOTTOM;
break;
}
}
/**
* Attach a DataSet for the Axis to manage.
* @param d dataSet to attach
* @see graph.DataSet
*/
public void attachDataSet( DataSet d ) {
if( orientation == HORIZONTAL ) attachXdata( d );
else attachYdata( d );
}
/**
* Detach an attached DataSet
* @param d dataSet to detach
* @see graph.DataSet
*/
public void detachDataSet( DataSet d ) {
int i = 0;
if( d == null ) return;
if( orientation == HORIZONTAL ) {
d.xaxis = null;
} else {
d.yaxis = null;
}
dataset.removeElement(d);
if(!manualRange) resetRange();
}
/**
* Detach All attached dataSets.
*/
public void detachAll() {
int i;
DataSet d;
if( dataset.isEmpty() ) return;
if( orientation == HORIZONTAL ) {
for (i=0; i maximum) &&
! force_end_labels) {
//Try jumping range so rescaling does not happen too often
minimum = tryMinimum-0.05*(maximum-minimum);
maximum = tryMaximum+0.05*(maximum-minimum);
changed = true;
}
}
/**
* Return the position of the Axis.
* @return One of Axis.LEFT, Axis.RIGHT, Axis.TOP, or Axis.BOTTOM.
*/
public int getAxisPos() { return position; }
/**
* If the Axis is Vertical return true.
*/
public boolean isVertical() {
if( orientation == HORIZONTAL ) return false;
else return true;
}
/**
* Return the width of the axis.
* @param g graphics context.
*/
public int getAxisWidth(Graphics g) {
int i;
width = 0;
if( minimum == maximum ) return 0;
if( dataset.size() == 0 ) return 0;
calculateGridLabels();
exponent.setText(null);
if(label_exponent != 0) {
exponent.copyState(label);
exponent.setText("x10^"+String.valueOf(label_exponent));
}
if( orientation == HORIZONTAL ) {
width = label.getRHeight(g) + label.getLeading(g);
width += Math.max(title.getRHeight(g),exponent.getRHeight(g));
} else {
for(i=0; itrue if there are no inconsistencies.
*/
public boolean positionAxis(int xmin, int xmax, int ymin, int ymax ){
amin = null;
amax = null;
if( orientation == HORIZONTAL && ymin != ymax ) return false;
if( orientation == VERTICAL && xmin != xmax ) return false;
amin = new Point(xmin,ymin);
amax = new Point(xmax,ymax);
return true;
}
/**
* Draw the axis using the passed Graphics context.
* @param g Graphics context for drawing
*/
public void drawAxis(Graphics g) {
Graphics lg;
if( !redraw ) return;
if( minimum == maximum ) {
//System.out.println(
// "Axis: data minimum==maximum Trying to reset range!");
resetRange();
if( minimum == maximum ) {
// System.out.println(
// "Axis: Reseting Range failed! Axis not drawn!");
// MCC printing commented out 3/19/97
return;
}
}
if( amin.equals(amax) ) return;
if( width == 0 ) width = getAxisWidth(g);
lg = g.create();
if( force_end_labels ) {
minimum = label_start;
maximum = minimum + (label_count-1)*label_step;
}
/*
** For rotated text set the Component that is being drawn into
*/
title.setDrawingComponent(g2d);
label.setDrawingComponent(g2d);
exponent.setDrawingComponent(g2d);
if( orientation == HORIZONTAL) {
drawHAxis(lg);
} else {
drawVAxis(lg);
}
}
/**
* Set the title of the axis
* @param s string containing text.
*/
public void setTitleText(String s) { title.setText(s); }
/**
* Set the color of the title
* @param c Color of the title.
*/
public void setTitleColor(Color c) { title.setColor(c); }
/**
* Set the font of the title
* @param c Title font.
*/
public void setTitleFont(Font f) { title.setFont(f); }
/**
* Set the title rotation angle. Only multiples of 90 degrees allowed.
* @param a Title rotation angle in degrees.
*/
public void setTitleRotation(int a) { title.setRotation(a); }
/**
* Set the color of the labels
* @param c Color of the labels.
*/
public void setLabelColor(Color c) { label.setColor(c); }
/**
* Set the font of the labels.
* @param f font.
*/
public void setLabelFont(Font f) { label.setFont(f); }
/**
* Set the color of the exponent
* @param c Color.
*/
public void setExponentColor(Color c) { exponent.setColor(c); }
/**
* Set the font of the exponent
* @param f font.
*/
public void setExponentFont(Font f) { exponent.setFont(f); }
/**
* Is the range of the axis to be set automatically (based on the data)
* or manually by setting the values Axis.minimum and Axis.maximum?
* @param b boolean value.
*/
public void setManualRange(boolean b) { manualRange = b; }
/*
*********************
** Protected Methods
********************/
/**
* Draw a Horizontal Axis.
* @param g Graphics context.
*/
protected void drawHAxis(Graphics g) {
Graphics lg;
int i;
int j;
int x0,y0,x1,y1;
int direction;
int offset;
double minor_step;
Color c;
double vmin = minimum*1.001;
double vmax = maximum*1.001;
double scale = (amax.x - amin.x)/(maximum - minimum);
double val;
double minor;
// System.out.println("Drawing Horizontal Axis!");
if( axiscolor != null) g.setColor(axiscolor);
g.drawLine(amin.x,amin.y,amax.x,amax.y);
if(position == TOP ) direction = 1;
else direction = -1;
minor_step = label_step/(minor_tic_count+1);
val = label_start;
for(i=0; i= vmin && val <= vmax ) {
y0 = amin.y;
x0 = amin.x + (int)( ( val - minimum ) * scale);
if( Math.abs(label_value[i]) <= 0.0001 && drawzero ) {
c = g.getColor();
if(zerocolor != null) g.setColor(zerocolor);
g.drawLine(x0,y0,x0,y0+data_window.height*direction);
g.setColor(c);
} else
if( drawgrid ) {
c = g.getColor();
if(gridcolor != null) g.setColor(gridcolor);
g.drawLine(x0,y0,x0,y0+data_window.height*direction);
g.setColor(c);
}
x1 = x0;
y1 = y0 + major_tic_size*direction;
g.drawLine(x0,y0,x1,y1);
}
minor = val + minor_step;
for(j=0; j= vmin && minor <= vmax ) {
y0 = amin.y;
x0 = amin.x + (int)( ( minor - minimum ) * scale);
if( drawgrid ) {
c = g.getColor();
if(gridcolor != null) g.setColor(gridcolor);
g.drawLine(x0,y0,x0,y0+data_window.height*direction);
g.setColor(c);
}
x1 = x0;
y1 = y0 + minor_tic_size*direction;
g.drawLine(x0,y0,x1,y1);
}
minor += minor_step;
}
val += label_step;
}
if(position == TOP ) {
offset = - label.getLeading(g) - label.getDescent(g);
} else {
offset = + label.getLeading(g) + label.getAscent(g);
}
val = label_start;
for(i=0; i= vmin && val <= vmax ) {
y0 = amin.y + offset;
x0 = amin.x + (int)(( val - minimum ) * scale);
label.setText(label_string[i]);
label.draw(g,x0,y0,TextLine.CENTER);
}
val += label_step;
}
if( !exponent.isNull() ) {
if(position == TOP ) {
y0 = amin.y - label.getLeading(g)
- label.getDescent(g)
- exponent.getLeading(g)
- exponent.getDescent(g);
} else {
y0 = amax.y + label.getLeading(g)
+ label.getAscent(g)
+ exponent.getLeading(g)
+ exponent.getAscent(g);
}
x0 = amax.x;
exponent.draw(g,x0,y0,TextLine.LEFT);
}
if( !title.isNull() ) {
if(position == TOP ) {
y0 = amin.y - label.getLeading(g)
- label.getDescent(g)
- title.getLeading(g)
- title.getDescent(g);
} else {
y0 = amax.y + label.getLeading(g)
+ label.getAscent(g)
+ title.getLeading(g)
+ title.getAscent(g);
}
x0 = amin.x + ( amax.x - amin.x)/2;
title.draw(g,x0,y0,TextLine.CENTER);
}
}
/**
* Draw a Vertical Axis.
* @param g Graphics context.
*/
protected void drawVAxis(Graphics g) {
Graphics lg;
int i;
int j;
int x0,y0,x1,y1;
int direction;
int offset = 0;
double minor_step;
double minor;
Color c;
FontMetrics fm;
Color gc = g.getColor();
Font gf = g.getFont();
double vmin = minimum*1.001;
double vmax = maximum*1.001;
double scale = (amax.y - amin.y)/(maximum - minimum);
double val;
// System.out.println("Drawing Vertical Axis!");
if( axiscolor != null) g.setColor(axiscolor);
g.drawLine(amin.x,amin.y,amax.x,amax.y);
if(position == RIGHT ) direction = -1;
else direction = 1;
minor_step = label_step/(minor_tic_count+1);
val = label_start;
for(i=0; i= vmin && val <= vmax ) {
x0 = amin.x;
y0 = amax.y - (int)( ( val - minimum ) * scale);
if( Math.abs(label_value[i]) <= 0.0001 && drawzero ) {
c = g.getColor();
if(zerocolor != null) g.setColor(zerocolor);
g.drawLine(x0,y0,x0+data_window.width*direction,y0);
g.setColor(c);
} else
if( drawgrid ) {
c = g.getColor();
if(gridcolor != null) g.setColor(gridcolor);
g.drawLine(x0,y0,x0+data_window.width*direction,y0);
g.setColor(c);
}
x1 = x0 + major_tic_size*direction;
y1 = y0;
g.drawLine(x0,y0,x1,y1);
}
minor = val + minor_step;
for(j=0; j= vmin && minor <= vmax ) {
x0 = amin.x;
y0 = amax.y - (int)( ( minor - minimum ) * scale);
if( drawgrid ) {
c = g.getColor();
if(gridcolor != null) g.setColor(gridcolor);
g.drawLine(x0,y0,x0+ data_window.width*direction,y0);
g.setColor(c);
}
x1 = x0 + minor_tic_size*direction;
y1 = y0;
g.drawLine(x0,y0,x1,y1);
}
minor += minor_step;
}
val += label_step;
}
val = label_start;
for(i=0; i= vmin && val <= vmax ) {
x0 = amin.x + offset;
y0 = amax.y - (int)(( val - minimum ) * scale) +
label.getAscent(g)/2;
if(position == RIGHT ) {
label.setText(" "+label_string[i]);
label.draw(g,x0,y0,TextLine.LEFT);
} else {
label.setText(label_string[i]+" ");
label.draw(g,x0,y0,TextLine.RIGHT);
}
}
val += label_step;
}
if( !exponent.isNull() ) {
y0 = amin.y;
if(position == RIGHT ) {
x0 = amin.x + max_label_width + exponent.charWidth(g,' ');
exponent.draw(g,x0,y0,TextLine.LEFT);
} else {
x0 = amin.x - max_label_width - exponent.charWidth(g,' ');
exponent.draw(g,x0,y0,TextLine.RIGHT);
}
}
if( !title.isNull() ) {
y0 = amin.y + (amax.y-amin.y)/2;
if( title.getRotation() == 0 || title.getRotation() == 180 ) {
if(position == RIGHT ) {
x0 = amin.x + max_label_width + title.charWidth(g,' ');
title.draw(g,x0,y0,TextLine.LEFT);
} else {
x0 = amin.x - max_label_width - title.charWidth(g,' ');
title.draw(g,x0,y0,TextLine.RIGHT);
}
} else {
title.setJustification(TextLine.CENTER);
if(position == RIGHT ) {
x0 = amin.x + max_label_width - title.getLeftEdge(g)+
+ title.charWidth(g,' ');
} else {
x0 = amin.x - max_label_width - title.getRightEdge(g)
- title.charWidth(g,' ');
}
title.draw(g,x0,y0);
}
}
}
/**
* Attach a DataSet to a Horizontal Axis
* @param d dataset to attach.
*/
protected void attachXdata( DataSet d ) {
dataset.addElement(d);
d.xaxis = this;
if( dataset.size() == 1 ) {
minimum = d.dxmin;
maximum = d.dxmax;
} else {
if(minimum > d.dxmin) minimum = d.dxmin;
if(maximum < d.dxmax) maximum = d.dxmax;
}
}
/**
* Attach a DataSet to a Vertical Axis
* @param d dataset to attach.
*/
protected void attachYdata( DataSet d ) {
dataset.addElement(d);
d.yaxis = this;
if( dataset.size() == 1 ) {
minimum = d.dymin;
maximum = d.dymax;
} else {
if(minimum > d.dymin) minimum = d.dymin;
if(maximum < d.dymax) maximum = d.dymax;
}
}
/**
* calculate the labels
*/
protected void calculateGridLabels() {
double val;
int i;
int j;
if (Math.abs(minimum) > Math.abs(maximum) )
label_exponent = ((int)Math.floor(
log10(Math.abs(minimum))/3.0) )*3;
else
label_exponent = ((int)Math.floor(
log10(Math.abs(maximum))/3.0) )*3;
label_step = RoundUp( (maximum-minimum)/guess_label_number );
label_start = Math.floor( minimum/label_step )*label_step;
val = label_start;
label_count = 1;
while(val < maximum) { val += label_step; label_count++; }
label_string = new String[label_count];
label_value = new float[label_count];
// System.out.println("label_step="+label_step);
// System.out.println("label_start="+label_start);
// System.out.println("label_count="+label_count);
// System.out.println("label_exponent"+label_exponent);
for(i=0; i 5.0 ) val = 10.0;
else
if( val > 2.0 ) val = 5.0;
else
if( val > 1.0 ) val = 2.0;
else
val = 1.0;
if( exponent < 0 ) {
for(i=exponent; i<0; i++) { val /= 10.0; }
} else {
for(i=0; i