Java/Advanced Graphics/Curve

Материал из Java эксперт
Перейти к: навигация, поиск

Animated Graph

/*************************************************************************
*                                                                        *
*  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/            *
*                                                                        *
*************************************************************************/
// An applet belonging to the class AnimatedGraph displays a graph
// of a function that can depend on a parameter.  The value of the 
// parameter can be "animated" so that it ranges from one value ot
// another over a sequence of frames.
import java.awt.*;
import java.applet.Applet;
import java.util.*;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.functions.*;
import edu.hws.jcm.awt.*;

public class AnimatedGraph extends GenericGraphApplet {

   // Declare some private variables that are created in one method in
   // this class and used in a second method.
   private Function func;   // The function that is graphed.
   private Graph1D graph;   // The graph of the function.
   
   private Animator animator; // Animates the graph
   private Variable kVar;   // The parameter variable
   
   private VariableInput kMin, kMax, kIntervals;  // min, max, and number of intervals for the animator.  Might be null.

   protected void setUpParser() {  // Override this to create the animator and add its variable to the parser.
   
      int options = Animator.START_STOP_BUTTON | Animator.PAUSE_BUTTON  | Animator.LOOP_CHOICE;
      if  ( ! "no".equalsIgnoreCase(getParameter("UseNextAndPrev","yes")) )
         options |=  Animator.PREV_BUTTON | Animator.NEXT_BUTTON;
      animator = new Animator(options);
      kVar = animator.getValueAsVariable( getParameter("Parameter","k") );   
      parser.add(kVar);
      
      super.setUpParser();
      parameterDefaults = new Hashtable();
      String defaultFunction = xVar.getName() + " / (" + kVar.getName() + " - " + xVar.getName() + "^2)";
      parameterDefaults.put("Function",defaultFunction);
      if (! "no".equalsIgnoreCase(getParameter("UseAnimatorInputs")))
         parameterDefaults.put("TwoLimitsColumns","yes"); // change default if we need space for animator inputs
      
   } // end setUpParser()
   
   protected void setUpBottomPanel() {  // Overridden to add the sliders at the bottom of the applet.
      super.setUpBottomPanel();  // Do the default setup.
      
      // If there is a functionInput box, then the SOUTH position of the mainPanel already contains
      // the inputPanel that contains that box.  If so, add the animator to the SOUTH position of
      // the inputPanel.  (This is a good place, in general, to put extra input objects.)
      // If there is no inputPanel, then the SOUTH position of the mainPanel is empty, so put
      // the animator there.
      
      if (inputPanel != null)
         inputPanel.add(animator, BorderLayout.SOUTH);
      else
         mainPanel.add(animator, BorderLayout.SOUTH);
   } // end setUpBottomPanel()

   protected void setUpCanvas() { // Overridden to add the graph to the canvas and do other chores.
      super.setUpCanvas();  // Do the default setup.
      // When setUpCanvas() is called, the functionInput already exists, if one is
      // to be used, since it is created in setUpBopttomPanel(), which is called
      // before setUpCanvas.  If functionInput exists, add a graph of the function
      // from functionInput to the canvas.  If not, create a graph of the function
      // specified by the parameter named "Function" (or use sin(k*x) if none is specified).
      if (functionInput != null)
         func = functionInput.getFunction(xVar);
      else {
         String def = getParameter("Function");  // default value is set in setUpParser()
         func = new SimpleFunction( parser.parse(def), xVar );
      }
      // Create a graph of the function and add it to the canvas.
      
      graph = new Graph1D(func);
      graph.setColor(getColorParam("GraphColor", Color.magenta));
      canvas.add(graph);
      
      // Set up the min, max, and intervals property of the animator
      
      if  (! "no".equalsIgnoreCase(getParameter("UseAnimatorInputs"))) {
         kMin = new VariableInput(kVar.getName() + "Start",getParameter("ParameterMin","-2"));
         kMax = new VariableInput(kVar.getName() + "End",getParameter("ParameterMax","2"));
         kIntervals = new VariableInput("Intervals", getParameter("Intervals","25"));
         kIntervals.setInputStyle(VariableInput.INTEGER);
         kIntervals.setMin(1);
         kIntervals.setMax(1000);
         kMin.setOnUserAction(mainController);
         kMax.setOnUserAction(mainController);
         kIntervals.setOnUserAction(mainController);
         animator.setMin(kMin);
         animator.setMax(kMax);
         animator.setIntervals(kIntervals);
         if (limitsPanel != null) {
               // componets will be added to limitsPanel in setUpLimitsPanel()
            mainController.add(kMin);  // This is not done automatically, since they are in a limits panel  
            mainController.add(kMax);
            mainController.add(kIntervals);
         }
         else {
            JCMPanel ap = new JCMPanel(9,0);
            ap.setBackground(getColorParam("PanelBackground", Color.lightGray));
            ap.add(new Label(kMin.getName()));
            ap.add(kMin);
            ap.add(new Label());
            ap.add(new Label(kMax.getName()));
            ap.add(kMax);
            ap.add(new Label());
            ap.add(new Label(kIntervals.getName()));
            ap.add(kIntervals);
            ap.add(new Label());
            mainPanel.add(ap,BorderLayout.EAST);
         }
      }
      else {
         try {
            animator.setMin( (new Double(getParameter("ParameterMin","-2"))).doubleValue() );
            animator.setMax( (new Double(getParameter("ParameterMax","2"))).doubleValue() );
            animator.setIntervals( (int)Math.round((new Double(getParameter("Intervals","25"))).doubleValue()) );
         }
         catch (NumberFormatException e) {
         }
      }
      animator.setOnChange(mainController);
      // Add a DrawString to show the current value of the parameter
      if ( ! "no".equalsIgnoreCase(getParameter("ShowParameter","yes")) ) {
         DrawString param = new DrawString(kVar.getName() + " = #", DrawString.BOTTOM_LEFT, new Value[] { kVar });
         param.setBackgroundColor(canvas.getBackground());
         Color c = getColorParam("ParameterColor",Color.black);
         param.setColor(c);
         canvas.add(param);
      }
   } // end setUpCanvas

   protected void setUpLimitsPanel() {
     super.setUpLimitsPanel();
     if (limitsPanel != null && kMin != null) {  // add animator inputs to limits panel
         limitsPanel.addComponentPair(kMin,kMax);
         limitsPanel.addComponent(kIntervals);
     }
   }
 
   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 AnimatedGraph applet, the example string should contain
         // an expression that defines the function to be graphed.  This can optionally
         // be followed by a semicolon and a list of four to nine 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
         // next three numbers specify the minimum value for the parameter, the
         // maximum number, and the number of intervals in the animation.
         // The eigth number, if present, specifies the starting loop style
         // for the animation with the following code:  0 for once-through,
         // 1 for loop, and 2 for back-and-forth.  The ninth number, if
         // present, tells whether to start the animation immediately upon
         // loading.  If it is 1, the animation is started.  If it is
         // not specified or is any value other than 1, the animation is not started.
         
      animator.stop();
         
      int pos = example.indexOf(";");
      boolean startAnimation = false;
      double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
      if (pos > 0) { 
               // Get limits from example text.
         String nums = example.substring(pos+1);
         example = example.substring(0,pos);
         StringTokenizer toks = new StringTokenizer(nums, " ,");
         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.hasMoreTokens()) {
            try {
               double d = (new Double(toks.nextToken())).doubleValue();
               if (kMin == null)
                  animator.setMin(d);
               else
                  kMin.setVal(d);
            }
            catch (NumberFormatException e) {
            }
         }
         if (toks.hasMoreTokens()) {
            try {
               double d = (new Double(toks.nextToken())).doubleValue();
               if (kMax == null)
                  animator.setMax(d);
               else
                  kMax.setVal(d);
            }
            catch (NumberFormatException e) {
            }
         }
         if (toks.hasMoreTokens()) {
            try {
               int d = (int)Math.round((new Double(toks.nextToken())).doubleValue());
               if (kIntervals == null)
                  animator.setIntervals(d);
               else
                  kIntervals.setVal(d);
            }
            catch (NumberFormatException e) {
            }
         }
         if (toks.hasMoreTokens()) {
            try {
               int d = (int)Math.round((new Double(toks.nextToken())).doubleValue());
               animator.setLoopStyle(d);
            }
            catch (NumberFormatException e) {
            }
         }
         if (toks.hasMoreTokens()) {
            try {
               int d = (int)Math.round((new Double(toks.nextToken())).doubleValue());
               startAnimation = (d == 1);
            }
            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.
         try {
            func = new SimpleFunction( parser.parse(example), xVar );
            graph.setFunction(func);
         }
         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();
      mainController.rupute();
      if (startAnimation) {
         try {  // insert a small delay before animation starts
            synchronized(this) {
               wait(250);
            }
         }
         catch (InterruptedException e) {
         }
         animator.start();
      }
      
   } // end doLoadExample()
   
   public void stop() {  // stop animator when applet is stopped
      animator.stop();
      super.stop();
   }
 
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         Applet app = new AnimatedGraph();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   
   
} // end class FamiliesOfGraphs





Draw Spline

import java.awt.*;
import javax.swing.*;
import no.geosoft.cc.geometry.Geometry;
import no.geosoft.cc.geometry.Matrix4x4;
import no.geosoft.cc.geometry.spline.SplineFactory;
import no.geosoft.cc.graphics.*;

/**
 * G demo program. Demonstrates:
 *
 * <ul>
 * <li> A rudimentary sheet music library
 * <li> GObject extension
 * <li> Advanced geometry generation
 * </ul>
 * 
 * @author 



== Epsilon Delta ==






   
  <!-- start source code -->
   
    <source lang="java">
/*************************************************************************
*                                                                        *
*  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.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.*;
/**
 *  An applet for exploring the epsilon-delta definition of a limit.
 */
public class EpsilonDelta extends GenericGraphApplet {
   // Declare some private variables that are created in one method in
   // this class and used in a second method.
   private VariableInput xInput;
   private VariableInput epsilonInput;
   private VariableInput deltaInput;
   private VariableInput limitInput;
   
   private VariableSlider xSlider;
   private VariableSlider epsilonSlider;
   private VariableSlider deltaSlider;
   private VariableSlider limitSlider;
   private Controller subController;
   
   private Variable xValue, limitValue;
   private Function func;   // The function that is graphed.
   private Graph1D graph;   // The graph of the function.
   
   protected void setUpBottomPanel() {
      super.setUpBottomPanel();
      subController = new Controller();
      mainController.add(subController);
      JCMPanel inputs = new JCMPanel(3);
      subController.add(inputs);
      inputs.setBackground(getColorParam("PanelBackground", Color.lightGray));
      if (inputPanel == null)
         mainPanel.add(inputs,BorderLayout.SOUTH);
      else
         inputPanel.add(inputs,BorderLayout.SOUTH);
      JCMPanel left = new JCMPanel(0,1,2);
      JCMPanel right = new JCMPanel(0,1,2);
      JCMPanel middle = new JCMPanel(0,1,2);
      inputs.add(middle, BorderLayout.CENTER);
      inputs.add(left, BorderLayout.WEST);
      inputs.add(right, BorderLayout.EAST);
      double[] a = getNumericParam("AValue");
      double avalue = (a == null || a.length < 1)? 0 : a[0];
      if ("yes".equalsIgnoreCase(getParameter("UseAInput","yes"))) {
         xSlider = new VariableSlider();
         xInput = new VariableInput();
         xInput.setVal(avalue);
         xSlider.setVal(avalue);
         xInput.setThrowErrors(false);
         subController.add(new Tie(xSlider, xInput));
         xValue = xInput.getVariable();
         left.add(new Label("limit at a = ",Label.RIGHT));
         middle.add(xSlider);
         right.add(xInput);
      }
      else {
         xValue = new Variable();
         xValue.setVal(avalue);
      }
      a = getNumericParam("LimitValue");
      double Lvalue = (a == null || a.length < 1)? 1 : a[0];
      if ("yes".equalsIgnoreCase(getParameter("UseLimitInput","yes"))) {
         limitSlider = new VariableSlider();
         limitInput = new VariableInput();
         limitInput.setVal(Lvalue);
         limitSlider.setVal(Lvalue);
         limitInput.setThrowErrors(false);
         subController.add(new Tie(limitSlider, limitInput));
         limitValue = limitInput.getVariable();
         left.add(new Label(" test limit L = ",Label.RIGHT));
         middle.add(limitSlider);
         right.add(limitInput);
      }
      else {
         limitValue = new Variable();
         limitValue.setVal(Lvalue);
      }
      a = getNumericParam("EpsilonValue");
      double epsilonValue = (a == null || a.length < 1)? 0.25 : a[0];
      epsilonSlider = new VariableSlider(new Constant(0), new Constant(2));
      epsilonInput = new VariableInput();
      epsilonInput.setVal(epsilonValue);
      epsilonSlider.setVal(epsilonValue);
      epsilonInput.setThrowErrors(false);
      subController.add(new Tie(epsilonSlider, epsilonInput));
      left.add(new Label("epsilon = ", Label.RIGHT));
      middle.add(epsilonSlider);
      right.add(epsilonInput);
      a = getNumericParam("DeltaValue");
      double deltaValue = (a == null || a.length < 1)? 1 : a[0];
      deltaSlider = new VariableSlider(new Constant(0), new Constant(2));
      deltaInput = new VariableInput();
      deltaInput.setVal(deltaValue);
      deltaSlider.setVal(deltaValue);
      deltaInput.setThrowErrors(false);
      subController.add(new Tie(deltaSlider, deltaInput));
      left.add(new Label("delta = ", Label.RIGHT));
      middle.add(deltaSlider);
      right.add(deltaInput);
   }   

