# Integral Curves : Curve « Advanced Graphics « Java

Integral Curves

```/*************************************************************************
*                                                                        *
*  This source code file, and compiled classes derived from it, can      *
*  be used and distributed without restriction, including for commercial *
*  use.  (Attribution is not required but is appreciated.)               *
*                                                                        *
*   David J. Eck                                                         *
*   Department of Mathematics and Computer Science                       *
*   Hobart and William Smith Colleges                                    *
*   Geneva, New York 14456,   USA                                        *
*   Email: eck@hws.edu          WWW: http://math.hws.edu/eck/            *
*                                                                        *
*************************************************************************/

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

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

public class IntegralCurves extends GenericGraphApplet {

private Variable yVar;           // The seond variable, usually y.
private Function xFunc,yFunc;    // The functions that give the components of the vector field
private ExpressionInput functionInput2;  // For inputting yFunc.
private VectorField field;       // The vector/direction field

private Animator animator;       // for incrementally drawing integral curves.
private Vector curves = new Vector();           // Holds the integral curves

private VariableInput deltaT;    // input the deltaT for the curve
double dt = 0.1;  // The value of delat t in the case where there is no deltaT input box
private VariableInput xStart,yStart;  // Starting point for curve
private Choice methodChoice;     // select integration method
private Button startCurveButton; // user clicks to start curve from (x,y) in xStart, yStart input boxes
private Button clearButton;      // clears curves
private Color curveColor;        // color for integral curves
private Draw curveDrawer = new Draw(); // A DrawTemp object that draws one segment of the integral curves.

private double[] nextPoint = new double[2];  // Help in computing next point of integral curve.
private double[] params = new double[2];     // ditto

private static final int RK4 = 0, RK2 = 1, EULER = 2;   // constants for integration methos

private class Curve {  // holds the data for one integral curve
double dt;
int method;
double x,y; // point on the curve
double lastX = Double.NaN, lastY; // previous point, so we can draw a line.
}

private class Draw implements DrawTemp { // For drawing the next segment in each integral curve (as a DrawTemp)
public void draw(Graphics g, CoordinateRect coords) {
int size = curves.size();
g.setColor(curveColor);
for (int i = 0; i < size; i++) {
Curve c = (Curve)(curves.elementAt(i));
if (! (Double.isNaN(c.x) || Double.isNaN(c.y) || Double.isNaN(c.lastX) || Double.isNaN(c.lastY)) ) {
int x1 = coords.xToPixel(c.lastX);
int y1 = coords.yToPixel(c.lastY);
int x2 = coords.xToPixel(c.x);
int y2 = coords.yToPixel(c.y);
g.drawLine(x1,y1,x2,y2);
}
}
}
}

protected void setUpParser() {
// create the "y" variable; also set up some parameter defaults.
yVar = new Variable(getParameter("Variable2","y"));
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);

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")))
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) );

// 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);
}
functionInput = new ExpressionInput(getParameter("Function"),parser);
in1 = new JCMPanel();
functionInput.setOnUserAction(mainController);
functionInput2 = new ExpressionInput(getParameter("Function2"),parser);
in2 = new JCMPanel();
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);
if (computeButton != null)
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");

// 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();
yStart = new VariableInput();
bottom = new Panel();  // not a JCMPanel -- I don't want their contents checked automatically
startCurveButton = new Button("Start curve at:");
}

// 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) {
deltaT = new VariableInput(null,""+dt);
}
if (useChoice) {
methodChoice = new Choice();
}
if (bottom == null)
bottom = top;
else {
Panel p = new Panel();
p.setLayout(new BorderLayout());
bottom = p;
}
}
else  {
if (bottom == null)
bottom = new Panel();
}

// Add the panels "bottom" to the inputPanel, and ruturn
// if there are no function inputs.

if (in1 == null)
return;

// Add the function inputs and compute button to the inputPanel

Panel in = new JCMPanel(1,2);
if (computeButton != null) {
Panel p = new JCMPanel();
in = p;
}

} // end setUpBottomPanel()

public void actionPerformed(ActionEvent evt) {
// React if user presses return in xStart or yStart, or pass evt on to GenericGraphApplet
Object src = evt.getSource();
if (src == clearButton) {
canvas.clearErrorMessage();
curves.setSize(0);
animator.stop();
canvas.compute();  // force recompute of off-screen canvas!
}
else if (src == xStart || src == yStart || src == startCurveButton) {
// Start a curve from x and y values in xStart and yStart
canvas.clearErrorMessage();
double x=0, y=0;
try {
xStart.checkInput();
x = xStart.getVal();
yStart.checkInput();
y = yStart.getVal();
startCurve(x,y);
if (deltaT != null) {
deltaT.checkInput();
dt = deltaT.getVal();
if (dt <= 0) {
deltaT.requestFocus();
throw new JCMError("dt must be positive", deltaT);
}
}
}
catch (JCMError e) {
curves.setSize(0);
animator.stop();
canvas.setErrorMessage(null,"Illegal Data For Curve.  " + e.getMessage());
}
}
else
super.actionPerformed(evt);
} // end actionPerfromed

public void startCurve(double x, double y) {
// Start an integral curve at the point (x,y)
synchronized (curves) {
if (deltaT != null) {
try {
deltaT.checkInput();
dt = deltaT.getVal();
if (dt <= 0) {
deltaT.requestFocus();
throw new JCMError("dt must be positive", deltaT);
}
}
catch (JCMError e) {
curves.setSize(0);
animator.stop();
canvas.setErrorMessage(null,"Illegal Data For Curve.  " + e.getMessage());
return;
}
}
Curve c = new Curve();
c.dt = dt;
int method = (methodChoice == null)? RK4 : methodChoice.getSelectedIndex();
c.method = method;
c.x = x;
c.y = y;
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 {
}
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);
}

// 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
// curves to allow time for the redrawing (although it seems to block the redrawing, at least
// on some platforms).

if (animator != null) {
curves.setSize(0);
animator.stop();
}

int pos = example.indexOf(";");
if (pos == -1)
return; // illegal example -- must have two functions
String example2 = example.substring(pos+1);
example = example.substring(0,pos);
pos = example2.indexOf(";");

double[] limits = { -5,5,-5,5 }; // x- and y-limits to use

StringTokenizer toks = null;

if (pos > 0) {
// Get limits from example2 text.
String nums = example2.substring(pos+1);
example2 = example2.substring(0,pos);
toks = new StringTokenizer(nums, " ,");
if (toks.countTokens() >= 4) {
for (int i = 0; i < 4; i++) {
try {
Double d = new Double(toks.nextToken());
limits[i] = d.doubleValue();
}
catch (NumberFormatException e) {
}
}
}
if (toks.hasMoreTokens()) {
double d = Double.NaN;
try {
d = (new Double(toks.nextToken())).doubleValue();
}
catch (NumberFormatException e) {
}
if (Double.isNaN(d) || d <= 0 || d > 100)
d = 0.1;
if (deltaT != null)
deltaT.setVal(d);
else
dt = d;
}
}

// Set up the example data and recompute everything.

if (functionInput != null) {
// If there is a function input box, put the example text in it.
functionInput.setText(example);
functionInput2.setText(example2);
}
else {
// If there is no user input, set the function in the graph directly.
try {
Function f = new SimpleFunction( parser.parse(example), xVar );
((WrapperFunction)xFunc).setFunction(f);
Function g = new SimpleFunction( parser.parse(example2), xVar );
((WrapperFunction)yFunc).setFunction(g);
}
catch (ParseError e) {
// There should't be parse error's in the Web-page
// author's examples!  If there are, the function
// just won't change.
}
}
CoordinateRect coords = canvas.getCoordinateRect(0);
coords.setLimits(limits);
coords.setRestoreBuffer();
mainController.compute();

if (animator != null && toks != null) { // get any extra nums from the tokenizer and use them as starting points for curves
int ct = 2*(toks.countTokens()/2);
if (ct > 0) {
synchronized(curves) {
for (int i = 0; i < ct; i++) {
try {
double x = (new Double(toks.nextToken())).doubleValue();
double y = (new Double(toks.nextToken())).doubleValue();
startCurve(x,y);
}
catch (Exception e) {
}
}
if (curves.size() > 0) {  // start the curves going
try {
Thread.sleep(500);  // wait a bit to give the canvas time to start drawing itself.
}
catch (InterruptedException e) {
}
}
}
}
}

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.pack();
f.setSize (new Dimension (500, 500));
f.setVisible(true);
}

} // end class IntegralCurves

```
jcm1-source.zip( 532 k)

### Related examples in the same category

 1 Spline Editor 2 Draw Spline 3 Animated Graph 4 Epsilon Delta 5 Families Of Graphs 6 Trace curve 7 Input the function and draw the curve 8 Scatter Plot 9 Zoom interaction, Text background color, and the effect of transparency