[ prev | main ]

Series Grapher Applet



This applet displays the graph of a sum of the form "sum for
k = N to M of f(x,k)", where the lower and upper limits N and M
are specifed by sliders at the bottom of the applet. One of the
terms in the series is displayed in a separate graph. The term
that is displayed is specified by a third slider. The sample
function is the series for sin(x).

The following source code shows how the applet is built from
WCM components. This is a complicated applet, and not one whose
programming would be undertaken lightly.



// This one is rather tricky. To get the graph of the series, I had
// to write a custom Function, defined by a nested class at the end
// of this class. When this function is evaluated, the value of
// the index variable (kVar in the program) is changed as the summation
// is computed. Unfortunately, this means that kVar better not be the
// variable associated with the VariableSlider, term, since then setting
// its value would also change the value displayed on the slider. So,
// to make it work, kVar is a separater variable from the slider, and
// there is a custom Computable that copies the value of the slider to
// the value of kVar before the graph of one term in the series is drawn.
// This example also uses two Controllers, and here the two Controllers
// are pretty necessary since I don't want to recompute the summation
// graph unless there is no choice. The summation requires enough computation
// to require a noticable amount of time, even on a fast computer.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;

import javax.swing.JApplet;
import javax.swing.JLabel;
import javax.swing.JPanel;

import net.sourceforge.webcompmath.awt.Computable;
import net.sourceforge.webcompmath.awt.ComputeButton;
import net.sourceforge.webcompmath.awt.Controller;
import net.sourceforge.webcompmath.awt.DisplayLabel;
import net.sourceforge.webcompmath.awt.ExpressionInput;
import net.sourceforge.webcompmath.awt.VariableSlider;
import net.sourceforge.webcompmath.data.Cases;
import net.sourceforge.webcompmath.data.Constant;
import net.sourceforge.webcompmath.data.Expression;
import net.sourceforge.webcompmath.data.Function;
import net.sourceforge.webcompmath.data.Parser;
import net.sourceforge.webcompmath.data.Value;
import net.sourceforge.webcompmath.data.Variable;
import net.sourceforge.webcompmath.draw.CoordinateRect;
import net.sourceforge.webcompmath.draw.DisplayCanvas;
import net.sourceforge.webcompmath.draw.DrawBorder;
import net.sourceforge.webcompmath.draw.DrawString;
import net.sourceforge.webcompmath.draw.Graph1D;
import net.sourceforge.webcompmath.draw.LimitControlPanel;
import net.sourceforge.webcompmath.draw.WcmAxes;