   protected void setUpCanvas() {  // Override this to add more stuff to the canvas.
   
      // When setUpCanvas is called, the functionInput already exists, if one is
      // to be used, since it is created in setUpBopttomPanel(), which is called
      // before setUpCanvas.  If functionInput exists, add a graph of the function
      // from functionInput to the canvas.  If not, create a graph of the function
      // specified by the parameter named "Function".
      
      if (functionInput != null)
         func = functionInput.getFunction(xVar);
      else {
         String def = getParameter("Function", "abs(" + xVar.getName() + ") ^ " + xVar.getName());
         Function f = new SimpleFunction( parser.parse(def), xVar );
         func = new WrapperFunction(f);
      }
      graph = new Graph1D(func);
      graph.setColor(getColorParam("GraphColor", Color.black));
         
      Value xMinusDelta = new ValueMath(xValue, deltaInput, "-");
      Value xPlusDelta  = new ValueMath(xValue, deltaInput, "+");
      Value limitMinusEpsilon = new ValueMath(limitValue, epsilonInput, "-");
      Value limitPlusEpsilon  = new ValueMath(limitValue, epsilonInput, "+");
      
      Value xmin = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMIN);
      Value xmax = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMAX);
      Value ymin = canvas.getCoordinateRect().getValueObject(CoordinateRect.YMIN);
      Value ymax = canvas.getCoordinateRect().getValueObject(CoordinateRect.YMAX);
      
      if (xSlider != null) {
         xSlider.setMin(xmin);
         xSlider.setMax(xmax);
      }
      if (limitSlider != null) {
         limitSlider.setMin(ymin);
         limitSlider.setMax(ymax);
      }
      
      DrawGeometric deltaBox = new DrawGeometric(DrawGeometric.RECT_ABSOLUTE, xMinusDelta, ymin, xPlusDelta, ymax);
      deltaBox.setFillColor(new Color(225,255,225));
      deltaBox.setLineWidth(0);
      DrawGeometric epsilonBox = new DrawGeometric(DrawGeometric.RECT_ABSOLUTE, xmin, limitMinusEpsilon, xmax, limitPlusEpsilon);
      epsilonBox.setFillColor(new Color(255,230,230));
      epsilonBox.setLineWidth(0);
      DrawGeometric overlap = new DrawGeometric(DrawGeometric.RECT_ABSOLUTE, xMinusDelta, limitMinusEpsilon, xPlusDelta,limitPlusEpsilon);
      overlap.setFillColor(new Color(255,255,225));
      overlap.setColor(Color.yellow);
      DrawGeometric xLine = new DrawGeometric(DrawGeometric.LINE_ABSOLUTE, xValue, ymin, xValue, ymax);
      xLine.setColor(new Color(130,255,130));
      DrawGeometric limitLine = new DrawGeometric(DrawGeometric.LINE_ABSOLUTE, xmin, limitValue, xmax, limitValue);
      limitLine.setColor(new Color(255,150,150));
      
      canvas.add(deltaBox);
      canvas.add(epsilonBox);
      canvas.add(overlap);
      canvas.add(xLine);
      canvas.add(limitLine);
      
      DrawString ds = new DrawString("a = #\nL = #\nf(a) = #", DrawString.TOP_LEFT,
                               new Value[] { xValue, limitValue, new ValueMath(func,xValue) });
      ds.setBackgroundColor(Color.white);
      ds.setFrameWidth(1);
      
      subController.add(ds);
      subController.add(deltaBox);
      subController.add(epsilonBox);
      subController.add(overlap);
      subController.add(xLine);
      subController.add(limitLine);
      mainController.remove(canvas);
      mainController.add(graph);
      canvas.getCoordinateRect().setOnChange(mainController);
      
      deltaSlider.setOnUserAction(subController);
      epsilonSlider.setOnUserAction(subController);
      deltaInput.setOnTextChange(subController);
      epsilonInput.setOnTextChange(subController);
      subController.add(deltaSlider);
      subController.add(epsilonSlider);
      subController.add(deltaInput);
      subController.add(epsilonInput);
      if (xInput != null) {
         xSlider.setOnUserAction(subController);
         xInput.setOnTextChange(subController);
         subController.add(xSlider);
         subController.add(xInput);
      }
      if (limitInput != null) {
         limitSlider.setOnUserAction(subController);
         limitInput.setOnTextChange(subController);
         subController.add(limitSlider);
         subController.add(limitInput);
      }
      super.setUpCanvas();  // Do the common setup: Add the axes, grid, etc
      canvas.add(graph);
      canvas.add(ds);
      
   } // end setUpCanvas()
   
   
   

   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.
   
         // After the function definition, there can be a semicolon and
         // up to ten numbers (numbers can be separated by spaces and/or commas).
         // The first four numbers specify the limits on the coordinate rect.
         // .
   
      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, " ,");
         double nums[] = new double[toks.countTokens()];
         for (int i = 0; i < nums.length; i++) {
            try {
               nums[i] = (new Double(toks.nextToken())).doubleValue();
            }
            catch (Exception e) {
               nums[i] = Double.NaN;
            }
         }
         for (int i = 0; i < 4; i++)
            if (nums.length >= i && !Double.isNaN(nums[i]))
               limits[i] = nums[i];
         if (nums.length > 4 && !Double.isNaN(nums[4]))
             xValue.setVal( nums[4] );
         else
             xValue.setVal((limits[0]+limits[1])/2);
         if (nums.length > 5 && !Double.isNaN(nums[5]))
             limitValue.setVal( nums[5] );
         else
             limitValue.setVal((limits[0]+limits[1])/2);
         if (nums.length > 8 && !Double.isNaN(nums[8]))
             epsilonSlider.setMax( new Constant(nums[8]) );
         else
             epsilonSlider.setMax(new Constant(Math.abs(limits[2]-limits[3])/2));
         if (nums.length > 9 && !Double.isNaN(nums[9]))
             deltaSlider.setMax( new Constant(nums[9]) );
         else
             deltaSlider.setMax(new Constant(Math.abs(limits[0]-limits[1])/2));
         if (nums.length > 6 && !Double.isNaN(nums[6])) {
             epsilonInput.setVal( nums[6] );
             epsilonSlider.setVal( nums[6] );
         }
         else {
             epsilonInput.setVal(Math.abs(limits[2]-limits[3])/8);
             epsilonSlider.setVal(Math.abs(limits[2]-limits[3])/8);
         }
         if (nums.length > 7 && !Double.isNaN(nums[7])) {
             deltaInput.setVal( nums[7] );
             deltaSlider.setVal( nums[7] );
         }
         else {
             deltaInput.setVal(Math.abs(limits[0]-limits[1])/8);
             deltaSlider.setVal(Math.abs(limits[0]-limits[1])/8);
         }
      }
      
      // 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 {
            Function f = new SimpleFunction( parser.parse(example), 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();
      mainController.rupute();
      
   } // end doLoadExample()
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         Applet app = new EpsilonDelta();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   

} // end class EpsilonDelta





Families Of Graphs

/*************************************************************************
*                                                                        *
*  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/            *
*                                                                        *
*************************************************************************/
// An applet belonging to the class FamiliesOfGraphs displays a graph
// of a function that can depend on one or more parameters.  The values of
// the parameters are controlled by the user using sliders at the bottom of
// the applet.

import java.awt.*;
import java.applet.Applet;
import java.util.*;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.functions.*;
import edu.hws.jcm.awt.*;

public class FamiliesOfGraphs extends GenericGraphApplet {

   // Declare some private variables that are created in one method in
   // this class and used in a second method.
   private Function func;   // The function that is graphed.
   private Graph1D graph;   // The graph of the function.
   
   private Vector sliders;  // Elements of this vector are the VariableSlider
                            //   objects that represent the parameter values.
                            //   The sliders are created in the setUpParser() method.

   protected void setUpParser() {  // Override this to add VariableSliders to parser.
   
      // Get the data for the sliders from applet params named "Parameter", "Parameter1", ...
      // The sliders are created and the variables are added to the parser by the
      // addParameter() method, which is defined below.
      
      sliders = new Vector();
      int ct = 0;
      String param = getParameter("Parameter");
      if (param == null) {
         ct++;
         param = getParameter("Parameter" + ct);
      }
      while (true) {
         if (param == null)
            break;
         addParameter(param);
         ct++;
         param = getParameter("Parameter" + ct);
      }
      
      // If no parameters were specified in applet params, create one with name "k".
      
      if (sliders.size() == 0)
         addParameter("k");
         
      super.setUpParser();  // Call this last so function definitions 
                            // in applet params can use the parameter names 
                            // that have just been added to the parser
                            // (even though it"s probably not a good idea).
                            // Note that this also defines the independent variable,
                            // whose name is given by the applet param "Variable"
                            // and which is referred to as xVar in this program.
                            
      VariableSlider slide = (VariableSlider)sliders.elementAt(0);
      String def = getParameter("Function", "sin(" + slide.getName() + " * " + xVar.getName() + ")");
      parameterDefaults = new Hashtable();  // I want to set a different default value for
                                            // the "Function" applet param.
      parameterDefaults.put("Function",def);
         
                            
   } // end setUpParser()
   

   private void addParameter(String data) {
         // Create a VariableSlider from the information in name and add it to the
         // Vector of sliders.  The data must contain the name of the variable 
         // associated with the slider.  The name can be followed by a ";" and up to
         // three numbers.  (If there is no ";", a space after the name will do.)
         // The numbers can be separated by commas, spaces, or tabs.  The first
         // number gives the minimum value on the slider, the second gives the maximum,
         // and the third gives the initial value of the slider variable.
      double min = -5, max = 5, val = 0;  // min, max, and value for slider
      data = data.trim();
      int pos = data.indexOf(";");
      if (pos < 0)
         pos = data.indexOf(" ");
         
      String name; //  The name of the parameter
      if (pos < 0) {
            // If there is no space or ";", the data is just the name of the variable.
         name = data;
      }
      else {
            // Get the name from the front of the data, then look for min, max, and val.
          String nums = data.substring(pos+1);
          name = data.substring(0,pos).trim();
          StringTokenizer toks = new StringTokenizer(nums," ,\t");
          try {
             if (toks.hasMoreElements())
                 min = (new Double(toks.nextToken())).doubleValue();
             if (toks.hasMoreElements())
                 max = (new Double(toks.nextToken())).doubleValue();
             if (toks.hasMoreElements())
                 val = (new Double(toks.nextToken())).doubleValue();
          }
          catch (NumberFormatException e) {
             min = -5;
             max = 5;
             val = 0;
          }
      }
      
      // Create the slider, adding the associated variable to the parser, and set its value.
      
      VariableSlider slide = new VariableSlider(name, new Constant(min), new Constant(max), parser);
      slide.setVal(val);
      
      sliders.addElement(slide);  // Save the slider in the array of sliders for later use.
      
   } // end setUpParser();
   

   protected void setUpBottomPanel() {  // Overridden to add the sliders at the bottom of the applet.
      super.setUpBottomPanel();  // Do the default setup.
      // Create a panel holding all the sliders, with a display label for each slider to show its value.
      JCMPanel sliderPanel = new JCMPanel();
      sliderPanel.setLayout(new GridLayout(0,1,3,3));
      sliderPanel.setBackground(getColorParam("PanelBackground", Color.lightGray));
      for (int i = 0; i < sliders.size(); i++) {
         JCMPanel p = new JCMPanel();
         VariableSlider slide = (VariableSlider)sliders.elementAt(i);
         p.add(slide, BorderLayout.CENTER);
         p.add(new DisplayLabel("  " + slide.getName() + " = # ", new Value[] { slide.getVariable() } ), 
                      BorderLayout.EAST);
         sliderPanel.add(p);
         slide.setOnUserAction(mainController);
      }
      
      
      // If there is a functionInput box, then the SOUTH position of the mainPanel already contains
      // the inputPanel that contains that box.  If so, add the new panel to the SOUTH position of
      // the inputPanel.  (This is a good place, in general, to put extra input objects.)
      // If there is no inputPanel, then the SOUTH position of the mainPanel is empty, so put
      // the newly created panel there.
      
      if (inputPanel != null)
         inputPanel.add(sliderPanel, BorderLayout.SOUTH);
      else
         mainPanel.add(sliderPanel, BorderLayout.SOUTH);
   } // end setUpBottomPanel()

   protected void setUpCanvas() { // Overridden to add the graph to the canvas.
      super.setUpCanvas();  // Do the default setup.
      // When setUpCanvas() is called, the functionInput already exists, if one is
      // to be used, since it is created in setUpBopttomPanel(), which is called
      // before setUpCanvas.  If functionInput exists, add a graph of the function
      // from functionInput to the canvas.  If not, create a graph of the function
      // specified by the parameter named "Function" (or use sin(k*x) if none is specified).
      if (functionInput != null)
         func = functionInput.getFunction(xVar);
      else {
         String def = getParameter("Function");  // default value is set in setUpParser()
         func = new SimpleFunction( parser.parse(def), xVar );
      }
      // Create a graph of the function and add it to the canvas.
      
      graph = new Graph1D(func);
      graph.setColor(getColorParam("GraphColor", Color.magenta));
      canvas.add(graph);
   } // end setUpCanvas

   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 FamiliesOfGraphs applet, the example string should contain
         // an expression that defines the function to be graphed.  This can optionally
         // be followed by a semicolon and a list of four or more 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
         // remaining numbers occur in groups of three and specify the minimumn, 
         // maximum and values of the parameters, in the
         // same order that they were encountered in the setUpParser() method.
         
      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 nums = example.substring(pos+1);
         example = example.substring(0,pos);
         StringTokenizer toks = new StringTokenizer(nums, " ,");
         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) {
               }
            }
         }
         int i = 0;
         while (i < sliders.size() && toks.hasMoreElements()) {
               // Look for a value for the i-th slider.
            try {
                double min = (new Double(toks.nextToken())).doubleValue();
                double max = (new Double(toks.nextToken())).doubleValue();
                double d = (new Double(toks.nextToken())).doubleValue();
                VariableSlider slider = ((VariableSlider)sliders.elementAt(i));
                slider.setMin(new Constant(min));
                slider.setMax(new Constant(max));
                slider.setVal(d);
            }
            catch (Exception e) {
            }
            i++;
         }
      }
      
      // 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.
         try {
            func = new SimpleFunction( parser.parse(example), xVar );
            graph.setFunction(func);
         }
         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();
      mainController.rupute();
      
   } // end doLoadExample()
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         Applet app = new FamiliesOfGraphs();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   
} // end class FamiliesOfGraphs





Input the function and draw the curve

/*************************************************************************
*                                                                        *
*  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 edu.hws.jcm.data.*;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.awt.*;
public class GraphApplet1 extends java.applet.Applet {
   public void init() {
      Parser parser = new Parser();    // The Parser will take the user"s input
                                       //   and turn it into an Expression.  By
                                       //   default, a Parser knows about the 
                                       //   constants pi and e, the basic arithmetic
                                       //   operations + - * / ^ (where ^ is
                                       //   exponentiation), standard functions
                                       //   (sin, cos, tan, sec, csc, cot, sqrt,
                                       //   cubert, abs, ln, log2, log10, exp,
                                       //   trunc, round, floor, ceiling, arcsin,
                                       //   arccos, arctan).  There is also a
                                       //   conditional "?" operator in the style
                                       //   of Java and C++. 
                                       
      Variable x = new Variable("x");  // For the parser to know about the variable x,
      parser.add(x);                   //   that variable must be created and added
                                       //   to the parser.
      DisplayCanvas canvas = new DisplayCanvas();
         // A DisplayCanvas is the fundamental JCM class for displaying
         // graphical items such as axes and graphs.
      
      canvas.setUseOffscreenCanvas(false);  // By default, a DisplayCanvas uses
                                            //   "double buffering", which allows for
                                            //   smooth animation.  However, it does
                                            //   use extra memory, so I turn it off
                                            //   in this simple applet.  You might notice
                                            //   that the image flickers a bit when the
                                            //   applet is redrawn.
      
      canvas.setHandleMouseZooms(true);  // This tells the canvas to let the user
                                         //   zoom in and out by clicking, shift-clicking,
                                         //   and click-and-dragging on the canvas.
      
      LimitControlPanel limits = new LimitControlPanel();
         // A limit control panel can control the x- and y-limits on a DisplayCanvas.
         //   In the applet, the limit control panel is the gray area containing the
         //   input boxes for xmin, xmax, ymin, and ymax.  It also contains a
         //   "Set Limits" button (and can contain other buttons if you want).  The
         //   "Set Limits" button is a little redundant because pressing return
         //   in any of the input boxes will accomplish the same thing.  However, it
         //   has the advantage of giving the user something obvious to do to 
         //   set the limits.
         
      limits.addCoords(canvas);  // Tells the LimitControlPanel to control the
                                 //   x- and y-limits on this canvas.  The limits
                                 //   on the canvas and the values in the input
                                 //   boxes are synchronized.  (Try it by clicking
                                 //   on the graph.)
      
      ExpressionInput input = new ExpressionInput("sin(x)+2*cos(3*x)", parser);
         // An ExpressionInput is a text-input box where the user can enter
         //    an expression.  The string "sin(x)+2*cos(3*x)" provides the initial
         //    contents for the box.  The parser that is provided as the second 
         //    arguments knows about the variable named "x", which makes it 
         //    possible to use "x" in the expression.
         
      Function func = input.getFunction(x);  // To graph, I need a Function, not
                                             //    not an expression.  input.getFunction(x)
                                             //    gets the contents of the ExpressionInput,
                                             //    input, considered as a function of the
                                             //    variable x.
      Graph1D graph = new Graph1D(func);  // This represents a graph of the function, func.
                                          //   It will be added to the DisplayCanvas,
                                          //   which will make it appear on the screen.
      
      JCMPanel main = new JCMPanel();     // The interface for this applet is constructed
                                          //   entirely from JCMPanels.  This makes much
                                          //   of the JCM setup automatic.  This constructor
                                          //   makes a JCMPanel that uses a BorderLayout.
                                          
      main.add(canvas, BorderLayout.CENTER);  // Add the DisplayCanvas to the panel.
      main.add(input, BorderLayout.SOUTH);    // Add the ExprssionInput.
      main.add(limits, BorderLayout.EAST);    // Add the LimitControlPanel.
      
      main.setInsetGap(3);  // This leaves a gap of 3 pixels around the edges of the
                            //    panel, where the gray background shows through.
      setLayout(new BorderLayout());   // Set up the Applet itself.
      add(main, BorderLayout.CENTER);
      setBackground(Color.lightGray);      
      
      canvas.add( new Axes() );  // Add a set of Axes to the DisplayCanvas.  The labels
                                 //   on the applet are automatically adjusted when
                                 //   the limits on the canvas changes.
                                 
      canvas.add(graph);         // Add the graph to the canvas.  It will be redrawn
                                 //   whenever necessary.
      
      Controller controller = main.getController();
          // A Controller is what makes things happen in a JCM applet.  The
          // JCMPanel, main, has a controller that recomputes the JCM components
          // in the Panel.
      controller.setErrorReporter(canvas);  // Errors in the user"s input need to
                                            //   be reported somehow.  A Controller can
                                            //   have an ErrorReporter for this purpose.
                                            //   Currently, the alternatives are to use
                                            //   a canvas as an error reporter or to 
                                            //   use a "MessagePopup".  To see an error
                                            //   message in the applet, enter any expression
                                            //   with a syntax error and press return.
                                            //   Note that the blinking input cursor moves
                                            //   to the point in the expression where the
                                            //   error was discovered.
      
      limits.setErrorReporter(canvas);   // A LimitControlPanel also needs a place to
                                         //   report errors in the user"s input.
                                         
      main.gatherInputs();   // The JCMPanel must be told to respond to user inputs.
                             //    The gatherInputs() method is an easy way to do this,
                             //    in many cases.  In this applet, since there is only one
                             //    input, this is equivalent to the single command
                             //    "input.setOnUserAction(controller)," which tells the
                             //    input object to notify the controller when the user
                             //    presses return in the input box.  (Note that input boxes
                             //    in a LimitControlPanel are taken care of automatically.
                             //    They don"t need to notify a controller.  Also note that
                             //    I couldn"t use the gatherInputs(controller) method in
                             //    the previous ArithmeticApplet, since gatherInputs() 
                             //    calls the setOnUserAction() method of an input box, but in the
                             //    ArithmeticApplet, I wanted to call setOnTextChange().  The
                             //    difference is that with setOnUserAction(), the controller is
                             //    notified only when the user presses return in the input box
                             //    while with setOnTextChange(), the controller is notified
                             //    each time the text in the input box changes.)
      
   } // end init()
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         java.applet.Applet app = new GraphApplet1();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   
} // end class SimpleGraph1





Integral Curves

/*************************************************************************
*                                                                        *
*  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/            *
*                                                                        *
*************************************************************************/

