Subversion Repositories wimsdev

Rev

Blame | Last modification | View Log | RSS feed

  1. /*************************************************************************
  2. *                                                                        *
  3. *  This source code file, and compiled classes derived from it, can      *
  4. *  be used and distributed without restriction, including for commercial *
  5. *  use.  (Attribution is not required but is appreciated.)               *
  6. *                                                                        *
  7. *   David J. Eck                                                         *
  8. *   Department of Mathematics and Computer Science                       *
  9. *   Hobart and William Smith Colleges                                    *
  10. *   Geneva, New York 14456,   USA                                        *
  11. *   Email: eck@hws.edu          WWW: http://math.hws.edu/eck/            *
  12. *                                                                        *
  13. *************************************************************************/
  14.  
  15.  
  16.  
  17. import java.awt.*;
  18. import java.applet.Applet;
  19. import java.util.*;
  20. import edu.hws.jcm.draw.*;
  21. import edu.hws.jcm.data.*;
  22. import edu.hws.jcm.functions.*;
  23. import edu.hws.jcm.awt.*;
  24.  
  25. // The MultiApplet can display the graphs of several functions, in different colors.
  26. // By default, there is only one function, but you can configure the applet to
  27. // use more than one function with applet params.
  28. // The definitions of these functions can, optionally, use parameters whose
  29. // values are controled by sliders at the bottom of the applet.  
  30.  
  31. public class MultiGraph extends GenericGraphApplet {
  32.  
  33.  
  34.    private Vector sliders;  // Elements of this vector are the VariableSlider
  35.                             //   objects that represent the parameter values.
  36.                             //   The sliders are created in the setUpParser() method.
  37.                            
  38.    private ExprIn[] inputs;  // The function input boxes (or null if inputs aren't used)
  39.    private Graph1D[] graphs; // The graphs of the functions, in the case function input boxes are NOT used
  40.    private int functionCt;   // Number of functions -- size of inputs or graphs array
  41.    
  42.    private Color[] graphColors = { Color.magenta, new Color(0,180,0),
  43.                            Color.red, new Color(0,200,200),
  44.                            Color.orange, Color.gray, Color.blue, Color.pink };
  45.  
  46.  
  47.    private static class ColorPatch extends Canvas {
  48.           // a canvas with a preferred size
  49.       ColorPatch(Color c) {
  50.          setBackground(c);
  51.       }
  52.       public Dimension getPreferredSize() {
  53.          return new Dimension(25,10);
  54.       }
  55.       public void paint(Graphics g) {
  56.          g.drawRect(0,0,getSize().width-1,getSize().height-1);
  57.       }
  58.    }
  59.    
  60.    private static class ExprIn extends ExpressionInput {
  61.             // Doesn't throw an error if empty, just sets function in graph to null
  62.       Graph1D graph;  // Graph associated with this function input.
  63.       Function func;  // The function of x defined by this graph.
  64.       ExprIn(String definition, Parser p, Graph1D g, Variable v) {
  65.          super(definition,p);
  66.          graph = g;
  67.          func = getFunction(v);
  68.          if (definition.trim().length() > 0)
  69.             graph.setFunction(func);
  70.       }
  71.       public void checkInput() { // (will be called during constructor -- hence the funny bit with checking if graphe is null)
  72.          if (!hasChanged)
  73.             return;
  74.          String text = getText().trim();
  75.          if (text.length() == 0) {  // set graph's function to null so it doesn't have to do any computations.
  76.             if (graph != null)
  77.                graph.setFunction(null);
  78.             hasChanged = false;
  79.          }
  80.          else {
  81.             super.checkInput();
  82.             if (graph != null)
  83.                graph.setFunction(func);
  84.          }
  85.       }
  86.    }
  87.  
  88.    protected void setUpParser() {  // Override this to add VariableSliders to parser.
  89.    
  90.       // Get the data for any sliders from applet params named "Parameter", "Parameter1", ...
  91.       // The sliders are created and the variables are added to the parser by the
  92.       // addParameter() method, which is defined below.
  93.      
  94.       sliders = new Vector();
  95.       int ct = 0;
  96.       String param = getParameter("Parameter");
  97.       if (param == null) {
  98.          ct++;
  99.          param = getParameter("Parameter" + ct);
  100.       }
  101.       while (true) {
  102.          if (param == null)
  103.             break;
  104.          addParameter(param);
  105.          ct++;
  106.          param = getParameter("Parameter" + ct);
  107.       }
  108.      
  109.       super.setUpParser();  // Call this last so function definitions
  110.                             // in applet params can use the parameter names
  111.                                                        
  112.    } // end setUpParser()
  113.    
  114.  
  115.  
  116.    private void addParameter(String data) {
  117.          // Create a VariableSlider from the information in name and add it to the
  118.          // Vector of sliders.  The data must contain the name of the variable
  119.          // associated with the slider.  The name can be followed by a ";" and up to
  120.          // three numbers.  (If there is no ";", a space after the name will do.)
  121.          // The numbers can be separated by commas, spaces, or tabs.  The first
  122.          // number gives the minimum value on the slider, the second gives the maximum,
  123.          // and the third gives the initial value of the slider variable.
  124.  
  125.       double min = -5, max = 5, val = 0;  // min, max, and value for slider
  126.  
  127.       data = data.trim();
  128.       int pos = data.indexOf(';');
  129.       if (pos < 0)
  130.          pos = data.indexOf(' ');
  131.          
  132.       String name; //  The name of the parameter
  133.  
  134.       if (pos < 0) {
  135.             // If there is no space or ";", the data is just the name of the variable.
  136.          name = data;
  137.       }
  138.       else {
  139.             // Get the name from the front of the data, then look for min, max, and val.
  140.           String nums = data.substring(pos+1);
  141.           name = data.substring(0,pos).trim();
  142.           StringTokenizer toks = new StringTokenizer(nums," ,\t");
  143.           try {
  144.              if (toks.hasMoreElements())
  145.                  min = (new Double(toks.nextToken())).doubleValue();
  146.              if (toks.hasMoreElements())
  147.                  max = (new Double(toks.nextToken())).doubleValue();
  148.              if (toks.hasMoreElements())
  149.                  val = (new Double(toks.nextToken())).doubleValue();
  150.           }
  151.           catch (NumberFormatException e) {
  152.              min = -5;
  153.              max = 5;
  154.              val = 0;
  155.           }
  156.       }
  157.      
  158.       // Create the slider, adding the associated variable to the parser, and set its value.
  159.      
  160.       VariableSlider slide = new VariableSlider(name, new Constant(min), new Constant(max), parser);
  161.       slide.setVal(val);
  162.      
  163.       sliders.addElement(slide);  // Save the slider in the array of sliders for later use.
  164.      
  165.    } // end setUpParser();
  166.    
  167.    
  168.    private void getColors() { // get graph colors from color parameters, if any.
  169.      
  170.       Vector vec = new Vector();
  171.       int ct = 0;
  172.       Color c = getColorParam("GraphColor");
  173.       if (c == null) {
  174.          ct++;
  175.          c = getColorParam("GraphColor" + ct);
  176.       }
  177.       while (true) {
  178.          if (c == null)
  179.             break;
  180.          vec.addElement(c);
  181.          ct++;
  182.          c = getColorParam("GraphColor" + ct);
  183.       }
  184.       if (vec.size() > 0) {
  185.          graphColors = new Color[vec.size()];
  186.          for (int i = 0; i < vec.size(); i++)
  187.             graphColors[i] = (Color)vec.elementAt(i);
  188.       }
  189.    }
  190.    
  191.    private Vector getFunctions() {  // Read applet parms "Function", "Funcion1", ...
  192.                                     // Return a vector containing the function definition strings
  193.       Vector functions = new Vector();
  194.       int ct = 0;
  195.       String c = getParameter("Function");
  196.       if (c == null) {
  197.          ct++;
  198.          c = getParameter("Function" + ct);
  199.       }
  200.       while (true) {
  201.          if (c == null)
  202.             break;
  203.          functions.addElement(c);
  204.          ct++;
  205.          c = getParameter("Function" + ct);
  206.       }
  207.       if (functions.size() == 0)
  208.          functions.addElement( " abs( " + xVar.getName() + ") ^ " + xVar.getName() );
  209.       double[] d = getNumericParam("FunctionCount");
  210.       if (d == null || d.length == 0 || d[0] <= 0.5)
  211.          functionCt = functions.size();
  212.       else {
  213.          functionCt = (int)Math.round(d[0]);
  214.          if (functionCt < functions.size()) { // use number of functions specified as functionCt
  215.             functionCt = functions.size();
  216.          }
  217.          else {  // make extra empty functions to bring total up to functionCt
  218.             int extra = functionCt - functions.size();
  219.             for (int i = 0; i < extra; i++)
  220.                functions.addElement("");
  221.          }
  222.       }
  223.       return functions;
  224.    }
  225.    
  226.  
  227.    private Panel makeFunctionInput(Vector functions, int funcNum) {  
  228.            // make input box for specified function
  229.            // also adds the input box to the inputs[] array
  230.       Graph1D graph = new Graph1D();
  231.       graph.setColor(graphColors[funcNum % graphColors.length]);
  232.       ExprIn in = new ExprIn((String)functions.elementAt(funcNum),parser,graph,xVar);
  233.       in.setOnUserAction(mainController);
  234.       JCMPanel p = new JCMPanel();
  235.       p.add(in,BorderLayout.CENTER);
  236.       String name;
  237.       if (functions.size() > 1)
  238.          name = " " + getParameter("FunctionName","f") + (funcNum+1) + "(" + xVar.getName() + ") = ";
  239.       else
  240.          name = " " + getParameter("FunctionName","f") +  "(" + xVar.getName() + ") = ";
  241.       p.add(new Label(name), BorderLayout.WEST);
  242.       if (graphColors.length > 1 && functions.size() > 1)
  243.          p.add(new ColorPatch( graphColors[funcNum % graphColors.length] ), BorderLayout.EAST);
  244.       inputs[funcNum] = in;
  245.       return p;
  246.    }
  247.  
  248.  
  249.    protected void setUpBottomPanel() {  
  250.       // Overridden to create an appropriate input panel
  251.  
  252.       // Create a panel holding all the function inputs and
  253.       // sliders, with a display label for each slider to show its value.
  254.      
  255.       boolean funcInput = "yes".equalsIgnoreCase(getParameter("UseFunctionInput","yes"));
  256.      
  257.       if ( funcInput && "yes".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) { // make the compute button
  258.          String cname = getParameter("ComputeButtonName", "New Functions");
  259.          computeButton = new Button(cname);
  260.          computeButton.addActionListener(this);
  261.       }
  262.       Panel firstPanel = null;  // To help find a place for the compute button
  263.      
  264.       getColors();
  265.       Vector functions = getFunctions();
  266.  
  267.       if (!funcInput && sliders.size() == 0)  // nothing to put in the input panel
  268.          return;
  269.  
  270.       JCMPanel panel = new JCMPanel();
  271.       if (! "no".equalsIgnoreCase(getParameter("TwoInputColumns","no")))
  272.          panel.setLayout(new GridLayout(0,2,12,3));
  273.       else
  274.          panel.setLayout(new GridLayout(0,1,3,3));
  275.       panel.setBackground(getColorParam("PanelBackground", Color.lightGray));
  276.  
  277.       if (funcInput) { // make an input box for each function and add it to the panel
  278.          inputs = new ExprIn[functions.size()];
  279.          for (int i = 0; i < functions.size(); i++) {
  280.             Panel p = makeFunctionInput(functions,i);
  281.             if (firstPanel == null)
  282.                firstPanel = p;
  283.             panel.add(p);
  284.          }
  285.       }
  286.       else {  // just make graphs from the function definition strings.
  287.          graphs = new Graph1D[functions.size()];
  288.          for (int i = 0; i < functions.size(); i++) {
  289.             graphs[i] = new Graph1D();
  290.             graphs[i].setColor(graphColors[ i % graphColors.length ]);
  291.             String def = ((String)functions.elementAt(i)).trim();
  292.             if (def.length() > 0) {  // if the definition string is empty, leave graph's function undefined
  293.                 Function f = new SimpleFunction( parser.parse(def), xVar );
  294.                 graphs[i].setFunction(f);
  295.             }
  296.          }
  297.       }
  298.  
  299.       for (int i = 0; i < sliders.size(); i++) {  // add sliders to the input panel
  300.          JCMPanel p = new JCMPanel();
  301.          VariableSlider slide = (VariableSlider)sliders.elementAt(i);
  302.          p.add(slide, BorderLayout.CENTER);
  303.          p.add(new DisplayLabel("  " + slide.getName() + " = # ", new Value[] { slide.getVariable() } ),
  304.                       BorderLayout.EAST);
  305.          panel.add(p);
  306.          slide.setOnUserAction(mainController);
  307.       }
  308.      
  309.       if (computeButton != null) {  // find a place for the compute button!
  310.          if (functions.size() == 1)
  311.             firstPanel.add(computeButton, BorderLayout.EAST);
  312.          else if (limitsPanel == null) {
  313.             Panel p = new Panel();
  314.             p.add(computeButton);
  315.             panel.add(p);  
  316.          }
  317.          // otherwise, add it at the end of setUpLimitPanel();
  318.       }
  319.      
  320.       mainPanel.add(panel, BorderLayout.SOUTH);
  321.      
  322.    } // end setUpBottomPanel()
  323.  
  324.    protected void setUpLimitsPanel() { // add compute button if it hasn't been put somewhere else
  325.       super.setUpLimitsPanel();
  326.       if (limitsPanel != null && computeButton != null && functionCt != 1)
  327.          limitsPanel.addComponent(computeButton);
  328.    }
  329.  
  330.    protected void setUpCanvas() { // Overridden to add the graph to the canvas.
  331.  
  332.       super.setUpCanvas();  // Do the default setup.
  333.  
  334.       // set up bottom panel has already been defined
  335.       // add the graphs to the canvas
  336.      
  337.       if (graphs != null) {
  338.          for (int i = 0; i < graphs.length; i++)
  339.             canvas.add(graphs[i]);
  340.       }
  341.       else {
  342.          for (int i = 0; i < inputs.length; i++)
  343.             canvas.add(inputs[i].graph);
  344.       }
  345.  
  346.    } // end setUpCanvas
  347.  
  348.  
  349.  
  350.    protected void doLoadExample(String example) {
  351.          // This method is called when the user loads an example from the
  352.          // example menu (if there is one).  It overrides an empty method
  353.          // in GenericGraphApplet.
  354.          //   For the FamiliesOfGraphs applet, the example string should contain
  355.          // an expression that defines the function to be graphed.  This must
  356.          // be followed by a semicolon and list of zero or more numbers.
  357.          // Then there is another semicolon and one or more function definitions,
  358.          // separated by semicolons.  You can have as many function
  359.          // definitions as you have functions in your applet setup.
  360.          // (Note that having the numbers before the
  361.          // functions is different from the format of examples in all the
  362.          // other configurable applets.  This is to allow more than one function.)  Note that even if you leave
  363.          // out the numbers, you still need two semicolons.  The list of numbers has the following meaning:
  364.          // The first four numbers give the x- and y-limits to be used for the
  365.          // example.  If they are not present, then -5,5,-5,5 is used.  The
  366.          // remaining numbers occur in groups of three. Each group give the maximum, minimum, and value of a parameters that was defined
  367.          // with the "Parameter", "Parameter1", ... applet params.
  368.          
  369.       int pos = example.indexOf(";");
  370.      
  371.       double[] limits = { -5,5,-5,5 }; // x- and y-limits to use
  372.  
  373.       if (pos > 0) {
  374.                // Get limits from example text.
  375.          String nums = example.substring(0,pos);
  376.          example = example.substring(pos+1);
  377.          StringTokenizer toks = new StringTokenizer(nums, " ,");
  378.          if (toks.countTokens() >= 4) {
  379.             for (int i = 0; i < 4; i++) {
  380.                try {
  381.                    Double d = new Double(toks.nextToken());
  382.                    limits[i] = d.doubleValue();
  383.                }
  384.                catch (NumberFormatException e) {
  385.                }
  386.             }
  387.          }
  388.          int i = 0;
  389.          while (i < sliders.size() && toks.hasMoreElements()) {
  390.                // Look for a value for the i-th slider.
  391.             try {
  392.                 double min = (new Double(toks.nextToken())).doubleValue();
  393.                 double max = (new Double(toks.nextToken())).doubleValue();
  394.                 double d = (new Double(toks.nextToken())).doubleValue();
  395.                 VariableSlider slider = ((VariableSlider)sliders.elementAt(i));
  396.                 slider.setMin(new Constant(min));
  397.                 slider.setMax(new Constant(max));
  398.                 slider.setVal(d);
  399.             }
  400.             catch (Exception e) {
  401.             }
  402.             i++;
  403.          }
  404.       }
  405.      
  406.       // Set up the example data and recompute everything.
  407.       StringTokenizer toks = new StringTokenizer(example,";");
  408.       int funcNum = 0;
  409.       while (funcNum < functionCt) {
  410.          if (toks.hasMoreElements()) {  // define the function using definition from example text
  411.              String def = toks.nextToken();
  412.              if (graphs != null) {
  413.                 try {
  414.                     graphs[funcNum].setFunction(new SimpleFunction( parser.parse(def), xVar ));
  415.                  }
  416.                  catch (ParseError e) {
  417.                     graphs[funcNum].setFunction(null);
  418.                  }
  419.              }
  420.              else
  421.                 inputs[funcNum].setText(def);
  422.          }
  423.          else {  // function is undefined
  424.             if (graphs != null)
  425.                graphs[funcNum].setFunction(null);
  426.             else
  427.                inputs[funcNum].setText("");
  428.          }
  429.          funcNum++;
  430.       }
  431.  
  432.       CoordinateRect coords = canvas.getCoordinateRect(0);
  433.       coords.setLimits(limits);
  434.       coords.setRestoreBuffer();
  435.       mainController.compute();
  436.      
  437.    } // end doLoadExample()
  438.  
  439.    
  440. } // end class MultiGraph
  441.  
  442.