Draw the function
/************************************************************************* * * * 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.*; import edu.hws.jcm.draw.*; import edu.hws.jcm.data.*; import edu.hws.jcm.functions.*; import edu.hws.jcm.awt.*; // The MultiApplet can display the graphs of several functions, in different colors. // By default, there is only one function, but you can configure the applet to // use more than one function with applet params. // The definitions of these functions can, optionally, use parameters whose // values are controled by sliders at the bottom of the applet. public class MultiGraph extends GenericGraphApplet { private Vector sliders; // Elements of this vector are the VariableSlider // objects that represent the parameter values. // The sliders are created in the setUpParser() method. private ExprIn[] inputs; // The function input boxes (or null if inputs aren't used) private Graph1D[] graphs; // The graphs of the functions, in the case function input boxes are NOT used private int functionCt; // Number of functions -- size of inputs or graphs array private Color[] graphColors = { Color.magenta, new Color(0,180,0), Color.red, new Color(0,200,200), Color.orange, Color.gray, Color.blue, Color.pink }; private static class ColorPatch extends Canvas { // a canvas with a preferred size ColorPatch(Color c) { setBackground(c); } public Dimension getPreferredSize() { return new Dimension(25,10); } public void paint(Graphics g) { g.drawRect(0,0,getSize().width-1,getSize().height-1); } } private static class ExprIn extends ExpressionInput { // Doesn't throw an error if empty, just sets function in graph to null Graph1D graph; // Graph associated with this function input. Function func; // The function of x defined by this graph. ExprIn(String definition, Parser p, Graph1D g, Variable v) { super(definition,p); graph = g; func = getFunction(v); if (definition.trim().length() > 0) graph.setFunction(func); } public void checkInput() { // (will be called during constructor -- hence the funny bit with checking if graphe is null) boolean hasChanged = previousContents == null || !previousContents.equals(getText()); if (!hasChanged) return; String text = getText().trim(); if (text.length() == 0) { // set graph's function to null so it doesn't have to do any computations. if (graph != null) graph.setFunction(null); previousContents = getText(); } else { super.checkInput(); if (graph != null) graph.setFunction(func); } } } protected void setUpParser() { // Override this to add VariableSliders to parser. // Get the data for any 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); } super.setUpParser(); // Call this last so function definitions // in applet params can use the parameter names } // 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(); private void getColors() { // get graph colors from color parameters, if any. Vector vec = new Vector(); int ct = 0; Color c = getColorParam("GraphColor"); if (c == null) { ct++; c = getColorParam("GraphColor" + ct); } while (true) { if (c == null) break; vec.addElement(c); ct++; c = getColorParam("GraphColor" + ct); } if (vec.size() > 0) { graphColors = new Color[vec.size()]; for (int i = 0; i < vec.size(); i++) graphColors[i] = (Color)vec.elementAt(i); } } private Vector getFunctions() { // Read applet parms "Function", "Funcion1", ... // Return a vector containing the function definition strings Vector functions = new Vector(); int ct = 0; String c = getParameter("Function"); if (c == null) { ct++; c = getParameter("Function" + ct); } while (true) { if (c == null) break; functions.addElement(c); ct++; c = getParameter("Function" + ct); } if (functions.size() == 0) functions.addElement( " abs( " + xVar.getName() + ") ^ " + xVar.getName() ); double[] d = getNumericParam("FunctionCount"); if (d == null || d.length == 0 || d[0] <= 0.5) functionCt = functions.size(); else { functionCt = (int)Math.round(d[0]); if (functionCt < functions.size()) { // use number of functions specified as functionCt functionCt = functions.size(); } else { // make extra empty functions to bring total up to functionCt int extra = functionCt - functions.size(); for (int i = 0; i < extra; i++) functions.addElement(""); } } return functions; } private Panel makeFunctionInput(Vector functions, int funcNum) { // make input box for specified function // also adds the input box to the inputs[] array Graph1D graph = new Graph1D(); graph.setColor(graphColors[funcNum % graphColors.length]); ExprIn in = new ExprIn((String)functions.elementAt(funcNum),parser,graph,xVar); in.setOnUserAction(mainController); JCMPanel p = new JCMPanel(); p.add(in,BorderLayout.CENTER); String name; if (functions.size() > 1) name = " " + getParameter("FunctionName","f") + (funcNum+1) + "(" + xVar.getName() + ") = "; else name = " " + getParameter("FunctionName","f") + "(" + xVar.getName() + ") = "; p.add(new Label(name), BorderLayout.WEST); if (graphColors.length > 1 && functions.size() > 1) p.add(new ColorPatch( graphColors[funcNum % graphColors.length] ), BorderLayout.EAST); inputs[funcNum] = in; return p; } protected void setUpBottomPanel() { // Overridden to create an appropriate input panel // Create a panel holding all the function inputs and // sliders, with a display label for each slider to show its value. boolean funcInput = "yes".equalsIgnoreCase(getParameter("UseFunctionInput","yes")); if ( funcInput && "yes".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) { // make the compute button String cname = getParameter("ComputeButtonName", "New Functions"); computeButton = new Button(cname); computeButton.addActionListener(this); } Panel firstPanel = null; // To help find a place for the compute button getColors(); Vector functions = getFunctions(); if (!funcInput && sliders.size() == 0) // nothing to put in the input panel return; JCMPanel panel = new JCMPanel(); if (! "no".equalsIgnoreCase(getParameter("TwoInputColumns","no"))) panel.setLayout(new GridLayout(0,2,12,3)); else panel.setLayout(new GridLayout(0,1,3,3)); panel.setBackground(getColorParam("PanelBackground", Color.lightGray)); if (funcInput) { // make an input box for each function and add it to the panel inputs = new ExprIn[functions.size()]; for (int i = 0; i < functions.size(); i++) { Panel p = makeFunctionInput(functions,i); if (firstPanel == null) firstPanel = p; panel.add(p); } } else { // just make graphs from the function definition strings. graphs = new Graph1D[functions.size()]; for (int i = 0; i < functions.size(); i++) { graphs[i] = new Graph1D(); graphs[i].setColor(graphColors[ i % graphColors.length ]); String def = ((String)functions.elementAt(i)).trim(); if (def.length() > 0) { // if the definition string is empty, leave graph's function undefined Function f = new SimpleFunction( parser.parse(def), xVar ); graphs[i].setFunction(f); } } } for (int i = 0; i < sliders.size(); i++) { // add sliders to the input panel 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); panel.add(p); slide.setOnUserAction(mainController); } if (computeButton != null) { // find a place for the compute button! if (functions.size() == 1) firstPanel.add(computeButton, BorderLayout.EAST); else if (limitsPanel == null) { Panel p = new Panel(); p.add(computeButton); panel.add(p); } // otherwise, add it at the end of setUpLimitPanel(); } mainPanel.add(panel, BorderLayout.SOUTH); } // end setUpBottomPanel() protected void setUpLimitsPanel() { // add compute button if it hasn't been put somewhere else super.setUpLimitsPanel(); if (limitsPanel != null && computeButton != null && functionCt != 1) limitsPanel.addComponent(computeButton); } protected void setUpCanvas() { // Overridden to add the graph to the canvas. super.setUpCanvas(); // Do the default setup. // set up bottom panel has already been defined // add the graphs to the canvas if (graphs != null) { for (int i = 0; i < graphs.length; i++) canvas.add(graphs[i]); } else { for (int i = 0; i < inputs.length; i++) canvas.add(inputs[i].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 must // be followed by a semicolon and list of zero or more numbers. // Then there is another semicolon and one or more function definitions, // separated by semicolons. You can have as many function // definitions as you have functions in your applet setup. // (Note that having the numbers before the // functions is different from the format of examples in all the // other configurable applets. This is to allow more than one function.) Note that even if you leave // out the numbers, you still need two semicolons. The list of numbers has the following meaning: // 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. Each group give the maximum, minimum, and value of a parameters that was defined // with the "Parameter", "Parameter1", ... applet params. 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(0,pos); example = example.substring(pos+1); 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. StringTokenizer toks = new StringTokenizer(example,";"); int funcNum = 0; while (funcNum < functionCt) { if (toks.hasMoreElements()) { // define the function using definition from example text String def = toks.nextToken(); if (graphs != null) { try { graphs[funcNum].setFunction(new SimpleFunction( parser.parse(def), xVar )); } catch (ParseError e) { graphs[funcNum].setFunction(null); } } else inputs[funcNum].setText(def); } else { // function is undefined if (graphs != null) graphs[funcNum].setFunction(null); else inputs[funcNum].setText(""); } funcNum++; } CoordinateRect coords = canvas.getCoordinateRect(0); coords.setLimits(limits); coords.setRestoreBuffer(); mainController.compute(); } // end doLoadExample() public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new MultiGraph(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); } } // end class MultiGraph