// This applet displays a vector field (f1(x,y),f2(x,y)) and integral curves
// for that vector field (although the integral curve feature can be turned off
// with an applet param).  The drawing of the curves is animated; they are
// drawn segment-by-segment.  In the default setup, a curve is started when the 
// user clicks on the canvas.  A curve can also be started by entering the
// starting x and y coords in a pair of text input boxes and clicking a button.
import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;
import java.util.*;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.functions.*;
import edu.hws.jcm.awt.*;

public class IntegralCurves extends GenericGraphApplet {
   private Variable yVar;           // The seond variable, usually y.
   private Function xFunc,yFunc;    // The functions that give the components of the vector field
   private ExpressionInput functionInput2;  // For inputting yFunc.
   private VectorField field;       // The vector/direction field
   private Animator animator;       // for incrementally drawing integral curves.
   private Vector curves = new Vector();           // Holds the integral curves
   private VariableInput deltaT;    // input the deltaT for the curve
   double dt = 0.1;  // The value of delat t in the case where there is no deltaT input box
   private VariableInput xStart,yStart;  // Starting point for curve
   private Choice methodChoice;     // select integration method
   private Button startCurveButton; // user clicks to start curve from (x,y) in xStart, yStart input boxes
   private Button clearButton;      // clears curves
   private Color curveColor;        // color for integral curves
   private Draw curveDrawer = new Draw(); // A DrawTemp object that draws one segment of the integral curves.
   
   private double[] nextPoint = new double[2];  // Help in computing next point of integral curve.
   private double[] params = new double[2];     // ditto
   
   private static final int RK4 = 0, RK2 = 1, EULER = 2;   // constants for integration methos
   
   private class Curve {  // holds the data for one integral curve
      double dt;
      int method;
      double x,y; // point on the curve
      double lastX = Double.NaN, lastY; // previous point, so we can draw a line.
   }
   
   private class Draw implements DrawTemp { // For drawing the next segment in each integral curve (as a DrawTemp)
      public void draw(Graphics g, CoordinateRect coords) {
         int size = curves.size();
         g.setColor(curveColor);
         for (int i = 0; i < size; i++) {
            Curve c = (Curve)(curves.elementAt(i));
            if (! (Double.isNaN(c.x) || Double.isNaN(c.y) || Double.isNaN(c.lastX) || Double.isNaN(c.lastY)) ) {
               int x1 = coords.xToPixel(c.lastX);
               int y1 = coords.yToPixel(c.lastY);
               int x2 = coords.xToPixel(c.x);
               int y2 = coords.yToPixel(c.y);
               g.drawLine(x1,y1,x2,y2);
            }
         }
      }
   }
   protected void setUpParser() {
          // create the "y" variable; also set up some parameter defaults.
      yVar = new Variable(getParameter("Variable2","y"));
      parser.add(yVar);
      super.setUpParser();  // sets up xVar, among other things.
      parameterDefaults = new Hashtable();
      parameterDefaults.put("FunctionLabel", " f1(" + xVar.getName() + "," + yVar.getName() + ") = ");
      parameterDefaults.put("FunctionLabel2", " f2(" + xVar.getName() + "," + yVar.getName() + ") = ");
      parameterDefaults.put("Function", " " + yVar.getName() + " - 0.1*" + xVar.getName());
      parameterDefaults.put("Function2", " - " + xVar.getName() + " - 0.1*" + yVar.getName());
      defaultFrameSize = new int[] { 580, 440 };
   }
   protected void setUpCanvas() {  // Override this to add more stuff to the canvas.
   
      super.setUpCanvas();  // Do the common setup: Add the axes and
      
      // set up the vector field and add it to the canvas
      
      if (functionInput != null) {
         xFunc = functionInput.getFunction(new Variable[] {xVar,yVar});
         yFunc = functionInput2.getFunction(new Variable[] {xVar,yVar});
      }
      else {
         String xFuncDef = getParameter("Function");
         String yFuncDef = getParameter("Function2");
         Function f = new SimpleFunction( parser.parse(xFuncDef), new Variable[] {xVar,yVar} );
         xFunc = new WrapperFunction(f);
         f = new SimpleFunction( parser.parse(yFuncDef), new Variable[] {xVar,yVar} );
         yFunc = new WrapperFunction(f);
      }
      String type = (getParameter("VectorStyle", "") + "A").toUpperCase();
      int style = 0;
      switch (type.charAt(0)) {
         case "A": style = VectorField.ARROWS; break;
         case "L": style = VectorField.LINES; break;
         case "S": style = VectorField.SCALED_VECTORS; break;
      }
      field = new VectorField(xFunc,yFunc,style);
      Color color = getColorParam("VectorColor");
      if (color != null)
         field.setColor(color);
      int space = (style == VectorField.LINES)? 20 : 30;
      double[] d = getNumericParam("VectorSpacing");
      if (d != null && d.length > 0 && d[0] >= 1)
         space = (int)Math.round(d[0]);
      field.setPixelSpacing(space);
      canvas.add(field);  // Finally, add the graph to the canvas.
      curveColor = getColorParam("CurveColor", Color.magenta);
      // add a mouse listener to the canvas for starting curves.
      if ("yes".equalsIgnoreCase(getParameter("MouseStartsCurves","yes")) && "yes".equalsIgnoreCase(getParameter("DoCurves","yes")))
         canvas.addMouseListener(new MouseAdapter() {
                public void mousePressed(MouseEvent evt) {
                   CoordinateRect coords = canvas.getCoordinateRect();
                   double x = coords.pixelToX(evt.getX());
                   double y = coords.pixelToY(evt.getY());
                   if (xStart != null)
                      xStart.setVal(x);
                   if (yStart != null)
                      yStart.setVal(y);
                   startCurve(x,y);
                }
            });
   } // end setUpCanvas()
   
   
   protected void setUpBottomPanel() { 
         // Override this to make a panel containing controls.  This is complicated
         // because it"s possible to turn off a lot of the inputs with applet params.
         
      // Check on the value of delta t, which has to be set even if there are no input controls.
      double[] DT = getNumericParam("DeltaT"); 
      if  ( ! (DT == null || DT.length == 0 || DT[0] <= 0) )
         dt = DT[0];
      boolean doCurves = "yes".equalsIgnoreCase(getParameter("DoCurves","yes"));
      boolean useInputs = "yes".equalsIgnoreCase(getParameter("UseFunctionInput","yes"));
      if (!doCurves && !useInputs)  // no input controls at all.
         return;
      // make the input panel
      inputPanel = new JCMPanel();
      inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) );
      mainPanel.add(inputPanel,BorderLayout.SOUTH);
      // Make the function inputs and the compute button, if these are in the configuration.
      JCMPanel in1 = null, in2 = null;  // hold function inputs, if any
      if (useInputs) {
         if ( "yes".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) {
            String cname = getParameter("ComputeButtonName", "New Functions");
            computeButton = new Button(cname);
            computeButton.addActionListener(this);
         }
          functionInput = new ExpressionInput(getParameter("Function"),parser);
          in1 = new JCMPanel();
          in1.add(functionInput,BorderLayout.CENTER);
          in1.add(new Label(getParameter("FunctionLabel")), BorderLayout.WEST);
          functionInput.setOnUserAction(mainController);
          functionInput2 = new ExpressionInput(getParameter("Function2"),parser);
          in2 = new JCMPanel();
          in2.add(functionInput2,BorderLayout.CENTER);
          in2.add(new Label(getParameter("FunctionLabel2")), BorderLayout.WEST);
          functionInput2.setOnUserAction(mainController);
      }
      
      // If we"re not doing curves, all we have to do is put the function inputs in the inputPanel
      
      if (!doCurves) {  
         Panel p = new JCMPanel(2,1,3);
         p.add(in1);
         p.add(in2);
         inputPanel.add(p, BorderLayout.CENTER);
         if (computeButton != null)
            inputPanel.add(computeButton,BorderLayout.EAST);
         return;
      }
      
      // Now we know that doCurves is true.  First, make the animator and clear button
      animator = new Animator(Animator.STOP_BUTTON);
      animator.setStopButtonName("Stop Curves");
      animator.setOnChange(new Computable() { // animator drives curves
           public void compute() {
              extendCurves();
           }
        });
      mainController.add(new InputObject() { // curves must stop if main controller is triggered
            public void checkInput() {
               curves.setSize(0);
               animator.stop();
            }
            public void notifyControllerOnChange(Controller c) {
            }
        });
      clearButton = new Button("Clear");
      clearButton.addActionListener(this);
      // Make a panel to contain the xStart and yStart inputs, if they are in the configuration.
      Panel bottom = null;
      if ("yes".equalsIgnoreCase(getParameter("UseStartInputs","yes"))) {
         xStart = new VariableInput();
         xStart.addActionListener(this);
         yStart = new VariableInput();
         yStart.addActionListener(this);
         bottom = new Panel();  // not a JCMPanel -- I don"t want their contents checked automatically
         startCurveButton = new Button("Start curve at:");
         startCurveButton.addActionListener(this);
         bottom.add(startCurveButton);
         bottom.add(new Label(xVar.getName() + " ="));
         bottom.add(xStart);
         bottom.add(new Label(yVar.getName() + " ="));
         bottom.add(yStart);
      }
      
      // Now, make a panel to contain the methodChoice and deltaT input if they are in the configuration.
      // The animator and clear button will be added to this panel if it exists.  If not, and if
      // an xStart/yStart panel exists, then it will be added there.  If neither exists,
      // it goes in its own panel.  The variable bottom ends up pointing to a panel that
      // contains all the curve controls.
      
      boolean useChoice = "yes".equalsIgnoreCase(getParameter("UseMethodChoice","yes"));
      boolean useDelta = "yes".equalsIgnoreCase(getParameter("UseDeltaInput","yes"));
      if (useChoice || useDelta) {
         Panel top = new Panel();  // not a JCMPanel!
         if (useDelta) {
            top.add(new Label("dt ="));
            deltaT = new VariableInput(null,""+dt);
            top.add(deltaT);
         }
         if (useChoice) {
            top.add(new Label("Method:"));
            methodChoice = new Choice();
            methodChoice.add("Runge-Kutta 4");
            methodChoice.add("Runge-Kutta 2");
            methodChoice.add("Euler");
            top.add(methodChoice);
         }
         top.add(animator);
         top.add(clearButton);
         if (bottom == null)
            bottom = top;
         else {
            Panel p = new Panel();
            p.setLayout(new BorderLayout());
            p.add(top, BorderLayout.NORTH);
            p.add(bottom, BorderLayout.CENTER);
            bottom = p;
         }
      }
      else  {
         if (bottom == null)
            bottom = new Panel();
         bottom.add(animator);
         bottom.add(clearButton);
      }
      
      // Add the panels "bottom" to the inputPanel, and ruturn
      // if there are no function inputs.
      
      inputPanel.add(bottom, BorderLayout.CENTER);
      if (in1 == null)
         return;
      // Add the function inputs and compute button to the inputPanel         
         
      Panel in = new JCMPanel(1,2);
      in.add(in1);
      in.add(in2);
      if (computeButton != null) {
         Panel p = new JCMPanel();
         p.add(in,BorderLayout.CENTER);
         p.add(computeButton,BorderLayout.EAST);
         in = p;
      }
      inputPanel.add(in,BorderLayout.NORTH);
      
   } // end setUpBottomPanel()

   public void actionPerformed(ActionEvent evt) {
         // React if user presses return in xStart or yStart, or pass evt on to GenericGraphApplet
      Object src = evt.getSource();
      if (src == clearButton) {
          canvas.clearErrorMessage();
          curves.setSize(0);
          animator.stop();
          canvas.rupute();  // force recompute of off-screen canvas!
      }
      else if (src == xStart || src == yStart || src == startCurveButton) {
              // Start a curve from x and y values in xStart and yStart
         canvas.clearErrorMessage();
         double x=0, y=0;
         try {
            xStart.checkInput();
            x = xStart.getVal();
            yStart.checkInput();
            y = yStart.getVal();
            startCurve(x,y);
            if (deltaT != null) {
               deltaT.checkInput();
               dt = deltaT.getVal();
               if (dt <= 0) {
                  deltaT.requestFocus();
                  throw new JCMError("dt must be positive", deltaT);
               }
            }
         }
         catch (JCMError e) {
            curves.setSize(0);
            animator.stop();
            canvas.setErrorMessage(null,"Illegal Data For Curve.  " + e.getMessage());
         }
      }
      else
         super.actionPerformed(evt);
   } // end actionPerfromed
   
   public void startCurve(double x, double y) {
        // Start an integral curve at the point (x,y)
      synchronized (curves) {
         if (deltaT != null) {
            try {
               deltaT.checkInput();
               dt = deltaT.getVal();
               if (dt <= 0) {
                  deltaT.requestFocus();
                  throw new JCMError("dt must be positive", deltaT);
               }  
            }
            catch (JCMError e) {
               curves.setSize(0);
               animator.stop();
               canvas.setErrorMessage(null,"Illegal Data For Curve.  " + e.getMessage());
               return;
            }
         }
         Curve c = new Curve();
         c.dt = dt;
         int method = (methodChoice == null)? RK4 : methodChoice.getSelectedIndex();
         c.method = method;
         c.x = x;
         c.y = y;
         curves.addElement(c);
         animator.start();
      }
   }
   
   public void extendCurves() {
         // Add the next segment to each integral curve.  This function
         // is called repeatedly by the animator.
      synchronized(curves) {
         if (canvas == null || canvas.getCoordinateRect() == null)  // can happen when frame closes
            return;  
         while (canvas.getCoordinateRect().getWidth() <= 0) {
               // need this at startup to make sure that the canvas has appeared on the screen
             try {
                Thread.sleep(200);
             }
             catch (InterruptedException e) {
             }
         }
         int size = curves.size();
         for (int i = 0; i < size; i++) {
            Curve curve = (Curve)curves.elementAt(i);
            curve.lastX = curve.x;
            curve.lastY = curve.y;
            nextPoint(curve.x, curve.y, curve.dt, curve.method);
            curve.x = nextPoint[0];
            curve.y = nextPoint[1];
         }
         CoordinateRect c = canvas.getCoordinateRect();
         double pixelWidthLimit = 100000*c.getPixelWidth();
         double pixelHeightLimit = 100000*c.getPixelHeight();
         for (int i = size-1; i >= 0; i--) {
            Curve curve = (Curve)curves.elementAt(i);
            if (Double.isNaN(curve.x) || Double.isNaN(curve.y) ||
                    Math.abs(curve.x) > pixelWidthLimit || 
                    Math.abs(curve.y) > pixelWidthLimit) // stop processing this curve
               curves.removeElementAt(i);
         }
         if (curves.size() > 0)
            canvas.drawTemp(curveDrawer);
         else {
            animator.stop();
         }
      }
   }
   private void nextPoint(double x, double y, double dt, int method) {
         // Find next point from (x,y) by applying specified method over time interval dt
      switch (method) {
         case EULER:
            nextEuler(x,y,dt);
            break;
         case RK2:
            nextRK2(x,y,dt);
            break;
         case RK4:
            nextRK4(x,y,dt);
            break;
      }
   }
   
   private void nextEuler(double x, double y, double dt) {
      params[0] = x;
      params[1] = y;
      double dx = xFunc.getVal(params);
      double dy = yFunc.getVal(params);
      nextPoint[0] = x + dt*dx;
      nextPoint[1] = y + dt*dy;
   }
   
   private void nextRK2(double x, double y, double dt) {
      params[0] = x;
      params[1] = y;
      double dx1 = xFunc.getVal(params);
      double dy1 = yFunc.getVal(params);
      double x2 = x + dt*dx1;
      double y2 = y + dt*dy1;
      params[0] = x2;
      params[1] = y2;
      double dx2 = xFunc.getVal(params);
      double dy2 = yFunc.getVal(params);
      nextPoint[0] = x + 0.5*dt*(dx1+dx2);
      nextPoint[1] = y + 0.5*dt*(dy1+dy2);
   }
   
   private void nextRK4(double x, double y, double dt) {
      params[0] = x;
      params[1] = y;
      double dx1 = xFunc.getVal(params);
      double dy1 = yFunc.getVal(params);
      
      double x2 = x + 0.5*dt*dx1;
      double y2 = y + 0.5*dt*dy1;
      params[0] = x2;
      params[1] = y2;
      double dx2 = xFunc.getVal(params);
      double dy2 = yFunc.getVal(params);
      
      double x3 = x + 0.5*dt*dx2;
      double y3 = y + 0.5*dt*dy2;
      params[0] = x3;
      params[1] = y3;
      double dx3 = xFunc.getVal(params);
      double dy3 = yFunc.getVal(params);
      
      double x4 = x + dt*dx3;
      double y4 = y + dt*dy3;
      params[0] = x4;
      params[1] = y4;
      double dx4 = xFunc.getVal(params);
      double dy4 = yFunc.getVal(params);
      
      nextPoint[0] = x + (dt / 6) * (dx1 + 2 * dx2 + 2 * dx3 + dx4);
      nextPoint[1] = y + (dt / 6) * (dy1 + 2 * dy2 + 2 * dy3 + dy4);
   }
   
   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 IntegrapCurves applet, the example string should contain
         // two expression that defines the vector field, separated 
         // by a semicolon.  This can optionally
         // be followed by another semicolon and a list of numbers, separated by spaces and/or commas.
         // 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 next number, if present,
         // specifies a value for delta t.  If there are more numbers, they should come in pairs.
         // each pair specifies a point where a curve will be started when the
         // example is loaded.  There is a 0.5 second delay between loading and starting the
         // curves to allow time for the redrawing (although it seems to block the redrawing, at least
         // on some platforms).
         
      if (animator != null) {
         curves.setSize(0);
         animator.stop();
      }
         
      int pos = example.indexOf(";");
      if (pos == -1)
         return; // illegal example -- must have two functions
      String example2 = example.substring(pos+1);
      example = example.substring(0,pos);
      pos = example2.indexOf(";");   
      
         
      double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
      
      StringTokenizer toks = null;
      if (pos > 0) { 
               // Get limits from example2 text.
         String nums = example2.substring(pos+1);
         example2 = example2.substring(0,pos);
         toks = new StringTokenizer(nums, " ,");
         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.hasMoreTokens()) {
            double d = Double.NaN;
            try {
               d = (new Double(toks.nextToken())).doubleValue();
            }
            catch (NumberFormatException e) {
            }
            if (Double.isNaN(d) || d <= 0 || d > 100)
               d = 0.1;
             if (deltaT != null)
                deltaT.setVal(d);
             else
                dt = d;
         }
      }
      
      // 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);
         functionInput2.setText(example2);
      }
      else { 
           // If there is no user input, set the function in the graph directly.
         try {
            Function f = new SimpleFunction( parser.parse(example), xVar );
            ((WrapperFunction)xFunc).setFunction(f);
            Function g = new SimpleFunction( parser.parse(example2), xVar );
            ((WrapperFunction)yFunc).setFunction(g);
         }
         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();
      mainController.rupute();
      
      if (animator != null && toks != null) { // get any extra nums from the tokenizer and use them as starting points for curves
         int ct = 2*(toks.countTokens()/2);
         if (ct > 0) {
            synchronized(curves) {
               for (int i = 0; i < ct; i++) {
                  try {
                     double x = (new Double(toks.nextToken())).doubleValue();
                     double y = (new Double(toks.nextToken())).doubleValue();
                     startCurve(x,y);
                  }
                  catch (Exception e) {
                  }
               }
               if (curves.size() > 0) {  // start the curves going
                  try {  
                     Thread.sleep(500);  // wait a bit to give the canvas time to start drawing itself.
                  }
                  catch (InterruptedException e) {
                  }
               }
            }
         }      
      }
      
   } // end doLoadExample()
   
   public void stop() {  // stop animator when applet is stopped
      if (animator != null) {
         curves.setSize(0);
         animator.stop();
      }
      super.stop();
   }
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         Applet app = new IntegralCurves();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   
} // end class IntegralCurves





