import java.applet.*; import java.awt.*; /* This applet attempts to do some interesting stuff useful for teaching Calculus including graphs, integrals, ... I apologize for the cluttered appearance, but I designed it to do a LOT of stuff and still, hopefully, be useable in a 640 by 480 screen which lots of people still have. I hope you find it as useful as I have. Lynn Ziegler, 6/22/96. */ public class Calculus extends Applet { private int MaxPlottedFunctions=11; /* Total number of functions allowed to be plotted at once is MaxPlottedFunctions-1. These are referred to by their number (0-MaxPlottedFunctions-2). There is also a function with number MaxPlottedFunctions-1, but it is reserved for doing tangent lines at a point. */ private Quads [] CalculusQuads; /* This is an array of Quads, one Quad for each function to be dealt with. Each Quad holds all the information about a given function including its Infix string form, its converted Postfix string form, and its form as a series of quads of the form: Operator Operand1 Operand2 Result The quads are used by an Evaluate function built into each Quad whenever it is necessary to compute the function at a value. Each function is built up of functions like Atan, Cosh, Sin, Ln, *, /, +, -, ^ ... A Quad also contains an interval where the function is defined so that several Quads can be used for a piecewise defined function. */ private int CurrentFunction; /* The number of the function being dealt with right now. In this applet you are allowed to change from one function to another and have any of the functions displayed at the same time by clicking on the Activate checkbox. */ private QuadCanvas CalculusCanvas; /* CalculusCanvas is the drawing area for the function. It includes the global coordinates that the functions are being drawn in and choices like whether a grid or axes should be drawn. The Canvas has controls that allow plotting of axes and grids as well as the functions and allows plotting the area of numerical integration in yellow when that option is chosen. */ private Checkbox Activate; /* It is either checked (if the function is to be plotted, or not. */ private Button Compile; /* This button tells the current Quad to convert its function from Infix notation into Postfix notation and, then, into quads. It also turns the Activate Checkbox on for the current Quad so the function it represents will be plotted. If the Infix function was ILLEGAL, a message will appear in the output text field CalculatedY and the function will NO become active. */ private TextField Function; /* This is the text field where you enter a new function to become the Infix representation of the function for the CurrentFunction. Pressing the Activate button above sets the Infix string for the CurrentFunction to this and, then, sets the value of the CurrentFunction Quad's Infix string to the value in the field as well as converting the field into Postfix and quad form. */ private Checkbox ClearButton; // Clears everthing in the drawing area. private Button IntegrateIt; /* Calculates a numerical integral between the two limits LeftLimit and RightLimit for the CurrentFunction and causes the integration region to be plotted in yellow as it replots the functions. */ private double MyIntegral; /* Used to store the value of the numericla integration. */ private TextField LeftLimit; // The left integration limit private TextField RightLimit; // The right integration limit private TextField Integral; // Displays the result of integration. private Button SetLocalMaxMin; /* Sets max/min for the function. Thus, you might have a function that you only want plotted in a certain subinterval if, for example, it is not defined outside of that subinterval (Arcsin, for example) or if it is part of a piecewise function. You set the value in the text fields LocalXMin and LocalXMax and when this button is pressed, those define the interval of definition for the function. */ private TextField LocalXMin; // Minimum and Maximum X values for a private TextField LocalXMax; // function only defined for some values // (the "domain" of the function). private Button ComputeYValues; /* Used to enable a user to compute the value of the current function at a single x value (in the text field InputX) and display the result as CalculatedY. This turns our tool into a sort of Super Calculator for finding values of functions. */ private TextField InputX,CalculatedY; /* The input and output, respectively, for the ComputeYValues button directly above. */ private Choice FunctionChoice; /* This is a choice field that allows the user to change from one function to another. It allows values from 0 to MaxPlottedFunctions-2. (The function MaxPlottedFunctions-1 is reserved for plotting tangent lines.) */ private Choice ColorChoice; /* Used to choose the plotting color of the CurrentFunction. */ private Choice NumberOfGridLines; /* Choose how many gride lines to plot with. (Can be 0 to MaxGridLines-1). */ int MaxGridLines; /* Maximum number of Grid Lines it is possible to have. */ private Checkbox ToggleGrid; // Turns Grid plotting on or off private TextField YMin; // Minimum and Maximum values shown on private TextField YMax; // the screen during a plot. These private TextField XMin; // values are found as the YMax text private TextField XMax; /* field at the upper left corner of the plotting area, the YMin and XMin text fields, one above the other, at the lower left corner of the plotting area, and the XMax text field at the lower right corner. You can edit these fields and, then, when you hit any button that causes the plot to be redrawn (StartPlot, DrawAxes, Linear, IntegrateIt, and ToggleGrid), the plotting region will be changed to what you edited in. Notice that these fields are automatically changed by ZoomIn, ZoomOut, MoveLeft, MoveRight, MoveUp, and MoveDown. */ private Button StartPlot; // Button to start the plot private Checkbox DrawAxes; // Button to toggle drawing of axes private Checkbox Linear; /* Button to toggle whether to draw points or to draw little line segments. This is set for each function separately. For those functions like Sin(5x) in the interval [-1,1], line segments are necessary since the function is too "steep" so dot plotting looks like scattered dots. */ private Button ZoomIn,ZoomOut; /* Zooms plot in or out by doubling or halving the global coordinates along both axes. */ private Button MoveLeft,MoveRight,MoveUp,MoveDown; /* Moves Plot in the indicated direction by 1/4 of the drawing region. The scale remains the same, just the appropriate global coordinates change and the plot is redrawn. */ /* PlotColor is the choice of standard colors assigned initially to the Quads. These become the default colors for each of the different functions. */ private Color PlotColor[]={Color.red, Color.blue, Color.black, Color.cyan, Color.darkGray, Color.green, Color.magenta, Color.orange, Color.pink, Color.gray, Color.lightGray, Color.yellow}; // Actually, these strings are what the user sees in the choice button. private String PlotColorString[]={"red","blue","black","cyan","darkGray", "green","magenta","orange","pink","gray", "lightGray","yellow"}; private TextField Derivative; // Used to get the derivative and to private Button DerivativeButton; /* show the corresponding tangent line at a point where the user clicked in the plotting region. */ private Checkbox PlotDerivative; /* Plots the derivative of the current function in addition to the function itself. It is a toggle.*/ private Checkbox PlotIntegral; /* Plots the integral of the current function in addition to the function itself. It is a toggle. */ private char [] CurrentCharacter=new char[1]; // Dummy array used to // convert characters to strings. /* This procedure is used throughout this applet. It takes the current values which are stored in the TextFields XMin, XMax, YMin, YMax and calls CalculusCanvas.SetGlobalCoordinates on their values. This sets the plotting coordinates to these values. */ void SetGlobalCoordinates() { CalculusCanvas.SetGlobalCoordinates( Double.valueOf(XMin.getText()).doubleValue(), Double.valueOf(XMax.getText()).doubleValue(), Double.valueOf(YMin.getText()).doubleValue(), Double.valueOf(YMax.getText()).doubleValue()); } /* add is a routine to add a given component to our layout which we are doing using the GridBagLayout scheme. This add function is taken from the book "Core Java" by Gary Cornell and Cay S. Horstmann, The SunSoft Press, page 330 */ private void add(Component c, GridBagLayout gbl, GridBagConstraints gbc, int x, int y, int w, int h) { gbc.gridx=x; gbc.gridy=y; gbc.gridwidth=w; gbc.gridheight=h; gbl.setConstraints(c, gbc); add(c); } /* init always functions as the Constructor for an applet - this is no exception. It sets up the complete layout for all of the different components that make up my rather cluttered plotting applet.*/ public void init() { /* BagNumber is used to remember the row that the current components are being placed in. As the init moves along, each new row is indicated by a BagNumber++. */ int BagNumber=0; /* GridBagLayout is the method for placing the components. See the book Core Java from SunSoft Press mentioned above for details. */ GridBagLayout gbl=new GridBagLayout(); setLayout(gbl); GridBagConstraints gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.NONE; gbc.weightx=100; gbc.weighty=100; /* Create the initial array of functions we use throughout. Each Quad represents one of the functions we can set up and plot. */ CalculusQuads=new Quads[MaxPlottedFunctions]; for (int i=0;i80) NumberOfGridLines.select(0); else NumberOfGridLines.select(TempGridLines); CalculusCanvas.SetGlobalCoordinates( Double.valueOf(XMin.getText()).doubleValue(), Double.valueOf(XMax.getText()).doubleValue(), Double.valueOf(YMin.getText()).doubleValue(), Double.valueOf(YMax.getText()).doubleValue()); CalculusCanvas.NumberOfGridLines= NumberOfGridLines.getSelectedIndex(); Width=(CalculusCanvas.globalXmax- CalculusCanvas.globalXmin)/2.0; Height=(CalculusCanvas.globalYmax - CalculusCanvas.globalYmin)/2.0; CalculusCanvas.globalXmin=CalculusCanvas.globalXmin-Width; CalculusCanvas.globalXmax=CalculusCanvas.globalXmax+Width; CalculusCanvas.globalYmin=CalculusCanvas.globalYmin-Height; CalculusCanvas.globalYmax=CalculusCanvas.globalYmax+Height; XMin.setText(Double.toString(CalculusCanvas.globalXmin)); XMax.setText(Double.toString(CalculusCanvas.globalXmax)); YMin.setText(Double.toString(CalculusCanvas.globalYmin)); YMax.setText(Double.toString(CalculusCanvas.globalYmax)); CalculusCanvas.repaint(); return true; } // Move the coordinate system 1/4 of the width to the left. else if(event.target==MoveLeft) { Width=(CalculusCanvas.globalXmax- CalculusCanvas.globalXmin)/4.0; CalculusCanvas.globalXmin=CalculusCanvas.globalXmin-Width; CalculusCanvas.globalXmax=CalculusCanvas.globalXmax-Width; XMin.setText(Double.toString(CalculusCanvas.globalXmin)); XMax.setText(Double.toString(CalculusCanvas.globalXmax)); CalculusCanvas.repaint(); return true; } // Move the coordinate system 1/4 of the width to the right else if(event.target==MoveRight) { Width=(CalculusCanvas.globalXmax- CalculusCanvas.globalXmin)/4.0; CalculusCanvas.globalXmin=CalculusCanvas.globalXmin+Width; CalculusCanvas.globalXmax=CalculusCanvas.globalXmax+Width; XMin.setText(Double.toString(CalculusCanvas.globalXmin)); XMax.setText(Double.toString(CalculusCanvas.globalXmax)); CalculusCanvas.repaint(); return true; } // Move the coordinate system 1/4 of the width up. else if(event.target==MoveUp) { Height=(CalculusCanvas.globalYmax - CalculusCanvas.globalYmin)/4.0; CalculusCanvas.globalYmin=CalculusCanvas.globalYmin+Height; CalculusCanvas.globalYmax=CalculusCanvas.globalYmax+Height; YMin.setText(Double.toString(CalculusCanvas.globalYmin)); YMax.setText(Double.toString(CalculusCanvas.globalYmax)); CalculusCanvas.repaint(); return true; } // Move the coordinate system 1/4 of the widhth down. else if(event.target==MoveDown) { Height=(CalculusCanvas.globalYmax - CalculusCanvas.globalYmin)/4.0; CalculusCanvas.globalYmin=CalculusCanvas.globalYmin-Height; CalculusCanvas.globalYmax=CalculusCanvas.globalYmax-Height; YMin.setText(Double.toString(CalculusCanvas.globalYmin)); YMax.setText(Double.toString(CalculusCanvas.globalYmax)); CalculusCanvas.repaint(); return true; } /* IntegrateIt causes a numerical integral to be computed for the CurrentFunction between the values in text fields LeftLimit and RightLimit, shows that value in text field Integral, and, also, causes the functions to be replotted showing the integrated region in yellow. */ else if(event.target==IntegrateIt) { CalculusQuads[CurrentFunction].IntegrateIt=true; CalculusQuads[CurrentFunction].LeftLimit=Double.valueOf( LeftLimit.getText()).doubleValue(); CalculusQuads[CurrentFunction].RightLimit=Double.valueOf( RightLimit.getText()).doubleValue(); Integral.setText(Double.toString( CalculusQuads[CurrentFunction].Simpson( CalculusQuads[CurrentFunction].LeftLimit, CalculusQuads[CurrentFunction].RightLimit, 500))); CalculusCanvas.repaint(); return true; } /* Computes an approximate derivative for the function at a value previously clicked on the screen and, then, plots a tangent line at that point. The approximate derivative is shown in the text field Derivative. */ else if(event.target==DerivativeButton) { double Slope=0.0; double Intercept=0.0; if(CalculusCanvas.Clicked) { Width=CalculusCanvas.globalXmax-CalculusCanvas.globalXmin; Height=CalculusCanvas.globalYmax-CalculusCanvas.globalYmin; double dx=Width/CalculusCanvas.xSize/2.0; double x0=CalculusCanvas.globalXmin+ Width*((double) CalculusCanvas.ClickX)/ ((double) CalculusCanvas.xSize); double x1=x0+dx; x0=x0-dx; double y1=0.0; double y0=CalculusQuads[CurrentFunction].Evaluate(x0); if(!CalculusQuads[CurrentFunction].EvaluationError) y1=CalculusQuads[CurrentFunction].Evaluate(x1); if(CalculusQuads[CurrentFunction].EvaluationError) CalculusQuads[MaxPlottedFunctions-1].IsActive=false; else { CalculusQuads[MaxPlottedFunctions-1].IsActive=true; Slope=(y1-y0)/(x1-x0); x0=x0+dx; y0=CalculusQuads[CurrentFunction].Evaluate(x0); if(CalculusQuads[CurrentFunction].EvaluationError) Intercept=y1-Slope*x1; else Intercept=y0-Slope*x0; } Derivative.setText(Double.toString(Slope)); CalculusQuads[MaxPlottedFunctions-1].InfixFunction=new String( Double.toString(Slope)+"*x+("+ Double.toString(Intercept)+")"); CalculusQuads[MaxPlottedFunctions-1].Xmin=x0-50*dx; CalculusQuads[MaxPlottedFunctions-1].Xmax=x1+49*dx; CalculusQuads[MaxPlottedFunctions-1].ConversionError=false; CalculusQuads[MaxPlottedFunctions-1].MakeQuads(); Derivative.setText(Double.toString(Slope)); CalculusQuads[MaxPlottedFunctions-1].Linear=true; CalculusCanvas.repaint(); } else CalculusQuads[MaxPlottedFunctions-1].IsActive=false; return true; } /* PlotDerivative will plot a simple numerical derivative of the current function on the same graph in color Orange. */ else if(event.target==PlotDerivative) { CalculusQuads[CurrentFunction].PlotDerivative= PlotDerivative.getState(); CalculusCanvas.repaint(); return true; } /* PlotIntegral will plot a simple numerical integral from 0 to x for the current function on the same graph in color green. */ else if(event.target==PlotIntegral) { CalculusQuads[CurrentFunction].PlotIntegral= PlotIntegral.getState(); CalculusCanvas.repaint(); return true; } //Otherwise, let the superclass handle it. else return super.action(event,arg); } }