import java.applet.Applet;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.border.BevelBorder;
import functionParser.Evaluator;
import functionParser.FunctionParser;
import functionParser.FunctionParsingException;
/**
* LCGraph.java, a class designed to take a user inputed function and graph
* its level curve.
*
* Only works for functions of two variables.
*
*
* @author Andrew Trusty
*/
public class LCGraph extends Applet {
private JTextField functionInput;
private JTextField variableInput;
private JTextField nInput;
private JTextField xInput;
private JTextField epsInput;
private JPanel graph;
private double h = Math.pow(10, -10);
private boolean corrector = false;
private boolean graphed = false;
private boolean axes = false;
private int xmid = 190;
private int ymid = 90;
private int scale = 60;
private double[][] thePoints;
/**
* Initialize the applet.
*/
public void init() {
setLayout(null);
setSize(new Dimension(510, 300));
setBackground(Color.WHITE);
graph = new JPanel();
graph.addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent arg0) {
// do nothing
}
public void mouseMoved(MouseEvent arg0) {
displayLoc(arg0.getPoint());
}
});
JScrollPane graphPane = new JScrollPane(graph);
graphPane.setBorder(new BevelBorder(0));
graphPane.setBounds(10, 10, 380, 180);
JPanel inputPanel = new JPanel(null);
inputPanel.setBounds(0, 200, 400, 110);
inputPanel.setBackground(Color.WHITE);
// need epsilon, N, and x0
JLabel nLabel = new JLabel("N:");
nLabel.setBounds(10, 5, 20, 20);
nInput = new JTextField("65");
nInput.setBounds(23, 5, 40, 20);
JLabel epsLabel = new JLabel("epsilon:");
epsLabel.setBounds(70, 5, 45, 20);
epsInput = new JTextField(".1");
epsInput.setBounds(115, 5, 60, 20);
JLabel xLabel = new JLabel("X(0):");
xLabel.setBounds(183, 5, 100, 20);
xInput = new JTextField("1,0");
xInput.setBounds(210, 5, 170, 20);
JLabel varLabel = new JLabel("Variables (x,y):");
varLabel.setBounds(27, 30, 105, 20);
variableInput = new JTextField("x,y");
variableInput.setBounds(115, 30, 170, 20);
JLabel inLabel = new JLabel("Function: ");
inLabel.setBounds(61, 50, 75, 20);
functionInput = new JTextField("(x^2)+(y^2)");
functionInput.setBounds(115, 50, 170, 20);
JButton graphButton = new JButton("Graph");
graphButton.setBounds(295, 40, 80, 20);
graphButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
parseFunctionInput(functionInput.getText(),
variableInput.getText(), nInput.getText(),
xInput.getText(), epsInput.getText());
graphed = true;
}
});
JCheckBox useCorrector = new JCheckBox("Use Correction?");
useCorrector.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
corrector = !corrector;
if (graphed) {
parseFunctionInput(functionInput.getText(),
variableInput.getText(), nInput.getText(),
xInput.getText(), epsInput.getText());
}
}
});
useCorrector.setBackground(inputPanel.getBackground());
useCorrector.setBounds(115, 70, 200, 20);
JLabel scaleLbl = new JLabel("Scale");
scaleLbl.setBounds(432, 6, 100, 30);
JButton pScale = new JButton(">>");
pScale.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
scale += 3;
draw();
}
});
pScale.setBounds(450, 30, 50, 20);
JButton mScale = new JButton("<<");
mScale.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
scale -= 3;
draw();
}
});
mScale.setBounds(400, 30, 50, 20);
JLabel xLbl = new JLabel("X Viewpoint");
xLbl.setBounds(417, 60, 100, 20);
JButton pX = new JButton(">>");
pX.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
xmid -= 3;
draw();
}
});
pX.setBounds(450, 80, 50, 20);
JButton mX = new JButton("<<");
mX.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
xmid += 3;
draw();
}
});
mX.setBounds(400, 80, 50, 20);
JLabel yLbl = new JLabel("Y Viewpoint");
yLbl.setBounds(417, 110, 100, 20);
JButton pY = new JButton(">>");
pY.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ymid += 3;
draw();
}
});
pY.setBounds(450, 130, 50, 20);
JButton mY = new JButton("<<");
mY.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ymid -= 3;
draw();
}
});
mY.setBounds(400, 130, 50, 20);
inputPanel.add(useCorrector);
inputPanel.add(nLabel);
inputPanel.add(nInput);
inputPanel.add(xLabel);
inputPanel.add(xInput);
inputPanel.add(epsLabel);
inputPanel.add(epsInput);
inputPanel.add(graphButton);
inputPanel.add(varLabel);
inputPanel.add(variableInput);
inputPanel.add(inLabel);
inputPanel.add(functionInput);
add(scaleLbl);
add(mScale);
add(pScale);
add(xLbl);
add(mX);
add(pX);
add(yLbl);
add(mY);
add(pY);
add(inputPanel);
add(graphPane);
}
/**
* Parses the input from the text fields and creates the necessary
* variables to begin calculating the thePoints and calls the point
* calculation method.
*
* @param equation the string descriptioin of the equation
* @param variables the variables in the equation
* @param n the number of steps
* @param xnot the first point
* @param eps epsilon
*/
public void parseFunctionInput(String equation, String variables, String n,
String xnot, String eps) {
// SETUP VARIABLES
int numVars = variables.split(",").length;
String[] coords = xnot.split(",");
// code only works for 2 variables, check for 2
if ((numVars == 2) && (coords.length == 2)) {
try {
// get number of steps to take
int steps = Integer.parseInt(n);
// get epsilon
double epsilon = Double.parseDouble(eps);
// get starting point X0
double[] point = new double[2];
point[0] = Double.parseDouble(coords[0]);
point[1] = Double.parseDouble(coords[1]);
// create the thePoints array and add X0
thePoints = new double[steps + 1][steps + 1];
thePoints[0] = point;
// SETUP FUNCTION
// create a function with the given variables
FunctionParser parser = new FunctionParser(variables);
// create an Evaluator with the necessary number of variables
Evaluator eqEval = new Evaluator(numVars);
// parse the function with the new evaluator
try {
parser.parse(equation, eqEval);
} catch (FunctionParsingException fpe) {
System.err.println("Error parsing function.");
}
// begin loop of point approximation
calculatethePoints(steps, epsilon, eqEval);
} catch (NumberFormatException nfe) {
System.err.println("Invalid Input");
}
}
}
/**
* Determines the gradient at the given values using the given function
* evaluator.
*
* @param eqEval the funciton evaluator
* @param x value
* @param y value
* @return gradient array
*/
public double[] gradientAt(Evaluator eqEval, double x, double y) {
double xprime;
double yprime;
double fx;
double fxh;
double[] gradient = new double[2];
xprime = x + h;
yprime = y + h;
// find f(x current)
fx = eqEval.evaluate(x, y);
// find gradient component for the derivative of x at current x
fxh = eqEval.evaluate(xprime, y);
gradient[0] = (fxh - fx) / h;
// find gradient component for the derivative of y at current y
fxh = eqEval.evaluate(x, yprime);
gradient[1] = (fxh - fx) / h;
return gradient;
}
/**
* Returns a vector perpindicular to the given vector.
*
* @param vector to get a perpindicular vector for
* @return a perpindicular vector
*/
public double[] perpindicularVector(double[] vector) {
return new double[] {vector[1], -vector[0]};
}
/**
* Normalizes the given vector.
*
* @param vector to normalize
* @return the normalized vector
*/
public double[] normalizeVector(double[] vector) {
double sum1;
double sum2;
double[] u = new double[2];
sum1 = vector[0] * vector[0];
sum2 = vector[1] * vector[1];
sum1 += sum2;
sum1 = 1 / Math.sqrt(sum1);
u[0] = sum1 * vector[0];
u[1] = sum1 * vector[1];
return u;
}
/**
* Computes the norm of the given vector.
*
* @param vector to find norm of
* @return the norm
*/
public double vectorNorm(double[] vector) {
double sum1;
double sum2;
sum1 = vector[0] * vector[0];
sum2 = vector[1] * vector[1];
return sum1 + sum2;
}
/**
* Using the given values approximates enough points to fill the
* Points array then calls the draw function to graph the the points.
*
* @param steps how many steps to calculate
* @param epsilon to us
* @param eqEval the equation evaluator
*/
private void calculatethePoints(int steps, double epsilon,
Evaluator eqEval) {
double x;
double y;
double fxnew;
double fxold;
double normSqrd;
double component;
double[] gradient;
double[] v;
double[] u;
for (int j = 0; j < steps; j++) {
x = thePoints[j][0];
y = thePoints[j][1];
gradient = gradientAt(eqEval, x, y);
// get the vector perpindicular to the gradient
v = perpindicularVector(gradient);
// normalize v to obtain u and multiple u by epsilon
u = normalizeVector(v);
// scale u by epsilon
u[0] *= epsilon;
u[1] *= epsilon;
// add current x to this point to obtain the next x
u[0] = thePoints[j][0] + u[0];
u[1] = thePoints[j][1] + u[1];
if ((j > 0) && corrector) {
// Extra Credit Corrector
// compute f(x,y), if not equal to f(x-1,y-1) then correct
fxnew = eqEval.evaluate(u[0], u[1]);
fxold = eqEval.evaluate(thePoints[j - 1][0],
thePoints[j - 1][1]);
if (fxnew != fxold) { // need to correct
gradient = gradientAt(eqEval, u[0], u[1]);
// get norm^2 of gradient
normSqrd = Math.pow(vectorNorm(gradient), 2);
// calculate the number to multiply by the gradient
component = (fxold - fxnew) / normSqrd;
// scale the component by the gradient
gradient[0] *= component;
gradient[1] *= component;
// find new point by adding old point to changed gradient
u[0] += gradient[0];
u[1] += gradient[1];
}
}
// set the next x
thePoints[j + 1][0] = u[0];
thePoints[j + 1][1] = u[1];
} // end for loop
draw();
}
/**
* Connects the set of thePoints into a set of lines drawn
* on the graph JPanel.
*/
public void draw() {
Graphics g = graph.getGraphics();
g.setColor(graph.getBackground());
g.fillRect(0, 0, 380, 190);
g.setColor(Color.BLACK);
g.drawLine(0, ymid, 380, ymid);
g.drawLine(xmid, 0, xmid, 180);
if (thePoints != null) {
for (int i = 0; i < thePoints.length; i++) {
if (i < (thePoints.length - 2)) {
g.drawLine((int) (thePoints[i][0] * scale) + xmid,
-(int) (thePoints[i][1] * scale) + ymid,
(int) (thePoints[i + 1][0] * scale) + xmid,
-(int) (thePoints[i + 1][1] * scale) + ymid);
} else {
g.drawLine((int) (thePoints[i][0] * scale) + xmid,
-(int) (thePoints[i][1] * scale) + ymid,
(int) (thePoints[0][0] * scale) + xmid,
-(int) (thePoints[0][1] * scale) + ymid);
}
}
}
}
/**
* Displays the location of the cursor.
*
* @param p the point of the cursor
*/
private void displayLoc(Point p) {
if (!graphed && !axes) {
draw();
axes = true;
}
Graphics g = graph.getGraphics();
g.setColor(graph.getBackground());
g.fillRect(4, 4, 55, 15);
g.setColor(Color.BLACK);
g.drawString(((p.x - xmid) / scale) + ", " + (-(p.y - ymid) / scale),
5, 15);
}
/**
* Paints the applet.
*
* @param arg0 the Graphics object
*/
public void paint(Graphics arg0) {
super.paint(arg0);
draw();
}
}