Scatter Plot

/*************************************************************************
*                                                                        *
*  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 edu.hws.jcm.awt.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.draw.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.applet.Applet;
/**
 * A ScatterPlotApplet shows a scatter plot of data from a DataTableInput.
 * The user can enter the data in a two-column table that is shown in
 * the applet.  It is also possible to configure the applet with a menu
 * of file names.  These files, which must be in the same directory as
 * the Web page on which the applet appears, will appear in a menu.
 * A file can contain data for the table, with two numbers per line.
 * When the user loads the file, the data replaces the data in the table.
 */
public class ScatterPlotApplet extends Applet implements ActionListener {
   private Frame frame;       // If non-null, a separate window.
   private String frameTitle; // Title for the separate window.
   private Button launchButton;  // If non-null, then clicking this buttons opens a separate window.
   private String launchButtonName;  // Name for the launch button.
   
   private DataTableInput table;    //  The table for input of data.
   private ScatterPlot scatterPlot; //  The scatter plot of the data.
   private DisplayCanvas canvas;    //  The DisplayCanvas on which the plot is drawn.
   private Button loadFileButton;   //  When clicked, a data file is loaded.
   private Choice fileMenu;         //  Pop-up menu containing names of functions.
   private String[] fileNames;      //  Names of data files associated with menu entries.
   private Controller mainController;  // Controller from the main JCMPanel.
   /**
    * The init() method is called by the system to set up the applet. 
    * If the applet does not appear as a button, then init() creates the main panel of the applet
    * and calls setUpMainPanel to set it up.
    */
   public void init() {
      frameTitle = getParameter("FrameTitle"); // Get title to be used for separate window, if any.
      if (frameTitle == null) {
         frameTitle = "Scatter Plots";
         int pos = frameTitle.lastIndexOf(".");
         if (pos > -1)
            frameTitle =  frameTitle.substring(pos+1);
      }
      setLayout(new BorderLayout());
      int height = getSize().height;
      launchButtonName = getParameter("LaunchButtonName");
      if ( (height > 0 && height <= 50) || launchButtonName != null) {
              // Use a separater window and only show a button in the applet.
          if (launchButtonName == null)
               launchButtonName = "Launch " + frameTitle;
          launchButton = new Button(launchButtonName);
          add(launchButton, BorderLayout.CENTER);
          launchButton.addActionListener(this);
      }
      else {
             // Show the main panel in the applet, not in a separate window.
          add(makeMainPanel(), BorderLayout.CENTER);
      }
   }
   /*
    * Create the main panel of the applet.
    */ 
   public Panel makeMainPanel() {
   
      // Make the main panel
   
      JCMPanel panel = new JCMPanel(2);
      mainController = panel.getController();
      panel.setBackground(new Color(0,0,180));
      panel.setInsetGap(2);
      setLayout(new BorderLayout());
      
      // Make a DataInputTable with two columns
      
      table = new DataTableInput(null, 2);
      table.setColumnName(0, getParameter("ColumnName1", "X"));
      table.setColumnName(1, getParameter("ColumnName2", "Y"));
      table.setThrowErrors(true);
      if ( "yes".equalsIgnoreCase(getParameter("ShowColumnTitles","yes")))
         table.setShowColumnTitles(true);
      if ( "yes".equalsIgnoreCase(getParameter("ShowRowNumbers","yes")))
         table.setShowRowNumbers(true);
                                 
      // Make input boxes for getting expressions that can include
      // the variables associated with the table.  Initially, the 
      // expressions are just the column names.
      Parser parser = new Parser();
      table.addVariablesToParser(parser);
      ExpressionInput input1 = new ExpressionInput(table.getColumnName(0),parser);
      input1.setOnUserAction(mainController);
      ExpressionInput input2 = new ExpressionInput(table.getColumnName(1),parser);
      input2.setOnUserAction(mainController);
      
      // Make a scatter plot that graphs the first expressiong vs. the second expression.
      scatterPlot = new ScatterPlot(table, input1.getExpression(), input2.getExpression());
      if ( ! "yes".equalsIgnoreCase(getParameter("ShowRegressionLine","yes")))
        scatterPlot.setShowRegressionLine(false);
      if ( ! "yes".equalsIgnoreCase(getParameter("MissingValueIsError","yes")))
        scatterPlot.setMissingValueIsError(false);
        
      // Create the display canvas where the scater plot will be shown.
      canvas = new DisplayCanvas();
      canvas.add(new Axes());
      canvas.add(scatterPlot);
      mainController.setErrorReporter(canvas);
      
      // A compute button to recompute everything.
      
      ComputeButton computeButton = new ComputeButton("Update Display");
      computeButton.setOnUserAction(mainController);
      computeButton.setBackground(Color.lightGray);
      
      // A menu of files that can be loaded.  If no filenames are provided as
      // applet parameters, then menu is null.
      
      Panel menu = makefileMenu();
      
      // Lay out the components in the applet.
      
      JCMPanel inputPanel = null;
      Panel bottom = null;  //might not be a JCMPanel
      if ( "yes".equalsIgnoreCase(getParameter("UseExpressionInputs","yes"))) {
         inputPanel = new JCMPanel(1,2);
         inputPanel.setBackground(Color.lightGray);
         JCMPanel leftInput = new JCMPanel();
         leftInput.add(new Label("  Plot:  "), BorderLayout.WEST);
         leftInput.add(input1, BorderLayout.CENTER);
         inputPanel.add(leftInput);
         JCMPanel rightInput = new JCMPanel();
         rightInput.add(new Label(" versus: "), BorderLayout.WEST);
         rightInput.add(input2, BorderLayout.CENTER);
         inputPanel.add(rightInput);
         bottom = new JCMPanel(new BorderLayout(12,3));
         bottom.add(inputPanel, BorderLayout.CENTER);
         bottom.add(computeButton, BorderLayout.EAST);
      }
      
      if ( scatterPlot.getShowRegressionLine() && "yes".equalsIgnoreCase(getParameter("ShowStats","yes")) ) {
            // Make a display label to show some statistics about the data.
         DisplayLabel dl = new DisplayLabel(
               "Slope = #;  Intercept = #;  Correlation = #",
                new Value[] { scatterPlot.getValueObject(ScatterPlot.SLOPE), 
                              scatterPlot.getValueObject(ScatterPlot.INTERCEPT), 
                              scatterPlot.getValueObject(ScatterPlot.CORRELATION) }
            );
         dl.setAlignment(Label.CENTER);
         dl.setBackground(Color.lightGray);
         dl.setForeground(new Color(200,0,0));
         dl.setFont(new Font("Serif",Font.PLAIN,14));
         if (bottom != null) 
            bottom.add(dl, BorderLayout.SOUTH);
         else {
            bottom = new JCMPanel(new BorderLayout(12,3));
            bottom.add(dl, BorderLayout.CENTER);
            bottom.add(computeButton, BorderLayout.EAST);
         }
      }
      
      if (bottom == null) {
         if (menu != null)
            menu.add(computeButton, BorderLayout.EAST);
         else {
            bottom = new Panel();
            bottom.add(computeButton);
         }
      }
      
      panel.add(canvas, BorderLayout.CENTER);
      panel.add(table, BorderLayout.WEST);
      if (bottom != null)
         panel.add(bottom, BorderLayout.SOUTH);
      if (menu != null)
         panel.add(menu, BorderLayout.NORTH);
      else {
         String title = getParameter("PanelTitle");
         if (title != null) {
            Label pt = new Label(title, Label.CENTER);
            pt.setBackground(Color.lightGray);
            pt.setForeground(new Color(200,0,0));
            pt.setFont(new Font("Serif",Font.PLAIN,14));
            panel.add(pt, BorderLayout.NORTH);
         }
      }
         
      return panel;
      
   } // end makeMainPanel()
   
   
   private Panel makefileMenu() {
         // If the applet tag contains params named "File", "File1", "File2", ..., use
         // their values to make a file menu.  If the value of the param contains a ";",
         // then the first part, up to the ";", goes into the menu and the second part
         // is the name of the file.  If there is no ";", then the entire value is
         // shown in the menu and is also used as the name of the file.  The actual
         // files must be in the same directory as the Web page that contains the applet.
      Vector names = new Vector();
      fileMenu = new Choice();
      String file = getParameter("File");
      int ct = 1;
      if (file == null) {
         file = getParameter("File1");
         ct = 2;
      }
      while (file != null) {
         file = file.trim();
         int pos = file.indexOf(";");
         String menuEntry;
         if (pos == -1)
            menuEntry = file;
         else {
            menuEntry = file.substring(0,pos).trim();
            file = file.substring(pos+1).trim();
         }
         names.addElement(file);
         fileMenu.add(menuEntry);
         file = getParameter("File" + ct);
         ct++;
      }
      if (names.size() == 0) {
         fileMenu = null;
         return null;
      }
      else {
         fileNames  = new String[names.size()];
         for (int i = 0; i < names.size(); i++)
            fileNames[i] = (String)names.elementAt(i);
         Panel p = new Panel();
         p.setBackground(Color.lightGray);
         p.setLayout(new BorderLayout(5,5));
         p.add(fileMenu,BorderLayout.CENTER);
         loadFileButton = new Button("Load Data File: ");
         loadFileButton.addActionListener(this);
         p.add(loadFileButton,BorderLayout.WEST);
         fileMenu.setBackground(Color.white);
         return p;
      }
   }
   
   private void doLoadFile(String name) {
        // Load the file from the same directory as the Web page and put the data
        // from the file into the table.  The file should contain two numbers on
        // each line.
      InputStream in;
      try {
         URL url = new URL(getDocumentBase(), name);
         in = url.openStream();
      }
      catch (Exception e) {
         canvas.setErrorMessage(null,"Unable to open file named \"" + name + "\": " + e);
         return;
      }
      Reader inputReader = new InputStreamReader(in);
      try {
         table.readFromStream(inputReader);
         inputReader.close();
      }
      catch (Exception e) {
         canvas.setErrorMessage(null,"Unable to get data from file \"" + name + "\": " + e.getMessage());
         return;
      }
      mainController.rupute();
   }
   /**
    *  Respond when user clicks a button; not meant to be called directly.
    *  This opens and closes the separate window.
    */ 
   synchronized public void actionPerformed(ActionEvent evt) {
      Object source = evt.getSource();
      if (loadFileButton != null && source == loadFileButton) {
         doLoadFile( fileNames[fileMenu.getSelectedIndex()] );
      }
      else if (source == launchButton && launchButton != null) {
            // Open or close separate frame.
         launchButton.setEnabled(false);
         if (frame == null) {
            frame = new Frame(frameTitle);
            frame.add(makeMainPanel());
            frame.addWindowListener( new WindowAdapter() {
                  public void windowClosing(WindowEvent evt) {
                     frame.dispose();
                  }
                  public void windowClosed(WindowEvent evt) {
                     frameClosed();
                  }
               } );
            frame.pack();
            frame.setLocation(50,50);
            frame.show();
            launchButton.setLabel("Close Window");
            launchButton.setEnabled(true);
         }
         else {
            frame.dispose();
         }
      }
   }
   
   synchronized private void frameClosed() {
        // respond when separate window closes.
      frame = null;
      launchButton.setLabel(launchButtonName);
      launchButton.setEnabled(true);
   }
   
   /**
    *  Return the applet parameter with a given param name, but if no
    *  such applet param exists, return a default value instead.
    */
   protected String getParameter(String paramName, String defaultValue) {
       String val = getParameter(paramName);
       return (val == null)? defaultValue : val;
   }
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         Applet app = new SimpleGraph();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   
   
} // end class ScatterPlotApplet





Spline Editor

/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.beans.*;
import java.io.*;
import java.text.*;
import java.util.*;
import javax.imageio.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.image.*;
import org.jdesktop.animation.timing.interpolation.*;
import org.jdesktop.animation.timing.*;
import org.jdesktop.animation.timing.Animator.*;
import org.jdesktop.animation.timing.interpolation.*;
import java.awt.image.*;
import javax.swing.border.*;
import java.net.*;
public class SplineEditor extends JFrame {
    public SplineEditor() throws HeadlessException {
        super("Spline Editor");
        add(buildHeader(), BorderLayout.NORTH);
        add(buildControlPanel(), BorderLayout.CENTER);
        pack();
        setLocationRelativeTo(null);
        setResizable(false);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    private Component buildHeader() {
        ImageIcon icon = new ImageIcon(getClass().getResource("simulator.png"));
        HeaderPanel header = new HeaderPanel(icon,
                                             "Timing Framework Spline Editor",
                                             "Drag control points in the display to change the shape of the spline.",
                                             "Click the Copy Code button to generate the corresponding Java code.");
        return header;
    }
    private Component buildControlPanel() {
        return new SplineControlPanel();
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException e) {
                } catch (InstantiationException e) {
                } catch (IllegalAccessException e) {
                } catch (UnsupportedLookAndFeelException e) {
                }
                
                new SplineEditor().setVisible(true);
            }
        });
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


class EquationDisplay extends JComponent implements PropertyChangeListener {
    private static final Color COLOR_BACKGROUND = Color.WHITE;
    private static final Color COLOR_MAJOR_GRID = Color.GRAY.brighter();
    private static final Color COLOR_MINOR_GRID = new Color(220, 220, 220);
    private static final Color COLOR_AXIS = Color.BLACK;
    
