/*************************************************************************
* *
* This source code file, and compiled classes derived from it, can *
* be used and distributed without restriction, including for commercial *
* use. (Attribution is not required but is appreciated.) *
* *
* David J. Eck *
* Department of Mathematics and Computer Science *
* Hobart and William Smith Colleges *
* Geneva, New York 14456, USA *
* Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
* *
*************************************************************************/
// The ParametricCurve applet is a configurable applet that displays a parametric
// curve defined by two functions of a parameter t. There can be a "Tracer" button
// on the applet. When the user clicks this button, a crosshair is moved along
// the curve from beginning to end.
import java.awt.*;
import java.applet.Applet;
import java.util.StringTokenizer;
import java.util.Hashtable;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.functions.*;
import edu.hws.jcm.awt.*;
public class Parametric extends GenericGraphApplet {
// Declare some private variables that are created in one method in
// this class and used in a second method.
private Function xFunc,yFunc; // The functions that are graphed.
private ParametricCurve graph; // The graph of the function.
private Animator tracer; // for tracing the curve by moving a crosshair along it
private Crosshair crosshair; // Crosshair used for tracing the graph
private VariableInput tMin,tMax; // for inputting limits on t
private VariableInput tIntervals; // for inutting the number of intervals into which the t-range is divided
private ExpressionInput functionInput2; // for inputting yFunc; xFunc is input in functionInput
protected void setUpParameterDefaults() {
parameterDefaults = new Hashtable();
parameterDefaults.put("TwoLimitsColumns", "yes");
parameterDefaults.put("Variable","t");
parameterDefaults.put("XName","x"); // we need this so that xmin and xmax boxes are labeled correctly;
// without it, the name would come from the variable name, t, instead of x
parameterDefaults.put("FunctionLabel", " " + getParameter("XName") + "(" + getParameter("Variable") + ") = ");
parameterDefaults.put("FunctionLabel2", " " + getParameter("YName","y") + "(" + getParameter("Variable") + ") = ");
}
protected void setUpCanvas() { // Override this to add more stuff to the canvas.
super.setUpCanvas(); // Do the common setup: Add the axes and
// When setUpCanvas is called, the function inputs already exist, if they are
// to be used, since they are created in setUpBopttomPanel(), which is called
// before setUpCanvas(). If functionInput exists, add a graph of the functions
// from functionInput and functionInput2 to the canvas. If not, create a graph
// of the functions specified by the parameters named "Function" and "Function2".
if (functionInput != null) {
xFunc = functionInput.getFunction(xVar);
yFunc = functionInput2.getFunction(xVar);
}
else {
String xFuncDef = " cos(" + xVar.getName() + ") + cos(3*" + xVar.getName() + ")";
String yFuncDef = " sin(4*" + xVar.getName() + ") - sin(2*" + xVar.getName() + ")";
xFuncDef = getParameter("Function", xFuncDef);
yFuncDef = getParameter("Function2", yFuncDef);
Function f = new SimpleFunction( parser.parse(xFuncDef), xVar );
xFunc = new WrapperFunction(f);
f = new SimpleFunction( parser.parse(yFuncDef), xVar );
yFunc = new WrapperFunction(f);
}
graph = new ParametricCurve(xFunc,yFunc);
Color color = getColorParam("CurveColor");
if (color != null)
graph.setColor(color);
// if inputs are desired to control the parameter on the curve, set them up here
if ("no".equalsIgnoreCase(getParameter("UseParamInputs","yes"))) {
tMin = new VariableInput(xVar.getName() + "Start",getParameter("ParameterMin","-2"));
tMax = new VariableInput(xVar.getName() + "End",getParameter("ParameterMax","2"));
tIntervals = new VariableInput("Intervals", getParameter("Intervals","200"));
tIntervals.setInputStyle(VariableInput.INTEGER);
tIntervals.setMin(1);
tIntervals.setMax(5000);
tMin.setOnUserAction(mainController);
tMax.setOnUserAction(mainController);
tIntervals.setOnUserAction(mainController);
graph.setTMin(tMin);
graph.setTMax(tMax);
graph.setIntervals(tIntervals);
if (limitsPanel != null) {
// componets will be added to limitsPanel in setUpLimitsPanel()
mainController.add(tMin); // This is not done automatically, since they are in a limits panel
mainController.add(tMax);
mainController.add(tIntervals);
}
else {
JCMPanel ap = new JCMPanel(9,0);
ap.setBackground(getColorParam("PanelBackground", Color.lightGray));
ap.add(new Label(tMin.getName()));
ap.add(tMin);
ap.add(new Label());
ap.add(new Label(tMax.getName()));
ap.add(tMax);
ap.add(new Label());
ap.add(new Label(tIntervals.getName()));
ap.add(tIntervals);
ap.add(new Label());
mainPanel.add(ap,BorderLayout.EAST);
}
}
else {
try {
graph.setTMin( new Constant((new Double(getParameter("ParameterMin","-2"))).doubleValue()) );
graph.setTMax( new Constant((new Double(getParameter("ParameterMax","2"))).doubleValue()) );
graph.setIntervals( new Constant((new Double(getParameter("Intervals","25"))).doubleValue()) );
}
catch (NumberFormatException e) {
}
}
// If the applet is configured to have a tracer button, create it and add the crosshair to the canvas
if (! "no".equalsIgnoreCase( getParameter("UseTracer","yes") ) ) {
tracer = new Animator();
tracer.setMin(graph.getTMin());
tracer.setMax(graph.getTMax());
tracer.setUndefinedWhenNotRunning(true);
tracer.setStartButtonName("Trace Curve!");
double[] d = getNumericParam("TracerIntervals");
int ints;
if (d == null || d.length != 1)
ints = 100;
else
ints = (int)Math.round(d[0]);
if (ints <= 0)
tracer.setIntervals(graph.getIntervals());
else
tracer.setIntervals(ints);
Variable v = tracer.getValueAsVariable();
crosshair = new Crosshair( new ValueMath(xFunc,v), new ValueMath(yFunc,v) );
crosshair.setLineWidth(3);
crosshair.setColor(getColorParam("CrosshairColor",Color.gray));
canvas.add(crosshair);
if (inputPanel != null) {
inputPanel.add(tracer,BorderLayout.WEST);
}
else if (limitsPanel == null) {
Panel p = new Panel();
p.add(tracer);
mainPanel.add(p,BorderLayout.SOUTH);
}
// if inputPanel is null but limitsPanel is not null, the tracer will be
// added to the limit control panel in setUpLimitsPanel()
}
canvas.add(graph); // Finally, add the graph to the canvas.
} // end setUpCanvas()
protected void setUpLimitsPanel() {
super.setUpLimitsPanel();
if (limitsPanel != null && tMin != null) { // add parameter inputs to limits panel
limitsPanel.addComponentPair(tMin,tMax);
limitsPanel.addComponent(tIntervals);
}
if (inputPanel == null && tracer != null && limitsPanel != null) {
limitsPanel.addComponent(tracer);
}
}
protected void setUpBottomPanel() { // override this to make a panel containing inputs for two functions
if ( ! "no".equalsIgnoreCase(getParameter("UseFunctionInput", "yes")) ) {
inputPanel = new JCMPanel();
inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) );
Panel in = new JCMPanel(2,1);
inputPanel.add(in,BorderLayout.CENTER);
if ( ! "no".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) {
String cname = getParameter("ComputeButtonName", "New Functions");
computeButton = new Button(cname);
inputPanel.add(computeButton, BorderLayout.EAST);
computeButton.addActionListener(this);
}
String varName = getParameter("Variable");
String def = getParameter("Function");
if (def == null)
def = "cos(" + varName + ") + cos(3*" + varName + ")";
functionInput = new ExpressionInput(def,parser);
String label = getParameter("FunctionLabel");
if ("none".equalsIgnoreCase(label))
in.add(functionInput);
else {
Panel p = new JCMPanel();
p.add(functionInput,BorderLayout.CENTER);
p.add( new Label(label), BorderLayout.WEST );
in.add(p);
}
def = getParameter("Function2");
if (def == null)
def = "sin(4*" + varName + ") - sin(2*" + varName + ")";
functionInput2 = new ExpressionInput(def,parser);
label = getParameter("FunctionLabel2");
if ("none".equalsIgnoreCase(label))
in.add(functionInput2);
else {
Panel p = new JCMPanel();
p.add(functionInput2,BorderLayout.CENTER);
p.add( new Label(label), BorderLayout.WEST );
in.add(p);
}
mainPanel.add(inputPanel, BorderLayout.SOUTH);
functionInput.setOnUserAction(mainController);
functionInput2.setOnUserAction(mainController);
}
}
protected void setUpMainPanel() { // Override to set up controller for tracer, if there is one
super.setUpMainPanel(); // Do the common setup
if ( tracer == null ) {
return; // If the applet is not configured to use a trace button, there is nothing to do.
}
Controller traceController = new Controller(); // A controler that will only recompute the crosshair position
traceController.add(tracer);
traceController.add(crosshair);
tracer.setOnChange(traceController);
} // end setUpMainPanel()
protected void doLoadExample(String example) {
// This method is called when the user loads an example from the
// example menu (if there is one). It overrides an empty method
// in GenericGraphApplet.
// For the Parametric applet, the example string should contain
// two expression that defines the curve to be graphed, separated
// by a semicolon. This can optionally
// be followed by another semicolon and a list of four to seven numbers.
// The first four numbers give the x- and y-limits to be used for the
// example. If they are not present, then -5,5,-5,5 is used. The
// next three numbers specify the minimum value for the parameter, the
// maximum value, and the number of intervals into which the range of
// parameter values is divided.
if (tracer != null)
tracer.stop();
int pos = example.indexOf(";");
if (pos == -1)
return; // illegal example -- must have two functions
String example2 = example.substring(pos+1);
example = example.substring(0,pos);
pos = example2.indexOf(";");
double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
if (pos > 0) {
// Get limits from example2 text.
String nums = example2.substring(pos+1);
example2 = example2.substring(0,pos);
StringTokenizer toks = new StringTokenizer(nums, " ,");
if (toks.countTokens() >= 4) {
for (int i = 0; i < 4; i++) {
try {
Double d = new Double(toks.nextToken());
limits[i] = d.doubleValue();
}
catch (NumberFormatException e) {
}
}
}
if (toks.hasMoreTokens()) {
try {
double d = (new Double(toks.nextToken())).doubleValue();
if (tMin == null) {
graph.setTMin(new Constant(d));
if (tracer != null)
tracer.setMin(d);
}
else
tMin.setVal(d);
}
catch (NumberFormatException e) {
}
}
if (toks.hasMoreTokens()) {
try {
double d = (new Double(toks.nextToken())).doubleValue();
if (tMax == null) {
graph.setTMax(new Constant(d));
if (tracer != null)
tracer.setMax(d);
}
else
tMax.setVal(d);
}
catch (NumberFormatException e) {
}
}
if (toks.hasMoreTokens()) {
try {
int d = (int)Math.round((new Double(toks.nextToken())).doubleValue());
if (tIntervals == null) {
if (tracer != null && tracer.getIntervals() == graph.getIntervals())
tracer.setIntervals(d);
graph.setIntervals(new Constant(d));
}
else
tIntervals.setVal(d);
}
catch (NumberFormatException e) {
}
}
}
// Set up the example data and recompute everything.
if (functionInput != null) {
// If there is a function input box, put the example text in it.
functionInput.setText(example);
functionInput2.setText(example2);
}
else {
// If there is no user input, set the function in the graph directly.
try {
Function f = new SimpleFunction( parser.parse(example), xVar );
((WrapperFunction)xFunc).setFunction(f);
Function g = new SimpleFunction( parser.parse(example2), xVar );
((WrapperFunction)yFunc).setFunction(g);
}
catch (ParseError e) {
// There should't be parse error's in the Web-page
// author's examples! If there are, the function
// just won't change.
}
}
CoordinateRect coords = canvas.getCoordinateRect(0);
coords.setLimits(limits);
coords.setRestoreBuffer();
mainController.compute();
} // end doLoadExample()
public void stop() { // stop animator when applet is stopped
if (tracer != null)
tracer.stop();
super.stop();
}
public static void main(String[] a){
javax.swing.JFrame f = new javax.swing.JFrame();
Applet app = new Parametric();
app.init();
f.getContentPane().add (app);
f.pack();
f.setSize (new Dimension (500, 500));
f.setVisible(true);
}
} // end class Parametric
|