Subversion Repositories wimsdev

Rev

Blame | Last modification | View Log | RSS feed

  1. /*************************************************************************
  2. *                                                                        *
  3. *  This source code file, and compiled classes derived from it, can      *
  4. *  be used and distributed without restriction, including for commercial *
  5. *  use.  (Attribution is not required but is appreciated.)               *
  6. *                                                                        *
  7. *   David J. Eck                                                         *
  8. *   Department of Mathematics and Computer Science                       *
  9. *   Hobart and William Smith Colleges                                    *
  10. *   Geneva, New York 14456,   USA                                        *
  11. *   Email: eck@hws.edu          WWW: http://math.hws.edu/eck/            *
  12. *                                                                        *
  13. *************************************************************************/
  14.  
  15.  
  16. // Draws the graph of a function and its first derivative (and optionally
  17. // its second derivative).  It shows the tangent line to the graph and
  18. // marks the corresponding point on the graph of the derivative.  The
  19. // user controls the position of the tangent line with a slider and/or
  20. // a number-input box.  A formula for the derivative can be displayed
  21. // at the bototm of the applet.
  22.  
  23. import java.awt.*;
  24. import java.awt.event.*;
  25. import java.applet.Applet;
  26. import java.util.StringTokenizer;
  27. import edu.hws.jcm.draw.*;
  28. import edu.hws.jcm.data.*;
  29. import edu.hws.jcm.functions.*;
  30. import edu.hws.jcm.awt.*;
  31.  
  32.  
  33. public class Derivatives extends GenericGraphApplet {
  34.  
  35.    private String functionName;  // name of the fuction beging graphed, 'f' by default; used in labels etc
  36.    private Function func;   // The function that is graphed.
  37.    private Function deriv;         // derivative of func
  38.    private Expression derivExpression;  // The Expression that defines the derivative
  39.    private Function deriv2;        // if non-null, second derivative of func
  40.    private Controller subController = new Controller();  // Respond to changes in x-coord input; won't redraw graph
  41.  
  42.    private VariableInput xInput; // x-coord of point of tangency
  43.    
  44.    private class ExprLbl extends Label implements Computable {
  45.           // A class for displaying the formula for deriv
  46.       String label;
  47.       ExprLbl(String label) {
  48.          this.label = label;
  49.          compute();
  50.       }
  51.       public void compute() {
  52.          setText(label + derivExpression.toString());
  53.       }
  54.    }
  55.    
  56.    protected void setUpParameterDefaults() { // I don't want to use abs(x)^x as the default function, since it's derivative is so funny
  57.       parameterDefaults = new java.util.Hashtable();
  58.       parameterDefaults.put("Function", " tan(" + getParameter("Variable","x") + ")");
  59.    }
  60.    
  61.    protected void setUpMainPanel() {  // add a bunch of extra components at the end
  62.       super.setUpMainPanel();
  63.      
  64.       // now that limitsPanel has been set up, add the two extra coordinate rects to it
  65.      
  66.       if (limitsPanel != null) {
  67.          limitsPanel.addCoords(canvas.getCoordinateRect(1));  
  68.          if (deriv2 != null)
  69.             limitsPanel.addCoords(canvas.getCoordinateRect(2));  
  70.       }
  71.       else {  // CoordinateRects must synchronize with each other
  72.          Tie coordTie = new Tie(canvas.getCoordinateRect(0),canvas.getCoordinateRect(1));
  73.          if (deriv2 != null)
  74.             coordTie.add(canvas.getCoordinateRect(2));
  75.          canvas.getCoordinateRect(0).setSyncWith(coordTie);
  76.          canvas.getCoordinateRect(1).setSyncWith(coordTie);
  77.          if (deriv2 != null)
  78.             canvas.getCoordinateRect(2).setSyncWith(coordTie);
  79.       }
  80.      
  81.      
  82.       // Add controls at the bottom of the panel for setting the value of x.
  83.       // Also add the derivative formula, if it's supposed to be displayed
  84.    
  85.       Value xMin = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMIN);
  86.       Value xMax = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMAX);
  87.       canvas.getCoordinateRect().setOnChange(subController);
  88.       VariableSlider xSlider = new VariableSlider(xMin,xMax);
  89.       xSlider.setOnUserAction(subController);
  90.       xInput.setOnTextChange(subController);
  91.       subController.add(xSlider);
  92.       subController.add(xInput);
  93.       subController.add( new Tie(xSlider,xInput) );
  94.      
  95.       Panel p = new Panel();
  96.       p.setLayout(new BorderLayout(5,5));
  97.       p.add(xInput.withLabel(), BorderLayout.WEST);
  98.       p.add(xSlider, BorderLayout.CENTER);
  99.      
  100.       // If there is no limits panel, make it possible to add a RestoreLimits button to the input panel
  101.      
  102.       if (limitsPanel == null && ! "no".equalsIgnoreCase(getParameter("UseRestoreButton","no"))) {
  103.                // add button to left of slider
  104.            Button res = new Button("Restore Limits");
  105.            p.add(res, BorderLayout.EAST);
  106.            res.setBackground(Color.lightGray);
  107.            res.addActionListener( new ActionListener() {
  108.                    public void actionPerformed(ActionEvent evt) {
  109.                       canvas.getCoordinateRect(0).restore();
  110.                       canvas.getCoordinateRect(1).restore();
  111.                       if (deriv2  != null)
  112.                          canvas.getCoordinateRect(2).restore();
  113.                    }
  114.               });
  115.       }
  116.  
  117.       if ("yes".equalsIgnoreCase(getParameter("ShowFormula", "yes"))) { // add derivative formula
  118.          Panel s = new Panel();
  119.          s.setLayout(new GridLayout(2,1,3,3));
  120.          s.add(p);
  121.          ExprLbl lbl = new ExprLbl(" " + functionName + "'(" + xVar.getName() + ") = ");
  122.          mainController.add(lbl);
  123.          s.add(lbl);
  124.          p = s;
  125.       }
  126.      
  127.       if (inputPanel == null) {
  128.             // Add the control panel directly to the main panel
  129.          p.setBackground(getColorParam("PanelBackground",Color.lightGray));
  130.          mainPanel.add(p,BorderLayout.SOUTH);
  131.       }
  132.       else {
  133.             // Add control panel to bottom of input panel.
  134.          inputPanel.add(p,BorderLayout.SOUTH);
  135.       }
  136.      
  137.  
  138.    } // end setUpMainPanel
  139.  
  140.  
  141.    protected void setUpCanvas() {  // Override this to add more stuff to the canvas.
  142.                                    // I don't call super.setUpCanvas(), since
  143.                                    // the canvas in this case is quite a bit different
  144.                                    // from the standard one.
  145.  
  146.       boolean showSecond = ! "no".equalsIgnoreCase(getParameter("SecondDerivative","no"));
  147.  
  148.       xInput = new VariableInput(xVar.getName(), getParameter("X","1"));
  149.      
  150.       if (functionInput != null) {
  151.          func = functionInput.getFunction(xVar);
  152.          derivExpression = functionInput.getExpression().derivative(xVar);
  153.       }
  154.       else {
  155.          String def = getParameter("Function");
  156.          Expression exp = parser.parse(def);
  157.          Function f = new SimpleFunction( exp, xVar );
  158.          derivExpression = exp.derivative(xVar);
  159.          func = new WrapperFunction(f);
  160.       }
  161.       Graph1D graph = new Graph1D(func);
  162.       Color color = getColorParam("GraphColor",Color.black);
  163.       graph.setColor(color);
  164.       deriv = func.derivative(1);
  165.       Graph1D derivGraph = new Graph1D(deriv);
  166.       derivGraph.setColor(color);
  167.       Graph1D deriv2Graph = null;
  168.       if (showSecond) {
  169.          deriv2 = deriv.derivative(1);
  170.          deriv2Graph = new Graph1D(deriv2);
  171.          deriv2Graph.setColor(color);
  172.       }
  173.  
  174.       // Set up 2 or 3 coordinate retcs
  175.      
  176.       if (showSecond) {
  177.          canvas.addNewCoordinateRect(0, 1.0/3.0, 0, 1);
  178.          canvas.addNewCoordinateRect(1.0/3.0, 2.0/3.0, 0, 1);
  179.          canvas.addNewCoordinateRect(2.0/3.0, 1, 0, 1);
  180.       }
  181.       else {
  182.          canvas.addNewCoordinateRect(0, 0.5, 0, 1);
  183.          canvas.addNewCoordinateRect(0.5, 1, 0, 1);
  184.       }
  185.  
  186.       // do the type of stuff that's usually done in super.setUpCanvas
  187.      
  188.       color = getColorParam("CanvasColor");
  189.       if (color != null)
  190.          canvas.setBackground(color);
  191.       if (! "no".equalsIgnoreCase(getParameter("UsePanner", "no")) ) {
  192.          canvas.add(new Panner(),0);
  193.          canvas.add(new Panner(),1);
  194.          if (showSecond)
  195.             canvas.add(new Panner(),2);
  196.       }
  197.       if ( ! "no".equalsIgnoreCase(getParameter("UseGrid", "no")) ) {
  198.          Grid g = new Grid();
  199.          color = getColorParam("GridColor");
  200.          if (color != null)
  201.             g.setColor(color);
  202.          canvas.add(g,0);
  203.          g = new Grid();
  204.          color = getColorParam("GridColor");
  205.          if (color != null)
  206.             g.setColor(color);
  207.          canvas.add(g,1);
  208.          if (showSecond) {
  209.             g = new Grid();
  210.             color = getColorParam("GridColor");
  211.             if (color != null)
  212.                g.setColor(color);
  213.             canvas.add(g,2);
  214.          }
  215.       }
  216.       canvas.add(makeAxes(),0);
  217.       canvas.add(makeAxes(),1);
  218.       if (showSecond)
  219.          canvas.add(makeAxes(),2);
  220.       if ( ! "no".equalsIgnoreCase(getParameter("UseMouseZoom", "no")) )
  221.          canvas.setHandleMouseZooms(true);
  222.       if ( "yes".equalsIgnoreCase(getParameter("UseOffscreenCanvas", "yes")) )
  223.          canvas.setUseOffscreenCanvas(true);
  224.       mainController.setErrorReporter(canvas);
  225.       mainPanel.add(canvas, BorderLayout.CENTER);
  226.      
  227.       // add graphs, tangent lines etc.
  228.      
  229.       canvas.add(graph,0);
  230.       canvas.add(derivGraph,1);
  231.       if (showSecond)
  232.          canvas.add(deriv2Graph,2);
  233.          
  234.       Color tangentColor = getColorParam("TangentColor", Color.red);
  235.       Color tangentColor2 = getColorParam("TangentColor2", new Color(0, 180, 0));
  236.            
  237.       mainController.remove(canvas);
  238.       mainController.add(graph);
  239.       mainController.add(derivGraph);
  240.       if (showSecond)
  241.          mainController.add(deriv2Graph);
  242.      
  243.       subController = new Controller();
  244.       mainController.add(subController);
  245.      
  246.       TangentLine tan = new TangentLine(xInput, func);
  247.       Crosshair cross = new Crosshair(xInput,deriv);
  248.       tan.setColor(tangentColor);
  249.       cross.setColor(tangentColor);
  250.       canvas.add(tan, 0);
  251.       canvas.add(cross, 1);
  252.       subController.add(tan);
  253.       subController.add(cross);
  254.      
  255.       if (showSecond) {
  256.          tan = new TangentLine(xInput, deriv);
  257.          cross = new Crosshair(xInput, deriv2);
  258.          tan.setColor(tangentColor2);
  259.          cross.setColor(tangentColor2);
  260.          canvas.add(tan, 1);
  261.          canvas.add(cross, 2);
  262.          subController.add(tan);
  263.          subController.add(cross);
  264.       }
  265.  
  266.       functionName = getParameter("FunctionName", "f");
  267.  
  268.       String yName = getParameter("YName","y");
  269.       Color textColor = getColorParam("TextColor",Color.black);
  270.       Color bgColor = getColorParam("TextBackground",Color.white);
  271.       DrawString str;
  272.      
  273.       if ("yes".equalsIgnoreCase(getParameter("ShowGraphLabels","yes"))) {
  274.          str = new DrawString(yName + " = " + functionName + "(" + xVar.getName() + ")");
  275.          str.setColor(textColor);
  276.          str.setBackgroundColor(bgColor);
  277.          str.setFrameWidth(1);
  278.          canvas.add(str,0);
  279.          str = new DrawString(yName + " = " + functionName + " ' (" + xVar.getName() + ")");
  280.          str.setColor(textColor);
  281.          str.setBackgroundColor(bgColor);
  282.          str.setFrameWidth(1);
  283.          canvas.add(str,1);
  284.          if (showSecond) {
  285.              str = new DrawString(yName + " = " + functionName + " ' ' (" + xVar.getName() + ")");
  286.              str.setColor(textColor);
  287.              str.setBackgroundColor(bgColor);
  288.              str.setFrameWidth(1);
  289.              canvas.add(str,2);
  290.          }
  291.       }
  292.       if ("yes".equalsIgnoreCase(getParameter("ShowValues","yes"))) {
  293.           str = new DrawString(functionName + "(#) = #", DrawString.BOTTOM_LEFT, new Value[] { xInput, new ValueMath(func,xInput) });
  294.           str.setColor(textColor);
  295.           str.setBackgroundColor(bgColor);
  296.           str.setFrameWidth(1);
  297.           str.setNumSize(7);
  298.           canvas.add(str,0);
  299.           subController.add(str);
  300.           str = new DrawString(functionName + " ' (#) = #", DrawString.BOTTOM_LEFT, new Value[] { xInput, new ValueMath(deriv,xInput) });
  301.           str.setColor(textColor);
  302.           str.setBackgroundColor(bgColor);
  303.           str.setFrameWidth(1);
  304.           str.setNumSize(7);
  305.           canvas.add(str,1);
  306.           subController.add(str);
  307.           if (showSecond) {
  308.              str = new DrawString(functionName + " ' ' (#) = #", DrawString.BOTTOM_LEFT, new Value[] { xInput, new ValueMath(deriv2,xInput) });
  309.              str.setColor(textColor);
  310.              str.setBackgroundColor(bgColor);
  311.              str.setFrameWidth(1);
  312.              str.setNumSize(7);
  313.              canvas.add(str,2);
  314.              subController.add(str);
  315.           }
  316.       }
  317.  
  318.    } // end setUpCanvas()
  319.    
  320.    
  321.    protected void addCanvasBorder() { // override to add the border to each coordinate rect, and make default width equal to 1
  322.       int borderWidth;
  323.       double[] bw = getNumericParam("BorderWidth");
  324.       if (bw == null || bw.length == 0 || bw[0] > 25)
  325.          borderWidth = 2;
  326.       else
  327.          borderWidth = (int)Math.round(bw[0]);
  328.       if (borderWidth > 0) {
  329.          canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth  ), 0 );
  330.          canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth  ), 1 );
  331.          if (deriv2 != null)
  332.             canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth  ), 2 );
  333.       }
  334.    }
  335.  
  336.  
  337.  
  338.    protected void doLoadExample(String example) {
  339.          // This method is called when the user loads an example from the
  340.          // example menu (if there is one).  It overrides an empty method
  341.          // in GenericGraphApplet.
  342.          //   For the SecantTangent applet, the example string should contain
  343.          // an expression that defines the function to be graphed.  This can optionally
  344.          // be followed by a semicoloon and a list of four or five numbers.
  345.          // The first four numbers give the x- and y-limits to be used for the
  346.          // example.  If they are not present, then -5,5,-5,5 is used.  The
  347.          // fifth number, if present, gives the x-coord where the tangent line
  348.          // is drawn initially.
  349.    
  350.       int pos = example.indexOf(";");
  351.  
  352.       double[] limits = { -5,5,-5,5 };  // x- and y-limits to use
  353.      
  354.       if (pos > 0) { // get limits from example text
  355.          String limitsText = example.substring(pos+1);
  356.          example = example.substring(0,pos);
  357.          StringTokenizer toks = new StringTokenizer(limitsText, " ,");
  358.          if (toks.countTokens() >= 4) {
  359.             for (int i = 0; i < 4; i++) {
  360.                try {
  361.                    Double d = new Double(toks.nextToken());
  362.                    limits[i] = d.doubleValue();
  363.                }
  364.                catch (NumberFormatException e) {
  365.                }
  366.             }
  367.             if (toks.countTokens() > 0) { // Get point for tangent line
  368.                try {
  369.                    Double d = new Double(toks.nextToken());
  370.                    xInput.setVal( d.doubleValue() );
  371.                }
  372.                catch (NumberFormatException e) {
  373.                }
  374.             }
  375.          }
  376.       }
  377.      
  378.       // Set up the example data and recompute everything.
  379.  
  380.       if (functionInput != null) {
  381.             // If there is a function input box, put the example text in it.
  382.          functionInput.setText(example);
  383.       }
  384.       else {
  385.            // If there is no user input, set the function in the graph directly.
  386.            // Also, in this case, func is a "WrapperFunction".  Set the
  387.            // definition of that WrapperFunction to be the same as f
  388.          try {
  389.             Expression exp = parser.parse(example);
  390.             derivExpression = exp.derivative(xVar);
  391.             Function f = new SimpleFunction( exp, xVar );
  392.             ((WrapperFunction)func).setFunction(f);
  393.          }
  394.          catch (ParseError e) {  
  395.              // There should't be parse error's in the Web-page
  396.              // author's examples!  If there are, the function
  397.              // just won't change.
  398.          }
  399.       }
  400.       CoordinateRect coords = canvas.getCoordinateRect(0);
  401.       coords.setLimits(limits);
  402.       coords.setRestoreBuffer();
  403.       canvas.getCoordinateRect(1).setRestoreBuffer();
  404.       if (deriv2 != null)
  405.          canvas.getCoordinateRect(0).setRestoreBuffer();
  406.       mainController.compute();
  407.      
  408.    } // end doLoadExample()
  409.    
  410.  
  411.  
  412. } // end class SimpleGraph
  413.