    private static final float STROKE_AXIS = 1.2f;
    private static final float STROKE_GRID = 1.0f;
    
    private static final float COEFF_ZOOM = 1.1f;
    
    private java.util.List<DrawableEquation> equations;
    protected double minX;
    protected double maxX;
    protected double minY;
    protected double maxY;
    private double originX;
    private double originY;
    private double majorX;
    private int minorX;
    private double majorY;
    private int minorY;
    
    private boolean drawText = true;
    
    private Point dragStart;
    
    private NumberFormat formatter;
    private ZoomHandler zoomHandler;
    private PanMotionHandler panMotionHandler;
    private PanHandler panHandler;
    public EquationDisplay(double originX, double originY,
                           double minX, double maxX,
                           double minY, double maxY,
                           double majorX, int minorX,
                           double majorY, int minorY) {
        if (minX >= maxX) {
            throw new IllegalArgumentException("minX must be < to maxX");
        }
        
        if (originX < minX || originX > maxX) {
            throw new IllegalArgumentException("originX must be between minX and maxX");
        }
        if (minY >= maxY) {
            throw new IllegalArgumentException("minY must be < to maxY");
        }
        
        if (originY < minY || originY > maxY) {
            throw new IllegalArgumentException("originY must be between minY and maxY");
        }
        
        if (minorX <= 0) {
            throw new IllegalArgumentException("minorX must be > 0");
        }
        
        if (minorY <= 0) {
            throw new IllegalArgumentException("minorY must be > 0");
        }
        
        if (majorX <= 0.0) {
            throw new IllegalArgumentException("majorX must be > 0.0");
        }
        
        if (majorY <= 0.0) {
            throw new IllegalArgumentException("majorY must be > 0.0");
        }
        
        this.originX = originX;
        this.originY = originY;
        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;
        
        this.majorX = majorX;
        this.minorX = minorX;
        this.majorY = majorY;
        this.minorY = minorY;
        
        this.equations = new LinkedList<DrawableEquation>();
        
        this.formatter = NumberFormat.getInstance();
        this.formatter.setMaximumFractionDigits(2);
        
        panHandler = new PanHandler();
        addMouseListener(panHandler);
        panMotionHandler = new PanMotionHandler();
        addMouseMotionListener(panMotionHandler);
        zoomHandler = new ZoomHandler();
        addMouseWheelListener(zoomHandler);
    }
    
    @Override
    public void setEnabled(boolean enabled) {
        if (isEnabled() != enabled) {
            //super.setEnabled(enabled);
            if (enabled) {
                addMouseListener(panHandler);
                addMouseMotionListener(panMotionHandler);
                addMouseWheelListener(zoomHandler);
            } else {
                removeMouseListener(panHandler);
                removeMouseMotionListener(panMotionHandler);
                removeMouseWheelListener(zoomHandler);
            }
        }
    }
    
    public boolean isDrawText() {
        return drawText;
    }
    public void setDrawText(boolean drawText) {
        this.drawText = drawText;
    }
    public void addEquation(AbstractEquation equation, Color color) {
        if (equation != null && !equations.contains(equation)) {
            equation.addPropertyChangeListener(this);
            equations.add(new DrawableEquation(equation, color));
            repaint();
        }
    }
    
