/*************************************************************************
* *
* 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 SecantTangent applet shows the graph of a function with two points
// marked on the function. The tangent to the graph is drawn at the first
// of these points. A second line is drawn between the two points.
// Both of the points can be dragged by the user. Alternatively, the
// x-coords of the points can be typed into input boxes.
import java.awt.*;
import java.applet.Applet;
import java.util.StringTokenizer;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.functions.*;
import edu.hws.jcm.awt.*;
public class SecantTangent extends GenericGraphApplet {
private Function func; // The function that is graphed.
VariableInput x1Input = new VariableInput(); // x-coord where tangent is drawn.
VariableInput x2Input = new VariableInput(); // x-coord for other point of secant.
protected void setUpParameterDefaults() {
parameterDefaults =
new java.
util.
Hashtable();
String varName = getParameter
("Variable",
"x");
parameterDefaults.put("Function", " e ^ " + varName);
}
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 functionInput already exists, if one is
// to be used, since it is created in setUpBopttomPanel(), which is called
// before setUpCanvas. If functionInput exists, add a graph of the function
// from functionInput to the canvas. If not, create a graph of the function
// specified by the parameter named "Function".
if (functionInput != null)
func = functionInput.getFunction(xVar);
else {
String def = getParameter
("Function");
Function f = new SimpleFunction( parser.parse(def), xVar );
func = new WrapperFunction(f);
}
Graph1D graph = new Graph1D(func);
Color color = getColorParam
("GraphColor",
Color.
black);
graph.setColor(color);
// Create two DraggablePoint objects, which will be the points on the canvas
// that the user can drag. The x-coordinate of drag1 will be tied later to
// x1Input, so that either drag1 or x1Input can be used for setting the
// values of the point. Same for drag2 and x2Input.
Color tangentColor = getColorParam
("TangentColor",
Color.
red);
Color secantColor = getColorParam
("SecantColor",
new Color(0,
200,
0));
DraggablePoint drag1 = new DraggablePoint(); // point where tangent is drawn
DraggablePoint drag2 = new DraggablePoint(); // other point on secant line
drag1.clampY(func); // Both points are clamped to move along the function.
drag2.clampY(func);
drag1.setColor(tangentColor);
drag1.setGhostColor(lighten(tangentColor));
drag2.setColor(secantColor);
drag2.setGhostColor(lighten(secantColor));
// Create the tangent line and the secant line.
DrawGeometric secant = new DrawGeometric(DrawGeometric.INFINITE_LINE_ABSOLUTE,
drag1.getXVar(), drag1.getYVar(),
drag2.getXVar(), drag2.getYVar());
secant.setColor(secantColor);
TangentLine tangent = new TangentLine(drag1.getXVar(), func);
tangent.setColor(tangentColor);
canvas.add(drag1);
canvas.add(drag2);
canvas.add(tangent);
canvas.add(secant);
canvas.add(graph);
// Create a DrawString to display the slopes of the tangent and secant.
Value tangentSlope = new ValueMath(func.derivative(1), drag1.getXVar());
Value secantSlope = new ValueMath( new ValueMath(drag2.getYVar(), drag1.getYVar(), '-'),
new ValueMath(drag2.getXVar(), drag1.getXVar(), '-'),
'/');
DrawString info;
if ( "no".equalsIgnoreCase(getParameter("ShowTangentSlope","yes")) ) {
info = new DrawString( "Secant Slope = #",
DrawString.TOP_LEFT,
new Value[] { secantSlope } );
}
else {
info = new DrawString( "Secant Slope = #\nTangent Slope = #",
DrawString.TOP_LEFT,
new Value[] { secantSlope, tangentSlope } );
}
info.
setFont(new Font("Monospaced",
Font.
PLAIN,
10));
info.setNumSize(7);
info.
setColor(getColorParam
("SlopeTextColor",
Color.
black));
info.
setBackgroundColor(getColorParam
("SlopeTextBackground",
Color.
white));
info.setFrameWidth(1);
canvas.add(info);
// Create input boxes and add them to the bottom of the applet
Panel xIn =
new Panel(); // not a JCMPanel, since I don't want the input boxes to be controlled
// by the main controller anyway.
xIn.
setBackground(getColorParam
("PanelColor",
Color.
lightGray));
xIn.
add(new Label("Tangent at " + xVar.
getName() +
" = ",
Label.
RIGHT));
xIn.add(x1Input);
xIn.
add(new Label("Secant to " + xVar.
getName() +
" = ",
Label.
RIGHT));
xIn.add(x2Input);
// Put the inputs at the bottom of the inputPanel, if there is one, otherwise
// at the bottom of the mainPanel
if (inputPanel == null)
else
// Set up controllers. I want to arrange things so that the controls that position the
// two points on the graph do not cause the graph to be recomputted when they are changed.
Controller dragControl = new Controller(); // A controller to respond to dragging
mainController.remove(canvas);
mainController.add(graph);
mainController.add(dragControl); // Things in dragController should be recomputed when graph is changed
dragControl.add(x1Input); // dragControl checks the contents of the x-inputs
dragControl.add(x2Input); // and recomputes everything except the graph.
dragControl.add(drag1);
dragControl.add(drag2);
dragControl.add(tangent);
dragControl.add(secant);
dragControl.add(info);
drag1.setOnUserAction(dragControl); // dragControl.compute() is called when the
drag2.setOnUserAction(dragControl); // user drags one of the points or types
x1Input.setOnTextChange(dragControl); // in one of the x-input boxes.
x2Input.setOnTextChange(dragControl);
// By adding Tie's to dragControll, we make sure that the positions of the
// draggable points are synchronized with the contents of the x-input boxes.
dragControl.
add(new Tie((Tieable
)drag1.
getXVar(), x1Input
));
dragControl.
add(new Tie((Tieable
)drag2.
getXVar(), x2Input
));
// Get initial values for draggable points
double[] d1 = getNumericParam("X1");
double x1 = (d1 != null && d1.length == 1)? d1[0] : 0;
x1Input.setVal(x1);
drag1.setLocation(x1,0); // y-value will be changed to make the point lie on the curve
double[] d2 = getNumericParam("X2");
double x2 = (d2 != null && d2.length == 1)? d2[0] : 1;
x2Input.setVal(x2);
drag2.setLocation(x2,0);
} // end setUpCanvas()
private Color lighten
(Color c
) { // for making "Ghost" color of draggable point
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
int nr, ng, nb;
if (r <= 200 || g <= 200 || b <= 200) {
nb = 255 - (255 - b) / 3;
ng = 255 - (255 - g) / 3;
nr = 255 - (255 - r) / 3;
}
else {
nb = b / 2;
ng = g / 2;
nr = r / 2;
}
return new Color(nr,ng,nb
);
}
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 SecantTangent applet, the example string should contain
// an expression that defines the function to be graphed. This can optionally
// be followed by a semicoloon and a list of fourto six 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
// fifth number, if present, gives the x-coord where the tangent line
// is drawn. The sixth number gives the x-coord of the second point
// on the secant line.
int pos = example.indexOf(";");
double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
if (pos > 0) { // get limits from example text
String limitsText = example.
substring(pos+
1);
example = example.substring(0,pos);
if (toks.countTokens() >= 4) {
for (int i = 0; i < 4; i++) {
try {
limits[i] = d.doubleValue();
}
}
}
if (toks.countTokens() > 0) { // Get point for tangent line
try {
x1Input.setVal( d.doubleValue() );
}
}
}
if (toks.countTokens() > 0) { // Get other point for secant line
try {
x2Input.setVal( d.doubleValue() );
}
}
}
}
}
// 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);
}
else {
// If there is no user input, set the function in the graph directly.
// Also, in this case, func is a "WrapperFunction". Set the
// definition of that WrapperFunction to be the same as f
try {
Function f = new SimpleFunction( parser.parse(example), xVar );
((WrapperFunction)func).setFunction(f);
}
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()
} // end class SimpleGraph