package grapher2D;
import expressionConsole.ExpressionConsoleModel;
import generalMathClasses.CoordinateSpace2D;
import generalMathClasses.Window2D;
import graphicsUtilities.GeneralGraphicsUtilities;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JPanel;
import parser.ExpressionNode;
import parser.RecursiveDescentParser;
import parser.Value;
import valueTypes.BooleanValue;
import valueTypes.DecimalValue;
import variables.Variable;
/**
* The view of the Model View Controller paradigm for the Grapher. In this
* paradigm, the model stores information necessary to produce the view (in this
* case this information includes the function string, coordinate space, and
* some other things.) The controller is the UI which modifies the model (in
* this case it is the function field). The view is the graphical representation
* of the model. Typically there is some overlap between the roles of the view
* and the model (such as zooming by drawing a rectangle on the view, or
* displayed parameters in the controller which must be updated to reflect
* changes in the model. The model, view, and controller communicate using the
* Observer design pattern. The model is the Observable, and the controller and
* view are Observers of the model. When the model changes, it sends a
* notification event to all Observers, thus signaling them to update so that
* they reflect the current state of the model.
*
* @author Curran Kelleher
*
*/
public class Grapher2DView extends JPanel implements Observer, Runnable,
ComponentListener {
private static final long serialVersionUID = 6185291782620102768L;
/**
* The window that will translate between pixel space and coordinate space.
* It is constructed with a reference to the coordinate space stored in the
* model. It will always use this reference, so will automatically reflect
* any changes made to the coordinate space in the model.
*/
Window2D window = new Window2D(Grapher2DConstants.coordinateSpace);
/**
* A reference to the parser. (so we don't need call
* ExpressionConsoleModel.getInstance().getParser() more than once)
*/
RecursiveDescentParser parser = ExpressionConsoleModel.getInstance()
.getParser();
/**
* A reference to the function string variable. (so we don't need call
* Variable.getVariable(Grapher2DConstants.Grapher2DFunctionString) more
* than once)
*/
ExpressionNode functionStringVar = Variable
.getVariable(Grapher2DConstants.Grapher2DFunctionString);
/**
* A reference to the model's evaluation tree of the function to graph.
*/
ExpressionNode functionEvaluationTree = null;
/**
* A boolean flag signaling whether or not the view should be animating. It
* reflects the state of the Variable whose name is stored in
* Grapher2DConstants.Grapher2DAnimateFlag.
*/
boolean animate = true;
/**
* When this is set to true, the animation thread is stopped permanently.
*/
public boolean stopThread = false;
/**
* The evaluation tree to increment t.
*/
ExpressionNode tIncrementExpression = ExpressionConsoleModel.getInstance()
.getParser().parse("t = t + tIncrement");
/**
* Get references to the mapped x, y, and t variables used in the evaluation
* tree for use later
*/
Variable xVar = Variable.getVariable("x"),
tVar = Variable.getVariable("t"), yVar = Variable.getVariable("y");
/**
* A flag which keeps track of whether or not an error has occured.
*/
boolean error = false;
/**
* A flag which is set to true when the points are initialized for the first
* time. It is used to prevent premature attempts to draw the points.
*/
boolean pointsHaveBeenInitialized = false;
/**
* If error is true, the contents of this string will be displayed.
*/
String errorMessage = "";
// temporary variables declared here for optimization of re-use
CoordinateSpace2D coordinateSpace;
double maxMinusMinX, xVal, yVal;
DecimalValue xDecimalValue = new DecimalValue(0);
int numPoints = 0, i;
Point[] points = new Point[0];
Value result;
Dimension size = new Dimension();
/**
* Upon construction, the animation thread is started.
*
*/
public Grapher2DView() {
// Initialize and set up the Observer pattern with the appropriate
// Variables.
initializeVariables();
// initialize the size of the screen and resolution of the graph
updateSizes();
// start the animation thread
(new Thread(this)).start();
// set up the resize listener
addComponentListener(this);
}
/**
* Initializes and sets up the Observer pattern with the appropriate
* Variables.
*
*/
private void initializeVariables() {
// Observe the function string for changes
Variable
.getVariable(Grapher2DConstants.Grapher2DFunctionString)
.addObserver(this,
"Used by the Grapher2D drawing space as the descriprion of the graph to draw.");
// initialize the animation flag to true
Variable animationFlagVar = Variable
.getVariable(Grapher2DConstants.Grapher2DAnimateFlag);
animationFlagVar.set(new BooleanValue(true));
// Observe the animation flag for changes
animationFlagVar
.addObserver(
this,
"Used by the Grapher2D drawing space to signal when to turn animation on (value = true) and off (value = false)");
Variable resolutionVar = Variable
.getVariable(Grapher2DConstants.Grapher2DResolution);
// initialize the resolution to 500
resolutionVar.set(new DecimalValue(500));
// Observe the resolution variable for changes
resolutionVar
.addObserver(
this,
"Used by the Grapher2D drawing space to define the number of points used to construct the graph.");
// initialize tIncrement
Variable.getVariable(Grapher2DConstants.TimeIncrement).set(
new DecimalValue(0.1));
}
/**
* Called from Swing to repaint the panel.
*/
public void paint(Graphics g) {
// clear away the old crap
g.clearRect(0, 0, size.width, size.height);
// if there was an error, show it on the screen
if (error)
// g.drawString(errorMessage, 20, 20);
GeneralGraphicsUtilities.drawErrorMessage((Graphics2D) g, errorMessage,
size.width);
// otherwise connect the precomputed dots to draw the graph
else if (pointsHaveBeenInitialized)
for (int i = 0; i < numPoints - 1; i++)
g.drawLine(points[i].x, points[i].y, points[i + 1].x,
points[i + 1].y);
}
/**
* Responds to notifications from the function string Variable when it has
* changed.
*/
public void update(Observable o, Object arg) {
if (arg instanceof Variable) {
String variableName = ((Variable) arg).toString();
// if the function string has changed
if (variableName.equals(Grapher2DConstants.Grapher2DFunctionString)) {
// parse the function into a usable evaluation tree
functionEvaluationTree = parser.parse("executeFunction({"
+ functionStringVar.evaluate().toString() + "})");
//reset t to 0
tVar.set(new DecimalValue(0));
// the animation thread is constantly updating the graph and
// recalculating, so we the change is updated for, needn't do
// anything more here.
}
// if the animation flag has changed
else if (variableName
.equals(Grapher2DConstants.Grapher2DAnimateFlag))
// update our internal representation of it, whose state will be
// acted upon by the animation thread.
animate = Variable.getVariable(
Grapher2DConstants.Grapher2DAnimateFlag).evaluate()
.toString().equals("true") ? true : false;
// if the resolution has changed
else if (variableName
.equals(Grapher2DConstants.Grapher2DResolution))
// update the size of the screen and resolution of the graph
updateSizes();
}
}
/**
* Updates the size of the screen and resolution of the graph
*
*/
private void updateSizes() {
// store the size of the window for use later
size = getSize();
// make sure the window has the correct panel (pixel space) dimensions.
window.set(size);
// get the graph drawing resolution from the model
Value resolutionValue = Variable.getVariable(
Grapher2DConstants.Grapher2DResolution).evaluate();
int newNumPoints = 0;
if (resolutionValue instanceof DecimalValue)
newNumPoints = (int) ((DecimalValue) resolutionValue).value;
else {
ExpressionConsoleModel
.getInstance()
.enterErrorMessage(
"The variable "
+ Grapher2DConstants.Grapher2DResolution
+ ", used by the grapher to specify the resolution of the graph, has an invalid value, "
+ resolutionValue
+ ", so it will be reset to 500.");
ExpressionConsoleModel.getInstance().enterExpression(
Grapher2DConstants.Grapher2DResolution + " = 500");
}
// if the resolution has changed, re-allocate the points array
if (newNumPoints != numPoints)
points = new Point[numPoints = newNumPoints];
}
/**
* Calculates the points used to draw the graph.
*
*/
private void calculatePoints() {
// if the function is still not there,
if (functionEvaluationTree == null) {
// report the error that there is no function.
error = true;
errorMessage = "There is no function to display";
return;
} else
// otherwise there is no error
error = false;
// extract the coordinate space from the window so we can work with it
coordinateSpace = window.getCoordinateSpace();
// compute xMax - xMin, because we will use it many times
maxMinusMinX = coordinateSpace.xMax - coordinateSpace.xMin;
// calculate through the points
for (i = 0; i < numPoints; i++) {
// calculate the x value based on i
xVal = (double) i / numPoints * maxMinusMinX + coordinateSpace.xMin;
// set the x variable used in the evaluation tree
xDecimalValue.value = xVal;
xVar.set(xDecimalValue);
// evaluate the function
functionEvaluationTree.evaluate();
// get the value out of the y variable
result = yVar.evaluate();
// create a point at this index
points[i] = new Point();
// if there were no parsing or evaluation problems
if (result instanceof DecimalValue) {
error = false;
yVal = ((DecimalValue) result).value;
points[i].x = window.getXpixel(xVal);
points[i].y = window.getYpixel(yVal);
} else {
// if there was an error, report it.
error = true;
errorMessage = result.toString();
return;
}
}
if (!pointsHaveBeenInitialized)
// if we have gotten here, then the points have been properly
// initialized for the first time
pointsHaveBeenInitialized = true;
}
/**
* Called to start the animation Thread.
*/
public void run() {
// constantly update t and recalculate/redraw the graph
while (!stopThread) {
// only update the graph if the animate flag in the model is true
if (animate) {
// calculate the points used to draw the graph
calculatePoints();
// draw the graph
repaint();
// increment t
tIncrementExpression.evaluate();
}
// sleep for 20 milliseconds if the graph is good
// sleep for 200 milliseconds if there is an error or animation is
// turned off
try {
Thread.sleep(error || !animate ? 200 : 20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//for testing
//System.out.print(".");
}
}
/**
* Called when this panel is shown.
*/
public void componentShown(ComponentEvent e) {
}
/**
* Called when this panel is moved.
*/
public void componentHidden(ComponentEvent e) {
}
/**
* Called when this panel is moved.
*/
public void componentMoved(ComponentEvent e) {
}
/**
* Called when this panel is resized.
*/
public void componentResized(ComponentEvent e) {
updateSizes();
}
}