    public void removeEquation(AbstractEquation equation) {
        if (equation != null) {
            DrawableEquation toRemove = null;
            for (DrawableEquation drawable: equations) {
                if (drawable.getEquation() == equation) {
                    toRemove = drawable;
                    break;
                }
            }
            
            if (toRemove != null) {
                equation.removePropertyChangeListener(this);
                equations.remove(toRemove);
                repaint();
            }
        }
    }
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(400, 400);
    }
    public void propertyChange(PropertyChangeEvent evt) {
        repaint();
    }
    
    protected double yPositionToPixel(double position) {
        double height = (double) getHeight();
        return height - ((position - minY) * height / (maxY - minY));
    }
    protected double xPositionToPixel(double position) {
        return (position - minX) * (double) getWidth() / (maxX - minX);
    }
    
    protected double xPixelToPosition(double pixel) {
        double axisV = xPositionToPixel(originX);
        return (pixel - axisV) * (maxX - minX) / (double) getWidth();
    }
    
    protected double yPixelToPosition(double pixel) {
        double axisH = yPositionToPixel(originY);
        return (getHeight() - pixel - axisH) * (maxY - minY) / (double) getHeight();
    }
    @Override
    protected void paintComponent(Graphics g) {
        if (!isVisible()) {
            return;
        }
        
        Graphics2D g2 = (Graphics2D) g;
        setupGraphics(g2);
        paintBackground(g2);
        drawGrid(g2);
        drawAxis(g2);
        
        drawEquations(g2);
        
        paintInformation(g2);
    }
    
    protected void paintInformation(Graphics2D g2) {
    }
    private void drawEquations(Graphics2D g2) {
        for (DrawableEquation drawable: equations) {
            g2.setColor(drawable.getColor());
            drawEquation(g2, drawable.getEquation());
        }
    }
    private void drawEquation(Graphics2D g2, AbstractEquation equation) {
        float x = 0.0f;
        float y = (float) yPositionToPixel(equation.rupute(xPixelToPosition(0.0)));
        
        GeneralPath path = new GeneralPath();
        path.moveTo(x, y);
        
        for (x = 0.0f; x < getWidth(); x += 1.0f) {
            double position = xPixelToPosition(x);
            y = (float) yPositionToPixel(equation.rupute(position));
            path.lineTo(x, y);
        }
        
        g2.draw(path);
    }
    private void drawGrid(Graphics2D g2) {
        Stroke stroke = g2.getStroke();
        drawVerticalGrid(g2);
        drawHorizontalGrid(g2);
        if (drawText) {
            drawVerticalLabels(g2);
            drawHorizontalLabels(g2);
        }
        
        g2.setStroke(stroke);
    }
    
    private void drawHorizontalLabels(Graphics2D g2) {
        double axisV = xPositionToPixel(originX);
        g2.setColor(COLOR_AXIS);
        for (double y = originY + majorY; y < maxY + majorY; y += majorY) {
            int position = (int) yPositionToPixel(y);
            g2.drawString(formatter.format(y), (int) axisV + 5, position);
        }
        
        for (double y = originY - majorY; y > minY - majorY; y -= majorY) {
            int position = (int) yPositionToPixel(y);
            g2.drawString(formatter.format(y), (int) axisV + 5, position);
        }
    }
    
    private void drawHorizontalGrid(Graphics2D g2) {
        double minorSpacing = majorY / minorY;
        double axisV = xPositionToPixel(originX);
        
        Stroke gridStroke = new BasicStroke(STROKE_GRID);
        Stroke axisStroke = new BasicStroke(STROKE_AXIS);
        
        for (double y = originY + majorY; y < maxY + majorY; y += majorY) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorY; i++) {
                int position = (int) yPositionToPixel(y - i * minorSpacing);
                g2.drawLine(0, position, getWidth(), position);    
            }
            int position = (int) yPositionToPixel(y);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(0, position, getWidth(), position);
            
            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);
        }
        for (double y = originY - majorY; y > minY - majorY; y -= majorY) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorY; i++) {
                int position = (int) yPositionToPixel(y + i * minorSpacing);
                g2.drawLine(0, position, getWidth(), position);    
            }
            int position = (int) yPositionToPixel(y);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(0, position, getWidth(), position);
            
            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);
        }
    }
    private void drawVerticalLabels(Graphics2D g2) {
        double axisH = yPositionToPixel(originY);
        FontMetrics metrics = g2.getFontMetrics();
        
        g2.setColor(COLOR_AXIS);
        for (double x = originX + majorX; x < maxX + majorX; x += majorX) {
            int position = (int) xPositionToPixel(x);
            g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight());
        }
        for (double x = originX - majorX; x > minX - majorX; x -= majorX) {
            int position = (int) xPositionToPixel(x);
            g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight());
        }
    }
    
    private void drawVerticalGrid(Graphics2D g2) {
        double minorSpacing = majorX / minorX;
        double axisH = yPositionToPixel(originY);
        
        Stroke gridStroke = new BasicStroke(STROKE_GRID);
        Stroke axisStroke = new BasicStroke(STROKE_AXIS);
        
        for (double x = originX + majorX; x < maxX + majorX; x += majorX) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorX; i++) {
                int position = (int) xPositionToPixel(x - i * minorSpacing);
                g2.drawLine(position, 0, position, getHeight());    
            }
            int position = (int) xPositionToPixel(x);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(position, 0, position, getHeight());
            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);
        }
        for (double x = originX - majorX; x > minX - majorX; x -= majorX) {
            g2.setStroke(gridStroke);
            g2.setColor(COLOR_MINOR_GRID);
            for (int i = 0; i < minorX; i++) {
                int position = (int) xPositionToPixel(x + i * minorSpacing);
                g2.drawLine(position, 0, position, getHeight());    
            }
            int position = (int) xPositionToPixel(x);
            g2.setColor(COLOR_MAJOR_GRID);
            g2.drawLine(position, 0, position, getHeight());
            
            g2.setStroke(axisStroke);
            g2.setColor(COLOR_AXIS);
            g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);
        }
    }
    private void drawAxis(Graphics2D g2) {
        double axisH = yPositionToPixel(originY);
        double axisV = xPositionToPixel(originX);
        
        g2.setColor(COLOR_AXIS);
        Stroke stroke = g2.getStroke();
        g2.setStroke(new BasicStroke(STROKE_AXIS));
        
        g2.drawLine(0, (int) axisH, getWidth(), (int) axisH);
        g2.drawLine((int) axisV, 0, (int) axisV, getHeight());
        
        FontMetrics metrics = g2.getFontMetrics();
        g2.drawString(formatter.format(0.0), (int) axisV + 5, (int) axisH + metrics.getHeight());
        
        g2.setStroke(stroke);
    }
    protected void setupGraphics(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
    }
    protected void paintBackground(Graphics2D g2) {
        g2.setColor(COLOR_BACKGROUND);
        g2.fill(g2.getClipBounds());
    }
    private class DrawableEquation {
        private AbstractEquation equation;
        private Color color;
        DrawableEquation(AbstractEquation equation, Color color) {
            this.equation = equation;
            this.color = color;
        }
        
        AbstractEquation getEquation() {
            return equation;
        }
        
        Color getColor() {
            return color;
        }
    }
    
    private class ZoomHandler implements MouseWheelListener {
        public void mouseWheelMoved(MouseWheelEvent e) {
            double distanceX = maxX - minX;
            double distanceY = maxY - minY;
            
            double cursorX = minX + distanceX / 2.0;
            double cursorY = minY + distanceY / 2.0;
            
            int rotation = e.getWheelRotation();
            if (rotation < 0) {
                distanceX /= COEFF_ZOOM;
                distanceY /= COEFF_ZOOM;
            } else {
                distanceX *= COEFF_ZOOM;
                distanceY *= COEFF_ZOOM;
            }
            
            minX = cursorX - distanceX / 2.0;
            maxX = cursorX + distanceX / 2.0;
            minY = cursorY - distanceY / 2.0;
            maxY = cursorY + distanceY / 2.0;
            repaint();
        }
    }
    
    private class PanHandler extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            dragStart = e.getPoint();
        }
    }
    private class PanMotionHandler extends MouseMotionAdapter {
        @Override
        public void mouseDragged(MouseEvent e) {
            Point dragEnd = e.getPoint();
            double distance = xPixelToPosition(dragEnd.getX()) -
                              xPixelToPosition(dragStart.getX());
            minX -= distance;
            maxX -= distance;
            distance = yPixelToPosition(dragEnd.getY()) -
                       yPixelToPosition(dragStart.getY());
            minY -= distance;
            maxY -= distance;
            
            repaint();
            dragStart = dragEnd;
        }
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

interface Equation {
    public double compute(double variable);
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

abstract class AbstractEquation implements Equation {
    protected java.util.List<PropertyChangeListener> listeners;
    protected AbstractEquation() {
        this.listeners = new LinkedList<PropertyChangeListener>();
    }
    
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        if (listener != null && !listeners.contains(listener)) {
            listeners.add(listener);
        }
    }
    
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        if (listener != null) {
            listeners.remove(listener);
        }
    }
    
    protected void firePropertyChange(String propertyName,
                                      double oldValue,
                                      double newValue) {
        PropertyChangeEvent changeEvent = new PropertyChangeEvent(this,
                                                                  propertyName,
                                                                  oldValue,
                                                                  newValue);
        for (PropertyChangeListener listener: listeners) {
            listener.propertyChange(changeEvent);
        }
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
class AbstractSimulator extends JComponent {
    protected double time;
    
    public AbstractSimulator() {
        this.time = 0.0f;
    }
    public void setTime(double time) {
        this.time = time;
        repaint();
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
class BouncerSimulator extends AbstractSimulator {
    private static final Color COLOR_BACKGROUND = Color.WHITE;
    private BufferedImage image;
    public BouncerSimulator() {
        try {
            image = ImageIO.read(BouncerSimulator.class.getResource("item.png"));
        } catch (Exception e) { }
    }
    @Override
    protected void paintComponent(Graphics g) {
        if (!isVisible()) {
            return;
        }
        
        Graphics2D g2 = (Graphics2D) g;
        setupGraphics(g2);
        drawBackground(g2);
        drawItem(g2);
    }
    private void drawItem(Graphics2D g2) {
        double position = time;
        double xPos = position * getWidth() / 2;
        int width = getWidth() * 2 / 3;
        int x = (getWidth() - width) / 2;
        x += xPos;
        int y = getHeight() / 2;
        y -= image.getHeight() / 2;
        g2.drawImage(image, null, x, y);
    }
    private void setupGraphics(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
    }
    private void drawBackground(Graphics2D g2) {
        g2.setColor(COLOR_BACKGROUND);
        g2.fill(g2.getClipBounds());
    }
        
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(150, 100);
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

class DropSimulator extends AbstractSimulator {
    private static final Color COLOR_BACKGROUND = Color.WHITE;
    private BufferedImage image;
    private BufferedImage shadow;
    
    private float angle = 90;
    private int distance = 20;
    // cached values for fast painting
    private int distance_x = 0;
    private int distance_y = 0;
    
    public DropSimulator() {
        try {
            image = ImageIO.read(BouncerSimulator.class.getResource("icon.png"));
            ShadowFactory factory = new ShadowFactory(5, 0.5f, Color.BLACK);
            shadow = factory.createShadow(image);
        } catch (Exception e) { }
    }
    @Override
    protected void paintComponent(Graphics g) {
        if (!isVisible()) {
            return;
        }
        
        Graphics2D g2 = (Graphics2D) g;
        setupGraphics(g2);
        drawBackground(g2);
        drawItem(g2);
    }
    private void drawItem(Graphics2D g2) {
        double position = time;
        int width = (int) (shadow.getWidth() / 2 * (1.0 + position));
        int height = (int) (shadow.getHeight() / 2 * (1.0 + position));
        int x = (getWidth() - width) / 2;
        int y = (getHeight() - height) / 2;
        Composite composite = g2.getComposite();
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                                                   1.0f - (0.5f * (float) position)));
        computeShadowPosition((position * distance) + 1.0);
        g2.drawImage(shadow, x + distance_x, y + distance_y, width, height, null);
        
        g2.setComposite(composite);
        
        width = (int) (image.getWidth() / 2 * (1.0 + position));
        height = (int) (image.getHeight() / 2 * (1.0 + position));
        x = (getWidth() - width) / 2;
        y = (getHeight() - height) / 2;
        g2.drawImage(image, x, y, width, height, null);
    }
    private void setupGraphics(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                            RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    }
    private void drawBackground(Graphics2D g2) {
        g2.setColor(COLOR_BACKGROUND);
        g2.fill(g2.getClipBounds());
    }
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(150, 100);
    }
    private void computeShadowPosition(double distance) {
        double angleRadians = Math.toRadians(angle);
        distance_x = (int) (Math.cos(angleRadians) * distance);
        distance_y = (int) (Math.sin(angleRadians) * distance);
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

class HeaderPanel extends JPanel {
    private ImageIcon icon;
    HeaderPanel(ImageIcon icon,
                String title,
                String help1,
                String help2) {
        super(new BorderLayout());
        this.icon = icon;
        JPanel titlesPanel = new JPanel(new GridLayout(3, 1));
        titlesPanel.setOpaque(false);
        titlesPanel.setBorder(new EmptyBorder(12, 0, 12, 0));
        JLabel headerTitle = new JLabel(title);
        Font police = headerTitle.getFont().deriveFont(Font.BOLD);
        headerTitle.setFont(police);
        headerTitle.setBorder(new EmptyBorder(0, 12, 0, 0));
        titlesPanel.add(headerTitle);
        JLabel message;
        titlesPanel.add(message = new JLabel(help1));
        police = headerTitle.getFont().deriveFont(Font.PLAIN);
        message.setFont(police);
        message.setBorder(new EmptyBorder(0, 24, 0, 0));
        titlesPanel.add(message = new JLabel(help2));
        police = headerTitle.getFont().deriveFont(Font.PLAIN);
        message.setFont(police);
        message.setBorder(new EmptyBorder(0, 24, 0, 0));
        message = new JLabel(this.icon);
        message.setBorder(new EmptyBorder(0, 0, 0, 12));
        add(BorderLayout.WEST, titlesPanel);
        add(BorderLayout.EAST, message);
        add(BorderLayout.SOUTH, new JSeparator(JSeparator.HORIZONTAL));
        setPreferredSize(new Dimension(500, this.icon.getIconHeight() + 24));
    }
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (!isOpaque()) {
            return;
        }
        
        Rectangle bounds = g.getClipBounds();
        Color control = UIManager.getColor("control");
        int width = getWidth();
        Graphics2D g2 = (Graphics2D) g;
        Paint storedPaint = g2.getPaint();
        g2.setPaint(new GradientPaint(this.icon.getIconWidth(), 0, Color.white, width, 0, control));
        g2.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
        g2.setPaint(storedPaint);
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
class Java2dHelper {
    public static BufferedImage createCompatibleImage(int width, int height) {
        GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice screenDevice = environment.getDefaultScreenDevice();
        GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration();
        return configuration.createCompatibleImage(width, height);
    }
    
    public static BufferedImage loadCompatibleImage(URL resource) throws IOException {
        BufferedImage image = ImageIO.read(resource);
        BufferedImage compatibleImage = createCompatibleImage(image.getWidth(), image.getHeight());
        Graphics g = compatibleImage.getGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
        image = null;
        return compatibleImage;
    }
    
    public static BufferedImage createThumbnail(BufferedImage image, int requestedThumbSize) {
        float ratio = (float) image.getWidth() / (float) image.getHeight();
        int width = image.getWidth();
        BufferedImage thumb = image;
        
        do {
            width /= 2;
            if (width < requestedThumbSize) {
                width = requestedThumbSize;
            }
            
            BufferedImage temp = new BufferedImage(width, (int) (width / ratio), BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = temp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
            g2.dispose();
            thumb = temp;
        } while (width != requestedThumbSize);
        
        return thumb;
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * <p>A shadow factory generates a drop shadow for any given picture, respecting
 * the transparency channel if present. The resulting picture contains the
 * shadow only and to create a drop shadow effect you will need to stack the
 * original picture and the shadow generated by the factory. If you are using
 * Swing you can get this done very easily with the layout
 * {@link org.jdesktop.swingx.StackLayout}.</p>
 * <h2>Shadow Properties</h2>
 * <p>A shadow is defined by three properties:
 * <ul>
 *   <li><i>size</i>: The size, in pixels, of the shadow. This property also
 *   defines the fuzzyness.</li>
 *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
 *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be
 *   black only.</li>
 * </ul>
 * You can set these properties using the provided mutaters or the appropriate
 * constructor. Here are two ways of creating a green shadow of size 10 and
 * with an opacity of 50%:
 * <pre>
 * ShadowFactory factory = new ShadowFactory(10, 0.5f, Color.GREEN);
 * // ..
 * factory = new ShadowFactory();
 * factory.setSize(10);
 * factory.setOpacity(0.5f);
 * factory.setColor(Color.GREEN);
 * </pre>
 * The default constructor provides the following default values:
 * <ul>
 *   <li><i>size</i>: 5 pixels</li>
 *   <li><i>opacity</i>: 50%</li>
 *   <li><i>color</i>: Black</li>
 * </ul></p>
 * <h2>Shadow Quality</h2>
 * <p>The factory provides two shadow generation algorithms: <i>fast quality blur</i>
 * and <i>high quality blur</i>. You can select your preferred algorithm by
 * setting the appropriate rendering hint:
 * <pre>
 * ShadowFactory factory = new ShadowFactory();
 * factory.setRenderingHint(ShadowFactory.KEY_BLUR_QUALITY,
 *                          ShadowFactory.VALUE_BLUR_QUALITY_HIGH);
 * </pre>
 * The default rendering algorihtm is <code>VALUE_BLUR_QUALITY_FAST</code>.</p>
 * <p>The current implementation should provide the same quality with both
 * algorithms but performances are guaranteed to be better (about 30 times
 * faster) with the <i>fast quality blur</i>.</p>
 * <h2>Generating a Shadow</h2>
 * <p>A shadow is generated as a <code>BufferedImage</code> from another
 * <code>BufferedImage</code>. Once the factory is set up, you must call
 * {@link #createShadow} to actually generate the shadow:
 * <pre>
 * ShadowFactory factory = new ShadowFactory();
 * // factory setup
 * BufferedImage shadow = factory.createShadow(bufferedImage); 
 * </pre>
 * The resulting image is of type <code>BufferedImage.TYPE_INT_ARGB</code>.
 * Both dimensions of this image are larger than original image"s:
 * <ul>
 *   <li>new width = original width + 2 * shadow size</li>
 *   <li>new height = original height + 2 * shadow size</li>
 * </ul>
 * This must be taken into account when you need to create a drop shadow effect.</p>
 * <h2>Properties Changes</h2>
 * <p>This factory allows to register property change listeners with
 * {@link #addPropertyChangeListener}. Listening to properties changes is very
 * useful when you emebed the factory in a graphical component and give the API
 * user the ability to access the factory. By listening to properties changes,
 * you can easily repaint the component when needed.</p>
 * <h2>Threading Issues</h2>
 * <p><code>ShadowFactory</code> is not guaranteed to be thread-safe.</p>
 * 
 * @author Romain Guy <romain.guy@mac.ru>
 * @author S&eacute;bastien Petrucci <sebastien_petrucci@yahoo.fr>
 */
class ShadowFactory {
    /**
     * <p>Key for the blur quality rendering hint.</p>
     */
    public static final String KEY_BLUR_QUALITY = "blur_quality";
    /**
     * <p>Selects the fast rendering algorithm. This is the default rendering
     * hint for <code>KEY_BLUR_QUALITY</code>.</p>
     */
    public static final String VALUE_BLUR_QUALITY_FAST = "fast";
    
    /**
     * <p>Selects the high quality rendering algorithm. With current implementation,
     * This algorithm does not guarantee a better rendering quality and should
     * not be used.</p>
     */
    public static final String VALUE_BLUR_QUALITY_HIGH = "high";
    /**
     * <p>Identifies a change to the size used to render the shadow.</p>
     * <p>When the property change event is fired, the old value and the new
     * value are provided as <code>Integer</code> instances.</p>
     */
    public static final String SIZE_CHANGED_PROPERTY = "shadow_size";
    
    /**
     * <p>Identifies a change to the opacity used to render the shadow.</p>
     * <p>When the property change event is fired, the old value and the new
     * value are provided as <code>Float</code> instances.</p>
     */
    public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity";
    
    /**
     * <p>Identifies a change to the color used to render the shadow.</p>
     */
    public static final String COLOR_CHANGED_PROPERTY = "shadow_color";
    // size of the shadow in pixels (defines the fuzziness)
    private int size = 5;
    
    // opacity of the shadow
    private float opacity = 0.5f;
    
    // color of the shadow
    private Color color = Color.BLACK;
    // rendering hints map
    private HashMap<Object, Object> hints;
    
    // notifies listeners of properties changes
    private PropertyChangeSupport changeSupport;
    /**
     * <p>Creates a default good looking shadow generator.
     * The default shadow factory provides the following default values:
     * <ul>
     *   <li><i>size</i>: 5 pixels</li>
     *   <li><i>opacity</i>: 50%</li>
     *   <li><i>color</i>: Black</li>
     *   <li><i>rendering quality</i>: VALUE_BLUR_QUALITY_FAST</li>
     * </ul></p>
     * <p>These properties provide a regular, good looking shadow.</p>
     */
    public ShadowFactory() {
        this(5, 0.5f, Color.BLACK);
    }
    
    /**
     * <p>A shadow factory needs three properties to generate shadows.
     * These properties are:</p> 
     * <ul>
     *   <li><i>size</i>: The size, in pixels, of the shadow. This property also
     *   defines the fuzzyness.</li>
     *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>
     *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be
     *   black only.</li>
     * </ul></p>
     * <p>Besides these properties you can set rendering hints to control the
     * rendering process. The default rendering hints let the factory use the
     * fastest shadow generation algorithm.</p>
     * @param size The size of the shadow in pixels. Defines the fuzziness.
     * @param opacity The opacity of the shadow.
     * @param color The color of the shadow.
     * @see #setRenderingHint(Object, Object)
     */
    public ShadowFactory(final int size, final float opacity, final Color color) {
        hints = new HashMap<Object, Object>();
        hints.put(KEY_BLUR_QUALITY, VALUE_BLUR_QUALITY_FAST);
        
        changeSupport = new PropertyChangeSupport(this);
        setSize(size);
        setOpacity(opacity);
        setColor(color);
    }
    /**
     * <p>Add a PropertyChangeListener to the listener list. The listener is
     * registered for all properties. The same listener object may be added
     * more than once, and will be called as many times as it is added. If
     * <code>listener</code> is null, no exception is thrown and no action
     * is taken.</p> 
     * @param listener the PropertyChangeListener to be added
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.addPropertyChangeListener(listener);
    }
    /**
     * <p>Remove a PropertyChangeListener from the listener list. This removes
     * a PropertyChangeListener that was registered for all properties. If
     * <code>listener</code> was added more than once to the same event source,
     * it will be notified one less time after being removed. If
     * <code>listener</code> is null, or was never added, no exception is thrown
     * and no action is taken.</p>
     * @param listener
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        changeSupport.removePropertyChangeListener(listener);
    }
    /**
     * <p>Maps the specified rendering hint <code>key</code> to the specified
     * <code>value</code> in this <code>SahdowFactory</code> object.</p>
     * @param key The rendering hint key
     * @param value The rendering hint value
     */
    public void setRenderingHint(final Object key, final Object value) {
        hints.put(key, value);
    }
    /**
     * <p>Gets the color used by the factory to generate shadows.</p>
     * @return this factory"s shadow color
     */
    public Color getColor() {
        return color;
    }
    /**
     * <p>Sets the color used by the factory to generate shadows.</p>
     * <p>Consecutive calls to {@link #createShadow} will all use this color
     * until it is set again.</p>
     * <p>If the color provided is null, the previous color will be retained.</p>
     * @param shadowColor the generated shadows color
     */
    public void setColor(final Color shadowColor) {
        if (shadowColor != null) {
            Color oldColor = this.color;
            this.color = shadowColor;
            changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY,
                                             oldColor,
                                             this.color);
        }
    }
    /**
     * <p>Gets the opacity used by the factory to generate shadows.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque.</p>
     * @return this factory"s shadow opacity
     */
    public float getOpacity() {
        return opacity;
    }
    /**
     * <p>Sets the opacity used by the factory to generate shadows.</p>
     * <p>Consecutive calls to {@link #createShadow} will all use this color
     * until it is set again.</p>
     * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully
     * transparent and 1.0f fully opaque. If you provide a value out of these
     * boundaries, it will be restrained to the closest boundary.</p>
     * @param shadowOpacity the generated shadows opacity
     */
    public void setOpacity(final float shadowOpacity) {
        float oldOpacity = this.opacity;
        
        if (shadowOpacity < 0.0) {
            this.opacity = 0.0f;
        } else if (shadowOpacity > 1.0f) {
            this.opacity = 1.0f;
        } else {
            this.opacity = shadowOpacity;
        }
        
        changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY,
                                         new Float(oldOpacity),
                                         new Float(this.opacity));
    }
    /**
     * <p>Gets the size in pixel used by the factory to generate shadows.</p>
     * @return this factory"s shadow size
     */
    public int getSize() {
        return size;
    }
    /**
     * <p>Sets the size, in pixels, used by the factory to generate shadows.</p>
     * <p>The size defines the blur radius applied to the shadow to create the
     * fuzziness.</p>
     * <p>There is virtually no limit to the size but it has an impact on shadow
     * generation performances. The greater this value, the longer it will take 
     * to generate the shadow. Remember the generated shadow image dimensions 
     * are computed as follow:
     * <ul>
     *   <li>new width = original width + 2 * shadow size</li>
     *   <li>new height = original height + 2 * shadow size</li>
     * </ul>
     * The size cannot be negative. If you provide a negative value, the size
     * will be 0 instead.</p>
     * @param shadowSize the generated shadows size in pixels (fuzziness)
     */
    public void setSize(final int shadowSize) {
        int oldSize = this.size;
        
        if (shadowSize < 0) {
            this.size = 0;
        } else {
            this.size = shadowSize;
        }
        
        changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY,
                                         new Integer(oldSize),
                                         new Integer(this.size));
    }
    /**
     * <p>Generates the shadow for a given picture and the current properties
     * of the factory.</p>
     * <p>The generated shadow image dimensions are computed as follow:
     *  <ul>
     *  <li>new width = original width + 2 * shadow size</li>
     *  <li>new height = original height + 2 * shadow size</li>
     * </ul></p>
     * <p>The time taken by a call to this method depends on the size of the
     * shadow, the larger the longer it takes, and on the selected rendering
     * algorithm.</p>
     * @param image the picture from which the shadow must be cast
     * @return the picture containing the shadow of <code>image</code> 
     */
    public BufferedImage createShadow(final BufferedImage image) {
        if (hints.get(KEY_BLUR_QUALITY) == VALUE_BLUR_QUALITY_HIGH) {
            // the high quality algorithm is a 3-pass algorithm
            // it goes through all the pixels of the original picture at least
            // three times to generate the shadow
            // it is easy to understand but very slow
            BufferedImage subject = prepareImage(image);
            BufferedImage shadow = new BufferedImage(subject.getWidth(),
                                                     subject.getHeight(),
                                                     BufferedImage.TYPE_INT_ARGB);
            BufferedImage shadowMask = createShadowMask(subject);
            getLinearBlurOp(size).filter(shadowMask, shadow);
            return shadow;
        }
        // call the fast rendering algorithm
        return createShadowFast(image);
    }
    
    // prepares the picture for the high quality rendering algorithm
    private BufferedImage prepareImage(final BufferedImage image) {
        BufferedImage subject = new BufferedImage(image.getWidth() + size * 2,
                                                  image.getHeight() + size * 2,
                                                  BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = subject.createGraphics();
        g2.drawImage(image, null, size, size);
        g2.dispose();
        return subject;
    }
    // fast rendering algorithm
    // basically applies duplicates the picture and applies a size*size kernel
    // in only one pass.
    // the kernel is simulated by an horizontal and a vertical pass
    // implemented by S&eacute;bastien Petrucci
    private BufferedImage createShadowFast(final BufferedImage src) {
        int shadowSize = this.size;
        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();
        int dstWidth = srcWidth + size;
        int dstHeight = srcHeight + size;
        int left = (shadowSize - 1) >> 1;
        int right = shadowSize - left;
        int yStop = dstHeight - right;
        BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
                                              BufferedImage.TYPE_INT_ARGB);
        int shadowRgb = color.getRGB() & 0x00FFFFFF;
        int[] aHistory = new int[shadowSize];
        int historyIdx;
        int aSum;
        ColorModel srcColorModel = src.getColorModel();
        WritableRaster srcRaster = src.getRaster();
        int[] dstBuffer = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();
        int lastPixelOffset = right * dstWidth;
        float hSumDivider = 1.0f / size;
        float vSumDivider = opacity / size;
        // horizontal pass : extract the alpha mask from the source picture and
        // blur it into the destination picture
        for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {
            // first pixels are empty
            for (historyIdx = 0; historyIdx < shadowSize; ) {
                aHistory[historyIdx++] = 0;
            }
            aSum = 0;
            historyIdx = 0;
            // compute the blur average with pixels from the source image
            for (int srcX = 0; srcX < srcWidth; srcX++) {
                int a = (int) (aSum * hSumDivider); // calculate alpha value
                dstBuffer[dstOffset++] = a << 24;   // store the alpha value only
                                                    // the shadow color will be added in the next pass
                aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
                // extract the new pixel ...
                a = srcColorModel.getAlpha(srcRaster.getDataElements(srcX, srcY, null));
                aHistory[historyIdx] = a;   // ... and store its value into history
                aSum += a;                  // ... and add its value to the sum
                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
            // blur the end of the row - no new pixels to grab
            for (int i = 0; i < shadowSize; i++) {
                int a = (int) (aSum * hSumDivider);
                dstBuffer[dstOffset++] = a << 24;
                // substract the oldest pixel from the sum ... and nothing new to add !
                aSum -= aHistory[historyIdx];
                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
        }
        // vertical pass
        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
            aSum = 0;
            // first pixels are empty
            for (historyIdx = 0; historyIdx < left;) {
                aHistory[historyIdx++] = 0;
            }
            // and then they come from the dstBuffer
            for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
                int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha
                aHistory[historyIdx++] = a;                     // store into history
                aSum += a;                                      // and add to sum
            }
            bufferOffset = x;
            historyIdx = 0;
            // compute the blur average with pixels from the previous pass
            for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {
                int a = (int) (aSum * vSumDivider);             // calculate alpha value
                dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color
                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
                a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...
                aHistory[historyIdx] = a;                               // ... and store its value into history
                aSum += a;                                              // ... and add its value to the sum
                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
            // blur the end of the column - no pixels to grab anymore
            for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {
                int a = (int) (aSum * vSumDivider);
                dstBuffer[bufferOffset] = a << 24 | shadowRgb;
                aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum
                if (++historyIdx >= shadowSize) {
                    historyIdx -= shadowSize;
                }
            }
        }
        return dst;
    }
    // creates the shadow mask for the original picture
    // it colorize all the pixels with the shadow color according to their
    // original transparency
    private BufferedImage createShadowMask(final BufferedImage image) {
        BufferedImage mask = new BufferedImage(image.getWidth(),
                                               image.getHeight(),
                                               BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = mask.createGraphics();
        g2d.drawImage(image, 0, 0, null);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN,
                                                    opacity));
        g2d.setColor(color);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        g2d.dispose();
        return mask;
    }
    // creates a blur convolve operation by generating a kernel of
    // dimensions (size, size).
    private ConvolveOp getLinearBlurOp(final int size) {
        float[] data = new float[size * size];
        float value = 1.0f / (float) (size * size);
        for (int i = 0; i < data.length; i++) {
            data[i] = value;
        }
        return new ConvolveOp(new Kernel(size, size, data));
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

class SplineControlPanel extends JPanel {
    private SplineDisplay display;
    private DropSimulator dropSimulator = new DropSimulator();
    private BouncerSimulator bounceSimulator = new BouncerSimulator();
    
    private int linesCount = 0;
    
    private JLabel labelControl1;
    private JLabel labelControl2;
    private Animator controller;
    
    SplineControlPanel() {
        super(new BorderLayout());
        
        add(buildEquationDisplay(), BorderLayout.CENTER);
        add(buildDebugControls(), BorderLayout.EAST);
    }
    
    private Component buildDebugControls() {
        JButton button;
        JPanel debugPanel = new JPanel(new GridBagLayout());
        
        debugPanel.add(Box.createHorizontalStrut(150),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 0, 0, 0),
                0, 0));
        
//        button = addButton(debugPanel, "Create");
//        button.addActionListener(new ActionListener() {
//            public void actionPerformed(ActionEvent e) {
//                JFileChooser chooser = new JFileChooser(".");
//                int choice = chooser.showSaveDialog(SplineControlPanel.this);
//                if (choice == JFileChooser.CANCEL_OPTION) {
//                    return;
//                }
//                File file = chooser.getSelectedFile();
//                try {
//                    OutputStream out = new FileOutputStream(file);
//                    display.saveAsTemplate(out);
//                    out.close();
//                } catch (FileNotFoundException e1) {
//                } catch (IOException e1) {
//                }
//            }
//        });
        
        addSeparator(debugPanel, "Control Points");
        labelControl1 = addDebugLabel(debugPanel, "Point 1:", formatPoint(display.getControl1()));
        labelControl2 = addDebugLabel(debugPanel, "Point 2:", formatPoint(display.getControl2()));
        button = addButton(debugPanel, "Copy Code");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                NumberFormat formatter = getNumberFormatter();
                Point2D c1 = display.getControl1();
                Point2D c2 = display.getControl2();
                
                StringBuilder code = new StringBuilder();
                code.append("Spline spline = new Spline(");
                code.append(formatter.format(c1.getX())).append("f, ");
                code.append(formatter.format(c1.getY())).append("f, ");
                code.append(formatter.format(c2.getX())).append("f, ");
                code.append(formatter.format(c2.getY())).append("f);");
                
                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                clipboard.setContents(new StringSelection(code.toString()), null);
            }
        });
        
        addEmptySpace(debugPanel, 6);
        addSeparator(debugPanel, "Animation");
        
        button = addButton(debugPanel, "Play Sample");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                startSampleAnimation();
            }
        });
        
        addEmptySpace(debugPanel, 6);
        addSeparator(debugPanel, "Templates");
        debugPanel.add(createTemplates(),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.CENTER,
                GridBagConstraints.NONE,
                new Insets(0, 0, 0, 0),
                0, 0));
        
        addEmptySpace(debugPanel, 6);
        
        debugPanel.add(Box.createVerticalGlue(),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 1.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 0, 0, 0),
                0, 0));
        
        JPanel wrapper = new JPanel(new BorderLayout());
        wrapper.add(new JSeparator(JSeparator.VERTICAL), BorderLayout.WEST);
        wrapper.add(debugPanel);
        wrapper.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 6));
        
        return wrapper;
    }
    
    private Component createTemplates() {
        DefaultListModel model = new DefaultListModel();
        model.addElement(createTemplate(0.0, 0.0, 1.0, 1.0));
        model.addElement(createTemplate(0.0, 1.0, 0.0, 1.0));
        model.addElement(createTemplate(0.0, 1.0, 1.0, 1.0));
        model.addElement(createTemplate(0.0, 1.0, 1.0, 0.0));
        model.addElement(createTemplate(1.0, 0.0, 0.0, 1.0));
        model.addElement(createTemplate(1.0, 0.0, 1.0, 1.0));
        model.addElement(createTemplate(1.0, 0.0, 1.0, 0.0));
        
        JList list = new JList(model);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setCellRenderer(new TemplateCellRenderer());
        list.addListSelectionListener(new TemplateSelectionHandler());
        
        JScrollPane pane = new JScrollPane(list);
        pane.getViewport().setPreferredSize(new Dimension(98, 97 * 3));
        return pane;
    }
    
    private JButton addButton(JPanel debugPanel, String label) {
        JButton button;
        debugPanel.add(button = new JButton(label),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.CENTER,
                GridBagConstraints.NONE,
                new Insets(3, 0, 0, 0),
                0, 0));
        return button;
    }
    
    private String formatPoint(Point2D p) {
        NumberFormat formatter = getNumberFormatter();
        return "" + formatter.format(p.getX()) + ", " + formatter.format(p.getY());
    }
    
    private Component buildEquationDisplay() {
        JPanel panel = new JPanel(new BorderLayout());
        
        display = new SplineDisplay();
        display.addPropertyChangeListener("control1", new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                labelControl1.setText(formatPoint(display.getControl1()));
            }
        });
        display.addPropertyChangeListener("control2", new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                labelControl2.setText(formatPoint(display.getControl2()));
            }
        });
        
        panel.add(display, BorderLayout.NORTH);
        
        JPanel wrapper = new JPanel(new GridBagLayout());
        wrapper.add(new JSeparator(),
                new GridBagConstraints(0, 0,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(0, 0, 0, 0),
                0, 0));
        wrapper.add(bounceSimulator,
                new GridBagConstraints(0, 1,
                1, 1,
                1.0, 1.0,
                GridBagConstraints.CENTER,
                GridBagConstraints.BOTH,
                new Insets(0, 0, 0, 0),
                0, 0));
        wrapper.add(dropSimulator,
                new GridBagConstraints(1, 1,
                1, 1,
                1.0, 1.0,
                GridBagConstraints.CENTER,
                GridBagConstraints.BOTH,
                new Insets(0, 0, 0, 0),
                0, 0));
        panel.add(wrapper, BorderLayout.CENTER);
        
        return panel;
    }
    
    private JLabel addDebugLabel(JPanel panel, String label, String value) {
        JLabel labelComponent = new JLabel(label);
        panel.add(labelComponent,
                new GridBagConstraints(0, linesCount,
                1, 1,
                0.5, 0.0,
                GridBagConstraints.LINE_END,
                GridBagConstraints.NONE,
                new Insets(0, 6, 0, 0),
                0, 0));
        labelComponent = new JLabel(value);
        panel.add(labelComponent,
                new GridBagConstraints(1, linesCount++,
                1, 1,
                0.5, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 6, 0, 0),
                0, 0));
        return labelComponent;
    }
    
    private void addEmptySpace(JPanel panel, int size) {
        panel.add(Box.createVerticalStrut(size),
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.CENTER,
                GridBagConstraints.VERTICAL,
                new Insets(6, 0, 0, 0),
                0, 0));
    }
    
    private void addSeparator(JPanel panel, String label) {
        JPanel innerPanel = new JPanel(new GridBagLayout());
        innerPanel.add(new JLabel(label),
                new GridBagConstraints(0, 0,
                1, 1,
                0.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.NONE,
                new Insets(0, 0, 0, 0),
                0, 0));
        innerPanel.add(new JSeparator(),
                new GridBagConstraints(1, 0,
                1, 1,
                0.9, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(0, 6, 0, 6),
                0, 0));
        panel.add(innerPanel,
                new GridBagConstraints(0, linesCount++,
                2, 1,
                1.0, 0.0,
                GridBagConstraints.LINE_START,
                GridBagConstraints.HORIZONTAL,
                new Insets(6, 6, 6, 0),
                0, 0));
    }
    
    private void startSampleAnimation() {
        if (controller != null && controller.isRunning()) {
            controller.stop();
        }
        
        Point2D control1 = display.getControl1();
        Point2D control2 = display.getControl2();
        Interpolator splines = new SplineInterpolator((float) control1.getX(), 
                (float) control1.getY(),
                (float) control2.getX(), (float) control2.getY());
        KeyTimes times = new KeyTimes(0.0f, 1.0f);
        KeyValues values = KeyValues.create(0.0, 1.0);
        KeyFrames frames = new KeyFrames(values,times, splines);
        
        PropertySetter dropModifier = new PropertySetter(dropSimulator,
                "time", frames);
        PropertySetter bounceModifier = new PropertySetter(bounceSimulator,
                "time", frames);
        
        controller = new Animator(1000, 4, RepeatBehavior.REVERSE, dropModifier);
        controller.setResolution(10);
        controller.addTarget(bounceModifier);
        
        controller.start();
    }
    
    private Evaluator point2dInterpolator = new Point2DNonLinearInterpolator();
    
    private class Point2DNonLinearInterpolator extends Evaluator<Point2D> {
        private Point2D value;
        public Point2D evaluate(Point2D v0, Point2D v1,
                float fraction) {
            Point2D value = (Point2D)v0.clone();
            if (v0 != v1) {
                double x = value.getX();
                x += (v1.getX() - v0.getX()) * fraction;
                double y = value.getY();
                y += (v1.getY() - v0.getY()) * fraction;
                value.setLocation(x, y);
            } else {
                value.setLocation(v0.getX(), v0.getY());
            }
            return value;
        }
    }
    
    private class TemplateSelectionHandler implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
            if (e.getValueIsAdjusting()) {
                return;
            }
            
            JList list = (JList) e.getSource();
            Template template = (Template) list.getSelectedValue();
            if (template != null) {
                if (controller != null && controller.isRunning()) {
                    controller.stop();
                }
                
                controller = new Animator(300,
                        new PropertySetter(display, "control1",
                        point2dInterpolator, display.getControl1(),
                        template.getControl1()));
                controller.setResolution(10);
                controller.addTarget(new PropertySetter(display, "control2",
                        point2dInterpolator, display.getControl2(),
                        template.getControl2()));
                
                controller.start();
            }
        }
    }
    
    private static NumberFormat getNumberFormatter() {
        NumberFormat formatter = NumberFormat.getInstance(Locale.ENGLISH);
        formatter.setMinimumFractionDigits(2);
        formatter.setMaximumFractionDigits(2);
        return formatter;
    }
    
    private static Template createTemplate(double x1, double y1, double x2, double y2) {
        return new Template(new Point2D.Double(x1, y1),
                new Point2D.Double(x2, y2));
    }
    
    private static class TemplateCellRenderer extends DefaultListCellRenderer {
        private boolean isSelected;
        
        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index,
                boolean isSelected, boolean cellHasFocus) {
            Template template = (Template) value;
            this.setBackground(Color.WHITE);
            this.setIcon(new ImageIcon(template.getImage()));
            this.isSelected = isSelected;
            return this;
        }
        
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            
            if (isSelected) {
                g.setColor(new Color(0.0f, 0.0f, 0.7f, 0.1f));
                g.fillRect(0, 0, getWidth(), getHeight());
            }
        }
    }
    
    private static class Template {
        private Point2D control1;
        private Point2D control2;
        private Image image;
        
        public Template(Point2D control1, Point2D control2) {
            this.control1 = control1;
            this.control2 = control2;
        }
        
        public Point2D getControl1() {
            return control1;
        }
        
        public Point2D getControl2() {
            return control2;
        }
        
        public Image getImage() {
            if (image == null) {
                NumberFormat formatter = getNumberFormatter();
                
                String name = "";
                name += formatter.format(control1.getX()) + "-" + formatter.format(control1.getY());
                name += "-";
                name += formatter.format(control2.getX()) + "-" + formatter.format(control2.getY());
                
                try {
                    image = ImageIO.read(getClass().getResourceAsStream(name + ".png"));
                } catch (IOException e) {
                }
            }
            
            return image;
        }
    }
}
/**
 * Copyright (c) 2006, Sun Microsystems, Inc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
class SplineDisplay extends EquationDisplay {
    private static final double CONTROL_POINT_SIZE = 12.0;
    private Point2D control1 = new Point2D.Double(0.25, 0.75);
    private Point2D control2 = new Point2D.Double(0.75, 0.25);
    
    private Point2D selected = null;
    private Point dragStart = null;
    
    private boolean isSaving = false;
    
    private PropertyChangeSupport support;
    
    SplineDisplay() {
        super(0.0, 0.0,
              -0.1, 1.1, -0.1, 1.1,
              0.2, 6,
              0.2, 6);
        
        setEnabled(false);
        
        addMouseMotionListener(new ControlPointsHandler());
        addMouseListener(new SelectionHandler());
        
        support = new PropertyChangeSupport(this);
    }
    
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        support.addPropertyChangeListener(propertyName, listener);
    }
    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        support.removePropertyChangeListener(propertyName, listener);
    }
    public Point2D getControl1() {
        return (Point2D) control1.clone();
    }
    public Point2D getControl2() {
        return (Point2D) control2.clone();
    }
    
    public void setControl1(Point2D control1) {
        support.firePropertyChange("control1",
                                   (Point2D) this.control1.clone(),
                                   (Point2D) control1.clone());
        this.control1 = (Point2D) control1.clone();
        repaint();
    }
    public void setControl2(Point2D control2) {
        support.firePropertyChange("control2",
                                   (Point2D) this.control2.clone(),
                                   (Point2D) control2.clone());
        this.control2 = (Point2D) control2.clone();
        repaint();
    }
    
    synchronized void saveAsTemplate(OutputStream out) {
        BufferedImage image = Java2dHelper.createCompatibleImage(getWidth(), getHeight());
        Graphics g = image.getGraphics();
        isSaving = true;
        setDrawText(false);
        paint(g);
        setDrawText(true);
        isSaving = false;
        g.dispose();
        
        BufferedImage subImage = image.getSubimage((int) xPositionToPixel(0.0),
                                                   (int) yPositionToPixel(1.0),
                                                   (int) (xPositionToPixel(1.0) - xPositionToPixel(0.0)) + 1,
                                                   (int) (yPositionToPixel(0.0) - yPositionToPixel(1.0)) + 1);
        
        try {
            ImageIO.write(subImage, "PNG", out);
        } catch (IOException e) {
        }
        
        image.flush();
        subImage = null;
        image = null;
    }
    @Override
    protected void paintInformation(Graphics2D g2) {
        if (!isSaving) {
            paintControlPoints(g2);
        }
        paintSpline(g2);
    }
    private void paintControlPoints(Graphics2D g2) {
        paintControlPoint(g2, control1);
        paintControlPoint(g2, control2);
    }
        
    private void paintControlPoint(Graphics2D g2, Point2D control) {
        double origin_x = xPositionToPixel(control.getX());
        double origin_y = yPositionToPixel(control.getY());
        double pos = control == control1 ? 0.0 : 1.0;
        
        Ellipse2D outer = getDraggableArea(control);
        Ellipse2D inner = new Ellipse2D.Double(origin_x + 2.0 - CONTROL_POINT_SIZE / 2.0,
                                               origin_y + 2.0 - CONTROL_POINT_SIZE / 2.0,
                                               8.0, 8.0);
        
        Area circle = new Area(outer);
        circle.subtract(new Area(inner));
        
        Stroke stroke = g2.getStroke();
        g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
                                     5, new float[] { 5, 5 }, 0));
        g2.setColor(new Color(1.0f, 0.0f, 0.0f, 0.4f));
        g2.drawLine(0, (int) origin_y, (int) origin_x, (int) origin_y);
        g2.drawLine((int) origin_x, (int) origin_y, (int) origin_x, getHeight());
        g2.setStroke(stroke);
        
        if (selected == control) {
            g2.setColor(new Color(1.0f, 1.0f, 1.0f, 1.0f));
        } else {
            g2.setColor(new Color(0.8f, 0.8f, 0.8f, 0.6f));
        }
        g2.fill(inner);
        
        g2.setColor(new Color(0.0f, 0.0f, 0.5f, 0.5f));
        g2.fill(circle);
        
        g2.drawLine((int) origin_x, (int) origin_y,
                    (int) xPositionToPixel(pos), (int) yPositionToPixel(pos));
    }
    private Ellipse2D getDraggableArea(Point2D control) {
        Ellipse2D outer = new Ellipse2D.Double(xPositionToPixel(control.getX()) - CONTROL_POINT_SIZE / 2.0,
                                               yPositionToPixel(control.getY()) - CONTROL_POINT_SIZE / 2.0,
                                               CONTROL_POINT_SIZE, CONTROL_POINT_SIZE);
        return outer;
    }
    private void paintSpline(Graphics2D g2) {
        CubicCurve2D spline = new CubicCurve2D.Double(xPositionToPixel(0.0), yPositionToPixel(0.0),
                                                      xPositionToPixel(control1.getX()),
                                                      yPositionToPixel(control1.getY()),
                                                      xPositionToPixel(control2.getX()),
                                                      yPositionToPixel(control2.getY()),
                                                      xPositionToPixel(1.0), yPositionToPixel(1.0));
        g2.setColor(new Color(0.0f, 0.3f, 0.0f, 1.0f));
        g2.draw(spline);
    }
    
    private void resetSelection() {
        Point2D oldSelected = selected;
        selected = null;
        
        if (oldSelected != null) {
            Rectangle bounds = getDraggableArea(oldSelected).getBounds();
            repaint(bounds.x, bounds.y, bounds.width, bounds.height);
        }
    }
    
    private class ControlPointsHandler extends MouseMotionAdapter {
        @Override
        public void mouseMoved(MouseEvent e) {
            Ellipse2D area1 = getDraggableArea(control1);
            Ellipse2D area2 = getDraggableArea(control2);
            
            if (area1.contains(e.getPoint()) || area2.contains(e.getPoint())) {
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
            } else {
                setCursor(Cursor.getDefaultCursor());
            }
        }
        @Override
        public void mouseDragged(MouseEvent e) {
            if (selected == null) {
                return;
            }
            
            Point dragEnd = e.getPoint();
            double distance = xPixelToPosition(dragEnd.getX()) -
                              xPixelToPosition(dragStart.getX());
            double x = selected.getX() + distance;
            if (x < 0.0) {
                x = 0.0;
            } else if (x > 1.0) {
                x = 1.0;
            }
            
            distance = yPixelToPosition(dragEnd.getY()) -
                       yPixelToPosition(dragStart.getY());
            double y = selected.getY() + distance;
            if (y < 0.0) {
                y = 0.0;
            } else if (y > 1.0) {
                y = 1.0;
            }
            Point2D selectedCopy = (Point2D) selected.clone();
            selected.setLocation(x, y);
            support.firePropertyChange("control" + (selected == control1 ? "1" : "2"),
                                       selectedCopy, (Point2D) selected.clone());
            
            repaint();
            double xPos = xPixelToPosition(dragEnd.getX());
            double yPos = -yPixelToPosition(dragEnd.getY());
            
            if (xPos >= 0.0 && xPos <= 1.0) {
                dragStart.setLocation(dragEnd.getX(), dragStart.getY());
            }
            if (yPos >= 0.0 && yPos <= 1.0) {
                dragStart.setLocation(dragStart.getX(), dragEnd.getY());
            }
        }
    }
    
    private class SelectionHandler extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            Ellipse2D area1 = getDraggableArea(control1);
            Ellipse2D area2 = getDraggableArea(control2);
            
            if (area1.contains(e.getPoint())) {
                selected = control1;
                dragStart = e.getPoint();
                
                Rectangle bounds = area1.getBounds();
                repaint(bounds.x, bounds.y, bounds.width, bounds.height);
            } else if (area2.contains(e.getPoint())) {
                selected = control2;
                dragStart = e.getPoint();
                
                Rectangle bounds = area2.getBounds();
                repaint(bounds.x, bounds.y, bounds.width, bounds.height);
            } else {
                resetSelection();
            }
        }
        @Override
        public void mouseReleased(MouseEvent e) {
            resetSelection();
        }
    }
}





Trace curve

/*************************************************************************
*                                                                        *
*  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/            *
*                                                                        *
*************************************************************************/

