Java/Advanced Graphics/Curve
Содержание
- 1 Animated Graph
- 2 Draw Spline
- 3 Epsilon Delta
- 4 Families Of Graphs
- 5 Input the function and draw the curve
- 6 Integral Curves
- 7 Scatter Plot
- 8 Spline Editor
- 9 Shadow Properties
- 10 Shadow Quality
- 11 Generating a Shadow
- 12 Properties Changes
- 13 Threading Issues
- 14 Trace curve
- 15 Zoom interaction, Text background color, and the effect of transparency
Animated Graph
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
// An applet belonging to the class AnimatedGraph displays a graph // of a function that can depend on a parameter. The value of the // parameter can be "animated" so that it ranges from one value ot // another over a sequence of frames. import java.awt.*; import java.applet.Applet; import java.util.*; import edu.hws.jcm.draw.*; import edu.hws.jcm.data.*; import edu.hws.jcm.functions.*; import edu.hws.jcm.awt.*;
public class AnimatedGraph extends GenericGraphApplet {
// Declare some private variables that are created in one method in // this class and used in a second method. private Function func; // The function that is graphed. private Graph1D graph; // The graph of the function. private Animator animator; // Animates the graph private Variable kVar; // The parameter variable private VariableInput kMin, kMax, kIntervals; // min, max, and number of intervals for the animator. Might be null.
protected void setUpParser() { // Override this to create the animator and add its variable to the parser. int options = Animator.START_STOP_BUTTON | Animator.PAUSE_BUTTON | Animator.LOOP_CHOICE; if ( ! "no".equalsIgnoreCase(getParameter("UseNextAndPrev","yes")) ) options |= Animator.PREV_BUTTON | Animator.NEXT_BUTTON; animator = new Animator(options); kVar = animator.getValueAsVariable( getParameter("Parameter","k") ); parser.add(kVar); super.setUpParser(); parameterDefaults = new Hashtable(); String defaultFunction = xVar.getName() + " / (" + kVar.getName() + " - " + xVar.getName() + "^2)"; parameterDefaults.put("Function",defaultFunction); if (! "no".equalsIgnoreCase(getParameter("UseAnimatorInputs"))) parameterDefaults.put("TwoLimitsColumns","yes"); // change default if we need space for animator inputs } // end setUpParser() protected void setUpBottomPanel() { // Overridden to add the sliders at the bottom of the applet. super.setUpBottomPanel(); // Do the default setup. // If there is a functionInput box, then the SOUTH position of the mainPanel already contains // the inputPanel that contains that box. If so, add the animator to the SOUTH position of // the inputPanel. (This is a good place, in general, to put extra input objects.) // If there is no inputPanel, then the SOUTH position of the mainPanel is empty, so put // the animator there. if (inputPanel != null) inputPanel.add(animator, BorderLayout.SOUTH); else mainPanel.add(animator, BorderLayout.SOUTH); } // end setUpBottomPanel()
protected void setUpCanvas() { // Overridden to add the graph to the canvas and do other chores. super.setUpCanvas(); // Do the default setup. // When setUpCanvas() is called, the functionInput already exists, if one is // to be used, since it is created in setUpBopttomPanel(), which is called // before setUpCanvas. If functionInput exists, add a graph of the function // from functionInput to the canvas. If not, create a graph of the function // specified by the parameter named "Function" (or use sin(k*x) if none is specified). if (functionInput != null) func = functionInput.getFunction(xVar); else { String def = getParameter("Function"); // default value is set in setUpParser() func = new SimpleFunction( parser.parse(def), xVar ); } // Create a graph of the function and add it to the canvas. graph = new Graph1D(func); graph.setColor(getColorParam("GraphColor", Color.magenta)); canvas.add(graph); // Set up the min, max, and intervals property of the animator if (! "no".equalsIgnoreCase(getParameter("UseAnimatorInputs"))) { kMin = new VariableInput(kVar.getName() + "Start",getParameter("ParameterMin","-2")); kMax = new VariableInput(kVar.getName() + "End",getParameter("ParameterMax","2")); kIntervals = new VariableInput("Intervals", getParameter("Intervals","25")); kIntervals.setInputStyle(VariableInput.INTEGER); kIntervals.setMin(1); kIntervals.setMax(1000); kMin.setOnUserAction(mainController); kMax.setOnUserAction(mainController); kIntervals.setOnUserAction(mainController); animator.setMin(kMin); animator.setMax(kMax); animator.setIntervals(kIntervals); if (limitsPanel != null) { // componets will be added to limitsPanel in setUpLimitsPanel() mainController.add(kMin); // This is not done automatically, since they are in a limits panel mainController.add(kMax); mainController.add(kIntervals); } else { JCMPanel ap = new JCMPanel(9,0); ap.setBackground(getColorParam("PanelBackground", Color.lightGray)); ap.add(new Label(kMin.getName())); ap.add(kMin); ap.add(new Label()); ap.add(new Label(kMax.getName())); ap.add(kMax); ap.add(new Label()); ap.add(new Label(kIntervals.getName())); ap.add(kIntervals); ap.add(new Label()); mainPanel.add(ap,BorderLayout.EAST); } } else { try { animator.setMin( (new Double(getParameter("ParameterMin","-2"))).doubleValue() ); animator.setMax( (new Double(getParameter("ParameterMax","2"))).doubleValue() ); animator.setIntervals( (int)Math.round((new Double(getParameter("Intervals","25"))).doubleValue()) ); } catch (NumberFormatException e) { } } animator.setOnChange(mainController); // Add a DrawString to show the current value of the parameter if ( ! "no".equalsIgnoreCase(getParameter("ShowParameter","yes")) ) { DrawString param = new DrawString(kVar.getName() + " = #", DrawString.BOTTOM_LEFT, new Value[] { kVar }); param.setBackgroundColor(canvas.getBackground()); Color c = getColorParam("ParameterColor",Color.black); param.setColor(c); canvas.add(param); } } // end setUpCanvas
protected void setUpLimitsPanel() { super.setUpLimitsPanel(); if (limitsPanel != null && kMin != null) { // add animator inputs to limits panel limitsPanel.addComponentPair(kMin,kMax); limitsPanel.addComponent(kIntervals); } } protected void doLoadExample(String example) { // This method is called when the user loads an example from the // example menu (if there is one). It overrides an empty method // in GenericGraphApplet. // For the AnimatedGraph applet, the example string should contain // an expression that defines the function to be graphed. This can optionally // be followed by a semicolon and a list of four to nine numbers. // The first four numbers give the x- and y-limits to be used for the // example. If they are not present, then -5,5,-5,5 is used. The // next three numbers specify the minimum value for the parameter, the // maximum number, and the number of intervals in the animation. // The eigth number, if present, specifies the starting loop style // for the animation with the following code: 0 for once-through, // 1 for loop, and 2 for back-and-forth. The ninth number, if // present, tells whether to start the animation immediately upon // loading. If it is 1, the animation is started. If it is // not specified or is any value other than 1, the animation is not started. animator.stop(); int pos = example.indexOf(";"); boolean startAnimation = false; double[] limits = { -5,5,-5,5 }; // x- and y-limits to use if (pos > 0) { // Get limits from example text. String nums = example.substring(pos+1); example = example.substring(0,pos); StringTokenizer toks = new StringTokenizer(nums, " ,"); if (toks.countTokens() >= 4) { for (int i = 0; i < 4; i++) { try { Double d = new Double(toks.nextToken()); limits[i] = d.doubleValue(); } catch (NumberFormatException e) { } } } if (toks.hasMoreTokens()) { try { double d = (new Double(toks.nextToken())).doubleValue(); if (kMin == null) animator.setMin(d); else kMin.setVal(d); } catch (NumberFormatException e) { } } if (toks.hasMoreTokens()) { try { double d = (new Double(toks.nextToken())).doubleValue(); if (kMax == null) animator.setMax(d); else kMax.setVal(d); } catch (NumberFormatException e) { } } if (toks.hasMoreTokens()) { try { int d = (int)Math.round((new Double(toks.nextToken())).doubleValue()); if (kIntervals == null) animator.setIntervals(d); else kIntervals.setVal(d); } catch (NumberFormatException e) { } } if (toks.hasMoreTokens()) { try { int d = (int)Math.round((new Double(toks.nextToken())).doubleValue()); animator.setLoopStyle(d); } catch (NumberFormatException e) { } } if (toks.hasMoreTokens()) { try { int d = (int)Math.round((new Double(toks.nextToken())).doubleValue()); startAnimation = (d == 1); } catch (NumberFormatException e) { } } } // Set up the example data and recompute everything. if (functionInput != null) { // If there is a function input box, put the example text in it. functionInput.setText(example); } else { // If there is no user input, set the function in the graph directly. try { func = new SimpleFunction( parser.parse(example), xVar ); graph.setFunction(func); } catch (ParseError e) { // There should"t be parse error"s in the Web-page // author"s examples! If there are, the function // just won"t change. } } CoordinateRect coords = canvas.getCoordinateRect(0); coords.setLimits(limits); coords.setRestoreBuffer(); mainController.rupute(); if (startAnimation) { try { // insert a small delay before animation starts synchronized(this) { wait(250); } } catch (InterruptedException e) { } animator.start(); } } // end doLoadExample() public void stop() { // stop animator when applet is stopped animator.stop(); super.stop(); } public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new AnimatedGraph(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class FamiliesOfGraphs
</source>
Draw Spline
<source lang="java">
import java.awt.*; import javax.swing.*; import no.geosoft.cc.geometry.Geometry; import no.geosoft.cc.geometry.Matrix4x4; import no.geosoft.cc.geometry.spline.SplineFactory; import no.geosoft.cc.graphics.*;
/**
* G demo program. Demonstrates: **
-
*
- A rudimentary sheet music library *
- GObject extension *
- Advanced geometry generation *
* * @author
Epsilon Delta
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
import java.awt.*; import java.applet.Applet; import java.util.StringTokenizer; import edu.hws.jcm.draw.*; import edu.hws.jcm.data.*; import edu.hws.jcm.functions.*; import edu.hws.jcm.awt.*; /**
* An applet for exploring the epsilon-delta definition of a limit. */
public class EpsilonDelta extends GenericGraphApplet {
// Declare some private variables that are created in one method in // this class and used in a second method. private VariableInput xInput; private VariableInput epsilonInput; private VariableInput deltaInput; private VariableInput limitInput; private VariableSlider xSlider; private VariableSlider epsilonSlider; private VariableSlider deltaSlider; private VariableSlider limitSlider; private Controller subController; private Variable xValue, limitValue; private Function func; // The function that is graphed. private Graph1D graph; // The graph of the function. protected void setUpBottomPanel() { super.setUpBottomPanel(); subController = new Controller(); mainController.add(subController); JCMPanel inputs = new JCMPanel(3); subController.add(inputs); inputs.setBackground(getColorParam("PanelBackground", Color.lightGray)); if (inputPanel == null) mainPanel.add(inputs,BorderLayout.SOUTH); else inputPanel.add(inputs,BorderLayout.SOUTH); JCMPanel left = new JCMPanel(0,1,2); JCMPanel right = new JCMPanel(0,1,2); JCMPanel middle = new JCMPanel(0,1,2); inputs.add(middle, BorderLayout.CENTER); inputs.add(left, BorderLayout.WEST); inputs.add(right, BorderLayout.EAST); double[] a = getNumericParam("AValue"); double avalue = (a == null || a.length < 1)? 0 : a[0]; if ("yes".equalsIgnoreCase(getParameter("UseAInput","yes"))) { xSlider = new VariableSlider(); xInput = new VariableInput(); xInput.setVal(avalue); xSlider.setVal(avalue); xInput.setThrowErrors(false); subController.add(new Tie(xSlider, xInput)); xValue = xInput.getVariable(); left.add(new Label("limit at a = ",Label.RIGHT)); middle.add(xSlider); right.add(xInput); } else { xValue = new Variable(); xValue.setVal(avalue); } a = getNumericParam("LimitValue"); double Lvalue = (a == null || a.length < 1)? 1 : a[0]; if ("yes".equalsIgnoreCase(getParameter("UseLimitInput","yes"))) { limitSlider = new VariableSlider(); limitInput = new VariableInput(); limitInput.setVal(Lvalue); limitSlider.setVal(Lvalue); limitInput.setThrowErrors(false); subController.add(new Tie(limitSlider, limitInput)); limitValue = limitInput.getVariable(); left.add(new Label(" test limit L = ",Label.RIGHT)); middle.add(limitSlider); right.add(limitInput); } else { limitValue = new Variable(); limitValue.setVal(Lvalue); } a = getNumericParam("EpsilonValue"); double epsilonValue = (a == null || a.length < 1)? 0.25 : a[0]; epsilonSlider = new VariableSlider(new Constant(0), new Constant(2)); epsilonInput = new VariableInput(); epsilonInput.setVal(epsilonValue); epsilonSlider.setVal(epsilonValue); epsilonInput.setThrowErrors(false); subController.add(new Tie(epsilonSlider, epsilonInput)); left.add(new Label("epsilon = ", Label.RIGHT)); middle.add(epsilonSlider); right.add(epsilonInput); a = getNumericParam("DeltaValue"); double deltaValue = (a == null || a.length < 1)? 1 : a[0]; deltaSlider = new VariableSlider(new Constant(0), new Constant(2)); deltaInput = new VariableInput(); deltaInput.setVal(deltaValue); deltaSlider.setVal(deltaValue); deltaInput.setThrowErrors(false); subController.add(new Tie(deltaSlider, deltaInput)); left.add(new Label("delta = ", Label.RIGHT)); middle.add(deltaSlider); right.add(deltaInput); }
protected void setUpCanvas() { // Override this to add more stuff to the canvas. // When setUpCanvas is called, the functionInput already exists, if one is // to be used, since it is created in setUpBopttomPanel(), which is called // before setUpCanvas. If functionInput exists, add a graph of the function // from functionInput to the canvas. If not, create a graph of the function // specified by the parameter named "Function". if (functionInput != null) func = functionInput.getFunction(xVar); else { String def = getParameter("Function", "abs(" + xVar.getName() + ") ^ " + xVar.getName()); Function f = new SimpleFunction( parser.parse(def), xVar ); func = new WrapperFunction(f); } graph = new Graph1D(func); graph.setColor(getColorParam("GraphColor", Color.black)); Value xMinusDelta = new ValueMath(xValue, deltaInput, "-"); Value xPlusDelta = new ValueMath(xValue, deltaInput, "+"); Value limitMinusEpsilon = new ValueMath(limitValue, epsilonInput, "-"); Value limitPlusEpsilon = new ValueMath(limitValue, epsilonInput, "+"); Value xmin = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMIN); Value xmax = canvas.getCoordinateRect().getValueObject(CoordinateRect.XMAX); Value ymin = canvas.getCoordinateRect().getValueObject(CoordinateRect.YMIN); Value ymax = canvas.getCoordinateRect().getValueObject(CoordinateRect.YMAX); if (xSlider != null) { xSlider.setMin(xmin); xSlider.setMax(xmax); } if (limitSlider != null) { limitSlider.setMin(ymin); limitSlider.setMax(ymax); } DrawGeometric deltaBox = new DrawGeometric(DrawGeometric.RECT_ABSOLUTE, xMinusDelta, ymin, xPlusDelta, ymax); deltaBox.setFillColor(new Color(225,255,225)); deltaBox.setLineWidth(0); DrawGeometric epsilonBox = new DrawGeometric(DrawGeometric.RECT_ABSOLUTE, xmin, limitMinusEpsilon, xmax, limitPlusEpsilon); epsilonBox.setFillColor(new Color(255,230,230)); epsilonBox.setLineWidth(0); DrawGeometric overlap = new DrawGeometric(DrawGeometric.RECT_ABSOLUTE, xMinusDelta, limitMinusEpsilon, xPlusDelta,limitPlusEpsilon); overlap.setFillColor(new Color(255,255,225)); overlap.setColor(Color.yellow); DrawGeometric xLine = new DrawGeometric(DrawGeometric.LINE_ABSOLUTE, xValue, ymin, xValue, ymax); xLine.setColor(new Color(130,255,130)); DrawGeometric limitLine = new DrawGeometric(DrawGeometric.LINE_ABSOLUTE, xmin, limitValue, xmax, limitValue); limitLine.setColor(new Color(255,150,150)); canvas.add(deltaBox); canvas.add(epsilonBox); canvas.add(overlap); canvas.add(xLine); canvas.add(limitLine); DrawString ds = new DrawString("a = #\nL = #\nf(a) = #", DrawString.TOP_LEFT, new Value[] { xValue, limitValue, new ValueMath(func,xValue) }); ds.setBackgroundColor(Color.white); ds.setFrameWidth(1); subController.add(ds); subController.add(deltaBox); subController.add(epsilonBox); subController.add(overlap); subController.add(xLine); subController.add(limitLine); mainController.remove(canvas); mainController.add(graph); canvas.getCoordinateRect().setOnChange(mainController); deltaSlider.setOnUserAction(subController); epsilonSlider.setOnUserAction(subController); deltaInput.setOnTextChange(subController); epsilonInput.setOnTextChange(subController); subController.add(deltaSlider); subController.add(epsilonSlider); subController.add(deltaInput); subController.add(epsilonInput); if (xInput != null) { xSlider.setOnUserAction(subController); xInput.setOnTextChange(subController); subController.add(xSlider); subController.add(xInput); } if (limitInput != null) { limitSlider.setOnUserAction(subController); limitInput.setOnTextChange(subController); subController.add(limitSlider); subController.add(limitInput); } super.setUpCanvas(); // Do the common setup: Add the axes, grid, etc canvas.add(graph); canvas.add(ds); } // end setUpCanvas()
protected void doLoadExample(String example) { // This method is called when the user loads an example from the // example menu (if there is one). It overrides an empty method // in GenericGraphApplet. // After the function definition, there can be a semicolon and // up to ten numbers (numbers can be separated by spaces and/or commas). // The first four numbers specify the limits on the coordinate rect. // . int pos = example.indexOf(";"); double[] limits = { -5,5,-5,5 }; // x- and y-limits to use if (pos > 0) { // get limits from example text String limitsText = example.substring(pos+1); example = example.substring(0,pos); StringTokenizer toks = new StringTokenizer(limitsText, " ,"); double nums[] = new double[toks.countTokens()]; for (int i = 0; i < nums.length; i++) { try { nums[i] = (new Double(toks.nextToken())).doubleValue(); } catch (Exception e) { nums[i] = Double.NaN; } } for (int i = 0; i < 4; i++) if (nums.length >= i && !Double.isNaN(nums[i])) limits[i] = nums[i]; if (nums.length > 4 && !Double.isNaN(nums[4])) xValue.setVal( nums[4] ); else xValue.setVal((limits[0]+limits[1])/2); if (nums.length > 5 && !Double.isNaN(nums[5])) limitValue.setVal( nums[5] ); else limitValue.setVal((limits[0]+limits[1])/2); if (nums.length > 8 && !Double.isNaN(nums[8])) epsilonSlider.setMax( new Constant(nums[8]) ); else epsilonSlider.setMax(new Constant(Math.abs(limits[2]-limits[3])/2)); if (nums.length > 9 && !Double.isNaN(nums[9])) deltaSlider.setMax( new Constant(nums[9]) ); else deltaSlider.setMax(new Constant(Math.abs(limits[0]-limits[1])/2)); if (nums.length > 6 && !Double.isNaN(nums[6])) { epsilonInput.setVal( nums[6] ); epsilonSlider.setVal( nums[6] ); } else { epsilonInput.setVal(Math.abs(limits[2]-limits[3])/8); epsilonSlider.setVal(Math.abs(limits[2]-limits[3])/8); } if (nums.length > 7 && !Double.isNaN(nums[7])) { deltaInput.setVal( nums[7] ); deltaSlider.setVal( nums[7] ); } else { deltaInput.setVal(Math.abs(limits[0]-limits[1])/8); deltaSlider.setVal(Math.abs(limits[0]-limits[1])/8); } } // Set up the example data and recompute everything. if (functionInput != null) { // If there is a function input box, put the example text in it. functionInput.setText(example); } else { // If there is no user input, set the function in the graph directly. // Also, in this case, func is a "WrapperFunction". Set the // definition of that WrapperFunction to be the same as f try { Function f = new SimpleFunction( parser.parse(example), xVar ); ((WrapperFunction)func).setFunction(f); } catch (ParseError e) { // There should"t be parse error"s in the Web-page // author"s examples! If there are, the function // just won"t change. } } CoordinateRect coords = canvas.getCoordinateRect(0); coords.setLimits(limits); coords.setRestoreBuffer(); mainController.rupute(); } // end doLoadExample() public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new EpsilonDelta(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class EpsilonDelta
</source>
Families Of Graphs
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
// An applet belonging to the class FamiliesOfGraphs displays a graph // of a function that can depend on one or more parameters. The values of // the parameters are controlled by the user using sliders at the bottom of // the applet.
import java.awt.*; import java.applet.Applet; import java.util.*; import edu.hws.jcm.draw.*; import edu.hws.jcm.data.*; import edu.hws.jcm.functions.*; import edu.hws.jcm.awt.*;
public class FamiliesOfGraphs extends GenericGraphApplet {
// Declare some private variables that are created in one method in // this class and used in a second method. private Function func; // The function that is graphed. private Graph1D graph; // The graph of the function. private Vector sliders; // Elements of this vector are the VariableSlider // objects that represent the parameter values. // The sliders are created in the setUpParser() method.
protected void setUpParser() { // Override this to add VariableSliders to parser. // Get the data for the sliders from applet params named "Parameter", "Parameter1", ... // The sliders are created and the variables are added to the parser by the // addParameter() method, which is defined below. sliders = new Vector(); int ct = 0; String param = getParameter("Parameter"); if (param == null) { ct++; param = getParameter("Parameter" + ct); } while (true) { if (param == null) break; addParameter(param); ct++; param = getParameter("Parameter" + ct); } // If no parameters were specified in applet params, create one with name "k". if (sliders.size() == 0) addParameter("k"); super.setUpParser(); // Call this last so function definitions // in applet params can use the parameter names // that have just been added to the parser // (even though it"s probably not a good idea). // Note that this also defines the independent variable, // whose name is given by the applet param "Variable" // and which is referred to as xVar in this program. VariableSlider slide = (VariableSlider)sliders.elementAt(0); String def = getParameter("Function", "sin(" + slide.getName() + " * " + xVar.getName() + ")"); parameterDefaults = new Hashtable(); // I want to set a different default value for // the "Function" applet param. parameterDefaults.put("Function",def); } // end setUpParser()
private void addParameter(String data) { // Create a VariableSlider from the information in name and add it to the // Vector of sliders. The data must contain the name of the variable // associated with the slider. The name can be followed by a ";" and up to // three numbers. (If there is no ";", a space after the name will do.) // The numbers can be separated by commas, spaces, or tabs. The first // number gives the minimum value on the slider, the second gives the maximum, // and the third gives the initial value of the slider variable. double min = -5, max = 5, val = 0; // min, max, and value for slider data = data.trim(); int pos = data.indexOf(";"); if (pos < 0) pos = data.indexOf(" "); String name; // The name of the parameter if (pos < 0) { // If there is no space or ";", the data is just the name of the variable. name = data; } else { // Get the name from the front of the data, then look for min, max, and val. String nums = data.substring(pos+1); name = data.substring(0,pos).trim(); StringTokenizer toks = new StringTokenizer(nums," ,\t"); try { if (toks.hasMoreElements()) min = (new Double(toks.nextToken())).doubleValue(); if (toks.hasMoreElements()) max = (new Double(toks.nextToken())).doubleValue(); if (toks.hasMoreElements()) val = (new Double(toks.nextToken())).doubleValue(); } catch (NumberFormatException e) { min = -5; max = 5; val = 0; } } // Create the slider, adding the associated variable to the parser, and set its value. VariableSlider slide = new VariableSlider(name, new Constant(min), new Constant(max), parser); slide.setVal(val); sliders.addElement(slide); // Save the slider in the array of sliders for later use. } // end setUpParser();
protected void setUpBottomPanel() { // Overridden to add the sliders at the bottom of the applet. super.setUpBottomPanel(); // Do the default setup. // Create a panel holding all the sliders, with a display label for each slider to show its value. JCMPanel sliderPanel = new JCMPanel(); sliderPanel.setLayout(new GridLayout(0,1,3,3)); sliderPanel.setBackground(getColorParam("PanelBackground", Color.lightGray)); for (int i = 0; i < sliders.size(); i++) { JCMPanel p = new JCMPanel(); VariableSlider slide = (VariableSlider)sliders.elementAt(i); p.add(slide, BorderLayout.CENTER); p.add(new DisplayLabel(" " + slide.getName() + " = # ", new Value[] { slide.getVariable() } ), BorderLayout.EAST); sliderPanel.add(p); slide.setOnUserAction(mainController); } // If there is a functionInput box, then the SOUTH position of the mainPanel already contains // the inputPanel that contains that box. If so, add the new panel to the SOUTH position of // the inputPanel. (This is a good place, in general, to put extra input objects.) // If there is no inputPanel, then the SOUTH position of the mainPanel is empty, so put // the newly created panel there. if (inputPanel != null) inputPanel.add(sliderPanel, BorderLayout.SOUTH); else mainPanel.add(sliderPanel, BorderLayout.SOUTH); } // end setUpBottomPanel()
protected void setUpCanvas() { // Overridden to add the graph to the canvas. super.setUpCanvas(); // Do the default setup. // When setUpCanvas() is called, the functionInput already exists, if one is // to be used, since it is created in setUpBopttomPanel(), which is called // before setUpCanvas. If functionInput exists, add a graph of the function // from functionInput to the canvas. If not, create a graph of the function // specified by the parameter named "Function" (or use sin(k*x) if none is specified). if (functionInput != null) func = functionInput.getFunction(xVar); else { String def = getParameter("Function"); // default value is set in setUpParser() func = new SimpleFunction( parser.parse(def), xVar ); } // Create a graph of the function and add it to the canvas. graph = new Graph1D(func); graph.setColor(getColorParam("GraphColor", Color.magenta)); canvas.add(graph); } // end setUpCanvas
protected void doLoadExample(String example) { // This method is called when the user loads an example from the // example menu (if there is one). It overrides an empty method // in GenericGraphApplet. // For the FamiliesOfGraphs applet, the example string should contain // an expression that defines the function to be graphed. This can optionally // be followed by a semicolon and a list of four or more numbers. // The first four numbers give the x- and y-limits to be used for the // example. If they are not present, then -5,5,-5,5 is used. The // remaining numbers occur in groups of three and specify the minimumn, // maximum and values of the parameters, in the // same order that they were encountered in the setUpParser() method. int pos = example.indexOf(";"); double[] limits = { -5,5,-5,5 }; // x- and y-limits to use if (pos > 0) { // Get limits from example text. String nums = example.substring(pos+1); example = example.substring(0,pos); StringTokenizer toks = new StringTokenizer(nums, " ,"); if (toks.countTokens() >= 4) { for (int i = 0; i < 4; i++) { try { Double d = new Double(toks.nextToken()); limits[i] = d.doubleValue(); } catch (NumberFormatException e) { } } } int i = 0; while (i < sliders.size() && toks.hasMoreElements()) { // Look for a value for the i-th slider. try { double min = (new Double(toks.nextToken())).doubleValue(); double max = (new Double(toks.nextToken())).doubleValue(); double d = (new Double(toks.nextToken())).doubleValue(); VariableSlider slider = ((VariableSlider)sliders.elementAt(i)); slider.setMin(new Constant(min)); slider.setMax(new Constant(max)); slider.setVal(d); } catch (Exception e) { } i++; } } // Set up the example data and recompute everything. if (functionInput != null) { // If there is a function input box, put the example text in it. functionInput.setText(example); } else { // If there is no user input, set the function in the graph directly. try { func = new SimpleFunction( parser.parse(example), xVar ); graph.setFunction(func); } catch (ParseError e) { // There should"t be parse error"s in the Web-page // author"s examples! If there are, the function // just won"t change. } } CoordinateRect coords = canvas.getCoordinateRect(0); coords.setLimits(limits); coords.setRestoreBuffer(); mainController.rupute(); } // end doLoadExample() public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new FamiliesOfGraphs(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class FamiliesOfGraphs
</source>
Input the function and draw the curve
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
import java.awt.*; import edu.hws.jcm.data.*; import edu.hws.jcm.draw.*; import edu.hws.jcm.awt.*; public class GraphApplet1 extends java.applet.Applet {
public void init() { Parser parser = new Parser(); // The Parser will take the user"s input // and turn it into an Expression. By // default, a Parser knows about the // constants pi and e, the basic arithmetic // operations + - * / ^ (where ^ is // exponentiation), standard functions // (sin, cos, tan, sec, csc, cot, sqrt, // cubert, abs, ln, log2, log10, exp, // trunc, round, floor, ceiling, arcsin, // arccos, arctan). There is also a // conditional "?" operator in the style // of Java and C++. Variable x = new Variable("x"); // For the parser to know about the variable x, parser.add(x); // that variable must be created and added // to the parser. DisplayCanvas canvas = new DisplayCanvas(); // A DisplayCanvas is the fundamental JCM class for displaying // graphical items such as axes and graphs. canvas.setUseOffscreenCanvas(false); // By default, a DisplayCanvas uses // "double buffering", which allows for // smooth animation. However, it does // use extra memory, so I turn it off // in this simple applet. You might notice // that the image flickers a bit when the // applet is redrawn. canvas.setHandleMouseZooms(true); // This tells the canvas to let the user // zoom in and out by clicking, shift-clicking, // and click-and-dragging on the canvas. LimitControlPanel limits = new LimitControlPanel(); // A limit control panel can control the x- and y-limits on a DisplayCanvas. // In the applet, the limit control panel is the gray area containing the // input boxes for xmin, xmax, ymin, and ymax. It also contains a // "Set Limits" button (and can contain other buttons if you want). The // "Set Limits" button is a little redundant because pressing return // in any of the input boxes will accomplish the same thing. However, it // has the advantage of giving the user something obvious to do to // set the limits. limits.addCoords(canvas); // Tells the LimitControlPanel to control the // x- and y-limits on this canvas. The limits // on the canvas and the values in the input // boxes are synchronized. (Try it by clicking // on the graph.) ExpressionInput input = new ExpressionInput("sin(x)+2*cos(3*x)", parser); // An ExpressionInput is a text-input box where the user can enter // an expression. The string "sin(x)+2*cos(3*x)" provides the initial // contents for the box. The parser that is provided as the second // arguments knows about the variable named "x", which makes it // possible to use "x" in the expression. Function func = input.getFunction(x); // To graph, I need a Function, not // not an expression. input.getFunction(x) // gets the contents of the ExpressionInput, // input, considered as a function of the // variable x. Graph1D graph = new Graph1D(func); // This represents a graph of the function, func. // It will be added to the DisplayCanvas, // which will make it appear on the screen. JCMPanel main = new JCMPanel(); // The interface for this applet is constructed // entirely from JCMPanels. This makes much // of the JCM setup automatic. This constructor // makes a JCMPanel that uses a BorderLayout. main.add(canvas, BorderLayout.CENTER); // Add the DisplayCanvas to the panel. main.add(input, BorderLayout.SOUTH); // Add the ExprssionInput. main.add(limits, BorderLayout.EAST); // Add the LimitControlPanel. main.setInsetGap(3); // This leaves a gap of 3 pixels around the edges of the // panel, where the gray background shows through. setLayout(new BorderLayout()); // Set up the Applet itself. add(main, BorderLayout.CENTER); setBackground(Color.lightGray); canvas.add( new Axes() ); // Add a set of Axes to the DisplayCanvas. The labels // on the applet are automatically adjusted when // the limits on the canvas changes. canvas.add(graph); // Add the graph to the canvas. It will be redrawn // whenever necessary. Controller controller = main.getController(); // A Controller is what makes things happen in a JCM applet. The // JCMPanel, main, has a controller that recomputes the JCM components // in the Panel. controller.setErrorReporter(canvas); // Errors in the user"s input need to // be reported somehow. A Controller can // have an ErrorReporter for this purpose. // Currently, the alternatives are to use // a canvas as an error reporter or to // use a "MessagePopup". To see an error // message in the applet, enter any expression // with a syntax error and press return. // Note that the blinking input cursor moves // to the point in the expression where the // error was discovered. limits.setErrorReporter(canvas); // A LimitControlPanel also needs a place to // report errors in the user"s input. main.gatherInputs(); // The JCMPanel must be told to respond to user inputs. // The gatherInputs() method is an easy way to do this, // in many cases. In this applet, since there is only one // input, this is equivalent to the single command // "input.setOnUserAction(controller)," which tells the // input object to notify the controller when the user // presses return in the input box. (Note that input boxes // in a LimitControlPanel are taken care of automatically. // They don"t need to notify a controller. Also note that // I couldn"t use the gatherInputs(controller) method in // the previous ArithmeticApplet, since gatherInputs() // calls the setOnUserAction() method of an input box, but in the // ArithmeticApplet, I wanted to call setOnTextChange(). The // difference is that with setOnUserAction(), the controller is // notified only when the user presses return in the input box // while with setOnTextChange(), the controller is notified // each time the text in the input box changes.) } // end init() public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); java.applet.Applet app = new GraphApplet1(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class SimpleGraph1
</source>
Integral Curves
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
// This applet displays a vector field (f1(x,y),f2(x,y)) and integral curves // for that vector field (although the integral curve feature can be turned off // with an applet param). The drawing of the curves is animated; they are // drawn segment-by-segment. In the default setup, a curve is started when the // user clicks on the canvas. A curve can also be started by entering the // starting x and y coords in a pair of text input boxes and clicking a button. import java.awt.*; import java.awt.event.*; import java.applet.Applet; import java.util.*; import edu.hws.jcm.draw.*; import edu.hws.jcm.data.*; import edu.hws.jcm.functions.*; import edu.hws.jcm.awt.*;
public class IntegralCurves extends GenericGraphApplet {
private Variable yVar; // The seond variable, usually y. private Function xFunc,yFunc; // The functions that give the components of the vector field private ExpressionInput functionInput2; // For inputting yFunc. private VectorField field; // The vector/direction field private Animator animator; // for incrementally drawing integral curves. private Vector curves = new Vector(); // Holds the integral curves private VariableInput deltaT; // input the deltaT for the curve double dt = 0.1; // The value of delat t in the case where there is no deltaT input box private VariableInput xStart,yStart; // Starting point for curve private Choice methodChoice; // select integration method private Button startCurveButton; // user clicks to start curve from (x,y) in xStart, yStart input boxes private Button clearButton; // clears curves private Color curveColor; // color for integral curves private Draw curveDrawer = new Draw(); // A DrawTemp object that draws one segment of the integral curves. private double[] nextPoint = new double[2]; // Help in computing next point of integral curve. private double[] params = new double[2]; // ditto private static final int RK4 = 0, RK2 = 1, EULER = 2; // constants for integration methos private class Curve { // holds the data for one integral curve double dt; int method; double x,y; // point on the curve double lastX = Double.NaN, lastY; // previous point, so we can draw a line. } private class Draw implements DrawTemp { // For drawing the next segment in each integral curve (as a DrawTemp) public void draw(Graphics g, CoordinateRect coords) { int size = curves.size(); g.setColor(curveColor); for (int i = 0; i < size; i++) { Curve c = (Curve)(curves.elementAt(i)); if (! (Double.isNaN(c.x) || Double.isNaN(c.y) || Double.isNaN(c.lastX) || Double.isNaN(c.lastY)) ) { int x1 = coords.xToPixel(c.lastX); int y1 = coords.yToPixel(c.lastY); int x2 = coords.xToPixel(c.x); int y2 = coords.yToPixel(c.y); g.drawLine(x1,y1,x2,y2); } } } } protected void setUpParser() { // create the "y" variable; also set up some parameter defaults. yVar = new Variable(getParameter("Variable2","y")); parser.add(yVar); super.setUpParser(); // sets up xVar, among other things. parameterDefaults = new Hashtable(); parameterDefaults.put("FunctionLabel", " f1(" + xVar.getName() + "," + yVar.getName() + ") = "); parameterDefaults.put("FunctionLabel2", " f2(" + xVar.getName() + "," + yVar.getName() + ") = "); parameterDefaults.put("Function", " " + yVar.getName() + " - 0.1*" + xVar.getName()); parameterDefaults.put("Function2", " - " + xVar.getName() + " - 0.1*" + yVar.getName()); defaultFrameSize = new int[] { 580, 440 }; } protected void setUpCanvas() { // Override this to add more stuff to the canvas. super.setUpCanvas(); // Do the common setup: Add the axes and // set up the vector field and add it to the canvas if (functionInput != null) { xFunc = functionInput.getFunction(new Variable[] {xVar,yVar}); yFunc = functionInput2.getFunction(new Variable[] {xVar,yVar}); } else { String xFuncDef = getParameter("Function"); String yFuncDef = getParameter("Function2"); Function f = new SimpleFunction( parser.parse(xFuncDef), new Variable[] {xVar,yVar} ); xFunc = new WrapperFunction(f); f = new SimpleFunction( parser.parse(yFuncDef), new Variable[] {xVar,yVar} ); yFunc = new WrapperFunction(f); } String type = (getParameter("VectorStyle", "") + "A").toUpperCase(); int style = 0; switch (type.charAt(0)) { case "A": style = VectorField.ARROWS; break; case "L": style = VectorField.LINES; break; case "S": style = VectorField.SCALED_VECTORS; break; } field = new VectorField(xFunc,yFunc,style); Color color = getColorParam("VectorColor"); if (color != null) field.setColor(color); int space = (style == VectorField.LINES)? 20 : 30; double[] d = getNumericParam("VectorSpacing"); if (d != null && d.length > 0 && d[0] >= 1) space = (int)Math.round(d[0]); field.setPixelSpacing(space); canvas.add(field); // Finally, add the graph to the canvas. curveColor = getColorParam("CurveColor", Color.magenta); // add a mouse listener to the canvas for starting curves. if ("yes".equalsIgnoreCase(getParameter("MouseStartsCurves","yes")) && "yes".equalsIgnoreCase(getParameter("DoCurves","yes"))) canvas.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent evt) { CoordinateRect coords = canvas.getCoordinateRect(); double x = coords.pixelToX(evt.getX()); double y = coords.pixelToY(evt.getY()); if (xStart != null) xStart.setVal(x); if (yStart != null) yStart.setVal(y); startCurve(x,y); } }); } // end setUpCanvas() protected void setUpBottomPanel() { // Override this to make a panel containing controls. This is complicated // because it"s possible to turn off a lot of the inputs with applet params. // Check on the value of delta t, which has to be set even if there are no input controls. double[] DT = getNumericParam("DeltaT"); if ( ! (DT == null || DT.length == 0 || DT[0] <= 0) ) dt = DT[0]; boolean doCurves = "yes".equalsIgnoreCase(getParameter("DoCurves","yes")); boolean useInputs = "yes".equalsIgnoreCase(getParameter("UseFunctionInput","yes")); if (!doCurves && !useInputs) // no input controls at all. return; // make the input panel inputPanel = new JCMPanel(); inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) ); mainPanel.add(inputPanel,BorderLayout.SOUTH); // Make the function inputs and the compute button, if these are in the configuration. JCMPanel in1 = null, in2 = null; // hold function inputs, if any if (useInputs) { if ( "yes".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) { String cname = getParameter("ComputeButtonName", "New Functions"); computeButton = new Button(cname); computeButton.addActionListener(this); } functionInput = new ExpressionInput(getParameter("Function"),parser); in1 = new JCMPanel(); in1.add(functionInput,BorderLayout.CENTER); in1.add(new Label(getParameter("FunctionLabel")), BorderLayout.WEST); functionInput.setOnUserAction(mainController); functionInput2 = new ExpressionInput(getParameter("Function2"),parser); in2 = new JCMPanel(); in2.add(functionInput2,BorderLayout.CENTER); in2.add(new Label(getParameter("FunctionLabel2")), BorderLayout.WEST); functionInput2.setOnUserAction(mainController); } // If we"re not doing curves, all we have to do is put the function inputs in the inputPanel if (!doCurves) { Panel p = new JCMPanel(2,1,3); p.add(in1); p.add(in2); inputPanel.add(p, BorderLayout.CENTER); if (computeButton != null) inputPanel.add(computeButton,BorderLayout.EAST); return; } // Now we know that doCurves is true. First, make the animator and clear button animator = new Animator(Animator.STOP_BUTTON); animator.setStopButtonName("Stop Curves"); animator.setOnChange(new Computable() { // animator drives curves public void compute() { extendCurves(); } }); mainController.add(new InputObject() { // curves must stop if main controller is triggered public void checkInput() { curves.setSize(0); animator.stop(); } public void notifyControllerOnChange(Controller c) { } }); clearButton = new Button("Clear"); clearButton.addActionListener(this); // Make a panel to contain the xStart and yStart inputs, if they are in the configuration. Panel bottom = null; if ("yes".equalsIgnoreCase(getParameter("UseStartInputs","yes"))) { xStart = new VariableInput(); xStart.addActionListener(this); yStart = new VariableInput(); yStart.addActionListener(this); bottom = new Panel(); // not a JCMPanel -- I don"t want their contents checked automatically startCurveButton = new Button("Start curve at:"); startCurveButton.addActionListener(this); bottom.add(startCurveButton); bottom.add(new Label(xVar.getName() + " =")); bottom.add(xStart); bottom.add(new Label(yVar.getName() + " =")); bottom.add(yStart); } // Now, make a panel to contain the methodChoice and deltaT input if they are in the configuration. // The animator and clear button will be added to this panel if it exists. If not, and if // an xStart/yStart panel exists, then it will be added there. If neither exists, // it goes in its own panel. The variable bottom ends up pointing to a panel that // contains all the curve controls. boolean useChoice = "yes".equalsIgnoreCase(getParameter("UseMethodChoice","yes")); boolean useDelta = "yes".equalsIgnoreCase(getParameter("UseDeltaInput","yes")); if (useChoice || useDelta) { Panel top = new Panel(); // not a JCMPanel! if (useDelta) { top.add(new Label("dt =")); deltaT = new VariableInput(null,""+dt); top.add(deltaT); } if (useChoice) { top.add(new Label("Method:")); methodChoice = new Choice(); methodChoice.add("Runge-Kutta 4"); methodChoice.add("Runge-Kutta 2"); methodChoice.add("Euler"); top.add(methodChoice); } top.add(animator); top.add(clearButton); if (bottom == null) bottom = top; else { Panel p = new Panel(); p.setLayout(new BorderLayout()); p.add(top, BorderLayout.NORTH); p.add(bottom, BorderLayout.CENTER); bottom = p; } } else { if (bottom == null) bottom = new Panel(); bottom.add(animator); bottom.add(clearButton); } // Add the panels "bottom" to the inputPanel, and ruturn // if there are no function inputs. inputPanel.add(bottom, BorderLayout.CENTER); if (in1 == null) return; // Add the function inputs and compute button to the inputPanel Panel in = new JCMPanel(1,2); in.add(in1); in.add(in2); if (computeButton != null) { Panel p = new JCMPanel(); p.add(in,BorderLayout.CENTER); p.add(computeButton,BorderLayout.EAST); in = p; } inputPanel.add(in,BorderLayout.NORTH); } // end setUpBottomPanel()
public void actionPerformed(ActionEvent evt) { // React if user presses return in xStart or yStart, or pass evt on to GenericGraphApplet Object src = evt.getSource(); if (src == clearButton) { canvas.clearErrorMessage(); curves.setSize(0); animator.stop(); canvas.rupute(); // force recompute of off-screen canvas! } else if (src == xStart || src == yStart || src == startCurveButton) { // Start a curve from x and y values in xStart and yStart canvas.clearErrorMessage(); double x=0, y=0; try { xStart.checkInput(); x = xStart.getVal(); yStart.checkInput(); y = yStart.getVal(); startCurve(x,y); if (deltaT != null) { deltaT.checkInput(); dt = deltaT.getVal(); if (dt <= 0) { deltaT.requestFocus(); throw new JCMError("dt must be positive", deltaT); } } } catch (JCMError e) { curves.setSize(0); animator.stop(); canvas.setErrorMessage(null,"Illegal Data For Curve. " + e.getMessage()); } } else super.actionPerformed(evt); } // end actionPerfromed public void startCurve(double x, double y) { // Start an integral curve at the point (x,y) synchronized (curves) { if (deltaT != null) { try { deltaT.checkInput(); dt = deltaT.getVal(); if (dt <= 0) { deltaT.requestFocus(); throw new JCMError("dt must be positive", deltaT); } } catch (JCMError e) { curves.setSize(0); animator.stop(); canvas.setErrorMessage(null,"Illegal Data For Curve. " + e.getMessage()); return; } } Curve c = new Curve(); c.dt = dt; int method = (methodChoice == null)? RK4 : methodChoice.getSelectedIndex(); c.method = method; c.x = x; c.y = y; curves.addElement(c); animator.start(); } } public void extendCurves() { // Add the next segment to each integral curve. This function // is called repeatedly by the animator. synchronized(curves) { if (canvas == null || canvas.getCoordinateRect() == null) // can happen when frame closes return; while (canvas.getCoordinateRect().getWidth() <= 0) { // need this at startup to make sure that the canvas has appeared on the screen try { Thread.sleep(200); } catch (InterruptedException e) { } } int size = curves.size(); for (int i = 0; i < size; i++) { Curve curve = (Curve)curves.elementAt(i); curve.lastX = curve.x; curve.lastY = curve.y; nextPoint(curve.x, curve.y, curve.dt, curve.method); curve.x = nextPoint[0]; curve.y = nextPoint[1]; } CoordinateRect c = canvas.getCoordinateRect(); double pixelWidthLimit = 100000*c.getPixelWidth(); double pixelHeightLimit = 100000*c.getPixelHeight(); for (int i = size-1; i >= 0; i--) { Curve curve = (Curve)curves.elementAt(i); if (Double.isNaN(curve.x) || Double.isNaN(curve.y) || Math.abs(curve.x) > pixelWidthLimit || Math.abs(curve.y) > pixelWidthLimit) // stop processing this curve curves.removeElementAt(i); } if (curves.size() > 0) canvas.drawTemp(curveDrawer); else { animator.stop(); } } } private void nextPoint(double x, double y, double dt, int method) { // Find next point from (x,y) by applying specified method over time interval dt switch (method) { case EULER: nextEuler(x,y,dt); break; case RK2: nextRK2(x,y,dt); break; case RK4: nextRK4(x,y,dt); break; } } private void nextEuler(double x, double y, double dt) { params[0] = x; params[1] = y; double dx = xFunc.getVal(params); double dy = yFunc.getVal(params); nextPoint[0] = x + dt*dx; nextPoint[1] = y + dt*dy; } private void nextRK2(double x, double y, double dt) { params[0] = x; params[1] = y; double dx1 = xFunc.getVal(params); double dy1 = yFunc.getVal(params); double x2 = x + dt*dx1; double y2 = y + dt*dy1; params[0] = x2; params[1] = y2; double dx2 = xFunc.getVal(params); double dy2 = yFunc.getVal(params); nextPoint[0] = x + 0.5*dt*(dx1+dx2); nextPoint[1] = y + 0.5*dt*(dy1+dy2); } private void nextRK4(double x, double y, double dt) { params[0] = x; params[1] = y; double dx1 = xFunc.getVal(params); double dy1 = yFunc.getVal(params); double x2 = x + 0.5*dt*dx1; double y2 = y + 0.5*dt*dy1; params[0] = x2; params[1] = y2; double dx2 = xFunc.getVal(params); double dy2 = yFunc.getVal(params); double x3 = x + 0.5*dt*dx2; double y3 = y + 0.5*dt*dy2; params[0] = x3; params[1] = y3; double dx3 = xFunc.getVal(params); double dy3 = yFunc.getVal(params); double x4 = x + dt*dx3; double y4 = y + dt*dy3; params[0] = x4; params[1] = y4; double dx4 = xFunc.getVal(params); double dy4 = yFunc.getVal(params); nextPoint[0] = x + (dt / 6) * (dx1 + 2 * dx2 + 2 * dx3 + dx4); nextPoint[1] = y + (dt / 6) * (dy1 + 2 * dy2 + 2 * dy3 + dy4); } protected void doLoadExample(String example) { // This method is called when the user loads an example from the // example menu (if there is one). It overrides an empty method // in GenericGraphApplet. // For the IntegrapCurves applet, the example string should contain // two expression that defines the vector field, separated // by a semicolon. This can optionally // be followed by another semicolon and a list of numbers, separated by spaces and/or commas. // The first four numbers give the x- and y-limits to be used for the // example. If they are not present, then -5,5,-5,5 is used. The next number, if present, // specifies a value for delta t. If there are more numbers, they should come in pairs. // each pair specifies a point where a curve will be started when the // example is loaded. There is a 0.5 second delay between loading and starting the // curves to allow time for the redrawing (although it seems to block the redrawing, at least // on some platforms). if (animator != null) { curves.setSize(0); animator.stop(); } int pos = example.indexOf(";"); if (pos == -1) return; // illegal example -- must have two functions String example2 = example.substring(pos+1); example = example.substring(0,pos); pos = example2.indexOf(";"); double[] limits = { -5,5,-5,5 }; // x- and y-limits to use StringTokenizer toks = null; if (pos > 0) { // Get limits from example2 text. String nums = example2.substring(pos+1); example2 = example2.substring(0,pos); toks = new StringTokenizer(nums, " ,"); if (toks.countTokens() >= 4) { for (int i = 0; i < 4; i++) { try { Double d = new Double(toks.nextToken()); limits[i] = d.doubleValue(); } catch (NumberFormatException e) { } } } if (toks.hasMoreTokens()) { double d = Double.NaN; try { d = (new Double(toks.nextToken())).doubleValue(); } catch (NumberFormatException e) { } if (Double.isNaN(d) || d <= 0 || d > 100) d = 0.1; if (deltaT != null) deltaT.setVal(d); else dt = d; } } // Set up the example data and recompute everything. if (functionInput != null) { // If there is a function input box, put the example text in it. functionInput.setText(example); functionInput2.setText(example2); } else { // If there is no user input, set the function in the graph directly. try { Function f = new SimpleFunction( parser.parse(example), xVar ); ((WrapperFunction)xFunc).setFunction(f); Function g = new SimpleFunction( parser.parse(example2), xVar ); ((WrapperFunction)yFunc).setFunction(g); } catch (ParseError e) { // There should"t be parse error"s in the Web-page // author"s examples! If there are, the function // just won"t change. } } CoordinateRect coords = canvas.getCoordinateRect(0); coords.setLimits(limits); coords.setRestoreBuffer(); mainController.rupute(); if (animator != null && toks != null) { // get any extra nums from the tokenizer and use them as starting points for curves int ct = 2*(toks.countTokens()/2); if (ct > 0) { synchronized(curves) { for (int i = 0; i < ct; i++) { try { double x = (new Double(toks.nextToken())).doubleValue(); double y = (new Double(toks.nextToken())).doubleValue(); startCurve(x,y); } catch (Exception e) { } } if (curves.size() > 0) { // start the curves going try { Thread.sleep(500); // wait a bit to give the canvas time to start drawing itself. } catch (InterruptedException e) { } } } } } } // end doLoadExample() public void stop() { // stop animator when applet is stopped if (animator != null) { curves.setSize(0); animator.stop(); } super.stop(); } public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new IntegralCurves(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class IntegralCurves
</source>
Scatter Plot
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
import edu.hws.jcm.awt.*; import edu.hws.jcm.data.*; import edu.hws.jcm.draw.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; import java.applet.Applet; /**
* A ScatterPlotApplet shows a scatter plot of data from a DataTableInput. * The user can enter the data in a two-column table that is shown in * the applet. It is also possible to configure the applet with a menu * of file names. These files, which must be in the same directory as * the Web page on which the applet appears, will appear in a menu. * A file can contain data for the table, with two numbers per line. * When the user loads the file, the data replaces the data in the table. */
public class ScatterPlotApplet extends Applet implements ActionListener {
private Frame frame; // If non-null, a separate window. private String frameTitle; // Title for the separate window. private Button launchButton; // If non-null, then clicking this buttons opens a separate window. private String launchButtonName; // Name for the launch button. private DataTableInput table; // The table for input of data. private ScatterPlot scatterPlot; // The scatter plot of the data. private DisplayCanvas canvas; // The DisplayCanvas on which the plot is drawn. private Button loadFileButton; // When clicked, a data file is loaded. private Choice fileMenu; // Pop-up menu containing names of functions. private String[] fileNames; // Names of data files associated with menu entries. private Controller mainController; // Controller from the main JCMPanel. /** * The init() method is called by the system to set up the applet. * If the applet does not appear as a button, then init() creates the main panel of the applet * and calls setUpMainPanel to set it up. */ public void init() { frameTitle = getParameter("FrameTitle"); // Get title to be used for separate window, if any. if (frameTitle == null) { frameTitle = "Scatter Plots"; int pos = frameTitle.lastIndexOf("."); if (pos > -1) frameTitle = frameTitle.substring(pos+1); } setLayout(new BorderLayout()); int height = getSize().height; launchButtonName = getParameter("LaunchButtonName"); if ( (height > 0 && height <= 50) || launchButtonName != null) { // Use a separater window and only show a button in the applet. if (launchButtonName == null) launchButtonName = "Launch " + frameTitle; launchButton = new Button(launchButtonName); add(launchButton, BorderLayout.CENTER); launchButton.addActionListener(this); } else { // Show the main panel in the applet, not in a separate window. add(makeMainPanel(), BorderLayout.CENTER); } } /* * Create the main panel of the applet. */ public Panel makeMainPanel() { // Make the main panel JCMPanel panel = new JCMPanel(2); mainController = panel.getController(); panel.setBackground(new Color(0,0,180)); panel.setInsetGap(2); setLayout(new BorderLayout()); // Make a DataInputTable with two columns table = new DataTableInput(null, 2); table.setColumnName(0, getParameter("ColumnName1", "X")); table.setColumnName(1, getParameter("ColumnName2", "Y")); table.setThrowErrors(true); if ( "yes".equalsIgnoreCase(getParameter("ShowColumnTitles","yes"))) table.setShowColumnTitles(true); if ( "yes".equalsIgnoreCase(getParameter("ShowRowNumbers","yes"))) table.setShowRowNumbers(true); // Make input boxes for getting expressions that can include // the variables associated with the table. Initially, the // expressions are just the column names. Parser parser = new Parser(); table.addVariablesToParser(parser); ExpressionInput input1 = new ExpressionInput(table.getColumnName(0),parser); input1.setOnUserAction(mainController); ExpressionInput input2 = new ExpressionInput(table.getColumnName(1),parser); input2.setOnUserAction(mainController); // Make a scatter plot that graphs the first expressiong vs. the second expression. scatterPlot = new ScatterPlot(table, input1.getExpression(), input2.getExpression()); if ( ! "yes".equalsIgnoreCase(getParameter("ShowRegressionLine","yes"))) scatterPlot.setShowRegressionLine(false); if ( ! "yes".equalsIgnoreCase(getParameter("MissingValueIsError","yes"))) scatterPlot.setMissingValueIsError(false); // Create the display canvas where the scater plot will be shown. canvas = new DisplayCanvas(); canvas.add(new Axes()); canvas.add(scatterPlot); mainController.setErrorReporter(canvas); // A compute button to recompute everything. ComputeButton computeButton = new ComputeButton("Update Display"); computeButton.setOnUserAction(mainController); computeButton.setBackground(Color.lightGray); // A menu of files that can be loaded. If no filenames are provided as // applet parameters, then menu is null. Panel menu = makefileMenu(); // Lay out the components in the applet. JCMPanel inputPanel = null; Panel bottom = null; //might not be a JCMPanel if ( "yes".equalsIgnoreCase(getParameter("UseExpressionInputs","yes"))) { inputPanel = new JCMPanel(1,2); inputPanel.setBackground(Color.lightGray); JCMPanel leftInput = new JCMPanel(); leftInput.add(new Label(" Plot: "), BorderLayout.WEST); leftInput.add(input1, BorderLayout.CENTER); inputPanel.add(leftInput); JCMPanel rightInput = new JCMPanel(); rightInput.add(new Label(" versus: "), BorderLayout.WEST); rightInput.add(input2, BorderLayout.CENTER); inputPanel.add(rightInput); bottom = new JCMPanel(new BorderLayout(12,3)); bottom.add(inputPanel, BorderLayout.CENTER); bottom.add(computeButton, BorderLayout.EAST); } if ( scatterPlot.getShowRegressionLine() && "yes".equalsIgnoreCase(getParameter("ShowStats","yes")) ) { // Make a display label to show some statistics about the data. DisplayLabel dl = new DisplayLabel( "Slope = #; Intercept = #; Correlation = #", new Value[] { scatterPlot.getValueObject(ScatterPlot.SLOPE), scatterPlot.getValueObject(ScatterPlot.INTERCEPT), scatterPlot.getValueObject(ScatterPlot.CORRELATION) } ); dl.setAlignment(Label.CENTER); dl.setBackground(Color.lightGray); dl.setForeground(new Color(200,0,0)); dl.setFont(new Font("Serif",Font.PLAIN,14)); if (bottom != null) bottom.add(dl, BorderLayout.SOUTH); else { bottom = new JCMPanel(new BorderLayout(12,3)); bottom.add(dl, BorderLayout.CENTER); bottom.add(computeButton, BorderLayout.EAST); } } if (bottom == null) { if (menu != null) menu.add(computeButton, BorderLayout.EAST); else { bottom = new Panel(); bottom.add(computeButton); } } panel.add(canvas, BorderLayout.CENTER); panel.add(table, BorderLayout.WEST); if (bottom != null) panel.add(bottom, BorderLayout.SOUTH); if (menu != null) panel.add(menu, BorderLayout.NORTH); else { String title = getParameter("PanelTitle"); if (title != null) { Label pt = new Label(title, Label.CENTER); pt.setBackground(Color.lightGray); pt.setForeground(new Color(200,0,0)); pt.setFont(new Font("Serif",Font.PLAIN,14)); panel.add(pt, BorderLayout.NORTH); } } return panel; } // end makeMainPanel() private Panel makefileMenu() { // If the applet tag contains params named "File", "File1", "File2", ..., use // their values to make a file menu. If the value of the param contains a ";", // then the first part, up to the ";", goes into the menu and the second part // is the name of the file. If there is no ";", then the entire value is // shown in the menu and is also used as the name of the file. The actual // files must be in the same directory as the Web page that contains the applet. Vector names = new Vector(); fileMenu = new Choice(); String file = getParameter("File"); int ct = 1; if (file == null) { file = getParameter("File1"); ct = 2; } while (file != null) { file = file.trim(); int pos = file.indexOf(";"); String menuEntry; if (pos == -1) menuEntry = file; else { menuEntry = file.substring(0,pos).trim(); file = file.substring(pos+1).trim(); } names.addElement(file); fileMenu.add(menuEntry); file = getParameter("File" + ct); ct++; } if (names.size() == 0) { fileMenu = null; return null; } else { fileNames = new String[names.size()]; for (int i = 0; i < names.size(); i++) fileNames[i] = (String)names.elementAt(i); Panel p = new Panel(); p.setBackground(Color.lightGray); p.setLayout(new BorderLayout(5,5)); p.add(fileMenu,BorderLayout.CENTER); loadFileButton = new Button("Load Data File: "); loadFileButton.addActionListener(this); p.add(loadFileButton,BorderLayout.WEST); fileMenu.setBackground(Color.white); return p; } } private void doLoadFile(String name) { // Load the file from the same directory as the Web page and put the data // from the file into the table. The file should contain two numbers on // each line. InputStream in; try { URL url = new URL(getDocumentBase(), name); in = url.openStream(); } catch (Exception e) { canvas.setErrorMessage(null,"Unable to open file named \"" + name + "\": " + e); return; } Reader inputReader = new InputStreamReader(in); try { table.readFromStream(inputReader); inputReader.close(); } catch (Exception e) { canvas.setErrorMessage(null,"Unable to get data from file \"" + name + "\": " + e.getMessage()); return; } mainController.rupute(); } /** * Respond when user clicks a button; not meant to be called directly. * This opens and closes the separate window. */ synchronized public void actionPerformed(ActionEvent evt) { Object source = evt.getSource(); if (loadFileButton != null && source == loadFileButton) { doLoadFile( fileNames[fileMenu.getSelectedIndex()] ); } else if (source == launchButton && launchButton != null) { // Open or close separate frame. launchButton.setEnabled(false); if (frame == null) { frame = new Frame(frameTitle); frame.add(makeMainPanel()); frame.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent evt) { frame.dispose(); } public void windowClosed(WindowEvent evt) { frameClosed(); } } ); frame.pack(); frame.setLocation(50,50); frame.show(); launchButton.setLabel("Close Window"); launchButton.setEnabled(true); } else { frame.dispose(); } } } synchronized private void frameClosed() { // respond when separate window closes. frame = null; launchButton.setLabel(launchButtonName); launchButton.setEnabled(true); } /** * Return the applet parameter with a given param name, but if no * such applet param exists, return a default value instead. */ protected String getParameter(String paramName, String defaultValue) { String val = getParameter(paramName); return (val == null)? defaultValue : val; } public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new SimpleGraph(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class ScatterPlotApplet
</source>
Spline Editor
<source lang="java">
/**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
import java.awt.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.awt.geom.*; import java.beans.*; import java.io.*; import java.text.*; import java.util.*; import javax.imageio.*; import javax.swing.*; import javax.swing.event.*; import java.awt.image.*; import org.jdesktop.animation.timing.interpolation.*; import org.jdesktop.animation.timing.*; import org.jdesktop.animation.timing.Animator.*; import org.jdesktop.animation.timing.interpolation.*; import java.awt.image.*; import javax.swing.border.*; import java.net.*; public class SplineEditor extends JFrame {
public SplineEditor() throws HeadlessException { super("Spline Editor"); add(buildHeader(), BorderLayout.NORTH); add(buildControlPanel(), BorderLayout.CENTER); pack(); setLocationRelativeTo(null); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); } private Component buildHeader() { ImageIcon icon = new ImageIcon(getClass().getResource("simulator.png")); HeaderPanel header = new HeaderPanel(icon, "Timing Framework Spline Editor", "Drag control points in the display to change the shape of the spline.", "Click the Copy Code button to generate the corresponding Java code."); return header; } private Component buildControlPanel() { return new SplineControlPanel(); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (UnsupportedLookAndFeelException e) { } new SplineEditor().setVisible(true); } }); }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class EquationDisplay extends JComponent implements PropertyChangeListener {
private static final Color COLOR_BACKGROUND = Color.WHITE; private static final Color COLOR_MAJOR_GRID = Color.GRAY.brighter(); private static final Color COLOR_MINOR_GRID = new Color(220, 220, 220); private static final Color COLOR_AXIS = Color.BLACK; private static final float STROKE_AXIS = 1.2f; private static final float STROKE_GRID = 1.0f; private static final float COEFF_ZOOM = 1.1f; private java.util.List<DrawableEquation> equations; protected double minX; protected double maxX; protected double minY; protected double maxY; private double originX; private double originY; private double majorX; private int minorX; private double majorY; private int minorY; private boolean drawText = true; private Point dragStart; private NumberFormat formatter; private ZoomHandler zoomHandler; private PanMotionHandler panMotionHandler; private PanHandler panHandler; public EquationDisplay(double originX, double originY, double minX, double maxX, double minY, double maxY, double majorX, int minorX, double majorY, int minorY) { if (minX >= maxX) { throw new IllegalArgumentException("minX must be < to maxX"); } if (originX < minX || originX > maxX) { throw new IllegalArgumentException("originX must be between minX and maxX"); } if (minY >= maxY) { throw new IllegalArgumentException("minY must be < to maxY"); } if (originY < minY || originY > maxY) { throw new IllegalArgumentException("originY must be between minY and maxY"); } if (minorX <= 0) { throw new IllegalArgumentException("minorX must be > 0"); } if (minorY <= 0) { throw new IllegalArgumentException("minorY must be > 0"); } if (majorX <= 0.0) { throw new IllegalArgumentException("majorX must be > 0.0"); } if (majorY <= 0.0) { throw new IllegalArgumentException("majorY must be > 0.0"); } this.originX = originX; this.originY = originY; this.minX = minX; this.maxX = maxX; this.minY = minY; this.maxY = maxY; this.majorX = majorX; this.minorX = minorX; this.majorY = majorY; this.minorY = minorY; this.equations = new LinkedList<DrawableEquation>(); this.formatter = NumberFormat.getInstance(); this.formatter.setMaximumFractionDigits(2); panHandler = new PanHandler(); addMouseListener(panHandler); panMotionHandler = new PanMotionHandler(); addMouseMotionListener(panMotionHandler); zoomHandler = new ZoomHandler(); addMouseWheelListener(zoomHandler); } @Override public void setEnabled(boolean enabled) { if (isEnabled() != enabled) { //super.setEnabled(enabled); if (enabled) { addMouseListener(panHandler); addMouseMotionListener(panMotionHandler); addMouseWheelListener(zoomHandler); } else { removeMouseListener(panHandler); removeMouseMotionListener(panMotionHandler); removeMouseWheelListener(zoomHandler); } } } public boolean isDrawText() { return drawText; } public void setDrawText(boolean drawText) { this.drawText = drawText; } public void addEquation(AbstractEquation equation, Color color) { if (equation != null && !equations.contains(equation)) { equation.addPropertyChangeListener(this); equations.add(new DrawableEquation(equation, color)); repaint(); } } public void removeEquation(AbstractEquation equation) { if (equation != null) { DrawableEquation toRemove = null; for (DrawableEquation drawable: equations) { if (drawable.getEquation() == equation) { toRemove = drawable; break; } } if (toRemove != null) { equation.removePropertyChangeListener(this); equations.remove(toRemove); repaint(); } } } @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } public void propertyChange(PropertyChangeEvent evt) { repaint(); } protected double yPositionToPixel(double position) { double height = (double) getHeight(); return height - ((position - minY) * height / (maxY - minY)); } protected double xPositionToPixel(double position) { return (position - minX) * (double) getWidth() / (maxX - minX); } protected double xPixelToPosition(double pixel) { double axisV = xPositionToPixel(originX); return (pixel - axisV) * (maxX - minX) / (double) getWidth(); } protected double yPixelToPosition(double pixel) { double axisH = yPositionToPixel(originY); return (getHeight() - pixel - axisH) * (maxY - minY) / (double) getHeight(); } @Override protected void paintComponent(Graphics g) { if (!isVisible()) { return; } Graphics2D g2 = (Graphics2D) g; setupGraphics(g2); paintBackground(g2); drawGrid(g2); drawAxis(g2); drawEquations(g2); paintInformation(g2); } protected void paintInformation(Graphics2D g2) { } private void drawEquations(Graphics2D g2) { for (DrawableEquation drawable: equations) { g2.setColor(drawable.getColor()); drawEquation(g2, drawable.getEquation()); } } private void drawEquation(Graphics2D g2, AbstractEquation equation) { float x = 0.0f; float y = (float) yPositionToPixel(equation.rupute(xPixelToPosition(0.0))); GeneralPath path = new GeneralPath(); path.moveTo(x, y); for (x = 0.0f; x < getWidth(); x += 1.0f) { double position = xPixelToPosition(x); y = (float) yPositionToPixel(equation.rupute(position)); path.lineTo(x, y); } g2.draw(path); } private void drawGrid(Graphics2D g2) { Stroke stroke = g2.getStroke(); drawVerticalGrid(g2); drawHorizontalGrid(g2); if (drawText) { drawVerticalLabels(g2); drawHorizontalLabels(g2); } g2.setStroke(stroke); } private void drawHorizontalLabels(Graphics2D g2) { double axisV = xPositionToPixel(originX); g2.setColor(COLOR_AXIS); for (double y = originY + majorY; y < maxY + majorY; y += majorY) { int position = (int) yPositionToPixel(y); g2.drawString(formatter.format(y), (int) axisV + 5, position); } for (double y = originY - majorY; y > minY - majorY; y -= majorY) { int position = (int) yPositionToPixel(y); g2.drawString(formatter.format(y), (int) axisV + 5, position); } } private void drawHorizontalGrid(Graphics2D g2) { double minorSpacing = majorY / minorY; double axisV = xPositionToPixel(originX); Stroke gridStroke = new BasicStroke(STROKE_GRID); Stroke axisStroke = new BasicStroke(STROKE_AXIS); for (double y = originY + majorY; y < maxY + majorY; y += majorY) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorY; i++) { int position = (int) yPositionToPixel(y - i * minorSpacing); g2.drawLine(0, position, getWidth(), position); } int position = (int) yPositionToPixel(y); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(0, position, getWidth(), position); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position); } for (double y = originY - majorY; y > minY - majorY; y -= majorY) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorY; i++) { int position = (int) yPositionToPixel(y + i * minorSpacing); g2.drawLine(0, position, getWidth(), position); } int position = (int) yPositionToPixel(y); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(0, position, getWidth(), position); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position); } } private void drawVerticalLabels(Graphics2D g2) { double axisH = yPositionToPixel(originY); FontMetrics metrics = g2.getFontMetrics(); g2.setColor(COLOR_AXIS); for (double x = originX + majorX; x < maxX + majorX; x += majorX) { int position = (int) xPositionToPixel(x); g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight()); } for (double x = originX - majorX; x > minX - majorX; x -= majorX) { int position = (int) xPositionToPixel(x); g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight()); } } private void drawVerticalGrid(Graphics2D g2) { double minorSpacing = majorX / minorX; double axisH = yPositionToPixel(originY); Stroke gridStroke = new BasicStroke(STROKE_GRID); Stroke axisStroke = new BasicStroke(STROKE_AXIS); for (double x = originX + majorX; x < maxX + majorX; x += majorX) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorX; i++) { int position = (int) xPositionToPixel(x - i * minorSpacing); g2.drawLine(position, 0, position, getHeight()); } int position = (int) xPositionToPixel(x); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(position, 0, position, getHeight()); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3); } for (double x = originX - majorX; x > minX - majorX; x -= majorX) { g2.setStroke(gridStroke); g2.setColor(COLOR_MINOR_GRID); for (int i = 0; i < minorX; i++) { int position = (int) xPositionToPixel(x + i * minorSpacing); g2.drawLine(position, 0, position, getHeight()); } int position = (int) xPositionToPixel(x); g2.setColor(COLOR_MAJOR_GRID); g2.drawLine(position, 0, position, getHeight()); g2.setStroke(axisStroke); g2.setColor(COLOR_AXIS); g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3); } } private void drawAxis(Graphics2D g2) { double axisH = yPositionToPixel(originY); double axisV = xPositionToPixel(originX); g2.setColor(COLOR_AXIS); Stroke stroke = g2.getStroke(); g2.setStroke(new BasicStroke(STROKE_AXIS)); g2.drawLine(0, (int) axisH, getWidth(), (int) axisH); g2.drawLine((int) axisV, 0, (int) axisV, getHeight()); FontMetrics metrics = g2.getFontMetrics(); g2.drawString(formatter.format(0.0), (int) axisV + 5, (int) axisH + metrics.getHeight()); g2.setStroke(stroke); } protected void setupGraphics(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } protected void paintBackground(Graphics2D g2) { g2.setColor(COLOR_BACKGROUND); g2.fill(g2.getClipBounds()); } private class DrawableEquation { private AbstractEquation equation; private Color color; DrawableEquation(AbstractEquation equation, Color color) { this.equation = equation; this.color = color; } AbstractEquation getEquation() { return equation; } Color getColor() { return color; } } private class ZoomHandler implements MouseWheelListener { public void mouseWheelMoved(MouseWheelEvent e) { double distanceX = maxX - minX; double distanceY = maxY - minY; double cursorX = minX + distanceX / 2.0; double cursorY = minY + distanceY / 2.0; int rotation = e.getWheelRotation(); if (rotation < 0) { distanceX /= COEFF_ZOOM; distanceY /= COEFF_ZOOM; } else { distanceX *= COEFF_ZOOM; distanceY *= COEFF_ZOOM; } minX = cursorX - distanceX / 2.0; maxX = cursorX + distanceX / 2.0; minY = cursorY - distanceY / 2.0; maxY = cursorY + distanceY / 2.0; repaint(); } } private class PanHandler extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { dragStart = e.getPoint(); } } private class PanMotionHandler extends MouseMotionAdapter { @Override public void mouseDragged(MouseEvent e) { Point dragEnd = e.getPoint(); double distance = xPixelToPosition(dragEnd.getX()) - xPixelToPosition(dragStart.getX()); minX -= distance; maxX -= distance; distance = yPixelToPosition(dragEnd.getY()) - yPixelToPosition(dragStart.getY()); minY -= distance; maxY -= distance; repaint(); dragStart = dragEnd; } }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
interface Equation {
public double compute(double variable);
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
abstract class AbstractEquation implements Equation {
protected java.util.List<PropertyChangeListener> listeners; protected AbstractEquation() { this.listeners = new LinkedList<PropertyChangeListener>(); } public void addPropertyChangeListener(PropertyChangeListener listener) { if (listener != null && !listeners.contains(listener)) { listeners.add(listener); } } public void removePropertyChangeListener(PropertyChangeListener listener) { if (listener != null) { listeners.remove(listener); } } protected void firePropertyChange(String propertyName, double oldValue, double newValue) { PropertyChangeEvent changeEvent = new PropertyChangeEvent(this, propertyName, oldValue, newValue); for (PropertyChangeListener listener: listeners) { listener.propertyChange(changeEvent); } }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class AbstractSimulator extends JComponent {
protected double time; public AbstractSimulator() { this.time = 0.0f; } public void setTime(double time) { this.time = time; repaint(); }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class BouncerSimulator extends AbstractSimulator {
private static final Color COLOR_BACKGROUND = Color.WHITE; private BufferedImage image; public BouncerSimulator() { try { image = ImageIO.read(BouncerSimulator.class.getResource("item.png")); } catch (Exception e) { } } @Override protected void paintComponent(Graphics g) { if (!isVisible()) { return; } Graphics2D g2 = (Graphics2D) g; setupGraphics(g2); drawBackground(g2); drawItem(g2); } private void drawItem(Graphics2D g2) { double position = time; double xPos = position * getWidth() / 2; int width = getWidth() * 2 / 3; int x = (getWidth() - width) / 2; x += xPos; int y = getHeight() / 2; y -= image.getHeight() / 2; g2.drawImage(image, null, x, y); } private void setupGraphics(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } private void drawBackground(Graphics2D g2) { g2.setColor(COLOR_BACKGROUND); g2.fill(g2.getClipBounds()); } @Override public Dimension getPreferredSize() { return new Dimension(150, 100); }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class DropSimulator extends AbstractSimulator {
private static final Color COLOR_BACKGROUND = Color.WHITE; private BufferedImage image; private BufferedImage shadow; private float angle = 90; private int distance = 20; // cached values for fast painting private int distance_x = 0; private int distance_y = 0; public DropSimulator() { try { image = ImageIO.read(BouncerSimulator.class.getResource("icon.png")); ShadowFactory factory = new ShadowFactory(5, 0.5f, Color.BLACK); shadow = factory.createShadow(image); } catch (Exception e) { } } @Override protected void paintComponent(Graphics g) { if (!isVisible()) { return; } Graphics2D g2 = (Graphics2D) g; setupGraphics(g2); drawBackground(g2); drawItem(g2); } private void drawItem(Graphics2D g2) { double position = time; int width = (int) (shadow.getWidth() / 2 * (1.0 + position)); int height = (int) (shadow.getHeight() / 2 * (1.0 + position)); int x = (getWidth() - width) / 2; int y = (getHeight() - height) / 2; Composite composite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f - (0.5f * (float) position))); computeShadowPosition((position * distance) + 1.0); g2.drawImage(shadow, x + distance_x, y + distance_y, width, height, null); g2.setComposite(composite); width = (int) (image.getWidth() / 2 * (1.0 + position)); height = (int) (image.getHeight() / 2 * (1.0 + position)); x = (getWidth() - width) / 2; y = (getHeight() - height) / 2; g2.drawImage(image, x, y, width, height, null); } private void setupGraphics(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); } private void drawBackground(Graphics2D g2) { g2.setColor(COLOR_BACKGROUND); g2.fill(g2.getClipBounds()); } @Override public Dimension getPreferredSize() { return new Dimension(150, 100); } private void computeShadowPosition(double distance) { double angleRadians = Math.toRadians(angle); distance_x = (int) (Math.cos(angleRadians) * distance); distance_y = (int) (Math.sin(angleRadians) * distance); }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class HeaderPanel extends JPanel {
private ImageIcon icon; HeaderPanel(ImageIcon icon, String title, String help1, String help2) { super(new BorderLayout()); this.icon = icon; JPanel titlesPanel = new JPanel(new GridLayout(3, 1)); titlesPanel.setOpaque(false); titlesPanel.setBorder(new EmptyBorder(12, 0, 12, 0)); JLabel headerTitle = new JLabel(title); Font police = headerTitle.getFont().deriveFont(Font.BOLD); headerTitle.setFont(police); headerTitle.setBorder(new EmptyBorder(0, 12, 0, 0)); titlesPanel.add(headerTitle); JLabel message; titlesPanel.add(message = new JLabel(help1)); police = headerTitle.getFont().deriveFont(Font.PLAIN); message.setFont(police); message.setBorder(new EmptyBorder(0, 24, 0, 0)); titlesPanel.add(message = new JLabel(help2)); police = headerTitle.getFont().deriveFont(Font.PLAIN); message.setFont(police); message.setBorder(new EmptyBorder(0, 24, 0, 0)); message = new JLabel(this.icon); message.setBorder(new EmptyBorder(0, 0, 0, 12)); add(BorderLayout.WEST, titlesPanel); add(BorderLayout.EAST, message); add(BorderLayout.SOUTH, new JSeparator(JSeparator.HORIZONTAL)); setPreferredSize(new Dimension(500, this.icon.getIconHeight() + 24)); } public void paintComponent(Graphics g) { super.paintComponent(g); if (!isOpaque()) { return; } Rectangle bounds = g.getClipBounds(); Color control = UIManager.getColor("control"); int width = getWidth(); Graphics2D g2 = (Graphics2D) g; Paint storedPaint = g2.getPaint(); g2.setPaint(new GradientPaint(this.icon.getIconWidth(), 0, Color.white, width, 0, control)); g2.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); g2.setPaint(storedPaint); }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class Java2dHelper {
public static BufferedImage createCompatibleImage(int width, int height) { GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice screenDevice = environment.getDefaultScreenDevice(); GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration(); return configuration.createCompatibleImage(width, height); } public static BufferedImage loadCompatibleImage(URL resource) throws IOException { BufferedImage image = ImageIO.read(resource); BufferedImage compatibleImage = createCompatibleImage(image.getWidth(), image.getHeight()); Graphics g = compatibleImage.getGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); image = null; return compatibleImage; } public static BufferedImage createThumbnail(BufferedImage image, int requestedThumbSize) { float ratio = (float) image.getWidth() / (float) image.getHeight(); int width = image.getWidth(); BufferedImage thumb = image; do { width /= 2; if (width < requestedThumbSize) { width = requestedThumbSize; } BufferedImage temp = new BufferedImage(width, (int) (width / ratio), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = temp.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null); g2.dispose(); thumb = temp; } while (width != requestedThumbSize); return thumb; }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
*A shadow factory generates a drop shadow for any given picture, respecting * the transparency channel if present. The resulting picture contains the * shadow only and to create a drop shadow effect you will need to stack the * original picture and the shadow generated by the factory. If you are using * Swing you can get this done very easily with the layout * {@link org.jdesktop.swingx.StackLayout}.
*Shadow Properties
*A shadow is defined by three properties: *
-
*
- size: The size, in pixels, of the shadow. This property also * defines the fuzzyness. *
- opacity: The opacity, between 0.0 and 1.0, of the shadow. *
- color: The color of the shadow. Shadows are not meant to be * black only. *
* You can set these properties using the provided mutaters or the appropriate * constructor. Here are two ways of creating a green shadow of size 10 and * with an opacity of 50%:*
* ShadowFactory factory = new ShadowFactory(10, 0.5f, Color.GREEN); * // .. * factory = new ShadowFactory(); * factory.setSize(10); * factory.setOpacity(0.5f); * factory.setColor(Color.GREEN); *
* The default constructor provides the following default values:*
-
*
- size: 5 pixels *
- opacity: 50% *
- color: Black *
Shadow Quality
*The factory provides two shadow generation algorithms: fast quality blur * and high quality blur. You can select your preferred algorithm by * setting the appropriate rendering hint: *
* ShadowFactory factory = new ShadowFactory(); * factory.setRenderingHint(ShadowFactory.KEY_BLUR_QUALITY, * ShadowFactory.VALUE_BLUR_QUALITY_HIGH); ** The default rendering algorihtm is
VALUE_BLUR_QUALITY_FAST
.
* The current implementation should provide the same quality with both * algorithms but performances are guaranteed to be better (about 30 times * faster) with the fast quality blur.
*Generating a Shadow
*A shadow is generated as a BufferedImage
from another
* BufferedImage
. Once the factory is set up, you must call
* {@link #createShadow} to actually generate the shadow:
*
* ShadowFactory factory = new ShadowFactory(); * // factory setup * BufferedImage shadow = factory.createShadow(bufferedImage); *
* The resulting image is of type BufferedImage.TYPE_INT_ARGB
.
* Both dimensions of this image are larger than original image"s:
* -
*
- new width = original width + 2 * shadow size *
- new height = original height + 2 * shadow size *
Properties Changes
*This factory allows to register property change listeners with * {@link #addPropertyChangeListener}. Listening to properties changes is very * useful when you emebed the factory in a graphical component and give the API * user the ability to access the factory. By listening to properties changes, * you can easily repaint the component when needed.
*Threading Issues
*ShadowFactory
is not guaranteed to be thread-safe.
* * @author Romain Guy <romain.guy@mac.ru> * @author Sébastien Petrucci <sebastien_petrucci@yahoo.fr> */
class ShadowFactory {
/***
Key for the blur quality rendering hint.
*/ public static final String KEY_BLUR_QUALITY = "blur_quality"; /***
Selects the fast rendering algorithm. This is the default rendering
* hint for KEY_BLUR_QUALITY
.
*/ public static final String VALUE_BLUR_QUALITY_FAST = "fast"; /***
Selects the high quality rendering algorithm. With current implementation, * This algorithm does not guarantee a better rendering quality and should * not be used.
*/ public static final String VALUE_BLUR_QUALITY_HIGH = "high"; /***
Identifies a change to the size used to render the shadow.
*When the property change event is fired, the old value and the new
* value are provided as Integer
instances.
*/ public static final String SIZE_CHANGED_PROPERTY = "shadow_size"; /***
Identifies a change to the opacity used to render the shadow.
*When the property change event is fired, the old value and the new
* value are provided as Float
instances.
*/ public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity"; /***
Identifies a change to the color used to render the shadow.
*/ public static final String COLOR_CHANGED_PROPERTY = "shadow_color"; // size of the shadow in pixels (defines the fuzziness) private int size = 5; // opacity of the shadow private float opacity = 0.5f; // color of the shadow private Color color = Color.BLACK; // rendering hints map private HashMap<Object, Object> hints; // notifies listeners of properties changes private PropertyChangeSupport changeSupport; /***
Creates a default good looking shadow generator. * The default shadow factory provides the following default values: *
-
*
- size: 5 pixels *
- opacity: 50% *
- color: Black *
- rendering quality: VALUE_BLUR_QUALITY_FAST *
These properties provide a regular, good looking shadow.
*/ public ShadowFactory() { this(5, 0.5f, Color.BLACK); } /***
A shadow factory needs three properties to generate shadows. * These properties are:
*-
*
- size: The size, in pixels, of the shadow. This property also * defines the fuzzyness. *
- opacity: The opacity, between 0.0 and 1.0, of the shadow. *
- color: The color of the shadow. Shadows are not meant to be * black only. *
Besides these properties you can set rendering hints to control the * rendering process. The default rendering hints let the factory use the * fastest shadow generation algorithm.
* @param size The size of the shadow in pixels. Defines the fuzziness. * @param opacity The opacity of the shadow. * @param color The color of the shadow. * @see #setRenderingHint(Object, Object) */ public ShadowFactory(final int size, final float opacity, final Color color) { hints = new HashMap<Object, Object>(); hints.put(KEY_BLUR_QUALITY, VALUE_BLUR_QUALITY_FAST); changeSupport = new PropertyChangeSupport(this); setSize(size); setOpacity(opacity); setColor(color); } /***
Add a PropertyChangeListener to the listener list. The listener is
* registered for all properties. The same listener object may be added
* more than once, and will be called as many times as it is added. If
* listener
is null, no exception is thrown and no action
* is taken.
* @param listener the PropertyChangeListener to be added */ public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } /***
Remove a PropertyChangeListener from the listener list. This removes
* a PropertyChangeListener that was registered for all properties. If
* listener
was added more than once to the same event source,
* it will be notified one less time after being removed. If
* listener
is null, or was never added, no exception is thrown
* and no action is taken.
* @param listener */ public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } /***
Maps the specified rendering hint key
to the specified
* value
in this SahdowFactory
object.
* @param key The rendering hint key * @param value The rendering hint value */ public void setRenderingHint(final Object key, final Object value) { hints.put(key, value); } /***
Gets the color used by the factory to generate shadows.
* @return this factory"s shadow color */ public Color getColor() { return color; } /***
Sets the color used by the factory to generate shadows.
*Consecutive calls to {@link #createShadow} will all use this color * until it is set again.
*If the color provided is null, the previous color will be retained.
* @param shadowColor the generated shadows color */ public void setColor(final Color shadowColor) { if (shadowColor != null) { Color oldColor = this.color; this.color = shadowColor; changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY, oldColor, this.color); } } /***
Gets the opacity used by the factory to generate shadows.
*The opacity is comprised between 0.0f and 1.0f; 0.0f being fully * transparent and 1.0f fully opaque.
* @return this factory"s shadow opacity */ public float getOpacity() { return opacity; } /***
Sets the opacity used by the factory to generate shadows.
*Consecutive calls to {@link #createShadow} will all use this color * until it is set again.
*The opacity is comprised between 0.0f and 1.0f; 0.0f being fully * transparent and 1.0f fully opaque. If you provide a value out of these * boundaries, it will be restrained to the closest boundary.
* @param shadowOpacity the generated shadows opacity */ public void setOpacity(final float shadowOpacity) { float oldOpacity = this.opacity; if (shadowOpacity < 0.0) { this.opacity = 0.0f; } else if (shadowOpacity > 1.0f) { this.opacity = 1.0f; } else { this.opacity = shadowOpacity; } changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY, new Float(oldOpacity), new Float(this.opacity)); } /***
Gets the size in pixel used by the factory to generate shadows.
* @return this factory"s shadow size */ public int getSize() { return size; } /***
Sets the size, in pixels, used by the factory to generate shadows.
*The size defines the blur radius applied to the shadow to create the * fuzziness.
*There is virtually no limit to the size but it has an impact on shadow * generation performances. The greater this value, the longer it will take * to generate the shadow. Remember the generated shadow image dimensions * are computed as follow: *
-
*
- new width = original width + 2 * shadow size *
- new height = original height + 2 * shadow size *
* The size cannot be negative. If you provide a negative value, the size* will be 0 instead.
* @param shadowSize the generated shadows size in pixels (fuzziness) */ public void setSize(final int shadowSize) { int oldSize = this.size; if (shadowSize < 0) { this.size = 0; } else { this.size = shadowSize; } changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY, new Integer(oldSize), new Integer(this.size)); } /***
Generates the shadow for a given picture and the current properties * of the factory.
*The generated shadow image dimensions are computed as follow: *
-
*
- new width = original width + 2 * shadow size *
- new height = original height + 2 * shadow size *
The time taken by a call to this method depends on the size of the * shadow, the larger the longer it takes, and on the selected rendering * algorithm.
* @param image the picture from which the shadow must be cast
* @return the picture containing the shadow of image
*/
public BufferedImage createShadow(final BufferedImage image) {
if (hints.get(KEY_BLUR_QUALITY) == VALUE_BLUR_QUALITY_HIGH) {
// the high quality algorithm is a 3-pass algorithm
// it goes through all the pixels of the original picture at least
// three times to generate the shadow
// it is easy to understand but very slow
BufferedImage subject = prepareImage(image);
BufferedImage shadow = new BufferedImage(subject.getWidth(),
subject.getHeight(),
BufferedImage.TYPE_INT_ARGB);
BufferedImage shadowMask = createShadowMask(subject);
getLinearBlurOp(size).filter(shadowMask, shadow);
return shadow;
}
// call the fast rendering algorithm
return createShadowFast(image);
}
// prepares the picture for the high quality rendering algorithm
private BufferedImage prepareImage(final BufferedImage image) {
BufferedImage subject = new BufferedImage(image.getWidth() + size * 2,
image.getHeight() + size * 2,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = subject.createGraphics();
g2.drawImage(image, null, size, size);
g2.dispose();
return subject;
}
// fast rendering algorithm
// basically applies duplicates the picture and applies a size*size kernel
// in only one pass.
// the kernel is simulated by an horizontal and a vertical pass
// implemented by Sébastien Petrucci
private BufferedImage createShadowFast(final BufferedImage src) {
int shadowSize = this.size;
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
int dstWidth = srcWidth + size;
int dstHeight = srcHeight + size;
int left = (shadowSize - 1) >> 1;
int right = shadowSize - left;
int yStop = dstHeight - right;
BufferedImage dst = new BufferedImage(dstWidth, dstHeight,
BufferedImage.TYPE_INT_ARGB);
int shadowRgb = color.getRGB() & 0x00FFFFFF;
int[] aHistory = new int[shadowSize];
int historyIdx;
int aSum;
ColorModel srcColorModel = src.getColorModel();
WritableRaster srcRaster = src.getRaster();
int[] dstBuffer = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();
int lastPixelOffset = right * dstWidth;
float hSumDivider = 1.0f / size;
float vSumDivider = opacity / size;
// horizontal pass : extract the alpha mask from the source picture and
// blur it into the destination picture
for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {
// first pixels are empty
for (historyIdx = 0; historyIdx < shadowSize; ) {
aHistory[historyIdx++] = 0;
}
aSum = 0;
historyIdx = 0;
// compute the blur average with pixels from the source image
for (int srcX = 0; srcX < srcWidth; srcX++) {
int a = (int) (aSum * hSumDivider); // calculate alpha value
dstBuffer[dstOffset++] = a << 24; // store the alpha value only
// the shadow color will be added in the next pass
aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
// extract the new pixel ...
a = srcColorModel.getAlpha(srcRaster.getDataElements(srcX, srcY, null));
aHistory[historyIdx] = a; // ... and store its value into history
aSum += a; // ... and add its value to the sum
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
// blur the end of the row - no new pixels to grab
for (int i = 0; i < shadowSize; i++) {
int a = (int) (aSum * hSumDivider);
dstBuffer[dstOffset++] = a << 24;
// substract the oldest pixel from the sum ... and nothing new to add !
aSum -= aHistory[historyIdx];
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
}
// vertical pass
for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
aSum = 0;
// first pixels are empty
for (historyIdx = 0; historyIdx < left;) {
aHistory[historyIdx++] = 0;
}
// and then they come from the dstBuffer
for (int y = 0; y < right; y++, bufferOffset += dstWidth) {
int a = dstBuffer[bufferOffset] >>> 24; // extract alpha
aHistory[historyIdx++] = a; // store into history
aSum += a; // and add to sum
}
bufferOffset = x;
historyIdx = 0;
// compute the blur average with pixels from the previous pass
for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {
int a = (int) (aSum * vSumDivider); // calculate alpha value
dstBuffer[bufferOffset] = a << 24 | shadowRgb; // store alpha value + shadow color
aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24; // extract the new pixel ...
aHistory[historyIdx] = a; // ... and store its value into history
aSum += a; // ... and add its value to the sum
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
// blur the end of the column - no pixels to grab anymore
for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {
int a = (int) (aSum * vSumDivider);
dstBuffer[bufferOffset] = a << 24 | shadowRgb;
aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum
if (++historyIdx >= shadowSize) {
historyIdx -= shadowSize;
}
}
}
return dst;
}
// creates the shadow mask for the original picture
// it colorize all the pixels with the shadow color according to their
// original transparency
private BufferedImage createShadowMask(final BufferedImage image) {
BufferedImage mask = new BufferedImage(image.getWidth(),
image.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = mask.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN,
opacity));
g2d.setColor(color);
g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
g2d.dispose();
return mask;
}
// creates a blur convolve operation by generating a kernel of
// dimensions (size, size).
private ConvolveOp getLinearBlurOp(final int size) {
float[] data = new float[size * size];
float value = 1.0f / (float) (size * size);
for (int i = 0; i < data.length; i++) {
data[i] = value;
}
return new ConvolveOp(new Kernel(size, size, data));
}
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class SplineControlPanel extends JPanel {
private SplineDisplay display; private DropSimulator dropSimulator = new DropSimulator(); private BouncerSimulator bounceSimulator = new BouncerSimulator(); private int linesCount = 0; private JLabel labelControl1; private JLabel labelControl2; private Animator controller; SplineControlPanel() { super(new BorderLayout()); add(buildEquationDisplay(), BorderLayout.CENTER); add(buildDebugControls(), BorderLayout.EAST); } private Component buildDebugControls() { JButton button; JPanel debugPanel = new JPanel(new GridBagLayout()); debugPanel.add(Box.createHorizontalStrut(150), new GridBagConstraints(0, linesCount++, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
// button = addButton(debugPanel, "Create"); // button.addActionListener(new ActionListener() { // public void actionPerformed(ActionEvent e) { // JFileChooser chooser = new JFileChooser("."); // int choice = chooser.showSaveDialog(SplineControlPanel.this); // if (choice == JFileChooser.CANCEL_OPTION) { // return; // } // File file = chooser.getSelectedFile(); // try { // OutputStream out = new FileOutputStream(file); // display.saveAsTemplate(out); // out.close(); // } catch (FileNotFoundException e1) { // } catch (IOException e1) { // } // } // });
addSeparator(debugPanel, "Control Points"); labelControl1 = addDebugLabel(debugPanel, "Point 1:", formatPoint(display.getControl1())); labelControl2 = addDebugLabel(debugPanel, "Point 2:", formatPoint(display.getControl2())); button = addButton(debugPanel, "Copy Code"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { NumberFormat formatter = getNumberFormatter(); Point2D c1 = display.getControl1(); Point2D c2 = display.getControl2(); StringBuilder code = new StringBuilder(); code.append("Spline spline = new Spline("); code.append(formatter.format(c1.getX())).append("f, "); code.append(formatter.format(c1.getY())).append("f, "); code.append(formatter.format(c2.getX())).append("f, "); code.append(formatter.format(c2.getY())).append("f);"); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(new StringSelection(code.toString()), null); } }); addEmptySpace(debugPanel, 6); addSeparator(debugPanel, "Animation"); button = addButton(debugPanel, "Play Sample"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { startSampleAnimation(); } }); addEmptySpace(debugPanel, 6); addSeparator(debugPanel, "Templates"); debugPanel.add(createTemplates(), new GridBagConstraints(0, linesCount++, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); addEmptySpace(debugPanel, 6); debugPanel.add(Box.createVerticalGlue(), new GridBagConstraints(0, linesCount++, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); JPanel wrapper = new JPanel(new BorderLayout()); wrapper.add(new JSeparator(JSeparator.VERTICAL), BorderLayout.WEST); wrapper.add(debugPanel); wrapper.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 6)); return wrapper; } private Component createTemplates() { DefaultListModel model = new DefaultListModel(); model.addElement(createTemplate(0.0, 0.0, 1.0, 1.0)); model.addElement(createTemplate(0.0, 1.0, 0.0, 1.0)); model.addElement(createTemplate(0.0, 1.0, 1.0, 1.0)); model.addElement(createTemplate(0.0, 1.0, 1.0, 0.0)); model.addElement(createTemplate(1.0, 0.0, 0.0, 1.0)); model.addElement(createTemplate(1.0, 0.0, 1.0, 1.0)); model.addElement(createTemplate(1.0, 0.0, 1.0, 0.0)); JList list = new JList(model); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setCellRenderer(new TemplateCellRenderer()); list.addListSelectionListener(new TemplateSelectionHandler()); JScrollPane pane = new JScrollPane(list); pane.getViewport().setPreferredSize(new Dimension(98, 97 * 3)); return pane; } private JButton addButton(JPanel debugPanel, String label) { JButton button; debugPanel.add(button = new JButton(label), new GridBagConstraints(0, linesCount++, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(3, 0, 0, 0), 0, 0)); return button; } private String formatPoint(Point2D p) { NumberFormat formatter = getNumberFormatter(); return "" + formatter.format(p.getX()) + ", " + formatter.format(p.getY()); } private Component buildEquationDisplay() { JPanel panel = new JPanel(new BorderLayout()); display = new SplineDisplay(); display.addPropertyChangeListener("control1", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { labelControl1.setText(formatPoint(display.getControl1())); } }); display.addPropertyChangeListener("control2", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { labelControl2.setText(formatPoint(display.getControl2())); } }); panel.add(display, BorderLayout.NORTH); JPanel wrapper = new JPanel(new GridBagLayout()); wrapper.add(new JSeparator(), new GridBagConstraints(0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); wrapper.add(bounceSimulator, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); wrapper.add(dropSimulator, new GridBagConstraints(1, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); panel.add(wrapper, BorderLayout.CENTER); return panel; } private JLabel addDebugLabel(JPanel panel, String label, String value) { JLabel labelComponent = new JLabel(label); panel.add(labelComponent, new GridBagConstraints(0, linesCount, 1, 1, 0.5, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 6, 0, 0), 0, 0)); labelComponent = new JLabel(value); panel.add(labelComponent, new GridBagConstraints(1, linesCount++, 1, 1, 0.5, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 6, 0, 0), 0, 0)); return labelComponent; } private void addEmptySpace(JPanel panel, int size) { panel.add(Box.createVerticalStrut(size), new GridBagConstraints(0, linesCount++, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL, new Insets(6, 0, 0, 0), 0, 0)); } private void addSeparator(JPanel panel, String label) { JPanel innerPanel = new JPanel(new GridBagLayout()); innerPanel.add(new JLabel(label), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); innerPanel.add(new JSeparator(), new GridBagConstraints(1, 0, 1, 1, 0.9, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 6, 0, 6), 0, 0)); panel.add(innerPanel, new GridBagConstraints(0, linesCount++, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 6, 0), 0, 0)); } private void startSampleAnimation() { if (controller != null && controller.isRunning()) { controller.stop(); } Point2D control1 = display.getControl1(); Point2D control2 = display.getControl2(); Interpolator splines = new SplineInterpolator((float) control1.getX(), (float) control1.getY(), (float) control2.getX(), (float) control2.getY()); KeyTimes times = new KeyTimes(0.0f, 1.0f); KeyValues values = KeyValues.create(0.0, 1.0); KeyFrames frames = new KeyFrames(values,times, splines); PropertySetter dropModifier = new PropertySetter(dropSimulator, "time", frames); PropertySetter bounceModifier = new PropertySetter(bounceSimulator, "time", frames); controller = new Animator(1000, 4, RepeatBehavior.REVERSE, dropModifier); controller.setResolution(10); controller.addTarget(bounceModifier); controller.start(); } private Evaluator point2dInterpolator = new Point2DNonLinearInterpolator(); private class Point2DNonLinearInterpolator extends Evaluator<Point2D> { private Point2D value; public Point2D evaluate(Point2D v0, Point2D v1, float fraction) { Point2D value = (Point2D)v0.clone(); if (v0 != v1) { double x = value.getX(); x += (v1.getX() - v0.getX()) * fraction; double y = value.getY(); y += (v1.getY() - v0.getY()) * fraction; value.setLocation(x, y); } else { value.setLocation(v0.getX(), v0.getY()); } return value; } } private class TemplateSelectionHandler implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } JList list = (JList) e.getSource(); Template template = (Template) list.getSelectedValue(); if (template != null) { if (controller != null && controller.isRunning()) { controller.stop(); } controller = new Animator(300, new PropertySetter(display, "control1", point2dInterpolator, display.getControl1(), template.getControl1())); controller.setResolution(10); controller.addTarget(new PropertySetter(display, "control2", point2dInterpolator, display.getControl2(), template.getControl2())); controller.start(); } } } private static NumberFormat getNumberFormatter() { NumberFormat formatter = NumberFormat.getInstance(Locale.ENGLISH); formatter.setMinimumFractionDigits(2); formatter.setMaximumFractionDigits(2); return formatter; } private static Template createTemplate(double x1, double y1, double x2, double y2) { return new Template(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); } private static class TemplateCellRenderer extends DefaultListCellRenderer { private boolean isSelected; @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Template template = (Template) value; this.setBackground(Color.WHITE); this.setIcon(new ImageIcon(template.getImage())); this.isSelected = isSelected; return this; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (isSelected) { g.setColor(new Color(0.0f, 0.0f, 0.7f, 0.1f)); g.fillRect(0, 0, getWidth(), getHeight()); } } } private static class Template { private Point2D control1; private Point2D control2; private Image image; public Template(Point2D control1, Point2D control2) { this.control1 = control1; this.control2 = control2; } public Point2D getControl1() { return control1; } public Point2D getControl2() { return control2; } public Image getImage() { if (image == null) { NumberFormat formatter = getNumberFormatter(); String name = ""; name += formatter.format(control1.getX()) + "-" + formatter.format(control1.getY()); name += "-"; name += formatter.format(control2.getX()) + "-" + formatter.format(control2.getY()); try { image = ImageIO.read(getClass().getResourceAsStream(name + ".png")); } catch (IOException e) { } } return image; } }
} /**
* Copyright (c) 2006, Sun Microsystems, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
class SplineDisplay extends EquationDisplay {
private static final double CONTROL_POINT_SIZE = 12.0; private Point2D control1 = new Point2D.Double(0.25, 0.75); private Point2D control2 = new Point2D.Double(0.75, 0.25); private Point2D selected = null; private Point dragStart = null; private boolean isSaving = false; private PropertyChangeSupport support; SplineDisplay() { super(0.0, 0.0, -0.1, 1.1, -0.1, 1.1, 0.2, 6, 0.2, 6); setEnabled(false); addMouseMotionListener(new ControlPointsHandler()); addMouseListener(new SelectionHandler()); support = new PropertyChangeSupport(this); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { support.addPropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { support.removePropertyChangeListener(propertyName, listener); } public Point2D getControl1() { return (Point2D) control1.clone(); } public Point2D getControl2() { return (Point2D) control2.clone(); } public void setControl1(Point2D control1) { support.firePropertyChange("control1", (Point2D) this.control1.clone(), (Point2D) control1.clone()); this.control1 = (Point2D) control1.clone(); repaint(); } public void setControl2(Point2D control2) { support.firePropertyChange("control2", (Point2D) this.control2.clone(), (Point2D) control2.clone()); this.control2 = (Point2D) control2.clone(); repaint(); } synchronized void saveAsTemplate(OutputStream out) { BufferedImage image = Java2dHelper.createCompatibleImage(getWidth(), getHeight()); Graphics g = image.getGraphics(); isSaving = true; setDrawText(false); paint(g); setDrawText(true); isSaving = false; g.dispose(); BufferedImage subImage = image.getSubimage((int) xPositionToPixel(0.0), (int) yPositionToPixel(1.0), (int) (xPositionToPixel(1.0) - xPositionToPixel(0.0)) + 1, (int) (yPositionToPixel(0.0) - yPositionToPixel(1.0)) + 1); try { ImageIO.write(subImage, "PNG", out); } catch (IOException e) { } image.flush(); subImage = null; image = null; } @Override protected void paintInformation(Graphics2D g2) { if (!isSaving) { paintControlPoints(g2); } paintSpline(g2); } private void paintControlPoints(Graphics2D g2) { paintControlPoint(g2, control1); paintControlPoint(g2, control2); } private void paintControlPoint(Graphics2D g2, Point2D control) { double origin_x = xPositionToPixel(control.getX()); double origin_y = yPositionToPixel(control.getY()); double pos = control == control1 ? 0.0 : 1.0; Ellipse2D outer = getDraggableArea(control); Ellipse2D inner = new Ellipse2D.Double(origin_x + 2.0 - CONTROL_POINT_SIZE / 2.0, origin_y + 2.0 - CONTROL_POINT_SIZE / 2.0, 8.0, 8.0); Area circle = new Area(outer); circle.subtract(new Area(inner)); Stroke stroke = g2.getStroke(); g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5, new float[] { 5, 5 }, 0)); g2.setColor(new Color(1.0f, 0.0f, 0.0f, 0.4f)); g2.drawLine(0, (int) origin_y, (int) origin_x, (int) origin_y); g2.drawLine((int) origin_x, (int) origin_y, (int) origin_x, getHeight()); g2.setStroke(stroke); if (selected == control) { g2.setColor(new Color(1.0f, 1.0f, 1.0f, 1.0f)); } else { g2.setColor(new Color(0.8f, 0.8f, 0.8f, 0.6f)); } g2.fill(inner); g2.setColor(new Color(0.0f, 0.0f, 0.5f, 0.5f)); g2.fill(circle); g2.drawLine((int) origin_x, (int) origin_y, (int) xPositionToPixel(pos), (int) yPositionToPixel(pos)); } private Ellipse2D getDraggableArea(Point2D control) { Ellipse2D outer = new Ellipse2D.Double(xPositionToPixel(control.getX()) - CONTROL_POINT_SIZE / 2.0, yPositionToPixel(control.getY()) - CONTROL_POINT_SIZE / 2.0, CONTROL_POINT_SIZE, CONTROL_POINT_SIZE); return outer; } private void paintSpline(Graphics2D g2) { CubicCurve2D spline = new CubicCurve2D.Double(xPositionToPixel(0.0), yPositionToPixel(0.0), xPositionToPixel(control1.getX()), yPositionToPixel(control1.getY()), xPositionToPixel(control2.getX()), yPositionToPixel(control2.getY()), xPositionToPixel(1.0), yPositionToPixel(1.0)); g2.setColor(new Color(0.0f, 0.3f, 0.0f, 1.0f)); g2.draw(spline); } private void resetSelection() { Point2D oldSelected = selected; selected = null; if (oldSelected != null) { Rectangle bounds = getDraggableArea(oldSelected).getBounds(); repaint(bounds.x, bounds.y, bounds.width, bounds.height); } } private class ControlPointsHandler extends MouseMotionAdapter { @Override public void mouseMoved(MouseEvent e) { Ellipse2D area1 = getDraggableArea(control1); Ellipse2D area2 = getDraggableArea(control2); if (area1.contains(e.getPoint()) || area2.contains(e.getPoint())) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } else { setCursor(Cursor.getDefaultCursor()); } } @Override public void mouseDragged(MouseEvent e) { if (selected == null) { return; } Point dragEnd = e.getPoint(); double distance = xPixelToPosition(dragEnd.getX()) - xPixelToPosition(dragStart.getX()); double x = selected.getX() + distance; if (x < 0.0) { x = 0.0; } else if (x > 1.0) { x = 1.0; } distance = yPixelToPosition(dragEnd.getY()) - yPixelToPosition(dragStart.getY()); double y = selected.getY() + distance; if (y < 0.0) { y = 0.0; } else if (y > 1.0) { y = 1.0; } Point2D selectedCopy = (Point2D) selected.clone(); selected.setLocation(x, y); support.firePropertyChange("control" + (selected == control1 ? "1" : "2"), selectedCopy, (Point2D) selected.clone()); repaint(); double xPos = xPixelToPosition(dragEnd.getX()); double yPos = -yPixelToPosition(dragEnd.getY()); if (xPos >= 0.0 && xPos <= 1.0) { dragStart.setLocation(dragEnd.getX(), dragStart.getY()); } if (yPos >= 0.0 && yPos <= 1.0) { dragStart.setLocation(dragStart.getX(), dragEnd.getY()); } } } private class SelectionHandler extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { Ellipse2D area1 = getDraggableArea(control1); Ellipse2D area2 = getDraggableArea(control2); if (area1.contains(e.getPoint())) { selected = control1; dragStart = e.getPoint(); Rectangle bounds = area1.getBounds(); repaint(bounds.x, bounds.y, bounds.width, bounds.height); } else if (area2.contains(e.getPoint())) { selected = control2; dragStart = e.getPoint(); Rectangle bounds = area2.getBounds(); repaint(bounds.x, bounds.y, bounds.width, bounds.height); } else { resetSelection(); } } @Override public void mouseReleased(MouseEvent e) { resetSelection(); } }
}
</source>
Trace curve
<source lang="java">
/*************************************************************************
- *
- This source code file, and compiled classes derived from it, can *
- be used and distributed without restriction, including for commercial *
- use. (Attribution is not required but is appreciated.) *
- *
- David J. Eck *
- Department of Mathematics and Computer Science *
- Hobart and William Smith Colleges *
- Geneva, New York 14456, USA *
- Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
- *
- /
// The ParametricCurve applet is a configurable applet that displays a parametric // curve defined by two functions of a parameter t. There can be a "Tracer" button // on the applet. When the user clicks this button, a crosshair is moved along // the curve from beginning to end. import java.awt.*; import java.applet.Applet; import java.util.StringTokenizer; import java.util.Hashtable; import edu.hws.jcm.draw.*; import edu.hws.jcm.data.*; import edu.hws.jcm.functions.*; import edu.hws.jcm.awt.*;
public class Parametric extends GenericGraphApplet {
// Declare some private variables that are created in one method in // this class and used in a second method. private Function xFunc,yFunc; // The functions that are graphed. private ParametricCurve graph; // The graph of the function. private Animator tracer; // for tracing the curve by moving a crosshair along it private Crosshair crosshair; // Crosshair used for tracing the graph private VariableInput tMin,tMax; // for inputting limits on t private VariableInput tIntervals; // for inutting the number of intervals into which the t-range is divided private ExpressionInput functionInput2; // for inputting yFunc; xFunc is input in functionInput protected void setUpParameterDefaults() { parameterDefaults = new Hashtable(); parameterDefaults.put("TwoLimitsColumns", "yes"); parameterDefaults.put("Variable","t"); parameterDefaults.put("XName","x"); // we need this so that xmin and xmax boxes are labeled correctly; // without it, the name would come from the variable name, t, instead of x parameterDefaults.put("FunctionLabel", " " + getParameter("XName") + "(" + getParameter("Variable") + ") = "); parameterDefaults.put("FunctionLabel2", " " + getParameter("YName","y") + "(" + getParameter("Variable") + ") = "); } protected void setUpCanvas() { // Override this to add more stuff to the canvas. super.setUpCanvas(); // Do the common setup: Add the axes and // When setUpCanvas is called, the function inputs already exist, if they are // to be used, since they are created in setUpBopttomPanel(), which is called // before setUpCanvas(). If functionInput exists, add a graph of the functions // from functionInput and functionInput2 to the canvas. If not, create a graph // of the functions specified by the parameters named "Function" and "Function2". if (functionInput != null) { xFunc = functionInput.getFunction(xVar); yFunc = functionInput2.getFunction(xVar); } else { String xFuncDef = " cos(" + xVar.getName() + ") + cos(3*" + xVar.getName() + ")"; String yFuncDef = " sin(4*" + xVar.getName() + ") - sin(2*" + xVar.getName() + ")"; xFuncDef = getParameter("Function", xFuncDef); yFuncDef = getParameter("Function2", yFuncDef); Function f = new SimpleFunction( parser.parse(xFuncDef), xVar ); xFunc = new WrapperFunction(f); f = new SimpleFunction( parser.parse(yFuncDef), xVar ); yFunc = new WrapperFunction(f); } graph = new ParametricCurve(xFunc,yFunc); Color color = getColorParam("CurveColor"); if (color != null) graph.setColor(color); // if inputs are desired to control the parameter on the curve, set them up here if ("no".equalsIgnoreCase(getParameter("UseParamInputs","yes"))) { tMin = new VariableInput(xVar.getName() + "Start",getParameter("ParameterMin","-2")); tMax = new VariableInput(xVar.getName() + "End",getParameter("ParameterMax","2")); tIntervals = new VariableInput("Intervals", getParameter("Intervals","200")); tIntervals.setInputStyle(VariableInput.INTEGER); tIntervals.setMin(1); tIntervals.setMax(5000); tMin.setOnUserAction(mainController); tMax.setOnUserAction(mainController); tIntervals.setOnUserAction(mainController); graph.setTMin(tMin); graph.setTMax(tMax); graph.setIntervals(tIntervals); if (limitsPanel != null) { // componets will be added to limitsPanel in setUpLimitsPanel() mainController.add(tMin); // This is not done automatically, since they are in a limits panel mainController.add(tMax); mainController.add(tIntervals); } else { JCMPanel ap = new JCMPanel(9,0); ap.setBackground(getColorParam("PanelBackground", Color.lightGray)); ap.add(new Label(tMin.getName())); ap.add(tMin); ap.add(new Label()); ap.add(new Label(tMax.getName())); ap.add(tMax); ap.add(new Label()); ap.add(new Label(tIntervals.getName())); ap.add(tIntervals); ap.add(new Label()); mainPanel.add(ap,BorderLayout.EAST); } } else { try { graph.setTMin( new Constant((new Double(getParameter("ParameterMin","-2"))).doubleValue()) ); graph.setTMax( new Constant((new Double(getParameter("ParameterMax","2"))).doubleValue()) ); graph.setIntervals( new Constant((new Double(getParameter("Intervals","25"))).doubleValue()) ); } catch (NumberFormatException e) { } } // If the applet is configured to have a tracer button, create it and add the crosshair to the canvas if (! "no".equalsIgnoreCase( getParameter("UseTracer","yes") ) ) { tracer = new Animator(); tracer.setMin(graph.getTMin()); tracer.setMax(graph.getTMax()); tracer.setUndefinedWhenNotRunning(true); tracer.setStartButtonName("Trace Curve!"); double[] d = getNumericParam("TracerIntervals"); int ints; if (d == null || d.length != 1) ints = 100; else ints = (int)Math.round(d[0]); if (ints <= 0) tracer.setIntervals(graph.getIntervals()); else tracer.setIntervals(ints); Variable v = tracer.getValueAsVariable(); crosshair = new Crosshair( new ValueMath(xFunc,v), new ValueMath(yFunc,v) ); crosshair.setLineWidth(3); crosshair.setColor(getColorParam("CrosshairColor",Color.gray)); canvas.add(crosshair); if (inputPanel != null) { inputPanel.add(tracer,BorderLayout.WEST); } else if (limitsPanel == null) { Panel p = new Panel(); p.add(tracer); mainPanel.add(p,BorderLayout.SOUTH); } // if inputPanel is null but limitsPanel is not null, the tracer will be // added to the limit control panel in setUpLimitsPanel() } canvas.add(graph); // Finally, add the graph to the canvas. } // end setUpCanvas() protected void setUpLimitsPanel() { super.setUpLimitsPanel(); if (limitsPanel != null && tMin != null) { // add parameter inputs to limits panel limitsPanel.addComponentPair(tMin,tMax); limitsPanel.addComponent(tIntervals); } if (inputPanel == null && tracer != null && limitsPanel != null) { limitsPanel.addComponent(tracer); } } protected void setUpBottomPanel() { // override this to make a panel containing inputs for two functions if ( ! "no".equalsIgnoreCase(getParameter("UseFunctionInput", "yes")) ) { inputPanel = new JCMPanel(); inputPanel.setBackground( getColorParam("PanelBackground", Color.lightGray) ); Panel in = new JCMPanel(2,1); inputPanel.add(in,BorderLayout.CENTER); if ( ! "no".equalsIgnoreCase(getParameter("UseComputeButton", "yes")) ) { String cname = getParameter("ComputeButtonName", "New Functions"); computeButton = new Button(cname); inputPanel.add(computeButton, BorderLayout.EAST); computeButton.addActionListener(this); } String varName = getParameter("Variable"); String def = getParameter("Function"); if (def == null) def = "cos(" + varName + ") + cos(3*" + varName + ")"; functionInput = new ExpressionInput(def,parser); String label = getParameter("FunctionLabel"); if ("none".equalsIgnoreCase(label)) in.add(functionInput); else { Panel p = new JCMPanel(); p.add(functionInput,BorderLayout.CENTER); p.add( new Label(label), BorderLayout.WEST ); in.add(p); } def = getParameter("Function2"); if (def == null) def = "sin(4*" + varName + ") - sin(2*" + varName + ")"; functionInput2 = new ExpressionInput(def,parser); label = getParameter("FunctionLabel2"); if ("none".equalsIgnoreCase(label)) in.add(functionInput2); else { Panel p = new JCMPanel(); p.add(functionInput2,BorderLayout.CENTER); p.add( new Label(label), BorderLayout.WEST ); in.add(p); } mainPanel.add(inputPanel, BorderLayout.SOUTH); functionInput.setOnUserAction(mainController); functionInput2.setOnUserAction(mainController); } } protected void setUpMainPanel() { // Override to set up controller for tracer, if there is one super.setUpMainPanel(); // Do the common setup if ( tracer == null ) { return; // If the applet is not configured to use a trace button, there is nothing to do. } Controller traceController = new Controller(); // A controler that will only recompute the crosshair position traceController.add(tracer); traceController.add(crosshair); tracer.setOnChange(traceController); } // end setUpMainPanel() protected void doLoadExample(String example) { // This method is called when the user loads an example from the // example menu (if there is one). It overrides an empty method // in GenericGraphApplet. // For the Parametric applet, the example string should contain // two expression that defines the curve to be graphed, separated // by a semicolon. This can optionally // be followed by another semicolon and a list of four to seven numbers. // The first four numbers give the x- and y-limits to be used for the // example. If they are not present, then -5,5,-5,5 is used. The // next three numbers specify the minimum value for the parameter, the // maximum value, and the number of intervals into which the range of // parameter values is divided. if (tracer != null) tracer.stop(); int pos = example.indexOf(";"); if (pos == -1) return; // illegal example -- must have two functions String example2 = example.substring(pos+1); example = example.substring(0,pos); pos = example2.indexOf(";"); double[] limits = { -5,5,-5,5 }; // x- and y-limits to use if (pos > 0) { // Get limits from example2 text. String nums = example2.substring(pos+1); example2 = example2.substring(0,pos); StringTokenizer toks = new StringTokenizer(nums, " ,"); if (toks.countTokens() >= 4) { for (int i = 0; i < 4; i++) { try { Double d = new Double(toks.nextToken()); limits[i] = d.doubleValue(); } catch (NumberFormatException e) { } } } if (toks.hasMoreTokens()) { try { double d = (new Double(toks.nextToken())).doubleValue(); if (tMin == null) { graph.setTMin(new Constant(d)); if (tracer != null) tracer.setMin(d); } else tMin.setVal(d); } catch (NumberFormatException e) { } } if (toks.hasMoreTokens()) { try { double d = (new Double(toks.nextToken())).doubleValue(); if (tMax == null) { graph.setTMax(new Constant(d)); if (tracer != null) tracer.setMax(d); } else tMax.setVal(d); } catch (NumberFormatException e) { } } if (toks.hasMoreTokens()) { try { int d = (int)Math.round((new Double(toks.nextToken())).doubleValue()); if (tIntervals == null) { if (tracer != null && tracer.getIntervals() == graph.getIntervals()) tracer.setIntervals(d); graph.setIntervals(new Constant(d)); } else tIntervals.setVal(d); } catch (NumberFormatException e) { } } } // Set up the example data and recompute everything. if (functionInput != null) { // If there is a function input box, put the example text in it. functionInput.setText(example); functionInput2.setText(example2); } else { // If there is no user input, set the function in the graph directly. try { Function f = new SimpleFunction( parser.parse(example), xVar ); ((WrapperFunction)xFunc).setFunction(f); Function g = new SimpleFunction( parser.parse(example2), xVar ); ((WrapperFunction)yFunc).setFunction(g); } catch (ParseError e) { // There should"t be parse error"s in the Web-page // author"s examples! If there are, the function // just won"t change. } } CoordinateRect coords = canvas.getCoordinateRect(0); coords.setLimits(limits); coords.setRestoreBuffer(); mainController.rupute(); } // end doLoadExample() public void stop() { // stop animator when applet is stopped if (tracer != null) tracer.stop(); super.stop(); } public static void main(String[] a){ javax.swing.JFrame f = new javax.swing.JFrame(); Applet app = new Parametric(); app.init(); f.getContentPane().add (app); f.pack(); f.setSize (new Dimension (500, 500)); f.setVisible(true); }
} // end class Parametric
</source>
Zoom interaction, Text background color, and the effect of transparency
<source lang="java">
import java.awt.*; import javax.swing.*; import no.geosoft.cc.graphics.*;
/**
* G demo program. Demonstrates: **
-
*
- Custom world extent *
- Zoom interaction *
- Text background color *
- The effect of transparency *
- Annotation layout algorithm *
* * @author