jcm1-source/ 0000755 0000765 0001132 00000000000 11741343635 012207 5 ustar dje faculty jcm1-source/ScatterPlotApplet.java 0000644 0000765 0001132 00000035563 11741343635 016500 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * 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.compute(); } /** * 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; } } // end class ScatterPlotApplet jcm1-source/FamiliesOfGraphs.java 0000644 0000765 0001132 00000031555 11741343635 016246 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * 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.compute(); } // end doLoadExample() } // end class FamiliesOfGraphs jcm1-source/EpsilonDelta.java 0000644 0000765 0001132 00000034646 11741343635 015452 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * 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.compute(); } // end doLoadExample() } // end class EpsilonDelta jcm1-source/GenericGraphApplet.java 0000644 0000765 0001132 00000112773 11741343635 016571 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * http://math.hws.edu/eck * **************************************************************************/ import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.*; import edu.hws.jcm.data.*; import edu.hws.jcm.draw.*; import edu.hws.jcm.awt.*; import edu.hws.jcm.functions.*; /** * GenericGraphApplet serves as a base class for applets that have a DisplayCanvas in the CENTER of * a BorderLayout and that are configurable by a large number of applet parameters. This base class * sets up the basic structure of the applet and processes many applet parameters. Most of the * work is done in methods that can be overridden in subclasses. If the height of * the applet is greater than 100 and if the applet parameter "LaunchButtonName" is not defined, * then the main panel of the applet appears in the applet itself. Otherwise, * the applet appears as a button. Clicking the button opens the main panel of the * applet in a separate, resizable frame. */ public class GenericGraphApplet extends Applet implements ActionListener, ItemListener { /** * The parser which is used for parsing input from the functionInput box. */ protected Parser parser; /** * The main panel of the applet, containing a display canvas and other components. */ protected JCMPanel mainPanel; /** * The Controller from the mainPanel. */ protected Controller mainController; /** * The canvas for displaying axes, graphs, etc. */ protected DisplayCanvas canvas; /** * Controls the limits of the x- and y-coordinates on the canvas. */ protected LimitControlPanel limitsPanel; /** * An input box for inputting an expression. */ protected ExpressionInput functionInput; /** * The variable that is created and added to the parser. The default name of this * variable is "x", but that can be changed using the "Variable" applet param. * The variable is created in the setUpParser() method. */ protected Variable xVar; /** * A panel containing the functionInput. It appears at the bottom of the main panel. */ protected JCMPanel inputPanel; /** * A panel containing examples specified in applet parameters, if any. It appears * at the top of the main panel. */ protected JCMPanel exampleMenuPanel; /** * A button that the user presses to recompute the display. (Ths user can also just * press return in one of the input boxes.) */ protected Button computeButton; /** * The string that is used as the title of the frame, if the applet appears as * a launch button. This title is also used on the launch button, unless the * applet parameter "LaunchButtonName" has a value. This is set in the init() method. */ protected String frameTitle; /** * The default size for the frame when the applet runs a launch button. * Can be overidden by the FrameSize applet param. */ protected int[] defaultFrameSize = { 550, 400} ; /** * A hash table that, if non-null, can hold values for applet params. While this is * not set to a non-null value in this class, its value can be set in the setUpAppletDefaults() * method. The values in this hash table will be returned by the getParameter() method * when no value is provided in the applet. For example, this class uses a default value * of "s" vor the the param "Variable". A subclass could put a different value in the * parameterDefaults hash table, and this value will be used in preference to "x". */ protected Hashtable parameterDefaults; /** * 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. If any error occurs during calls to setUpParser(), * setUpExampleMenu(), or setUpMainPanel(), then the applet will just show an error message, * and stack trace will be printed to standard output. */ public void init() { setUpParameterDefaults(); frameTitle = getParameter("FrameTitle"); if (frameTitle == null) { frameTitle = this.getClass().getName(); 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 < 100) || launchButtonName != null) { if (launchButtonName == null) launchButtonName = "Launch " + frameTitle; launchButton = new Button(launchButtonName); add(launchButton, BorderLayout.CENTER); launchButton.addActionListener(this); } else { mainPanel = new JCMPanel(); try { setUpMainPanel(); add(mainPanel, BorderLayout.CENTER); } catch (Exception e) { System.out.println("Error while opening applet:"); e.printStackTrace(); TextArea message = new TextArea("An error occurred while setting up the applet:\n\n"); message.append(e.toString()); add(message, BorderLayout.CENTER); } } } /** * This method is called to set up the main panel of the applet. The main panel object, of type JCMPanel, * already exists in the variable mainPanel when it is called. It begins by calling setUpParser() * and setUpExampleMenu(). This method processes applet parameters * "Insets", "BackgroundColor", and "ForegroundColor" to set the gap and colors of the panel. It * creates a DisplayCanvas and, if the applet parameter "UseLimitsPanel" is not equal to "no", it * creates a LimitControlPanel. The coordinate limits are set using the "Limits" applet param, if * presetn. This method assigns the Controller from the mainPanel to the member variable * mainController. It then calls setUpBottomPanel(), setUpTopPanel(), setUpCanvas(), addCanvasBorder(), * and, if there is a limits panel, setUpLimitsPanel(), in that order. Finally, if the value * of the applet parameter "LoadFirstExample" is not "no", and if any examples were specifed, then * the first ewxample is loaded. */ protected void setUpMainPanel() { parser = new Parser(null,0); setUpParser(); setUpExampleMenu(); double[] gap = getNumericParam("Insets"); if (gap == null || gap.length == 0 || gap[0] < 0 || gap[0] > 50) mainPanel.setInsetGap(3); else mainPanel.setInsetGap( (int)Math.round(gap[0]) ); Color color; color = getColorParam("BackgroundColor", Color.gray); mainPanel.setBackground(color); color = getColorParam("ForegroundColor", Color.black); mainPanel.setForeground(color); canvas = new DisplayCanvas(); double[] limits = getNumericParam("Limits"); if (limits != null && limits.length >= 4) canvas.getCoordinateRect().setLimits(limits); if ( "yes".equalsIgnoreCase(getParameter("UseLimitsPanel", "yes")) ) { String h = getParameter("XName"); if (h == null) h = getParameter("Variable","x"); String v =getParameter("YName","y"); limitsPanel = new LimitControlPanel( h+"min", h+"max", v+"min", v+"max", 0, false ); } mainController = mainPanel.getController(); setUpBottomPanel(); setUpTopPanel(); setUpCanvas(); addCanvasBorder(); if ( limitsPanel != null ) setUpLimitsPanel(); String loadDefault = (loadExampleButton == null)? "yes" : "no"; if (exampleStrings != null && exampleStrings.size() > 0 && ! "no".equalsIgnoreCase(getParameter("LoadFirstExample",loadDefault))) doLoadExample( (String)exampleStrings.elementAt(0) ); } /** * This method is called by mainPanel() to set up the display canvas and add it to the * main panel. The canvas already exists in the member variable canvas when this * method is called. This method adds only a set of axes to the canvas, sets the * mainController to report errors using the canvas, and adds the canvas to the * CENTER of the main panel. This method processes applet parameters "UsePanner", * "CanvasColor", "UseMouseZoom", and "UseOffscreenCanvas". Typically, this * method will be overridden in subclasses to add more Drawable items to the * canvas. In this case, super.setUpCanvas() should be called first. */ protected void setUpCanvas() { Color color; color = getColorParam("CanvasColor"); if (color != null) canvas.setBackground(color); if (! "no".equalsIgnoreCase(getParameter("UsePanner", "no")) ) canvas.add(new Panner()); if ( ! "no".equalsIgnoreCase(getParameter("UseGrid", "no")) ) { Grid g = new Grid(); color = getColorParam("GridColor"); if (color != null) g.setColor(color); canvas.add(g); } canvas.add(makeAxes()); if ( ! "no".equalsIgnoreCase(getParameter("UseMouseZoom", "no")) ) canvas.setHandleMouseZooms(true); if ( "yes".equalsIgnoreCase(getParameter("UseOffscreenCanvas", "yes")) ) canvas.setUseOffscreenCanvas(true); mainController.setErrorReporter(canvas); mainPanel.add(canvas, BorderLayout.CENTER); } /** * Construct a set of Axes, based on applet params "AxesColor", "AxesLightColor", * "XLabel", "YLabel", "LabelColor". */ protected Axes makeAxes() { Axes axes = new Axes(); Color color = getColorParam("AxesColor"); if (color != null) axes.setAxesColor(color); color = getColorParam("AxesLightColor"); if (color != null) axes.setLightAxesColor(color); String str = getParameter("XLabel"); if (str != null) axes.setXLabel(str); str = getParameter("YLabel"); axes.setYLabel(str); color = getColorParam("LabelColor"); if (color != null) axes.setLabelColor(color); return axes; } /** * This method is called by setUpMainPanel() to add a border to the canvas (since the border is typically * the last thing that should be added, on top of anything else in the canvas. It processes the * applet parameters "BorderWidth" and "BorderColor". If the border width is zero, no border is added. * (The default width is 2.) */ protected void addCanvasBorder() { int borderWidth; double[] bw = getNumericParam("BorderWidth"); if (bw == null || bw.length == 0 || bw[0] > 25) borderWidth = 2; else borderWidth = (int)Math.round(bw[0]); if (borderWidth > 0) canvas.add( new DrawBorder( getColorParam("BorderColor", Color.black), borderWidth ) ); } /** * This method checks the applet parameter "UseFunctionInput". If the value is anything but "no", * then a panel is created that contains an ExpressionInput (stored in the member variable functionInput) * and possibly a ComputeButton and lable for the input box. This panel is a JCMPanel using BorderLayout. * In is stored in the member variable inputPanel and is added to the SOUTH position of the mainPanel. * The method also processes applet parameters "Function", "UseComputeButton", "ComputeButtonName", and * "FunctionLabel". The ComputeButton, if it exists, is stored in the member variable computeButton. * Note that nothing at all is done by this method if the value of the applet parameter "UseFunctionInput" is no. * */ protected void setUpBottomPanel() { if ( "yes".equalsIgnoreCase(getParameter("UseFunctionInput", "yes")) ) { String func = getParameter("Function"); String varName = xVar.getName(); if (func == null) func = "abs(" + varName + " ) ^ " + varName; functionInput = new ExpressionInput(func,parser); inputPanel = new JCMPanel(); inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) ); inputPanel.add(functionInput, BorderLayout.CENTER); if ( "yes".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) { String cname = getParameter("ComputeButtonName", "New Function"); computeButton = new Button(cname); inputPanel.add(computeButton, BorderLayout.EAST); computeButton.addActionListener(this); } String flabel = getParameter("FunctionLabel"); if (flabel == null) flabel = " f(" + varName + ") = "; if ( ! "none".equalsIgnoreCase(flabel) ) inputPanel.add(new Label(flabel), BorderLayout.WEST); mainPanel.add(inputPanel, BorderLayout.SOUTH); functionInput.setOnUserAction(mainPanel.getController()); } } /** * This method sets up the limit control panel and adds it to the main panel. The limit control panel * already exists when this method is called and is stored in the member variable limitsPanel. The * applet parameters "TwoLimitsColumns", "UseSetLimitsButton", "UseZoomButtons", "UseEqualizeButtons", * "UseRestoreButton", "PanelBackground", and "LimitsOnLeft" are processed. The limits panel is * set to report its errors using the display canvas. */ protected void setUpLimitsPanel() { limitsPanel.addCoords(canvas); if ( ! "no".equalsIgnoreCase(getParameter("TwoLimitsColumns", "no")) ) limitsPanel.setUseTwoColumnsIfPossible(true); int buttons = 0; if ( "yes".equalsIgnoreCase(getParameter("UseSetLimitsButton", "yes")) ) buttons |= LimitControlPanel.SET_LIMITS; if ( ! "no".equalsIgnoreCase(getParameter("UseZoomButtons", "no")) ) buttons |= LimitControlPanel.ZOOM_IN | LimitControlPanel.ZOOM_OUT; if ( ! "no".equalsIgnoreCase(getParameter("UseEqualizeButton", "no")) ) buttons |= LimitControlPanel.EQUALIZE; if ( ! "no".equalsIgnoreCase(getParameter("UseRestoreButton", "no")) ) buttons |= LimitControlPanel.RESTORE; if (buttons != 0) limitsPanel.addButtons(buttons); limitsPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) ); if ( ! "yes".equalsIgnoreCase(getParameter("LimitsOnLeft", "no"))) mainPanel.add(limitsPanel, BorderLayout.EAST); else mainPanel.add(limitsPanel, BorderLayout.WEST); limitsPanel.setErrorReporter(canvas); } /** * This method is called by setUpMainPanel() to add a panel at the top of the applet. * If there is an example menu, then the panel containing that menu is added to the * NORTH position of the main panel. Otherwise, the value of the applet parameter * named "PanelTitle" is checked. If this value exists, it is placed in a label at the top of the panel. * The color of the text on the label is given by the valur of the applet parameter * "TitleColor" (default is new Color(200,0,0)). */ protected void setUpTopPanel() { if (exampleMenuPanel != null) mainPanel.add(exampleMenuPanel, BorderLayout.NORTH); else { String title = getParameter("PanelTitle"); if (title != null) { Label titleLabel = new Label(title, Label.CENTER); titleLabel.setForeground( getColorParam("TitleForeground", new Color(200,0,0)) ); titleLabel.setBackground( getColorParam("TitleBackground", Color.lightGray) ); titleLabel.setFont( new Font("Serif", Font.PLAIN, 14) ); mainPanel.add(titleLabel, BorderLayout.NORTH); } } } /** * This method processes applet parameters that specify examples for the applet and adds them * to a menu of examples. If any examples exist, they are placed in a menu and a panel * is created to hold the menu. The panel is stored in the instance variable exampleMenuPanel, * which is used in the setUpTopPanel() method. This method is called by init(). If you don't * want to process examples, you could override it to do nothing. If you do want to process * examples, you have to override the doLoadExample() method to process a string from the menu. * Examples are specified in applet parameters named "Example", "Example1", "Example2", .... * The value should consist of a name for the example (which appears in the menu) followed by * a ";" followed by a string that defines the example. The string that defines the example * is passed to the doLoadExample() method when the user loads the example. */ protected void setUpExampleMenu() { Vector strings = new Vector(); Vector names = new Vector(); int ct = 0; String paramName = "Example"; String param = getParameter(paramName); if (param == null) { ct++; paramName = "Example" + ct; param = getParameter(paramName); } while (true) { if (param == null) break; int pos = param.indexOf(';'); if (pos < 0) { strings.addElement(param); names.addElement(param); } else { strings.addElement(param.substring(pos+1)); names.addElement(param.substring(0,pos)); } ct++; paramName = "Example" + ct; param = getParameter(paramName); } if (strings.size() == 0) return; exampleStrings = strings; exampleStrings.trimToSize(); exampleMenuPanel = new JCMPanel(); if ("yes".equalsIgnoreCase(getParameter("UseLoadButton","yes"))) { loadExampleButton = new Button("Load Example: "); loadExampleButton.setBackground(Color.lightGray); loadExampleButton.addActionListener(this); } Component list; if (names.size() == 1) list = new Label( (String)names.elementAt(0), Label.CENTER ); else { exampleMenu = new Choice(); list = exampleMenu; for (int i = 0; i < names.size(); i++) exampleMenu.add( (String)names.elementAt(i) ); if (loadExampleButton == null) exampleMenu.addItemListener(this); } list.setBackground(Color.white); exampleMenuPanel.add(list, BorderLayout.CENTER); if (loadExampleButton != null) exampleMenuPanel.add(loadExampleButton, BorderLayout.WEST); } /** * This method is called by setUpMainPanel() to set up the parser to be used in the applet. * When it is called, a parser already esists and is stored in the member variable * named parser. This method configures the parser according to the values of * the applet parameters "StandardFunctions", "Booleans", "OptionalStars", "OptionalParens", * and "Factorials". It then looks for function defintions in applet parameters named * "Define", "Define1", "Define2", ...., and if any are found the functions are added * to the parser so that they can be used in expressions. A function definition can * take a form such as "g(x)=x^2" or "fred(s,t) = 3*s + sin(t)", for example, or it * can be defined by a table of values. A table function is defined using the * syntax decribed in the parseTableFuncDef() method. Finally, a variable is * created and added to the parser using the value of the applet param "Variable", * with "x" as the default variable name. */ protected void setUpParser() { if ( "yes".equalsIgnoreCase(getParameter("StandardFunctions", "yes")) ) parser.addOptions(Parser.STANDARD_FUNCTIONS); if ( "yes".equalsIgnoreCase(getParameter("Booleans", "yes")) ) parser.addOptions(Parser.BOOLEANS); if ( ! "no".equalsIgnoreCase(getParameter("OptionalStars", "no")) ) parser.addOptions(Parser.OPTIONAL_STARS); if ( ! "no".equalsIgnoreCase(getParameter("OptionalParens", "no")) ) parser.addOptions(Parser.OPTIONAL_PARENS); if ( ! "no".equalsIgnoreCase(getParameter("Factorials", "no")) ) parser.addOptions(Parser.FACTORIAL); if ( "yes".equalsIgnoreCase(getParameter("Summations", "yes")) ) parser.add(new SummationParser()); String str = getParameter("Define"); if (str != null) define(str); int ct = 1; while (true) { str = getParameter("Define" + ct); if (str == null) break; define(str); ct++; } xVar = new Variable( getParameter("Variable", "x") ); parser.add( xVar ); } /** * This method is called when the user loads an example from the example menu (if any). * The parameter is the string that defines the example. By default, this method does * nothhing. It should be overridden to load the example. */ protected void doLoadExample(String example) { } /** * This method, which is empty in the GenericGraphApplet class, can be defined in a subclass * to set default values for applet params that are different from the ones provided in * this class. The method should create a new HashTable, assign it to the instance * variable parameterDefaults, and then add name/value pairs to the hash table. This * method is called at the very beginning of the init() method. */ protected void setUpParameterDefaults() { } /** * Override the standard applet method getParameter(String) so that when no param value * is provided in the applet tag, and if parameterDefaults is non-null, it will check for a value * the parameterDefaults. (The parameterDefaults instance variable can be defined in the * setUpParameterDefaults() method.) */ public String getParameter(String paramName) { String val = super.getParameter(paramName); if (val == null && parameterDefaults != null) val = (String)parameterDefaults.get(paramName); return val; } /** * Get the value of an applet parameter, but return a default if the value is null. * * @param paramName The name of the applet parameter. * @param defaultValue The value to be returned if getParameter(paramName) is null. */ protected String getParameter(String paramName, String defaultValue) { String val = getParameter(paramName); return val == null ? defaultValue : val; } /** * Get The value of an applet parameter that consists of a list of numbers. * The parameter value, if any, is parsed and returned as array of double values. * The numbers can be separated by commas, spaces, tabs, or semicolons. If there * is a parse error, null is returned. */ protected double[] getNumericParam(String paramName) { return getNumericParam(paramName, null); } /** * Get The value of an applet parameter that consists of a list of numbers. * The parameter value, if any, is parsed and returned as array of double values. * The numbers can be separated by commas, spaces, tabs, or semicolons. * * @param paramName The name of the applet parameter. * @param defaultValue The value to be returned if getParameter(paramName) is null or is not a valid list of numbers. */ protected double[] getNumericParam(String paramName, double[] defaults) { String data = getParameter(paramName); if (data == null) return defaults; StringTokenizer tokenizer = new StringTokenizer(data," \t,;"); int count = tokenizer.countTokens(); if (count == 0) return defaults; double[] numbers = new double[count]; for (int i = 0; i < count; i++) { try { Double d = new Double(tokenizer.nextToken()); numbers[i] = d.doubleValue(); } catch (NumberFormatException e) { return defaults; } } return numbers; } /** * Get The value of an applet parameter that specifies a color. The color can be specfied * as a list of three numbers in the range 0 to 255 or by one of the standard color names * ("black", "red", "blue", "green", "yellow", "cyan", "magenta", "gray", "darkgray", * "lightgray", "pink", "orange", "white"). Color names are not case sensitive. If * the value of the applet parameter is null does not specify a legal color, then * the return value is null. */ protected Color getColorParam(String data) { return getColorParam(data,null); } /** * Get The value of an applet parameter that specifies a color. The color can be specfied * as a list of three numbers in the range 0 to 255 or by one of the standard color names * ("black", "red", "blue", "green", "yellow", "cyan", "magenta", "gray", "darkgray", * "lightgray", "pink", "orange", "white"). Color names are not case sensitive. * * @param paramName The name of the applet parameter. * @param defaultColor The value to be returned if getParameter(paramName) is null or is not a valid color. */ protected Color getColorParam(String paramName, Color defaultColor) { String data = getParameter(paramName); if (data == null || data.trim().length() == 0) return defaultColor; data = data.trim(); if (Character.isLetter(data.charAt(0))) { for (int i = 0; i < colorNames.length; i++) if (data.equalsIgnoreCase(colorNames[i])) return colors[i]; return defaultColor; } else { double[] nums = getNumericParam(paramName,null); if (nums == null || nums.length < 3) return defaultColor; if (nums[0] < 0 || nums[0] > 255 || nums[1] < 0 || nums[1] > 255 || nums[2] < 0 || nums[2] > 255) return defaultColor; return new Color((int)Math.round(nums[0]), (int)Math.round(nums[1]), (int)Math.round(nums[2])); } } // ------------------ Implementation details -------------------------------- /** * Releases the resources used by the display canvas when the applet is stopped. */ public void stop() { if (canvas != null && frame == null) canvas.releaseResources(); } /** * Closes the frame (if any) when the applet is destroyed. */ public synchronized void destroy() { if (frame != null) frame.dispose(); } private Choice exampleMenu; private Button loadExampleButton; private Button launchButton; private String launchButtonName; private Frame frame; private Vector exampleStrings; private String[] colorNames = { "black", "red", "blue", "green", "yellow", "cyan", "magenta", "gray", "darkgray", "lightgray", "pink", "orange", "white" }; private Color[] colors = { Color.black, Color.red, Color.blue, Color.green, Color.yellow, Color.cyan, Color.magenta, Color.gray, Color.darkGray, Color.lightGray, Color.pink, Color.orange, Color.white }; private void define(String str) { // Parse a function definition string from one of the applet parameters named // "Define", "Define1", "Define2", ... String funcDef = str; String name, def; String[] paramNames; int pos; try { pos = funcDef.indexOf("="); if (pos < 0) throw new ParseError("Missing \"=\"", null); def = funcDef.substring(pos+1).trim(); funcDef = funcDef.substring(0,pos); if (def.toLowerCase().startsWith("table")) { name = funcDef; pos = name.indexOf("("); if (pos > 0) name = name.substring(0,pos).trim(); TableFunction tf = parseTableFuncDef(def); tf.setName(name); parser.add(tf); } else { pos = funcDef.indexOf("("); if (pos < 0) throw new ParseError("Missing \"(\"", null); name = funcDef.substring(0,pos).trim(); if (name.length() == 0) throw new ParseError("Missing function name", null); funcDef = funcDef.substring(pos+1); pos = funcDef.indexOf(")"); if (pos < 0) throw new ParseError("Missing \")\"", null); funcDef = funcDef.substring(0,pos).trim(); if (funcDef.length() == 0) throw new ParseError("Missing parameter names", null); StringTokenizer toks = new StringTokenizer(funcDef,","); int ct = toks.countTokens(); paramNames = new String[ct]; for (int i = 0; i < ct; i++) paramNames[i] = toks.nextToken(); new ExpressionFunction(name, paramNames, def, parser); } } catch (ParseError e) { throw new IllegalArgumentException("Error parsing function \"" + str + "\":" + e.getMessage()); } } /** * Create a TableFunction from a string. The string can start with the word "table", * which is ignored. The next item can optionally be one of the table styles "smooth", * "linear", or "step". The default is "smooth". Then values must be specified. If the * next word is "intervals", it can be followed by numbers giving the number of * intervals, the minumum x, and the maximum x, and the y-values for up to (intervals+1) points * evenly distributed between xmin and xmax (unassigned y-values will be zero). * If no data is specified, a table function with * 6 intervals between xmin = -5 and xmax = 5 and all y-values zero is created. * If "intervals" is not specified, the * remaining items are numbers giving pairs of (x,y)-values. Items can be separated * spaces, tabs, and commas. A ParseError will be thrown if the data is illegal. */ protected TableFunction parseTableFuncDef(String def) { try { TableFunction func = new TableFunction(); StringTokenizer toks = new StringTokenizer(def, " \t,"); String tok = null; if (toks.hasMoreTokens()) { tok = toks.nextToken(); if (tok.equalsIgnoreCase("table") && toks.hasMoreTokens()) tok = toks.nextToken(); } if ("step".equalsIgnoreCase(tok)) { func.setStyle(TableFunction.STEP); if (toks.hasMoreTokens()) tok = toks.nextToken(); } else if ("linear".equalsIgnoreCase(tok)) { func.setStyle(TableFunction.PIECEWISE_LINEAR); if (toks.hasMoreTokens()) tok = toks.nextToken(); } else if ("smooth".equalsIgnoreCase(tok) && toks.hasMoreTokens()) { if (toks.hasMoreTokens()) tok = toks.nextToken(); } boolean useIntervals = "intervals".equalsIgnoreCase(tok); if (useIntervals && toks.hasMoreTokens()) tok = toks.nextToken(); double[] nums = new double[toks.countTokens() + 1]; try { nums[0] = (new Double(tok)).doubleValue(); } catch (NumberFormatException e) { throw new ParseError("Unexpected token \"" + tok + "\".",null); } try { for (int i = 1; i < nums.length; i++) nums[i] = (new Double(toks.nextToken())).doubleValue(); } catch (NumberFormatException e) { throw new ParseError("Illegal number.", null); } if (useIntervals) { int ct = (nums.length == 0)? 6 : (int)Math.round(nums[0]); if (ct < 1 || ct > 500) ct = 6; double xmin = (nums.length < 2)? -5 : nums[1]; double xmax = (nums.length < 3)? xmin + 10 : nums[2]; if (xmax <= xmin) throw new ParseError("xmax in table must be greater than xmin", null); func.addIntervals(ct,xmin,xmax); for (int i = 3; i < nums.length && (i-3) <= ct; i++) { if (i-3 < ct) func.setY(i-3,nums[i]); } } else { if (nums.length < 4) throw new ParseError("At least two points must be provided for table function.", null); if (nums.length % 2 == 1) throw new ParseError("Can't define an table function with an odd number of values.", null); for (int i = 0; i < nums.length/2; i++) { func.addPoint(nums[2*i],nums[2*i+1]); } } return func; } catch (Exception e) { throw new ParseError("Error while parsing table function: " + e.getMessage(), null); } } /** * Respond when user clicks a button; not meant to be called directly. */ public void actionPerformed(ActionEvent evt) { Object source = evt.getSource(); if (source == computeButton && computeButton != null) mainController.compute(); else if (source == launchButton && launchButton != null) doLaunchButton(); else if (source == loadExampleButton && exampleStrings != null) { if (exampleStrings.size() == 1) doLoadExample( (String)exampleStrings.elementAt(0) ); else doLoadExample( (String)exampleStrings.elementAt(exampleMenu.getSelectedIndex()) ); } } /** * Respond when user chooses an example from the example menu. (This will only * happen if the param UseLoadButton is not set to "yes".) */ public void itemStateChanged(ItemEvent evt) { if (evt.getSource() == exampleMenu) doLoadExample( (String)exampleStrings.elementAt(exampleMenu.getSelectedIndex()) ); } private synchronized void doLaunchButton() { launchButton.setEnabled(false); if (frame == null) { frame = new Frame(frameTitle); mainPanel = new JCMPanel(); try { setUpMainPanel(); frame.add(mainPanel, BorderLayout.CENTER); } catch (Throwable e) { System.out.println("Error while opening window:"); e.printStackTrace(); TextArea message = new TextArea("An error occurred while setting up this window:\n\n"); message.append(e.toString()); frame.add(message, BorderLayout.CENTER); } frame.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent evt) { frame.dispose(); } public void windowClosed(WindowEvent evt) { frameClosed(); } } ); double[] frameSize = getNumericParam("FrameSize"); if (frameSize == null || frameSize.length < 2 || frameSize[0] < 100 || frameSize[0] > 800 || frameSize[1] < 100 || frameSize[1] > 600) frame.setSize(defaultFrameSize[0], defaultFrameSize[1]); else frame.setSize((int)Math.round(frameSize[0]), (int)Math.round(frameSize[1])); frame.setLocation(50,50); frame.show(); launchButton.setLabel("Close Window"); launchButton.setEnabled(true); } else { frame.dispose(); } } private synchronized void frameClosed() { frame = null; launchButton.setLabel(launchButtonName); launchButton.setEnabled(true); mainPanel = null; canvas = null; limitsPanel = null; inputPanel = null; exampleMenuPanel = null; loadExampleButton = null; computeButton = null; parser = null; } } // end class GenericGraphApplet jcm1-source/IntegralCurves.java 0000644 0000765 0001132 00000062123 11741343635 016013 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * 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.compute(); // 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.compute(); 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(); } } // end class IntegralCurves jcm1-source/AnimatedGraph.java 0000644 0000765 0001132 00000033137 11741343635 015565 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * 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.compute(); 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(); } } // end class FamiliesOfGraphs jcm1-source/SecantTangent.java 0000644 0000765 0001132 00000032455 11741343635 015621 0 ustar dje faculty /************************************************************************** * Copyright (c) 2001, 2005 David J. Eck * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * ---- * * (Released under new license, April 2012.) * * * * David J. Eck * * Department of Mathematics and Computer Science * * Hobart and William Smith Colleges * * 300 Pulteney Street * * Geneva, NY 14456 * * eck@hws.edu * * http://math.hws.edu/eck * **************************************************************************/ // The SecantTangent applet shows the graph of a function with two points // marked on the function. The tangent to the graph is drawn at the first // of these points. A second line is drawn between the two points. // Both of the points can be dragged by the user. Alternatively, the // x-coords of the points can be typed into input boxes. 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.*; public class SecantTangent extends GenericGraphApplet { private Function func; // The function that is graphed. VariableInput x1Input = new VariableInput(); // x-coord where tangent is drawn. VariableInput x2Input = new VariableInput(); // x-coord for other point of secant. protected void setUpParameterDefaults() { parameterDefaults = new java.util.Hashtable(); String varName = getParameter("Variable","x"); parameterDefaults.put("Function", " e ^ " + varName); } 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 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"); Function f = new SimpleFunction( parser.parse(def), xVar ); func = new WrapperFunction(f); } Graph1D graph = new Graph1D(func); Color color = getColorParam("GraphColor",Color.black); graph.setColor(color); // Create two DraggablePoint objects, which will be the points on the canvas // that the user can drag. The x-coordinate of drag1 will be tied later to // x1Input, so that either drag1 or x1Input can be used for setting the // values of the point. Same for drag2 and x2Input. Color tangentColor = getColorParam("TangentColor", Color.red); Color secantColor = getColorParam("SecantColor",new Color(0,200,0)); DraggablePoint drag1 = new DraggablePoint(); // point where tangent is drawn DraggablePoint drag2 = new DraggablePoint(); // other point on secant line drag1.clampY(func); // Both points are clamped to move along the function. drag2.clampY(func); drag1.setColor(tangentColor); drag1.setGhostColor(lighten(tangentColor)); drag2.setColor(secantColor); drag2.setGhostColor(lighten(secantColor)); // Create the tangent line and the secant line. DrawGeometric secant = new DrawGeometric(DrawGeometric.INFINITE_LINE_ABSOLUTE, drag1.getXVar(), drag1.getYVar(), drag2.getXVar(), drag2.getYVar()); secant.setColor(secantColor); TangentLine tangent = new TangentLine(drag1.getXVar(), func); tangent.setColor(tangentColor); canvas.add(drag1); canvas.add(drag2); canvas.add(tangent); canvas.add(secant); canvas.add(graph); // Create a DrawString to display the slopes of the tangent and secant. Value tangentSlope = new ValueMath(func.derivative(1), drag1.getXVar()); Value secantSlope = new ValueMath( new ValueMath(drag2.getYVar(), drag1.getYVar(), '-'), new ValueMath(drag2.getXVar(), drag1.getXVar(), '-'), '/'); DrawString info; if ( "no".equalsIgnoreCase(getParameter("ShowTangentSlope","yes")) ) { info = new DrawString( "Secant Slope = #", DrawString.TOP_LEFT, new Value[] { secantSlope } ); } else { info = new DrawString( "Secant Slope = #\nTangent Slope = #", DrawString.TOP_LEFT, new Value[] { secantSlope, tangentSlope } ); } info.setFont(new Font("Monospaced",Font.PLAIN,10)); info.setNumSize(7); info.setColor(getColorParam("SlopeTextColor", Color.black)); info.setBackgroundColor(getColorParam("SlopeTextBackground",Color.white)); info.setFrameWidth(1); canvas.add(info); // Create input boxes and add them to the bottom of the applet Panel xIn = new Panel(); // not a JCMPanel, since I don't want the input boxes to be controlled // by the main controller anyway. xIn.setBackground(getColorParam("PanelColor",Color.lightGray)); xIn.setLayout(new GridLayout(1,4,3,3)); xIn.add(new Label("Tangent at " + xVar.getName() + " = ", Label.RIGHT)); xIn.add(x1Input); xIn.add(new Label("Secant to " + xVar.getName() + " = ", Label.RIGHT)); xIn.add(x2Input); // Put the inputs at the bottom of the inputPanel, if there is one, otherwise // at the bottom of the mainPanel if (inputPanel == null) mainPanel.add(xIn, BorderLayout.SOUTH); else inputPanel.add(xIn, BorderLayout.SOUTH); // Set up controllers. I want to arrange things so that the controls that position the // two points on the graph do not cause the graph to be recomputted when they are changed. Controller dragControl = new Controller(); // A controller to respond to dragging mainController.remove(canvas); mainController.add(graph); mainController.add(dragControl); // Things in dragController should be recomputed when graph is changed dragControl.add(x1Input); // dragControl checks the contents of the x-inputs dragControl.add(x2Input); // and recomputes everything except the graph. dragControl.add(drag1); dragControl.add(drag2); dragControl.add(tangent); dragControl.add(secant); dragControl.add(info); drag1.setOnUserAction(dragControl); // dragControl.compute() is called when the drag2.setOnUserAction(dragControl); // user drags one of the points or types x1Input.setOnTextChange(dragControl); // in one of the x-input boxes. x2Input.setOnTextChange(dragControl); // By adding Tie's to dragControll, we make sure that the positions of the // draggable points are synchronized with the contents of the x-input boxes. dragControl.add(new Tie((Tieable)drag1.getXVar(), x1Input)); dragControl.add(new Tie((Tieable)drag2.getXVar(), x2Input)); // Get initial values for draggable points double[] d1 = getNumericParam("X1"); double x1 = (d1 != null && d1.length == 1)? d1[0] : 0; x1Input.setVal(x1); drag1.setLocation(x1,0); // y-value will be changed to make the point lie on the curve double[] d2 = getNumericParam("X2"); double x2 = (d2 != null && d2.length == 1)? d2[0] : 1; x2Input.setVal(x2); drag2.setLocation(x2,0); } // end setUpCanvas() private Color lighten(Color c) { // for making "Ghost" color of draggable point int r = c.getRed(); int g = c.getGreen(); int b = c.getBlue(); int nr, ng, nb; if (r <= 200 || g <= 200 || b <= 200) { nb = 255 - (255 - b) / 3; ng = 255 - (255 - g) / 3; nr = 255 - (255 - r) / 3; } else { nb = b / 2; ng = g / 2; nr = r / 2; } return new Color(nr,ng,nb); } 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 SecantTangent applet, the example string should contain // an expression that defines the function to be graphed. This can optionally // be followed by a semicoloon and a list of fourto six 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 // fifth number, if present, gives the x-coord where the tangent line // is drawn. The sixth number gives the x-coord of the second point // on the secant line. 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, " ,"); 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.countTokens() > 0) { // Get point for tangent line try { Double d = new Double(toks.nextToken()); x1Input.setVal( d.doubleValue() ); } catch (NumberFormatException e) { } } if (toks.countTokens() > 0) { // Get other point for secant line try { Double d = new Double(toks.nextToken()); x2Input.setVal( d.doubleValue() ); } 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. // 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.compute(); } // end doLoadExample() } // end class SimpleGraph jcm1-source/README.html 0000644 0000765 0001132 00000002017 11741343635 014032 0 ustar dje faculty
About these source files:
This folder contains the source files for the JCM Project. This Version 1.0.
The Java files in this folder are for a set of configurable applets that use the JCM classes. See the Configurable Applet Collection for more information.
The sources for the JCM classes themselves are contained in the directories edu/hws/jcm/awt, edu/hws/jcm/data, edu/hws/jcm/draw, and edu/hws/jcm/functions.
The home page for the project is http://math.hws.edu/javamath/
David Eck, eck@hws.edu, http://math.hws.edu/eck/