/**
* This is an example WCM applet
*/
public class SeriesGrapherApplet extends JApplet {

private static final long serialVersionUID = 8644112419670416093L;

private DisplayCanvas termCanvas, seriesCanvas;

/**
* Overrides init() in JApplet
*
* @see java.applet.Applet#init()
*/
public void init() {

/*
* Separate canvasses for the term graph and the summation graph. In
* order to create these canvasses with non-standard x- and y-limits, I
* provide a CoordinateRect for the canvas as an argument to the
* constructor.
*/
termCanvas = new DisplayCanvas(new CoordinateRect(-15, 15, -3, 3));
seriesCanvas = new DisplayCanvas(new CoordinateRect(-15, 15, -3, 3));

/*
* Setting the second parameter in the constructor for LimitControlPanel
* to true causes the components in the panel to be laid out in two
* columns instead of one.
*/
LimitControlPanel limits = new LimitControlPanel(
LimitControlPanel.SET_LIMITS, true);

/*
* The LimitControlPanel controls both canvasses, so the coords on the
* canvasses are synchronized.
*/
limits.addCoords(termCanvas);
limits.addCoords(seriesCanvas);

VariableSlider lower = new VariableSlider(new Constant(0),
new Constant(10));
// These sliders will only have integer values.
lower.setIntegerValued(true);

lower.setVal(0);
VariableSlider upper = new VariableSlider(lower, new Constant(20));
upper.setIntegerValued(true);
/*
* This variable is declared final so that it can be used in the custom
* Computable class that is declared below.
*/
final VariableSlider term = new VariableSlider(lower, upper);
term.setIntegerValued(true);
term.setVal(0);

/*
* You can specify a number of different options for a parser. Here,
* I've turned on factorials, which are not allowed by default.
*/
Parser parser = new Parser(Parser.DEFAULT_OPTIONS | Parser.FACTORIAL);

/*
* This variable is declared final so that it can be used in the custom
* Computable class that is declared below.
*/
final Variable kVar = new Variable("k");

Variable xVar = new Variable("x");
parser.add(kVar);
parser.add(xVar);

ExpressionInput input = new ExpressionInput(
"(-1)^k * x^(2*k+1) / (2*k+1)!", parser);

ComputeButton button = new ComputeButton("New");

DrawString seriesString = new DrawString("Sum from k = # to #",
DrawString.TOP_LEFT, new Value[] { lower, upper });
seriesString.setColor(new Color(0, 120, 0));
seriesString.setFont(new Font("SansSerif", Font.BOLD, 10));

DrawString termString = new DrawString("Term with k = #",
DrawString.TOP_LEFT, new Value[] { term });
termString.setColor(new Color(0, 120, 0));
termString.setFont(new Font("SansSerif", Font.BOLD, 10));

seriesCanvas.add(new WcmAxes());
seriesCanvas.add(new Graph1D(new SeriesFunc(input.getExpression(),
xVar, kVar, lower, upper)));
seriesCanvas.add(new DrawBorder());
seriesCanvas.add(seriesString);

termCanvas.add(new WcmAxes());
termCanvas.add(new Graph1D(input.getFunction(xVar)));
termCanvas.add(new DrawBorder());
termCanvas.add(termString);

/*
* A controller that will redraw the term graph when necessary.
*/
Controller termController = new Controller();

/*
* This custom Computable object copies the value specified on the term
* slider to the variable, kVar, which is used in the user's expression.
*/
termController.add(new Computable() {
public void compute() {
kVar.setVal(term.getVal());
}
});

termController.add(term);
/*
* Add a DisplayCanvas to a controller is the same as adding everything
* that the canvas contains.
*/
termController.add(termCanvas);
/*
* termController repsonds to changes in the value of the term slider.
*/
term.setOnUserAction(termController);
/*
* This controller responds to any other change.
*/
Controller graphController = new Controller();

graphController.add(lower);
graphController.add(upper);
graphController.add(seriesCanvas);
graphController.add(input);
/*
* termController is a "sub-controller" of graphController
*/
graphController.add(termController);

lower.setOnUserAction(graphController);
upper.setOnUserAction(graphController);
input.setOnUserAction(graphController);
button.setOnUserAction(graphController);

setBackground(Color.lightGray);
setLayout(new BorderLayout(3, 3));
JPanel top = new JPanel();
top.setLayout(new GridLayout(2, 1, 3, 3));
top.add(seriesCanvas);
top.add(termCanvas);
add(top, BorderLayout.CENTER);
JPanel bot = new JPanel();
bot.setLayout(new GridLayout(1, 2, 3, 3));
JPanel botLeft = new JPanel();
botLeft.setLayout(new GridLayout(5, 1, 3, 3));
bot.add(botLeft);
bot.add(limits);
add(bot, BorderLayout.SOUTH);

botLeft.add(new JLabel("Enter the formula for a term, f(x,k): "));
JPanel inputPanel = new JPanel();
inputPanel.setLayout(new BorderLayout(3, 3));
inputPanel.add(input, BorderLayout.CENTER);
inputPanel.add(button, BorderLayout.EAST);
botLeft.add(inputPanel);
botLeft.add(slidePanel("Lower limit k = #", lower, graphController));
botLeft.add(slidePanel("Upper limit k = #", upper, graphController));
botLeft.add(slidePanel("Show term for k = #", term, termController));

graphController.setErrorReporter(termCanvas);
limits.setErrorReporter(termCanvas);

} // end doInit()

JPanel slidePanel(String s, VariableSlider v, Controller c) {
JPanel p = new JPanel();
p.setLayout(new GridLayout(1, 2, 3, 3));
DisplayLabel dl = new DisplayLabel(s, new Value[] { v });
p.add(v);
p.add(dl);
c.add(dl);
return p;
}

/**
* @see java.awt.Container#getInsets()
*/
public Insets getInsets() {
// Leave a border of three pixels around the edges of the applet.
return new Insets(3, 3, 3, 3);
}

private static class SeriesFunc implements Function {

/*
* This class defines a Function that represents the summation of a
* function of two variables, x and n, for n between a specified lower
* limit and upper limit.
*/

private static final long serialVersionUID = 321811273393720768L;

Expression func; // The function of two variables.

Variable variable, index; // The two variables.

Value start, stop; // The upper and lower limit.

SeriesFunc(Expression exp, Variable x, Variable n, Value low, Value high) {
func = exp;
variable = x;
index = n;
start = low;
stop = high;
}

/**
* @see net.sourceforge.webcompmath.data.Function#getArity()
*/
public int getArity() {
// The summation is a function of one variable.
return 1;
}

/**
* @see net.sourceforge.webcompmath.data.Function#getValueWithCases(double[],
* net.sourceforge.webcompmath.data.Cases)
*/
public double getValueWithCases(double[] x, Cases cases) {
/*
* Evaluate the summati0n at x[0]. (Information used in continutity
* checks is stored in cases.)
*/
double sum = 0;
int a = (int) Math.round(start.getVal());
int b = (int) Math.round(stop.getVal());
double save = index.getVal();
variable.setVal(x[0]);
for (int i = a; i <= b; i++) {
index.setVal(i);
sum += func.getValueWithCases(cases);
}
index.setVal(save);
return sum;
}

/**
* @see net.sourceforge.webcompmath.data.Function#getVal(double[])
*/
public double getVal(double[] arguments) {
return getValueWithCases(arguments, null);
}

/*
* The remaining methods are reuired by the Function interface, but they
* are not used in this applet, so I have not bothered to define them
* corretly (as I would have in a class meant for general use.
*/

/**
* @see net.sourceforge.webcompmath.data.Function#derivative(int)
*/
public Function derivative(int wrt) {
return null;
}

/**
* @see net.sourceforge.webcompmath.data.Function#derivative(net.sourceforge.webcompmath.data.Variable)
*/
public Function derivative(Variable x) {
return null;
}

/**
* @see net.sourceforge.webcompmath.data.Function#dependsOn(net.sourceforge.webcompmath.data.Variable)
*/
public boolean dependsOn(Variable x) {
return false;
}

/**
* @return no name
*/
public String getName() {
return null;
}

/**
* @param s
*/
public void setName(String s) {
}

}

} // end class SeriesGrapherApplet


[ prev | main ]