### Solution for

Programming Exercise 9.5

THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to
the following exercise from this on-line
Java textbook.

**Exercise 9.5:**
This exercises uses the class `Expr`, which was described
in Exercise 9.4. For this exercise, you should write an
applet that can graph a function, `f(x)`, whose definition
is entered by the user. The applet should have a text-input box
where the user can enter an expression involving the variable `x`,
such as `x^2` or `sin(x-3)/x`.
This expression is the definition of the function. When the user
presses return in the text input box, the applet should use the
contents of the text input box to construct an object of type
`Expr`. If an error is found in the definition, then the
applet should display an error message. Otherwise, it should
display a graph of the function. (Note: A `JTextField`
generates an `ActionEvent` when the user presses return.)

The applet will need a `JPanel` for displaying the graph.
To keep things simple, this panel should represent a fixed
region in the xy-plane, defined by `-5 <= x <= 5`
and `-5 <= y <= 5`. To draw the graph, compute
a large number of points and connect them with line segments.
(This method does not handle discontinuous functions properly;
doing so is very hard, so you shouldn't try to do it for this exercise.)
My applet divides the interval `-5 <= x <= 5`
into 300 subintervals and uses the 301 endpoints of these subintervals
for drawing the graph. Note that the function might be undefined
at one of these `x`-values. In that case, you have to skip
that point.

A point on the graph has the form `(x,y)` where
`y` is obtained by evaluating the user's expression at
the given value of `x`. You will have to convert these
real numbers to the integer coordinates of the corresponding
pixel on the canvas. The formulas for the conversion are:

a = (int)( (x + 5)/10 * width );
b = (int)( (5 - y)/10 * height );

where `a` and `b` are the horizontal and vertical
coordinates of the pixel, and `width` and `height`
are the width and height of the canvas.

Here is my solution to this exercise:

**Discussion**

My solution uses a nested class `Graph` for displaying the
graph. This class is defined as a subclass of `JPanel`. It has
an instance variable, `func`, of type `Expr` that represents
the function to be drawn. If there is no function, then `func` is null.
This is true when the applet is first started and when the user's input
has been found to be illegal. The `paintComponent()` method checks the value of
`func` to decide what to draw. If `func` is `null`, then
the paint method simply draws a message on the panel stating that no function is
available. Otherwise, it draws a pair of axes and the graph of the function.

The interesting work in class `Graph` is done in the
`drawFunction()` method, which is called by the `paintComponent()` method.
This function draws the graph of the function for `-5 <= x <= 5`.
This interval on the `x` axis is divided into 300 subintervals.
Since the length of the interval is 10, the length of each subinterval is given by
`dx`, where `dx` is `10.0/300`. The `x` values for
the points that I want to plot are given by `-5`, `-5+dx`,
`-5+2*dx`, and so on. Each `x`-value is obtained by adding
`dx` to the previous value. For each `x` value, the
`y`-value of the point on the graph is computed as `func.value(x)`.
As the points on the graph are computed, line segments are drawn to connect pairs
of points (unless the `y`-value of either point is undefined). An
algorithm for the `drawFunction()` method is:

Let dx = 10.0 / 300;
Let x = -5 // Get the first point
Let y = func.value(x);
for i = 1 to 300:
Let prevx = x // Save the previous point
Let prevy = y
Let x = x + dx // Get the next point
Let y = func.value(y)
if neither y nor prevy is Double.NaN:
draw a line segment from (prevx,prevy) to (x,y)

The method for drawing the line segment uses the conversion from
real coordinates to integer pixel coordinates that is given in the
exercise. By the way, more general conversion formulas can be given in
the case where `x` extends from `xmin` to `xmax`
and `y` extends from `ymin` to `ymax`. The general
formulas are:

a = (int)( (x - xmin) / (xmax - xmin) * width );
b = (int)( (ymax - y) / (ymax - ymin) * height );

The formulas for `a` and `b` are of slightly different
form to reflect the fact that `a` increases from 0 to `width`
as `x` increases from `xmin` to `xmax`, while
`b` **decreases** from `height` to 0 as `y` increases
from `ymin` to `ymax`. You could improve the applet by adding
text input boxes where the user can enter values for `xmin`,
`xmax`, `ymin`, and `ymax`.

In the main applet class, the `init()` method lays out the
components in the content pane of the applet with a `BorderLayout` that
has a vertical gap, to allow some space between the graph and the
components that are above it and below it. The "North"
component is a `JLabel` that is used to display messages to the
user. The "South" component is a `JTextField` where the user
enters the definition of the function. And the "Center" component is
the panel where the graph is drawn. The applet registers itself
as a listener for `ActionEvents` from the `JTextField`.
This means that when the user presses return in the `JTextField`,
the applet's `actionPerformed()` method will be called.

(After viewing my applet for the first time, I was dissatisfied with the
appearance of the label. There was no space between the text of the
label and the gray background of the component. I decided to fix this
by adding an `EmptyBorder` to the label to allow more space
around the text where the white background of the label shows through.
Borders were covered in Section 7.2.)

The `actionPerformed()` method just has to get the
user's input string from the `JTextField`. It tries to use
this string to construct an object of type `Expr`. The
constructor throws an `IllegalArgumentException` if the
string contains an error, so the constructor is called in a
`try` statement that can catch and handle the error.
If an error occurs, then the error message in the exception
object is displayed in the `JLabel` at the top of the applet,
and the graph is cleared. If no error occurs,
the graph is set to display the user's function, and the
`JLabel` is set to display the generic message, "Enter a
function and press return." The code for all this is:

Expr function; // The user's function.
try {
String def = functionInput.getText();
function = new Expr(def);
graph.setFunction(function);
message.setText(" Enter a function and press return.");
}
catch (IllegalArgumentException e) {
graph.clearFunction();
message.setText(e.getMessage());
}

The complete solution follows.

**The Solution**

/*
The SimpleGrapher applet can draw graphs of functions input by the
user. The user enters the definition of the function in a text
input box. When the user presses return, the function is graphed.
(Unless the definition contains an error. In that case, an error
message is displayed.)
The graph is drawn on a canvas which represents the region of the
(x,y)-plane given by -5 <= x <= 5 and -5 <= y <= 5. Any part of
the graph that lies outside this region is not shown. The graph
is drawn by plotting 301 points and joining them with lines. This
does not handle discontinuous functions properly.
This file defines two class files, SimpleGrapher.class and
SimpleGrapher$Graph.class. It also requires Expr.class,
which is defined in by a separate file, Expr.java.
That file contains a full description of the syntax
of legal function definitions.
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleGrapher extends JApplet implements ActionListener{
Graph graph; // The JPanel that will display the graph.
// The nested class, Graph, is defined below.
JTextField functionInput; // A text input box where the user enters the
// definition of the function.
JLabel message; // A label for displaying messages to the user,
// including error messages when the function definition
// is illegal.
public void init() {
// Initialize the applet by creating and laying out the
// components. The applet listens for ActionEvents from
// the text input box, so that it can respond when the
// user presses return.
setBackground(Color.gray);
getContentPane().setBackground(Color.gray);
getContentPane().setLayout(new BorderLayout(2,2));
graph = new Graph();
getContentPane().add(graph, BorderLayout.CENTER);
message = new JLabel(" Enter a function and press return");
message.setBackground(Color.white);
message.setForeground(Color.red);
message.setOpaque(true);
message.setBorder( BorderFactory.createEmptyBorder(5,0,5,0) );
getContentPane().add(message, BorderLayout.NORTH);
functionInput = new JTextField();
functionInput.setBackground(Color.white);
functionInput.addActionListener(this);
getContentPane().add(functionInput, BorderLayout.SOUTH);
} // end init()
public Insets getInsets() {
// Specify a 3-pixel border around the edges of the applet.
return new Insets(3,3,3,3);
}
public void actionPerformed(ActionEvent evt) {
// This will be called when the user presses return in
// the text input box. Get the user's function definition
// from the box and use it to create a new object of type
// Expr. Tell the canvas to graph this function. If the
// definition is illegal, an IllegalArgumentException is
// thrown by the Expr constructor. If this happens,
// the graph is cleared and an error message is displayed
// in the message label.
Expr function; // The user's function.
try {
String def = functionInput.getText();
function = new Expr(def);
graph.setFunction(function);
message.setText(" Enter a function and press return.");
}
catch (IllegalArgumentException e) {
graph.clearFunction();
message.setText(e.getMessage());
}
} // end actionPerformed()
//-------------------------- Nested class ----------------------------
static class Graph extends JPanel {
// A object of this class can display the graph of a function
// on the region of the (x,y)-plane given by -5 <= x <= 5 and
// -5 <= y <= 5. The graph is drawn very simply, by plotting
// 301 points and connecting them with line segments.
Expr func; // The definition of the function that is to be graphed.
// If the value is null, no graph is drawn.
Graph() {
// Constructor.
setBackground(Color.white);
func = null;
}
public void setFunction(Expr exp) {
// Set the canvas to graph the function whose definition is
// given by the function exp.
func = exp;
repaint();
}
public void clearFunction() {
// Set the canvas to draw no graph at all.
func = null;
repaint();
}
public void paintComponent(Graphics g) {
// Draw the graph of the function or, if func is null,
// display a message that there is no function to be graphed.
super.paintComponent(g); // Fill with background color, white.
if (func == null) {
g.drawString("No function is available.", 20, 30);
}
else {
g.drawString("y = " + func.getDefinition(), 5, 15);
drawAxes(g);
drawFunction(g);
}
}
void drawAxes(Graphics g) {
// Draw horizontal and vertical axes in the middle of the
// canvas. A 5-pixel border is left at the ends of the axes.
int width = getWidth();
int height = getHeight();
g.setColor(Color.blue);
g.drawLine(5, height/2, width-5, height/2);
g.drawLine(width/2, 5, width/2, height-5);
}
void drawFunction(Graphics g) {
// Draw the graph of the function defined by the instance
// variable func. Just plot 301 points with lines
// between them.
double x, y; // A point on the graph. y is f(x).
double prevx, prevy; // The previous point on the graph.
double dx; // Difference between the x-values of consecutive
// points on the graph.
dx = 10.0 / 300;
g.setColor(Color.red);
/* Compute the first point. */
x = -5;
y = func.value(x);
/* Compute each of the other 300 points, and draw a line segment
between each consecutive pair of points. Note that if the
function is undefined at one of the points in a pair, then
the line segment is not drawn. */
for (int i = 1; i <= 300; i++) {
prevx = x; // Save the coords of the previous point.
prevy = y;
x += dx; // Get the coords of the next point.
y = func.value(x);
if ( (! Double.isNaN(y)) && (! Double.isNaN(prevy)) ) {
// Draw a line segment between the two points.
putLine(g, prevx, prevy, x, y);
}
} // end for
} // end drawFunction()
void putLine(Graphics g, double x1, double y1,
double x2, double y2) {
// Draw a line segment from the point (x1,y1) to (x2,y2).
// These real values must be scaled to get the integer
// coordinates of the corresponding pixels.
int a1, b1; // Pixel coordinates corresponding to (x1,y1).
int a2, b2; // Pixel coordinates corresponding to (x2,y2).
int width = getWidth(); // Width of the canvas.
int height = getHeight(); // Height of the canvas.
a1 = (int)( (x1 + 5) / 10 * width );
b1 = (int)( (5 - y1) / 10 * height );
a2 = (int)( (x2 + 5) / 10 * width );
b2 = (int)( (5 - y2) / 10 * height );
if (Math.abs(y1) < 30000 && Math.abs(y2) < 30000) {
// Only draw lines for reasonable y-values.
// This should not be necessary, I don't think,
// but I got a problem when y was very large.)
g.drawLine(a1,b1,a2,b2);
}
} // end putLine()
} // end nested class Graph
} // end class SimpleGrapher

[ Exercises
| Chapter Index
| Main Index
]