// The ParametricCurve applet is a configurable applet that displays a parametric
// curve defined by two functions of a parameter t.  There can be a "Tracer" button
// on the applet.  When the user clicks this button, a crosshair is moved along
// the curve from beginning to end.
import java.awt.*;
import java.applet.Applet;
import java.util.StringTokenizer;
import java.util.Hashtable;
import edu.hws.jcm.draw.*;
import edu.hws.jcm.data.*;
import edu.hws.jcm.functions.*;
import edu.hws.jcm.awt.*;

public class Parametric extends GenericGraphApplet {
   // Declare some private variables that are created in one method in
   // this class and used in a second method.
   private Function xFunc,yFunc;    // The functions that are graphed.
   private ParametricCurve graph;   // The graph of the function.
   private Animator tracer;         // for tracing the curve by moving a crosshair along it
   private Crosshair crosshair;     // Crosshair used for tracing the graph
   private VariableInput tMin,tMax; // for inputting limits on t
   private VariableInput tIntervals; // for inutting the number of intervals into which the t-range is divided
   private ExpressionInput functionInput2;  // for inputting yFunc; xFunc is input in functionInput
   
   protected void setUpParameterDefaults() {
      parameterDefaults = new Hashtable();
      parameterDefaults.put("TwoLimitsColumns", "yes");
      parameterDefaults.put("Variable","t");
      parameterDefaults.put("XName","x");  // we need this so that xmin and xmax boxes are labeled correctly;
                                           // without it, the name would come from the variable name, t, instead of x
      parameterDefaults.put("FunctionLabel", "  " + getParameter("XName") + "(" + getParameter("Variable") + ") = ");
      parameterDefaults.put("FunctionLabel2", "  " + getParameter("YName","y") + "(" + getParameter("Variable") + ") = ");
   }
   protected void setUpCanvas() {  // Override this to add more stuff to the canvas.
   
      super.setUpCanvas();  // Do the common setup: Add the axes and
      // When setUpCanvas is called, the function inputs already exist, if they are
      // to be used, since they are created in setUpBopttomPanel(), which is called
      // before setUpCanvas().  If functionInput exists, add a graph of the functions
      // from functionInput and functionInput2 to the canvas.  If not, create a graph 
      // of the functions specified by the parameters named "Function" and "Function2".
      
      if (functionInput != null) {
         xFunc = functionInput.getFunction(xVar);
         yFunc = functionInput2.getFunction(xVar);
      }
      else {
         String xFuncDef = " cos(" + xVar.getName() + ") + cos(3*" + xVar.getName() + ")";
         String yFuncDef = " sin(4*" + xVar.getName() + ") - sin(2*" + xVar.getName() + ")";
         xFuncDef = getParameter("Function", xFuncDef);
         yFuncDef = getParameter("Function2", yFuncDef);
         Function f = new SimpleFunction( parser.parse(xFuncDef), xVar );
         xFunc = new WrapperFunction(f);
         f = new SimpleFunction( parser.parse(yFuncDef), xVar );
         yFunc = new WrapperFunction(f);
      }
      graph = new ParametricCurve(xFunc,yFunc);
      Color color = getColorParam("CurveColor");
      if (color != null)
         graph.setColor(color);
         
      // if inputs are desired to control the parameter on the curve, set them up here
         
      if  ("no".equalsIgnoreCase(getParameter("UseParamInputs","yes"))) {
         tMin = new VariableInput(xVar.getName() + "Start",getParameter("ParameterMin","-2"));
         tMax = new VariableInput(xVar.getName() + "End",getParameter("ParameterMax","2"));
         tIntervals = new VariableInput("Intervals", getParameter("Intervals","200"));
         tIntervals.setInputStyle(VariableInput.INTEGER);
         tIntervals.setMin(1);
         tIntervals.setMax(5000);
         tMin.setOnUserAction(mainController);
         tMax.setOnUserAction(mainController);
         tIntervals.setOnUserAction(mainController);
         graph.setTMin(tMin);
         graph.setTMax(tMax);
         graph.setIntervals(tIntervals);
         if (limitsPanel != null) {
               // componets will be added to limitsPanel in setUpLimitsPanel()
            mainController.add(tMin);  // This is not done automatically, since they are in a limits panel  
            mainController.add(tMax);
            mainController.add(tIntervals);
         }
         else {
            JCMPanel ap = new JCMPanel(9,0);
            ap.setBackground(getColorParam("PanelBackground", Color.lightGray));
            ap.add(new Label(tMin.getName()));
            ap.add(tMin);
            ap.add(new Label());
            ap.add(new Label(tMax.getName()));
            ap.add(tMax);
            ap.add(new Label());
            ap.add(new Label(tIntervals.getName()));
            ap.add(tIntervals);
            ap.add(new Label());
            mainPanel.add(ap,BorderLayout.EAST);
         }
      }
      else {
         try {
            graph.setTMin( new Constant((new Double(getParameter("ParameterMin","-2"))).doubleValue()) );
            graph.setTMax( new Constant((new Double(getParameter("ParameterMax","2"))).doubleValue()) );
            graph.setIntervals( new Constant((new Double(getParameter("Intervals","25"))).doubleValue()) );
         }
         catch (NumberFormatException e) {
         }
      }
      // If the applet is configured to have a tracer button, create it and add the crosshair to the canvas
      if (! "no".equalsIgnoreCase( getParameter("UseTracer","yes") ) ) {
         tracer = new Animator();
         tracer.setMin(graph.getTMin());
         tracer.setMax(graph.getTMax());
         tracer.setUndefinedWhenNotRunning(true);
         tracer.setStartButtonName("Trace Curve!");
         double[] d = getNumericParam("TracerIntervals");
         int ints;
         if (d == null || d.length != 1)
            ints = 100;
         else
            ints = (int)Math.round(d[0]);
         if (ints <= 0)
            tracer.setIntervals(graph.getIntervals());
         else
            tracer.setIntervals(ints);
         Variable v = tracer.getValueAsVariable();
         crosshair = new Crosshair( new ValueMath(xFunc,v), new ValueMath(yFunc,v) );
         crosshair.setLineWidth(3);
         crosshair.setColor(getColorParam("CrosshairColor",Color.gray));
         canvas.add(crosshair);
            
         if (inputPanel != null) {
            inputPanel.add(tracer,BorderLayout.WEST);
         }
         else if (limitsPanel == null) {
            Panel p = new Panel();
            p.add(tracer);
            mainPanel.add(p,BorderLayout.SOUTH);
         }
         // if inputPanel is null but limitsPanel is not null, the tracer will be
         //    added to the limit control panel in setUpLimitsPanel()
      }
      canvas.add(graph);  // Finally, add the graph to the canvas.
   } // end setUpCanvas()
   
