Derivatives
/************************************************************************* * * * 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/ * * * *************************************************************************/ // Draws the graph of a function and its first derivative (and optionally // its second derivative). It shows the tangent line to the graph and // marks the corresponding point on the graph of the derivative. The // user controls the position of the tangent line with a slider and/or // a number-input box. A formula for the derivative can be displayed // at the bototm of the applet. import java.awt.*; import java.awt.event.*; 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 Derivatives extends GenericGraphApplet { private String functionName; // name of the fuction beging graphed, 'f' by default; used in labels etc private Function func; // The function that is graphed. private Function deriv; // derivative of func private Expression derivExpression; // The Expression that defines the derivative private Function deriv2; // if non-null, second derivative of func private Controller subController = new Controller(); // Respond to changes in x-coord input; won't redraw graph private VariableInput xInput; // x-coord of point of tangency private class ExprLbl extends Label implements Computable { // A class for displaying the formula for deriv String label; ExprLbl(String label) { this.label = label; compute(); } public void compute() { setText(label + derivExpression.toString()); } } protected void setUpParameterDefaults() { // I don't want to use abs(x)^x as the default function, since it's derivative is so funny parameterDefaults = new java.util.Hashtable(); parameterDefaults.put("Function", " tan(" + getParameter("Variable","x") + ")"); } protected void setUpMainPanel() { // add a bunch of extra components at the end super.setUpMainPanel(); // now that limitsPanel has been set up, add the two extra coordinate rects to it if (limitsPanel != null) { limitsPanel.addCoords(canvas.getCoordinateRect(1)); if (deriv2 != null) limitsPanel.addCoords(canvas.getCoordinateRect(2)); } else { // CoordinateRects must synchronize with each other Tie coordTie = new Tie(canvas.getCoordinateRect(0),canvas.getCoordinateRect(1)); if (deriv2 != null) coordTie.add(canvas.getCoordinateRect(2)); canvas.getCoordinateRect(0).setSyncWith(coordTie); canvas.getCoordinateRect(1).setSyncWith(coordTie); if (deriv2 != null) canvas.getCoordinateRect(2).setSyncWith(coordTie); } // Add controls at the bottom of the panel for setting the value of x. // Also add the derivative formula, if it's supposed to be displayed Value xMin = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMIN); Value xMax = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMAX); canvas.getCoordinateRect().setOnChange(subController); VariableSlider xSlider = new VariableSlider(xMin,xMax); xSlider.setOnUserAction(subController); xInput.setOnTextChange(subController); subController.add(xSlider); subController.add(xInput); subController.add( new Tie(xSlider,xInput) ); Panel p = new Panel(); p.setLayout(new BorderLayout(5,5)); p.add(xInput.withLabel(), BorderLayout.WEST); p.add(xSlider, BorderLayout.CENTER); // If there is no limits panel, make it possible to add a RestoreLimits button to the input panel if (limitsPanel == null && ! "no".equalsIgnoreCase(getParameter("UseRestoreButton","no"))) { // add button to left of slider Button res = new Button("Restore Limits"); p.add(res, BorderLayout.EAST); res.setBackground(Color.lightGray); res.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent evt) { canvas.getCoordinateRect(0).restore(); canvas.getCoordinateRect(1).restore(); if (deriv2 != null) canvas.getCoordinateRect(2).restore(); } }); } if ("yes".equalsIgnoreCase(getParameter("ShowFormula", "yes"))) { // add derivative formula Panel s = new Panel(); s.setLayout(new GridLayout(2,1,3,3)); s.add(p); ExprLbl lbl = new ExprLbl(" " + functionName + "'(" + xVar.getName() + ") = "); mainController.add(lbl); s.add(lbl); p = s; } if (inputPanel == null) { // Add the control panel directly to the main panel p.setBackground(getColorParam("PanelBackground",Color.lightGray)); mainPanel.add(p,BorderLayout.SOUTH); } else { // Add control panel to bottom of input panel. inputPanel.add(p,BorderLayout.SOUTH); } } // end setUpMainPanel protected void setUpCanvas() { // Override this to add more stuff to the canvas. // I don't call super.setUpCanvas(), since // the canvas in this case is quite a bit different // from the standard one. boolean showSecond = ! "no".equalsIgnoreCase(getParameter("SecondDerivative","no")); xInput = new VariableInput(xVar.getName(), getParameter("X","1")); if (functionInput != null) { func = functionInput.getFunction(xVar); derivExpression = functionInput.getExpression().derivative(xVar); } else { String def = getParameter("Function"); Expression exp = parser.parse(def); Function f = new SimpleFunction( exp, xVar ); derivExpression = exp.derivative(xVar); func = new WrapperFunction(f); } Graph1D graph = new Graph1D(func); Color color = getColorParam("GraphColor",Color.black); graph.setColor(color); deriv = func.derivative(1); Graph1D derivGraph = new Graph1D(deriv); derivGraph.setColor(color); Graph1D deriv2Graph = null; if (showSecond) { deriv2 = deriv.derivative(1); deriv2Graph = new Graph1D(deriv2); deriv2Graph.setColor(color); } // Set up 2 or 3 coordinate retcs if (showSecond) { canvas.addNewCoordinateRect(0, 1.0/3.0, 0, 1); canvas.addNewCoordinateRect(1.0/3.0, 2.0/3.0, 0, 1); canvas.addNewCoordinateRect(2.0/3.0, 1, 0, 1); } else { canvas.addNewCoordinateRect(0, 0.5, 0, 1); canvas.addNewCoordinateRect(0.5, 1, 0, 1); } // do the type of stuff that's usually done in super.setUpCanvas color = getColorParam("CanvasColor"); if (color != null) canvas.setBackground(color); if (! "no".equalsIgnoreCase(getParameter("UsePanner", "no")) ) { canvas.add(new Panner(),0); canvas.add(new Panner(),1); if (showSecond) canvas.add(new Panner(),2); } if ( ! "no".equalsIgnoreCase(getParameter("UseGrid", "no")) ) { Grid g = new Grid(); color = getColorParam("GridColor"); if (color != null) g.setColor(color); canvas.add(g,0); g = new Grid(); color = getColorParam("GridColor"); if (color != null) g.setColor(color); canvas.add(g,1); if (showSecond) { g = new Grid(); color = getColorParam("GridColor"); if (color != null) g.setColor(color); canvas.add(g,2); } } canvas.add(makeAxes(),0); canvas.add(makeAxes(),1); if (showSecond) canvas.add(makeAxes(),2); if ( ! "no".equalsIgnoreCase(getParameter("UseMouseZoom", "no")) ) canvas.setHandleMouseZooms(true); if ( "yes".equalsIgnoreCase(getParameter("UseOffscreenCanvas", "yes")) ) canvas.setUseOffscreenCanvas(true); mainController.setErrorReporter(canvas); mainPanel.add(canvas, BorderLayout.CENTER); // add graphs, tangent lines etc. canvas.add(graph,0); canvas.add(derivGraph,1); if (showSecond) canvas.add(deriv2Graph,2); Color tangentColor = getColorParam("TangentColor", Color.red); Color tangentColor2 = getColorParam("TangentColor2", new Color(0, 180, 0)); mainController.remove(canvas); mainController.add(graph); mainController.add(derivGraph); if (showSecond) mainController.add(deriv2Graph); subController = new Controller(); mainController.add(subController); TangentLine tan = new TangentLine(xInput, func); Crosshair cross = new Crosshair(xInput,deriv); tan.setColor(tangentColor); cross.setColor(tangentColor); canvas.add(tan, 0); canvas.add(cross, 1); subController.add(tan); subController.add(cross); if (showSecond) { tan = new TangentLine(xInput, deriv); cross = new Crosshair(xInput, deriv2); tan.setColor(tangentColor2); cross.setColor(tangentColor2); canvas.add(tan, 1); canvas.add(cross, 2); subController.add(tan); subController.add(cross); } functionName = getParameter("FunctionName", "f"); String yName = getParameter("YName","y"); Color textColor = getColorParam("TextColor",Color.black); Color bgColor = getColorParam("TextBackground",Color.white); DrawString str; if ("yes".equalsIgnoreCase(getParameter("ShowGraphLabels","yes"))) { str = new DrawString(yName + " = " + functionName + "(" + xVar.getName() + ")"); str.setColor(textColor); str.setBackgroundColor(bgColor); str.setFrameWidth(1); canvas.add(str,0); str = new DrawString(yName + " = " + functionName + " ' (" + xVar.getName() + ")"); str.setColor(textColor); str.setBackgroundColor(bgColor); str.setFrameWidth(1); canvas.add(str,1); if (showSecond) { str = new DrawString(yName + " = " + functionName + " ' ' (" + xVar.getName() + ")"); str.setColor(textColor); str.setBackgroundColor(bgColor); str.setFrameWidth(1); canvas.add(str,2); } } if ("yes".equalsIgnoreCase(getParameter("ShowValues","yes"))) { str = new DrawString(functionName + "(#) = #", DrawString.BOTTOM_LEFT, new Value[] { xInput, new ValueMath(func,xInput) }); str.setColor(textColor); str.setBackgroundColor(bgColor); str.setFrameWidth(1); str.setNumSize(7); canvas.add(str,0); subController.add(str); str = new DrawString(functionName + " ' (#) = #", DrawString.BOTTOM_LEFT, new Value[] { xInput, new ValueMath(deriv,xInput) }); str.setColor(textColor); str.setBackgroundColor(bgColor); str.setFrameWidth(1); str.setNumSize(7); canvas.add(str,1); subController.add(str); if (showSecond) { str = new DrawString(functionName + " ' ' (#) = #", DrawString.BOTTOM_LEFT, new Value[] { xInput, new ValueMath(deriv2,xInput) }); str.setColor(textColor); str.setBackgroundColor(bgColor); str.setFrameWidth(1); str.setNumSize(7); canvas.add(str,2); subController.add(str); } } } // end setUpCanvas() protected void addCanvasBorder() { // override to add the border to each coordinate rect, and make default width equal to 1 int borderWidth; double[] bw = getNumericParam("BorderWidth"); if (bw == null || bw.length == 0 || bw[0] > 25) borderWidth = 2; else borderWidth = (int)Math.round(bw[0]); if (borderWidth > 0) { canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth ), 0 ); canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth ), 1 ); if (deriv2 != null) canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth ), 2 ); } } 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 four or five 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 initially. 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); StringTokenizer toks = new StringTokenizer(limitsText, " ,"); 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.countTokens() > 0) { // Get point for tangent line try { Double d = new Double(toks.nextToken()); xInput.setVal( d.doubleValue() ); } 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); } 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 { Expression exp = parser.parse(example); derivExpression = exp.derivative(xVar); Function f = new SimpleFunction( exp, 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(); canvas.getCoordinateRect(1).setRestoreBuffer(); if (deriv2 != null) canvas.getCoordinateRect(0).setRestoreBuffer(); mainController.compute(); } // end doLoadExample() public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new Derivatives(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); } } // end class SimpleGraph