package freegraph; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.util.*; /** * GraphPlane is a X-Y or cartesian coordinate plane which draws the axis and a * list of GraphPlaneItems (typically instances of ExpressionEvaluator). * The GraphPlane handles the zooming with the mouse and can be monitored * with GraphPlaneListener to receive notification of axis changes or * mouse position changes.
* No Graphics2D is used to keep it compatible with Java 1.1.
*/
public class GraphPlane extends Component implements ActionListener
{
private double xMin = -20;
private double yMin = -20;
private double xMax = 20;
private double yMax = 20;
private double xTick = 5;
private double yTick = 5;
private int xZoomStart = 0;
private int yZoomStart = 0;
private int xZoomCurr = 0;
private int yZoomCurr = 0;
private boolean mouseDown = false;
private int widthVar = 0;
private int heightVar = 0;
private Color axisColor = Color.blue;
private Color outlineColor = Color.black;
private Color bgColor = Color.lightGray;
private Color zoomColor = Color.red;
private InvertRectangle invRect;
private PopupMenu pupMenuGraphPlane = new PopupMenu();
private MenuItem mniReset = new MenuItem();
private MenuItem mniZoomout = new MenuItem();
private MenuItem mniZoomin = new MenuItem();
private Vector graphPlaneItemsList = new Vector();
private Vector graphPlaneListenerList = new Vector();
private boolean drawGrid = true;
public static int XMIN_DEFAULT = -20;
public static int YMIN_DEFAULT = -20;
public static int XMAX_DEFAULT = 20;
public static int YMAX_DEFAULT = 20;
/****
* constructs a new GraphPlane
*/
public GraphPlane()
{
init();
System.out.println("GraphPlane, Copyright 1998 by Martin Lovell, " +
"ALL RIGHTS RESERVED");
}
/***
* initializes a new GraphPlane by creating the popup menu items and
* setting up mouse listeners for the component
*/
private void init()
{
mniReset.setLabel("Reset");
mniZoomout.setLabel("Zoom Out");
mniZoomin.setLabel("Zoom In");
pupMenuGraphPlane.add(mniReset);
pupMenuGraphPlane.add(mniZoomout);
pupMenuGraphPlane.add(mniZoomin);
pupMenuGraphPlane.addActionListener(this);
add(pupMenuGraphPlane);
addMouseListener(new GraphPlaneMouseAdapter(this));
addMouseMotionListener(new GraphPlaneMouseMotionAdapter(this));
}
/****
* returns the maximum X value for the GraphPlane
*/
public double getXMax()
{
return xMax;
}
/****
* returns the minimum X value for the GraphPlane
*/
public double getXMin()
{
return xMin;
}
/****
* returns the maximum Y value for the GraphPlane
*/
public double getYMax()
{
return yMax;
}
/****
* returns the minimum Y value for the GraphPlane
*/
public double getYMin()
{
return yMin;
}
/****
* returns an Enumeration of the GraphPlanItems
*/
public Enumeration graphPlaneItems()
{
return graphPlaneItemsList.elements();
}
/**
* adds a GraphPlaneItem to be drawn when the graph is drawn
*/
public void addGraphPlaneItem(GraphPlaneItem gp)
{
graphPlaneItemsList.addElement(gp);
}
/**
* removes a GraphPlaneItem
*/
public void removeGraphPlaneItem(GraphPlaneItem gp)
{
graphPlaneItemsList.removeElement(gp);
}
/****
* adds a GraphPlaneListener to receive events about the mouse location and
* the size of the GraphPlane.
*/
public void addGraphPlaneListener(GraphPlaneListener l)
{
graphPlaneListenerList.addElement(l);
}
/***
* removes a GraphPlaneListener
*/
public void removeGraphPlaneListener(GraphPlaneListener l)
{
graphPlaneListenerList.removeElement(l);
}
/*****
* resets the GraphPlane to its default values
*/
public void reset()
{
xMin = XMIN_DEFAULT;
yMin = YMIN_DEFAULT;
xMax = XMAX_DEFAULT;
yMax = YMAX_DEFAULT;
repaint();
}
/****
* zooms in the graph
*/
public void zoomout()
{
double dX = (xMax - xMin) / 2;
double dY = (yMax - yMin) / 2;
xMin = xMin - dX;
yMin = yMin - dY;
xMax = xMax + dX;
yMax = yMax + dY;
repaint();
}
/***
* zooms out the graph
*/
public void zoomin()
{
double dX = (xMax - xMin) / 4;
double dY = (yMax - yMin) / 4;
xMin = xMin + dX;
yMin = yMin + dY;
xMax = xMax - dX;
yMax = yMax - dY;
repaint();
}
/***
* dispatches action events from the popup menu.
*/
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals("Reset")) reset();
else
if (e.getActionCommand().equals("Zoom Out")) zoomout();
else
if (e.getActionCommand().equals("Zoom In")) zoomin();
}
/***
* sets up some instance variables used during painting
*/
private void setupBoundVars()
{
Rectangle brect = getBounds();
widthVar = brect.width - 1;
heightVar = brect.height - 1;
}
/***
* paints the graph by drawing the axis and then drawing each
* GraphPlaneItem.
*/
public void paint(Graphics g)
{
super.paint(g);
drawGraph(g);
for (int i = 0; i < graphPlaneItemsList.size(); i++)
{
((GraphPlaneItem)graphPlaneItemsList.elementAt(i)).drawItem(this,
g, 'X');
System.out.println("Drawing item: " +
graphPlaneItemsList.elementAt(i));
}
}
/***
* converts a double to a string with at most 9 digits
*/
private String doubleToString(double x)
{
int numDigits = 9;
StringBuffer str;
str = new StringBuffer(Double.toString(x));
String s = str.toString();
int i = s.indexOf('E');
if (i == -1)
{
i = s.indexOf('.');
if (i != 0 && str.length() > i + numDigits) str.setLength(i + numDigits);
}
else
if (i > numDigits)
{
int j;
for (j=i; j
* roundTo(0.5, 7.624) should return 7.5.
* @param roundX the number to round to
* @param value the number to round
*/
double roundTo(double roundX, double value)
{
return Math.rint(value / roundX) * roundX;
}
public void mouseClicked(MouseEvent event)
{
}
/**
* If the left mouse button is pressed, global variables are used to
* store the mouse location for use with a zoom.
* If right mouse button is pressed, the popup menu is
* displayed.
*/
public void mousePressed(MouseEvent event)
{
if ((event.getModifiers() == Event.META_MASK) ||
(event.isPopupTrigger()))
{
// because e.isPopupTrigger() isn't working in 1.1.2 WinAWT
pupMenuGraphPlane.show(this, event.getX(), event.getY());
}
else
{
mouseDown = true;
xZoomStart = event.getX();
yZoomStart = event.getY();
xZoomCurr = event.getX();
yZoomCurr = event.getY();
}
}
/**
* If a zoom rectangle is being drawn, the graph's axis are changed and
* the graph is updated. When zooming, the values are rounded based on
* the range and domain of the graph.
*/
public void mouseReleased(MouseEvent event)
{
if (!mouseDown) return;
Graphics g = getGraphics();
g.setColor(bgColor);
drawInvertRect(g, xZoomStart, yZoomStart, xZoomCurr, yZoomCurr);
mouseDown = false;
if (!(Math.abs(xZoomStart - xZoomCurr) < 10 ||
Math.abs(yZoomStart - yZoomCurr) < 10))
{
double xa = deconvertX(xZoomStart);
double xb = deconvertX(xZoomCurr);
double ya = deconvertY(yZoomStart);
double yb = deconvertY(yZoomCurr);
if (xa < xb)
{
xMin = xa;
xMax = xb;
}
else
{
xMax = xa;
xMin = xb;
}
if (ya < yb)
{
yMin = ya;
yMax = yb;
}
else
{
yMax = ya;
yMin = yb;
}
/**
* Rounds based on the range and domain
*/
double r;
double log10;
log10 = (Math.log((xMax - xMin)/2)/Math.log(10));
if (log10 >= 0) r = 1;
else r = Math.pow(10, Math.rint(log10 - 2));
xMin = roundTo(r, xMin);
xMax = roundTo(r, xMax);
if (xMin == xMax) xMax = xMax + r;
log10 = (Math.log((yMax - yMin)/2)/Math.log(10));
if (log10 >= 0) r = 1;
else r = Math.pow(10, Math.rint(log10 - 2));
yMin = roundTo(r, yMin);
yMax = roundTo(r, yMax);
if (yMin == yMax) yMax = yMax + r;
}
repaint();
}
/**
* draws the zoom (invert) rectangle
*/
public void mouseDragged(MouseEvent event)
{
if (mouseDown)
{
Graphics g = getGraphics();
if ((xZoomStart != xZoomCurr) || (yZoomStart != yZoomCurr))
{
g.setColor(bgColor);
drawInvertRect(g, xZoomStart, yZoomStart,
xZoomCurr, yZoomCurr);
drawGraph(g);
}
xZoomCurr = event.getX();
yZoomCurr = event.getY();
g.setColor(zoomColor);
drawInvertRect(g, xZoomStart, yZoomStart, xZoomCurr, yZoomCurr);
}
}
/**
* converts the position of the mouse to graph coordinates and fires
* event to GraphPlaneListeners. If the mouse position on a pixel
* which could also be represented by a number more round than the
* calculated value, the number is rounded.
*/
public void mouseMoved(MouseEvent event)
{
double xPos = deconvertX(event.getX());
double yPos = deconvertY(event.getY());
if (convertX(xPos) == convertX(roundTo(0.01, xPos)))
xPos = roundTo(0.01, xPos);
if (convertY(yPos) == convertY(roundTo(0.01, yPos)))
yPos = roundTo(0.01, yPos);
if (convertX(xPos) == convertX(roundTo(0.1, xPos)))
xPos = roundTo(0.1, xPos);
if (convertY(yPos) == convertY(roundTo(0.1, yPos)))
yPos = roundTo(0.1, yPos);
if (convertX(xPos) == convertX(Math.rint(xPos)))
xPos = Math.rint(xPos);
if (convertY(yPos) == convertY(Math.rint(yPos)))
yPos = Math.rint(yPos);
xPos = shortenDouble(xPos);
yPos = shortenDouble(yPos);
for (int i =0; i < graphPlaneListenerList.size(); i++)
((GraphPlaneListener)graphPlaneListenerList.
elementAt(i)).mousePosMoved(xPos, yPos);
}
}
/**
* Adapter used to pass events to appropriate methods in GraphPlane
*/
class GraphPlaneMouseAdapter extends java.awt.event.MouseAdapter
{
GraphPlane adaptee;
GraphPlaneMouseAdapter(GraphPlane graphPlane)
{
adaptee = graphPlane;
}
public void mouseClicked(MouseEvent event)
{
adaptee.mouseClicked(event);
}
public void mousePressed(MouseEvent event)
{
adaptee.mousePressed(event);
}
public void mouseReleased(MouseEvent event)
{
adaptee.mouseReleased(event);
}
}
/**
* Adapter used to pass events to appropriate methods in GraphPlane
*/
class GraphPlaneMouseMotionAdapter extends java.awt.event.MouseMotionAdapter
{
GraphPlane adaptee;
GraphPlaneMouseMotionAdapter(GraphPlane graphPlane)
{
adaptee = graphPlane;
}
public void mouseDragged(MouseEvent event)
{
adaptee.mouseDragged(event);
}
public void mouseMoved(MouseEvent event)
{
adaptee.mouseMoved(event);
}
}