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. // This applet displays a vector field (f1(x,y),f2(x,y)) and integral curves
  17. // for that vector field (although the integral curve feature can be turned off
  18. // with an applet param).  The drawing of the curves is animated; they are
  19. // drawn segment-by-segment.  In the default setup, a curve is started when the
  20. // user clicks on the canvas.  A curve can also be started by entering the
  21. // starting x and y coords in a pair of text input boxes and clicking a button.
  22.  
  23. import java.awt.*;
  24. import java.awt.event.*;
  25. import java.applet.Applet;
  26. import java.util.*;
  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.  
  34. public class IntegralCurves extends GenericGraphApplet {
  35.  
  36.    private Variable yVar;           // The seond variable, usually y.
  37.    private Function xFunc,yFunc;    // The functions that give the components of the vector field
  38.    private ExpressionInput functionInput2;  // For inputting yFunc.
  39.    private VectorField field;       // The vector/direction field
  40.  
  41.    private Animator animator;       // for incrementally drawing integral curves.
  42.    private Vector curves = new Vector();           // Holds the integral curves
  43.  
  44.    private VariableInput deltaT;    // input the deltaT for the curve
  45.    double dt = 0.1;  // The value of delat t in the case where there is no deltaT input box
  46.    private VariableInput xStart,yStart;  // Starting point for curve
  47.    private Choice methodChoice;     // select integration method
  48.    private Button startCurveButton; // user clicks to start curve from (x,y) in xStart, yStart input boxes
  49.    private Button clearButton;      // clears curves
  50.    private Color curveColor;        // color for integral curves
  51.    private Draw curveDrawer = new Draw(); // A DrawTemp object that draws one segment of the integral curves.
  52.    
  53.    private double[] nextPoint = new double[2];  // Help in computing next point of integral curve.
  54.    private double[] params = new double[2];     // ditto
  55.    
  56.    private static final int RK4 = 0, RK2 = 1, EULER = 2;   // constants for integration methos
  57.    
  58.  
  59.    private class Curve {  // holds the data for one integral curve
  60.       double dt;
  61.       int method;
  62.       double x,y; // point on the curve
  63.       double lastX = Double.NaN, lastY; // previous point, so we can draw a line.
  64.    }
  65.    
  66.    private class Draw implements DrawTemp { // For drawing the next segment in each integral curve (as a DrawTemp)
  67.       public void draw(Graphics g, CoordinateRect coords) {
  68.          int size = curves.size();
  69.          g.setColor(curveColor);
  70.          for (int i = 0; i < size; i++) {
  71.             Curve c = (Curve)(curves.elementAt(i));
  72.             if (! (Double.isNaN(c.x) || Double.isNaN(c.y) || Double.isNaN(c.lastX) || Double.isNaN(c.lastY)) ) {
  73.                int x1 = coords.xToPixel(c.lastX);
  74.                int y1 = coords.yToPixel(c.lastY);
  75.                int x2 = coords.xToPixel(c.x);
  76.                int y2 = coords.yToPixel(c.y);
  77.                g.drawLine(x1,y1,x2,y2);
  78.             }
  79.          }
  80.       }
  81.    }
  82.  
  83.    protected void setUpParser() {
  84.           // create the "y" variable; also set up some parameter defaults.
  85.       yVar = new Variable(getParameter("Variable2","y"));
  86.       parser.add(yVar);
  87.       super.setUpParser();  // sets up xVar, among other things.
  88.       parameterDefaults = new Hashtable();
  89.       parameterDefaults.put("FunctionLabel", " f1(" + xVar.getName() + "," + yVar.getName() + ") = ");
  90.       parameterDefaults.put("FunctionLabel2", " f2(" + xVar.getName() + "," + yVar.getName() + ") = ");
  91.       parameterDefaults.put("Function", " " + yVar.getName() + " - 0.1*" + xVar.getName());
  92.       parameterDefaults.put("Function2", " - " + xVar.getName() + " - 0.1*" + yVar.getName());
  93.       defaultFrameSize = new int[] { 580, 440 };
  94.    }
  95.  
  96.    protected void setUpCanvas() {  // Override this to add more stuff to the canvas.
  97.    
  98.       super.setUpCanvas();  // Do the common setup: Add the axes and
  99.      
  100.       // set up the vector field and add it to the canvas
  101.      
  102.       if (functionInput != null) {
  103.          xFunc = functionInput.getFunction(new Variable[] {xVar,yVar});
  104.          yFunc = functionInput2.getFunction(new Variable[] {xVar,yVar});
  105.       }
  106.       else {
  107.          String xFuncDef = getParameter("Function");
  108.          String yFuncDef = getParameter("Function2");
  109.          Function f = new SimpleFunction( parser.parse(xFuncDef), new Variable[] {xVar,yVar} );
  110.          xFunc = new WrapperFunction(f);
  111.          f = new SimpleFunction( parser.parse(yFuncDef), new Variable[] {xVar,yVar} );
  112.          yFunc = new WrapperFunction(f);
  113.       }
  114.       String type = (getParameter("VectorStyle", "") + "A").toUpperCase();
  115.       int style = 0;
  116.       switch (type.charAt(0)) {
  117.          case 'A': style = VectorField.ARROWS; break;
  118.          case 'L': style = VectorField.LINES; break;
  119.          case 'S': style = VectorField.SCALED_VECTORS; break;
  120.       }
  121.       field = new VectorField(xFunc,yFunc,style);
  122.       Color color = getColorParam("VectorColor");
  123.       if (color != null)
  124.          field.setColor(color);
  125.       int space = (style == VectorField.LINES)? 20 : 30;
  126.       double[] d = getNumericParam("VectorSpacing");
  127.       if (d != null && d.length > 0 && d[0] >= 1)
  128.          space = (int)Math.round(d[0]);
  129.       field.setPixelSpacing(space);
  130.  
  131.       canvas.add(field);  // Finally, add the graph to the canvas.
  132.  
  133.       curveColor = getColorParam("CurveColor", Color.magenta);
  134.  
  135.       // add a mouse listener to the canvas for starting curves.
  136.  
  137.       if ("yes".equalsIgnoreCase(getParameter("MouseStartsCurves","yes")) && "yes".equalsIgnoreCase(getParameter("DoCurves","yes")))
  138.          canvas.addMouseListener(new MouseAdapter() {
  139.                 public void mousePressed(MouseEvent evt) {
  140.                    CoordinateRect coords = canvas.getCoordinateRect();
  141.                    double x = coords.pixelToX(evt.getX());
  142.                    double y = coords.pixelToY(evt.getY());
  143.                    if (xStart != null)
  144.                       xStart.setVal(x);
  145.                    if (yStart != null)
  146.                       yStart.setVal(y);
  147.                    startCurve(x,y);
  148.                 }
  149.             });
  150.  
  151.    } // end setUpCanvas()
  152.    
  153.  
  154.    
  155.    protected void setUpBottomPanel() {
  156.          // Override this to make a panel containing controls.  This is complicated
  157.          // because it's possible to turn off a lot of the inputs with applet params.
  158.          
  159.       // Check on the value of delta t, which has to be set even if there are no input controls.
  160.       double[] DT = getNumericParam("DeltaT");
  161.       if  ( ! (DT == null || DT.length == 0 || DT[0] <= 0) )
  162.          dt = DT[0];
  163.  
  164.       boolean doCurves = "yes".equalsIgnoreCase(getParameter("DoCurves","yes"));
  165.       boolean useInputs = "yes".equalsIgnoreCase(getParameter("UseFunctionInput","yes"));
  166.       if (!doCurves && !useInputs)  // no input controls at all.
  167.          return;
  168.  
  169.       // make the input panel
  170.  
  171.       inputPanel = new JCMPanel();
  172.       inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) );
  173.       mainPanel.add(inputPanel,BorderLayout.SOUTH);
  174.  
  175.       // Make the function inputs and the compute button, if these are in the configuration.
  176.  
  177.       JCMPanel in1 = null, in2 = null;  // hold function inputs, if any
  178.       if (useInputs) {
  179.          if ( "yes".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) {
  180.             String cname = getParameter("ComputeButtonName", "New Functions");
  181.             computeButton = new Button(cname);
  182.             computeButton.addActionListener(this);
  183.          }
  184.           functionInput = new ExpressionInput(getParameter("Function"),parser);
  185.           in1 = new JCMPanel();
  186.           in1.add(functionInput,BorderLayout.CENTER);
  187.           in1.add(new Label(getParameter("FunctionLabel")), BorderLayout.WEST);
  188.           functionInput.setOnUserAction(mainController);
  189.           functionInput2 = new ExpressionInput(getParameter("Function2"),parser);
  190.           in2 = new JCMPanel();
  191.           in2.add(functionInput2,BorderLayout.CENTER);
  192.           in2.add(new Label(getParameter("FunctionLabel2")), BorderLayout.WEST);
  193.           functionInput2.setOnUserAction(mainController);
  194.       }
  195.      
  196.       // If we're not doing curves, all we have to do is put the function inputs in the inputPanel
  197.      
  198.       if (!doCurves) {  
  199.          Panel p = new JCMPanel(2,1,3);
  200.          p.add(in1);
  201.          p.add(in2);
  202.          inputPanel.add(p, BorderLayout.CENTER);
  203.          if (computeButton != null)
  204.             inputPanel.add(computeButton,BorderLayout.EAST);
  205.          return;
  206.       }
  207.      
  208.       // Now we know that doCurves is true.  First, make the animator and clear button
  209.  
  210.       animator = new Animator(Animator.STOP_BUTTON);
  211.       animator.setStopButtonName("Stop Curves");
  212.       animator.setOnChange(new Computable() { // animator drives curves
  213.            public void compute() {
  214.               extendCurves();
  215.            }
  216.         });
  217.       mainController.add(new InputObject() { // curves must stop if main controller is triggered
  218.             public void checkInput() {
  219.                curves.setSize(0);
  220.                animator.stop();
  221.             }
  222.             public void notifyControllerOnChange(Controller c) {
  223.             }
  224.         });
  225.       clearButton = new Button("Clear");
  226.       clearButton.addActionListener(this);
  227.  
  228.       // Make a panel to contain the xStart and yStart inputs, if they are in the configuration.
  229.  
  230.       Panel bottom = null;
  231.       if ("yes".equalsIgnoreCase(getParameter("UseStartInputs","yes"))) {
  232.          xStart = new VariableInput();
  233.          xStart.addActionListener(this);
  234.          yStart = new VariableInput();
  235.          yStart.addActionListener(this);
  236.          bottom = new Panel();  // not a JCMPanel -- I don't want their contents checked automatically
  237.          startCurveButton = new Button("Start curve at:");
  238.          startCurveButton.addActionListener(this);
  239.          bottom.add(startCurveButton);
  240.          bottom.add(new Label(xVar.getName() + " ="));
  241.          bottom.add(xStart);
  242.          bottom.add(new Label(yVar.getName() + " ="));
  243.          bottom.add(yStart);
  244.       }
  245.      
  246.       // Now, make a panel to contain the methodChoice and deltaT input if they are in the configuration.
  247.       // The animator and clear button will be added to this panel if it exists.  If not, and if
  248.       // an xStart/yStart panel exists, then it will be added there.  If neither exists,
  249.       // it goes in its own panel.  The variable bottom ends up pointing to a panel that
  250.       // contains all the curve controls.
  251.      
  252.       boolean useChoice = "yes".equalsIgnoreCase(getParameter("UseMethodChoice","yes"));
  253.       boolean useDelta = "yes".equalsIgnoreCase(getParameter("UseDeltaInput","yes"));
  254.       if (useChoice || useDelta) {
  255.          Panel top = new Panel();  // not a JCMPanel!
  256.          if (useDelta) {
  257.             top.add(new Label("dt ="));
  258.             deltaT = new VariableInput(null,""+dt);
  259.             top.add(deltaT);
  260.          }
  261.          if (useChoice) {
  262.             top.add(new Label("Method:"));
  263.             methodChoice = new Choice();
  264.             methodChoice.add("Runge-Kutta 4");
  265.             methodChoice.add("Runge-Kutta 2");
  266.             methodChoice.add("Euler");
  267.             top.add(methodChoice);
  268.          }
  269.          top.add(animator);
  270.          top.add(clearButton);
  271.          if (bottom == null)
  272.             bottom = top;
  273.          else {
  274.             Panel p = new Panel();
  275.             p.setLayout(new BorderLayout());
  276.             p.add(top, BorderLayout.NORTH);
  277.             p.add(bottom, BorderLayout.CENTER);
  278.             bottom = p;
  279.          }
  280.       }
  281.       else  {
  282.          if (bottom == null)
  283.             bottom = new Panel();
  284.          bottom.add(animator);
  285.          bottom.add(clearButton);
  286.       }
  287.      
  288.       // Add the panels "bottom" to the inputPanel, and ruturn
  289.       // if there are no function inputs.
  290.      
  291.       inputPanel.add(bottom, BorderLayout.CENTER);
  292.       if (in1 == null)
  293.          return;
  294.  
  295.       // Add the function inputs and compute button to the inputPanel        
  296.          
  297.       Panel in = new JCMPanel(1,2);
  298.       in.add(in1);
  299.       in.add(in2);
  300.       if (computeButton != null) {
  301.          Panel p = new JCMPanel();
  302.          p.add(in,BorderLayout.CENTER);
  303.          p.add(computeButton,BorderLayout.EAST);
  304.          in = p;
  305.       }
  306.       inputPanel.add(in,BorderLayout.NORTH);
  307.      
  308.    } // end setUpBottomPanel()
  309.  
  310.  
  311.    public void actionPerformed(ActionEvent evt) {
  312.          // React if user presses return in xStart or yStart, or pass evt on to GenericGraphApplet
  313.       Object src = evt.getSource();
  314.       if (src == clearButton) {
  315.           canvas.clearErrorMessage();
  316.           curves.setSize(0);
  317.           animator.stop();
  318.           canvas.compute();  // force recompute of off-screen canvas!
  319.       }
  320.       else if (src == xStart || src == yStart || src == startCurveButton) {
  321.               // Start a curve from x and y values in xStart and yStart
  322.          canvas.clearErrorMessage();
  323.          double x=0, y=0;
  324.          try {
  325.             xStart.checkInput();
  326.             x = xStart.getVal();
  327.             yStart.checkInput();
  328.             y = yStart.getVal();
  329.             startCurve(x,y);
  330.             if (deltaT != null) {
  331.                deltaT.checkInput();
  332.                dt = deltaT.getVal();
  333.                if (dt <= 0) {
  334.                   deltaT.requestFocus();
  335.                   throw new JCMError("dt must be positive", deltaT);
  336.                }
  337.             }
  338.          }
  339.          catch (JCMError e) {
  340.             curves.setSize(0);
  341.             animator.stop();
  342.             canvas.setErrorMessage(null,"Illegal Data For Curve.  " + e.getMessage());
  343.          }
  344.       }
  345.       else
  346.          super.actionPerformed(evt);
  347.    } // end actionPerfromed
  348.    
  349.    public void startCurve(double x, double y) {
  350.         // Start an integral curve at the point (x,y)
  351.       synchronized (curves) {
  352.          if (deltaT != null) {
  353.             try {
  354.                deltaT.checkInput();
  355.                dt = deltaT.getVal();
  356.                if (dt <= 0) {
  357.                   deltaT.requestFocus();
  358.                   throw new JCMError("dt must be positive", deltaT);
  359.                }  
  360.             }
  361.             catch (JCMError e) {
  362.                curves.setSize(0);
  363.                animator.stop();
  364.                canvas.setErrorMessage(null,"Illegal Data For Curve.  " + e.getMessage());
  365.                return;
  366.             }
  367.          }
  368.          Curve c = new Curve();
  369.          c.dt = dt;
  370.          int method = (methodChoice == null)? RK4 : methodChoice.getSelectedIndex();
  371.          c.method = method;
  372.          c.x = x;
  373.          c.y = y;
  374.          curves.addElement(c);
  375.          animator.start();
  376.       }
  377.    }
  378.    
  379.    public void extendCurves() {
  380.          // Add the next segment to each integral curve.  This function
  381.          // is called repeatedly by the animator.
  382.       synchronized(curves) {
  383.          if (canvas == null || canvas.getCoordinateRect() == null)  // can happen when frame closes
  384.             return;  
  385.          while (canvas.getCoordinateRect().getWidth() <= 0) {
  386.                // need this at startup to make sure that the canvas has appeared on the screen
  387.              try {
  388.                 Thread.sleep(200);
  389.              }
  390.              catch (InterruptedException e) {
  391.              }
  392.          }
  393.          int size = curves.size();
  394.          for (int i = 0; i < size; i++) {
  395.             Curve curve = (Curve)curves.elementAt(i);
  396.             curve.lastX = curve.x;
  397.             curve.lastY = curve.y;
  398.             nextPoint(curve.x, curve.y, curve.dt, curve.method);
  399.             curve.x = nextPoint[0];
  400.             curve.y = nextPoint[1];
  401.          }
  402.          CoordinateRect c = canvas.getCoordinateRect();
  403.          double pixelWidthLimit = 100000*c.getPixelWidth();
  404.          double pixelHeightLimit = 100000*c.getPixelHeight();
  405.          for (int i = size-1; i >= 0; i--) {
  406.             Curve curve = (Curve)curves.elementAt(i);
  407.             if (Double.isNaN(curve.x) || Double.isNaN(curve.y) ||
  408.                     Math.abs(curve.x) > pixelWidthLimit ||
  409.                     Math.abs(curve.y) > pixelWidthLimit) // stop processing this curve
  410.                curves.removeElementAt(i);
  411.          }
  412.          if (curves.size() > 0)
  413.             canvas.drawTemp(curveDrawer);
  414.          else {
  415.             animator.stop();
  416.          }
  417.       }
  418.    }
  419.  
  420.    private void nextPoint(double x, double y, double dt, int method) {
  421.          // Find next point from (x,y) by applying specified method over time interval dt
  422.       switch (method) {
  423.          case EULER:
  424.             nextEuler(x,y,dt);
  425.             break;
  426.          case RK2:
  427.             nextRK2(x,y,dt);
  428.             break;
  429.          case RK4:
  430.             nextRK4(x,y,dt);
  431.             break;
  432.       }
  433.    }
  434.    
  435.    private void nextEuler(double x, double y, double dt) {
  436.       params[0] = x;
  437.       params[1] = y;
  438.       double dx = xFunc.getVal(params);
  439.       double dy = yFunc.getVal(params);
  440.       nextPoint[0] = x + dt*dx;
  441.       nextPoint[1] = y + dt*dy;
  442.    }
  443.    
  444.    private void nextRK2(double x, double y, double dt) {
  445.       params[0] = x;
  446.       params[1] = y;
  447.       double dx1 = xFunc.getVal(params);
  448.       double dy1 = yFunc.getVal(params);
  449.       double x2 = x + dt*dx1;
  450.       double y2 = y + dt*dy1;
  451.       params[0] = x2;
  452.       params[1] = y2;
  453.       double dx2 = xFunc.getVal(params);
  454.       double dy2 = yFunc.getVal(params);
  455.       nextPoint[0] = x + 0.5*dt*(dx1+dx2);
  456.       nextPoint[1] = y + 0.5*dt*(dy1+dy2);
  457.    }
  458.    
  459.    private void nextRK4(double x, double y, double dt) {
  460.       params[0] = x;
  461.       params[1] = y;
  462.       double dx1 = xFunc.getVal(params);
  463.       double dy1 = yFunc.getVal(params);
  464.      
  465.       double x2 = x + 0.5*dt*dx1;
  466.       double y2 = y + 0.5*dt*dy1;
  467.       params[0] = x2;
  468.       params[1] = y2;
  469.       double dx2 = xFunc.getVal(params);
  470.       double dy2 = yFunc.getVal(params);
  471.      
  472.       double x3 = x + 0.5*dt*dx2;
  473.       double y3 = y + 0.5*dt*dy2;
  474.       params[0] = x3;
  475.       params[1] = y3;
  476.       double dx3 = xFunc.getVal(params);
  477.       double dy3 = yFunc.getVal(params);
  478.      
  479.       double x4 = x + dt*dx3;
  480.       double y4 = y + dt*dy3;
  481.       params[0] = x4;
  482.       params[1] = y4;
  483.       double dx4 = xFunc.getVal(params);
  484.       double dy4 = yFunc.getVal(params);
  485.      
  486.       nextPoint[0] = x + (dt / 6) * (dx1 + 2 * dx2 + 2 * dx3 + dx4);
  487.       nextPoint[1] = y + (dt / 6) * (dy1 + 2 * dy2 + 2 * dy3 + dy4);
  488.    }
  489.    
  490.    protected void doLoadExample(String example) {
  491.          // This method is called when the user loads an example from the
  492.          // example menu (if there is one).  It overrides an empty method
  493.          // in GenericGraphApplet.
  494.          //   For the IntegrapCurves applet, the example string should contain
  495.          // two expression that defines the vector field, separated
  496.          // by a semicolon.  This can optionally
  497.          // be followed by another semicolon and a list of numbers, separated by spaces and/or commas.
  498.          // The first four numbers give the x- and y-limits to be used for the
  499.          // example.  If they are not present, then -5,5,-5,5 is used.  The next number, if present,
  500.          // specifies a value for delta t.  If there are more numbers, they should come in pairs.
  501.          // each pair specifies a point where a curve will be started when the
  502.          // example is loaded.  There is a 0.5 second delay between loading and starting the
  503.          // curves to allow time for the redrawing (although it seems to block the redrawing, at least
  504.          // on some platforms).
  505.          
  506.       if (animator != null) {
  507.          curves.setSize(0);
  508.          animator.stop();
  509.       }
  510.          
  511.       int pos = example.indexOf(";");
  512.       if (pos == -1)
  513.          return; // illegal example -- must have two functions
  514.       String example2 = example.substring(pos+1);
  515.       example = example.substring(0,pos);
  516.       pos = example2.indexOf(";");  
  517.      
  518.          
  519.       double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
  520.      
  521.       StringTokenizer toks = null;
  522.  
  523.       if (pos > 0) {
  524.                // Get limits from example2 text.
  525.          String nums = example2.substring(pos+1);
  526.          example2 = example2.substring(0,pos);
  527.          toks = new StringTokenizer(nums, " ,");
  528.          if (toks.countTokens() >= 4) {
  529.             for (int i = 0; i < 4; i++) {
  530.                try {
  531.                    Double d = new Double(toks.nextToken());
  532.                    limits[i] = d.doubleValue();
  533.                }
  534.                catch (NumberFormatException e) {
  535.                }
  536.             }
  537.          }
  538.          if (toks.hasMoreTokens()) {
  539.             double d = Double.NaN;
  540.             try {
  541.                d = (new Double(toks.nextToken())).doubleValue();
  542.             }
  543.             catch (NumberFormatException e) {
  544.             }
  545.             if (Double.isNaN(d) || d <= 0 || d > 100)
  546.                d = 0.1;
  547.              if (deltaT != null)
  548.                 deltaT.setVal(d);
  549.              else
  550.                 dt = d;
  551.          }
  552.       }
  553.      
  554.       // Set up the example data and recompute everything.
  555.  
  556.       if (functionInput != null) {
  557.             // If there is a function input box, put the example text in it.
  558.          functionInput.setText(example);
  559.          functionInput2.setText(example2);
  560.       }
  561.       else {
  562.            // If there is no user input, set the function in the graph directly.
  563.          try {
  564.             Function f = new SimpleFunction( parser.parse(example), xVar );
  565.             ((WrapperFunction)xFunc).setFunction(f);
  566.             Function g = new SimpleFunction( parser.parse(example2), xVar );
  567.             ((WrapperFunction)yFunc).setFunction(g);
  568.          }
  569.          catch (ParseError e) {  
  570.              // There should't be parse error's in the Web-page
  571.              // author's examples!  If there are, the function
  572.              // just won't change.
  573.          }
  574.       }
  575.       CoordinateRect coords = canvas.getCoordinateRect(0);
  576.       coords.setLimits(limits);
  577.       coords.setRestoreBuffer();
  578.       mainController.compute();
  579.      
  580.       if (animator != null && toks != null) { // get any extra nums from the tokenizer and use them as starting points for curves
  581.          int ct = 2*(toks.countTokens()/2);
  582.          if (ct > 0) {
  583.             synchronized(curves) {
  584.                for (int i = 0; i < ct; i++) {
  585.                   try {
  586.                      double x = (new Double(toks.nextToken())).doubleValue();
  587.                      double y = (new Double(toks.nextToken())).doubleValue();
  588.                      startCurve(x,y);
  589.                   }
  590.                   catch (Exception e) {
  591.                   }
  592.                }
  593.                if (curves.size() > 0) {  // start the curves going
  594.                   try {  
  595.                      Thread.sleep(500);  // wait a bit to give the canvas time to start drawing itself.
  596.                   }
  597.                   catch (InterruptedException e) {
  598.                   }
  599.                }
  600.             }
  601.          }      
  602.       }
  603.      
  604.    } // end doLoadExample()
  605.    
  606.  
  607.    public void stop() {  // stop animator when applet is stopped
  608.       if (animator != null) {
  609.          curves.setSize(0);
  610.          animator.stop();
  611.       }
  612.       super.stop();
  613.    }
  614.  
  615.  
  616. } // end class IntegralCurves
  617.