|
/*************************************************************************
* *
* 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/ *
* *
*************************************************************************/
import java.awt.*;
import java.awt.event.*;
import java.util.StringTokenizer;
import java.applet.Applet;
import edu.hws.jcm.data.*;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.awt.*;
import edu.hws.jcm.functions.*;
// A Function composation applet displays the graphs of two functions, f(x) and g(x), and their
// coposition, g(f(x)). It marks points (x,f(x)), (f(x),g(f(x)), and (x,g(f(x))) on the respective
// graphs. The user controls the value of x by dragging a red square along the x-axis of the first
// graph. The functions f and g can be given as table functions instead of expressions. The
// user controls this by clicking on check boxes. When a table function is displayed, the user
// can modify it by dragging its points. Optionally, the apple can also display tangent lines
// to the graphs. The slopes of the tangent line are supposed to illustrate the chain rule.
// Note: This inherits from GenericGraphApplet, but a lot of code in that applet is superceded
// here. In particular, there is no possibility of having a limit control panel.
public class FunctionComposition extends GenericGraphApplet implements ActionListener, ItemListener {
Button zoomInButton, zoomOutButton, restoreButton, equalizeButton; // Control buttons for
// setting limits on coordinate rects.
Button fComputeButton, gComputeButton; // Buttons that the user can press when she enters a new function.
// If the displayed function is a table function, the function's values
// are all reset to zero.
Variable pointX; // The x-coordinate that is marked on the graph of f(x)
// There are two possibilities for each function -- an expression or a table.
// I need variables for keeping track of both possibilities. They are swapped in
// and out when the user clicks on a check box. They can also be swapped when
// an example is loaded. The data for the functions:
Checkbox fCheck, gCheck; // checkboxes for switching between the two versions of the functions.
ExpressionInput fInput, gInput; // Input boxes for the definitions of f and g.
Function fFunc, gFunc; // The functions, defined by expressions.
Graph1D fGraph, gGraph; // The graphs of the functions defined by expressions
TableFunction fTable, gTable; // The functions, defined by tables.
TableFunctionGraph fTableGraph, gTableGraph; // The graphs of the functions defined by tables.
boolean fTableShown, gTableShown; // keep track of which version of the function is shown.
String fSaveText, gSaveText; // Save the text in the input box while a table shown is shown.
WrapperFunction fWrapper, gWrapper; // These functions refer to f and g, whichever versions of
// f and g are currently in effect. They are used to form
// the composition function, g(f(x)). So the composed function
// is valid however f and g are represented.
public void setUpMainPanel() { // Set up the applet.
// basic setup, with three coordinate rects in the canvas
mainController = new Controller();
defaultFrameSize = new int[] { 606, 306 };
Color textColor = getColorParam("TextColor", Color.black);
Color canvasBackground = getColorParam("CanvasColor", Color.white);
boolean useInputs = ! "no".equalsIgnoreCase(getParameter("UseFunctionInput", "yes"));
double[] gap = getNumericParam("Insets");
if (gap == null || gap.length == 0 || gap[0] < 0 || gap[0] > 50)
mainPanel.setInsetGap(3);
else
mainPanel.setInsetGap( (int)Math.round(gap[0]) );
parser = new Parser(null,0);
setUpParser();
setUpExampleMenu();
setUpTopPanel();
Color color;
color = getColorParam("BackgroundColor", Color.gray);
mainPanel.setBackground(color);
color = getColorParam("ForegroundColor", Color.black);
mainPanel.setForeground(color);
double[] limits = getNumericParam("Limits");
if (limits == null || limits.length < 4)
limits = new double[] { -5, 5, -5 ,5 };
canvas = new DisplayCanvas();
mainPanel.add(canvas,BorderLayout.CENTER); // add canvas to panel
canvas.setBackground(canvasBackground);
if ( ! "no".equalsIgnoreCase(getParameter("UseMouseZoom", "no")) )
canvas.setHandleMouseZooms(true);
if ( ! "no".equalsIgnoreCase(getParameter("UseOffscreenCanvas", "yes")) )
canvas.setUseOffscreenCanvas(true);
canvas.addCoordinateRect(new CoordinateRect(limits[0],limits[1],limits[2],limits[3]), 0, 1.0/3.0, 0, 1,null);
canvas.addCoordinateRect(new CoordinateRect(limits[0],limits[1],limits[2],limits[3]),1.0/3.0, 2.0/3.0, 0,1,null);
canvas.addCoordinateRect(new CoordinateRect(limits[0],limits[1],limits[2],limits[3]),2.0/3.0, 1, 0, 1, null);
if ( ! "no".equalsIgnoreCase(getParameter("UseGrid", "no")) ) {
color = getColorParam("GridColor");
Grid g = new Grid();
if (color != null)
g.setColor(color);
canvas.add(g,0);
g = new Grid();
if (color != null)
g.setColor(color);
canvas.add(g,1);
g = new Grid();
if (color != null)
g.setColor(color);
canvas.add(g,2);
}
canvas.add(makeAxes(), 0);
canvas.add(makeAxes(), 1);
canvas.add(makeAxes(), 2);
// Make the expression functions
fSaveText = getParameter("Function"," 3 - " + xVar.getName() + "^2/2");
gSaveText = getParameter("SecondFunction", " sin(" + xVar.getName() + ")");
if (useInputs) {
fInput = new ExpressionInput(fSaveText, parser);
gInput = new ExpressionInput(gSaveText, parser);
fFunc = fInput.getFunction(xVar);
gFunc = gInput.getFunction(xVar);
}
else {
fFunc = new SimpleFunction( parser.parse(fSaveText), xVar );
gFunc = new SimpleFunction( parser.parse(gSaveText), xVar );
}
fGraph = new Graph1D(fFunc);
gGraph = new Graph1D(gFunc);
// Create the wrapper functions for f and g and use it to make a compostion function
fWrapper = new WrapperFunction(fFunc);
fWrapper.setName("f");
gWrapper = new WrapperFunction(gFunc);
gWrapper.setName("g");
Parser p1 = new Parser(); // parser for making the composition function
p1.add(fWrapper);
p1.add(gWrapper);
ExpressionFunction comp = new ExpressionFunction("h", new String[] { "x" }, "g(f(" + xVar.getName() + "))", p1);
Graph1D compositionGraph = new Graph1D(comp);
fTableShown = gTableShown = false;
// Make table functions. If a table function is specified in applet params, but no
// expression is specified, show the table function.
String tf = getParameter("TableFunction");
if (tf != null) {
try {
fTable = parseTableFuncDef(tf);
}
catch (Exception e) {
tf = null;
}
}
if (tf == null) {
fTable = new TableFunction();
fTable.addIntervals(6, -5, 5);
}
fTableGraph = new TableFunctionGraph(fTable);
fTableGraph.setInteractive(true);
if (getParameter("Function") == null && tf != null) { // show table function at startup
fGraph.setVisible(false);
fTableShown = true;
fWrapper.setFunction(fTable);
if (fInput != null) {
fInput.setEnabled(false);
fInput.setThrowErrors(false);
fInput.setText("Drag points to modify function.");
}
}
else {
fTableGraph.setVisible(false);
}
tf = getParameter("SecondTableFunction");
if (tf != null) {
try {
gTable = parseTableFuncDef(tf);
}
catch (Exception e) {
tf = null;
}
}
if (tf == null) {
gTable = new TableFunction();
gTable.addIntervals(6, -5, 5);
}
gTableGraph = new TableFunctionGraph(gTable);
gTableGraph.setInteractive(true);
if (getParameter("SecondFunction") == null && tf != null) { // show table function at startup
gGraph.setVisible(false);
gTableShown = true;
gWrapper.setFunction(gTable);
if (gInput != null) {
gInput.setEnabled(false);
gInput.setThrowErrors(false);
gInput.setText("Drag points to modify function.");
}
}
else {
gTableGraph.setVisible(false);
}
// Create the stuff for marking points on each graph
DraggablePoint point = new DraggablePoint(DraggablePoint.SQUARE); // gives x-coord
Color pointColor1 = getColorParam("PointColor1", Color.red);
Color pointColor2 = getColorParam("PointColor2", new Color(0,200,0));
Color pointColor3 = getColorParam("PointColor3", new Color(100,100,255));
point.setColor(pointColor1);
point.clampY(0);
point.setLocation(1,0);
canvas.add(point,0);
pointX = point.getXVar();
Value fOfX = new ValueMath(fWrapper, pointX);
Value gOfFOfX = new ValueMath(gWrapper, fOfX);
DrawGeometric line1;
line1 = new DrawGeometric( DrawGeometric.LINE_ABSOLUTE, pointX, new Constant(0), pointX, fOfX );
line1.setColor(pointColor1);
canvas.add(line1, 0);
DrawGeometric line2;
line2 = new DrawGeometric( DrawGeometric.LINE_ABSOLUTE, pointX, fOfX, new Constant(0), fOfX );
line2.setColor(pointColor2);
canvas.add(line2, 0);
DrawGeometric line3;
line3 = new DrawGeometric( DrawGeometric.LINE_ABSOLUTE, fOfX, new Constant(0), fOfX, gOfFOfX );
line3.setColor(pointColor2);
canvas.add(line3, 1);
DrawGeometric line4;
line4 = new DrawGeometric( DrawGeometric.LINE_ABSOLUTE, fOfX, gOfFOfX, new Constant(0), gOfFOfX );
line4.setColor(pointColor3);
canvas.add(line4, 1);
DrawGeometric line5;
line5 = new DrawGeometric( DrawGeometric.LINE_ABSOLUTE, pointX, new Constant(0), pointX, gOfFOfX );
line5.setColor(pointColor1);
canvas.add(line5, 2);
DrawGeometric line6;
line6 = new DrawGeometric( DrawGeometric.LINE_ABSOLUTE, pointX, gOfFOfX, new Constant(0), gOfFOfX );
line6.setColor(pointColor3);
canvas.add(line6, 2);
line1.setLineWidth(2);
line2.setLineWidth(2);
line3.setLineWidth(2);
line4.setLineWidth(2);
line5.setLineWidth(2);
line6.setLineWidth(2);
// Add the graphs
Color gc = getColorParam("GraphColor", Color.magenta);
fGraph.setColor(gc);
gGraph.setColor(gc);
fTableGraph.setColor(gc);
gTableGraph.setColor(gc);
compositionGraph.setColor(gc);
canvas.add(fGraph, 0);
canvas.add(fTableGraph, 0);
canvas.add(gGraph, 1);
canvas.add(gTableGraph, 1);
canvas.add(compositionGraph, 2);
// Create tangent lines, if they are called for by an applet param
TangentLine tangent1=null, tangent2=null, tangent3=null;
DrawString ts1=null, ts2=null, ts3=null;
if (! "no".equalsIgnoreCase(getParameter("ShowTangents","no"))) {
Color tangentColor = getColorParam("TangentColor", Color.gray);
tangent1 = new TangentLine(pointX,fWrapper);
tangent1.setColor(tangentColor);
canvas.add(tangent1,0);
tangent2 = new TangentLine(fOfX,gWrapper);
tangent2.setColor(tangentColor);
canvas.add(tangent2,1);
tangent3 = new TangentLine(pointX,comp);
tangent3.setColor(tangentColor);
canvas.add(tangent3,2);
if ("yes".equalsIgnoreCase(getParameter("ShowSlopes","yes"))) {
ts1 = new DrawString("slope = #", DrawString.TOP_RIGHT,
new Value[] { new ValueMath(fWrapper.derivative(1), pointX) });
ts1.setColor(textColor);
ts1.setNumSize(6);
canvas.add(ts1,0);
ts2 = new DrawString("slope = #", DrawString.TOP_RIGHT,
new Value[] { new ValueMath(gWrapper.derivative(1), fOfX) });
ts2.setColor(textColor);
ts2.setNumSize(6);
canvas.add(ts2,1);
ts3 = new DrawString("slope = #", DrawString.TOP_RIGHT,
new Value[] { new ValueMath(comp.derivative(1), pointX) });
ts3.setColor(textColor);
ts3.setNumSize(6);
canvas.add(ts3,2);
}
}
// Create DrawStrings for displaying information over the graphs.
if ("yes".equalsIgnoreCase(getParameter("ShowFunctionNames","yes"))) {
DrawString d = new DrawString("y=f(" + xVar.getName() + ")");
d.setColor(textColor);
canvas.add(d, 0);
d = new DrawString("y=g(" + xVar.getName() + ")");
d.setColor(textColor);
canvas.add(d, 1);
d = new DrawString("y=g(f(" + xVar.getName() + "))");
d.setColor(textColor);
canvas.add(d, 2);
}
DrawString ds1=null, ds2=null, ds3=null;
if ("yes".equalsIgnoreCase(getParameter("ShowCoordinates","yes"))) {
ds1 = new DrawString("f(#) = #", DrawString.BOTTOM_CENTER, new Value[] { pointX, fOfX });
ds1.setNumSize(6);
ds1.setColor(textColor);
ds1.setBackgroundColor(canvasBackground);
canvas.add(ds1, 0);
ds2 = new DrawString("g(#) = #", DrawString.BOTTOM_CENTER, new Value[] { fOfX, gOfFOfX });
ds2.setNumSize(6);
ds2.setColor(textColor);
ds2.setBackgroundColor(canvasBackground);
canvas.add(ds2, 1);
ds3 = new DrawString("g(f(#)) = #", DrawString.BOTTOM_CENTER, new Value[] { pointX, gOfFOfX });
ds3.setNumSize(6);
ds3.setColor(textColor);
ds3.setBackgroundColor(canvasBackground);
canvas.add(ds3, 2);
}
// Add panner and borders
if (! "no".equalsIgnoreCase(getParameter("UsePanner", "no")) ) {
canvas.add(new Panner(), 0);
canvas.add(new Panner(), 1);
canvas.add(new Panner(), 2);
}
int borderWidth;
double[] bw = getNumericParam("BorderWidth");
if (bw == null || bw.length == 0 || bw[0] > 25)
borderWidth = 1;
else
borderWidth = (int)Math.round(bw[0]);
if (borderWidth > 0) {
Color bc = getColorParam("BorderColor", Color.black);
canvas.add(new DrawBorder(bc, borderWidth), 0);
canvas.add(new DrawBorder(bc, borderWidth), 1);
canvas.add(new DrawBorder(bc, borderWidth), 2);
}
// Set up the bottom panel to hold inputs and control buttons, unless an
// applet param has specified that no input panel is desired.
if ( useInputs ) {
Panel bottom = new Panel();
bottom.setLayout(new BorderLayout(3,3));
bottom.setBackground(getColorParam("PanelBackground", Color.lightGray));
mainPanel.add(bottom,BorderLayout.SOUTH);
Panel left = new Panel();
left.setLayout(new GridLayout(0,1));
bottom.add(left, BorderLayout.CENTER);
Panel right = new Panel();
right.setLayout(new GridLayout(0,2));
bottom.add(right, BorderLayout.EAST);
Panel fPanel = new Panel();
fPanel.setLayout(new BorderLayout());
fPanel.add(new Label(" f(" + xVar.getName() + ") = "), BorderLayout.WEST);
fPanel.add(fInput, BorderLayout.CENTER);
Panel fp = new Panel();
fp.setLayout(new GridLayout(1,2));
fCheck = new Checkbox("Use Mouse");
if (fTableShown)
fCheck.setState(true);
fCheck.addItemListener(this);
fp.add(fCheck);
fComputeButton = new Button("New f(" + xVar.getName() + ")");
fComputeButton.addActionListener(this);
fp.add(fComputeButton);
fPanel.add(fp,BorderLayout.EAST);
left.add(fPanel);
Panel gPanel = new Panel();
gPanel.setLayout(new BorderLayout());
gPanel.add(new Label(" g(" + xVar.getName() + ") = "), BorderLayout.WEST);
gPanel.add(gInput, BorderLayout.CENTER);
Panel gp = new Panel();
gp.setLayout(new GridLayout(1,2));
gCheck = new Checkbox("Use Mouse");
if (gTableShown)
gCheck.setState(true);
gCheck.addItemListener(this);
gp.add(gCheck);
gComputeButton = new Button("New g(" + xVar.getName() + ")");
gComputeButton.addActionListener(this);
gp.add(gComputeButton);
gPanel.add(gp,BorderLayout.EAST);
left.add(gPanel);
&nbs
|