   protected void setUpLimitsPanel() {
      super.setUpLimitsPanel();
      if (limitsPanel != null && tMin != null) {  // add parameter inputs to limits panel
          limitsPanel.addComponentPair(tMin,tMax);
          limitsPanel.addComponent(tIntervals);
      }
      if (inputPanel == null && tracer != null && limitsPanel != null)  {
          limitsPanel.addComponent(tracer);
      }
   }
 
   
   protected void setUpBottomPanel() { // override this to make a panel containing inputs for two functions
      if ( ! "no".equalsIgnoreCase(getParameter("UseFunctionInput", "yes")) ) {
      
         inputPanel = new JCMPanel();
         inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) );
         Panel in = new JCMPanel(2,1);
         inputPanel.add(in,BorderLayout.CENTER);
       
         if ( ! "no".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) {
            String cname = getParameter("ComputeButtonName", "New Functions");
            computeButton = new Button(cname);
            inputPanel.add(computeButton, BorderLayout.EAST); 
            computeButton.addActionListener(this);
         }
         String varName = getParameter("Variable");
         String def = getParameter("Function");
         if (def == null)
            def = "cos(" + varName + ") + cos(3*" + varName + ")";
         functionInput = new ExpressionInput(def,parser);
         String label = getParameter("FunctionLabel");
         if ("none".equalsIgnoreCase(label))
            in.add(functionInput);
         else {
            Panel p = new JCMPanel();
            p.add(functionInput,BorderLayout.CENTER);
            p.add( new Label(label), BorderLayout.WEST );
            in.add(p);
         }
         
         def = getParameter("Function2");
         if (def == null)
            def = "sin(4*" + varName + ") - sin(2*" + varName + ")";
         functionInput2 = new ExpressionInput(def,parser);
         label = getParameter("FunctionLabel2");
         if ("none".equalsIgnoreCase(label))
            in.add(functionInput2);
         else {
            Panel p = new JCMPanel();
            p.add(functionInput2,BorderLayout.CENTER);
            p.add( new Label(label), BorderLayout.WEST );
            in.add(p);
         }
         
         mainPanel.add(inputPanel, BorderLayout.SOUTH);
         functionInput.setOnUserAction(mainController);
         functionInput2.setOnUserAction(mainController);
      }
   }
      
   protected void setUpMainPanel() { // Override to set up controller for tracer, if there is one
   
      super.setUpMainPanel(); // Do the common setup
      if ( tracer == null ) {
         return;  // If the applet is not configured to use a trace button, there is nothing to do.
      }
      Controller traceController = new Controller();  // A controler that will only recompute the crosshair position
      traceController.add(tracer);
      traceController.add(crosshair);
      tracer.setOnChange(traceController);
   } // end setUpMainPanel()
   
   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 Parametric applet, the example string should contain
         // two expression that defines the curve to be graphed, separated 
         // by a semicolon.  This can optionally
         // be followed by another semicolon and a list of four to seven 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
         // next three numbers specify the minimum value for the parameter, the
         // maximum value, and the number of intervals into which the range of
         // parameter values is divided.
         
      if (tracer != null)
         tracer.stop();
         
      int pos = example.indexOf(";");
      if (pos == -1)
         return; // illegal example -- must have two functions
      String example2 = example.substring(pos+1);
      example = example.substring(0,pos);
      pos = example2.indexOf(";");   
      
         
      double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
      if (pos > 0) { 
               // Get limits from example2 text.
         String nums = example2.substring(pos+1);
         example2 = example2.substring(0,pos);
         StringTokenizer toks = new StringTokenizer(nums, " ,");
         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.hasMoreTokens()) {
            try {
               double d = (new Double(toks.nextToken())).doubleValue();
               if (tMin == null) {
                  graph.setTMin(new Constant(d));
                  if (tracer != null)
                     tracer.setMin(d);
               }
               else
                  tMin.setVal(d);
            }
            catch (NumberFormatException e) {
            }
         }
         if (toks.hasMoreTokens()) {
            try {
               double d = (new Double(toks.nextToken())).doubleValue();
               if (tMax == null) {
                  graph.setTMax(new Constant(d));
                  if (tracer != null)
                     tracer.setMax(d);
               }
               else
                  tMax.setVal(d);
            }
            catch (NumberFormatException e) {
            }
         }
         if (toks.hasMoreTokens()) {
            try {
               int d = (int)Math.round((new Double(toks.nextToken())).doubleValue());
               if (tIntervals == null) {
                  if (tracer != null && tracer.getIntervals() == graph.getIntervals())
                     tracer.setIntervals(d);
                  graph.setIntervals(new Constant(d));
               }
               else
                  tIntervals.setVal(d);
            }
            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);
         functionInput2.setText(example2);
      }
      else { 
           // If there is no user input, set the function in the graph directly.
         try {
            Function f = new SimpleFunction( parser.parse(example), xVar );
            ((WrapperFunction)xFunc).setFunction(f);
            Function g = new SimpleFunction( parser.parse(example2), xVar );
            ((WrapperFunction)yFunc).setFunction(g);
         }
         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();
      mainController.rupute();
      
   } // end doLoadExample()
   
   public void stop() {  // stop animator when applet is stopped
      if (tracer != null)
         tracer.stop();
      super.stop();
   }
 
      public static void main(String[] a){
         javax.swing.JFrame f = new javax.swing.JFrame();
         Applet app = new Parametric();
         app.init();
         
         f.getContentPane().add (app);
         f.pack();
         f.setSize (new Dimension (500, 500));
         f.setVisible(true);
      }   

} // end class Parametric





Zoom interaction, Text background color, and the effect of transparency

   <source lang="java">

import java.awt.*; import javax.swing.*; import no.geosoft.cc.graphics.*;

/**

* G demo program. Demonstrates:
*
*
    *
  • Custom world extent *
  • Zoom interaction *
  • Text background color *
  • The effect of transparency *
  • Annotation layout algorithm *
* 
* @author