Drawing Lines
# The awt contains several graphics primitives. Rectangles are one and we’ve pretty much beaten them into the ground in the previous section. Lines are another. Within a graphics context there is one key line drawing method, drawLine(int x1, int y1, int x2, int y2). This method draws a straight line between the point (x1, y1) and the point (x2, y2). Here’s a simple applet that draws a line diagonally across the applet frame:
import java.applet.Applet;
import java.awt.*;
public class SimpleLine extends Applet {
int AppletHeight, AppletWidth;
public void init() {
Dimension d = size();
AppletHeight = d.height;
AppletWidth = d.width;
}
public void paint(Graphics g) {
g.drawLine(0, 0, AppletWidth, AppletHeight);
}
}
Graphing Functions
We’re now going to demonstrate the use of the drawLine() method to draw considerably non-straight figures. It is shown in advanced calculus that any reasonably well-behaved (should that be differentiable?) function can be approximated arbitrarily well by straight lines where quantities like &well-behaved” and “arbitrarily are precisely defined. I’ll spare you the details of the mathematical proof, but I will demonstrate its probability to you by producing an applet that does a very good job of graphing any function you care to throw at it. As usual we’ll develop it in pieces rather than just throwing it all out at once.
We begin with the skeleton applet. We’ll need to add some code to the paint method of the applet to make it draw something. Let’s begin by drawing a sine wave from the left hand side of the image to the right hand side. Here’s the complete program:
import java.applet.*;
import java.awt.*;
public class GraphApplet extends Applet {
int x0, xN, y0, yN;
public void init() {
// How big is the applet?
Dimension d = size();
x0 = 0;
xN = d.width-1;
y0=0;
yN=d.height-1;
}
public void paint(Graphics g) {
for (int x = x0; x < xN; x++) {
g.drawLine(x,(int) (yN*Math.sin(x)),x+1, (int) (yN*Math.sin(x+1)));
}
}
}
The meat of this applet is in the for loop of the paint method.
for (int x = x0; x < xN; x++) {
g.drawLine(x,(int) (yN*Math.sin(x)),x+1, (int) (yN*Math.sin(x+1)));
}
Here we loop across every x pixel of the applet. At each one we calculate the sine of that pixel. We also calculate the sine of the next pixel. This gives us two 2-D points and we draw a line between them. Since the sine of a real number is always between one and negative one, we scale the y value by yN. Finally we cast the y values to ints since sines are fundamentally floating point values but drawLine requires ints.
This applet runs but it's got a lot of problems. All of them can be related to two factors:
# Sines are floating point operations. To do a really useful graphing applet we need to be able to use floating point numbers.
# The coordinate system of an applet counts from (0,0) at the upper left hand corner to the right and down. The standard Cartesian coordinate system we expect graphs to use counts from (0,0) in the lower left hand corner to the right and up. The origin can be moved in both systems, for instance to the center of the applet, but we still need to transform between the y down and the y up coordinates.
There are a number of ways we can resolve this. The key to all of them, however, is to separate the data from the display. Since we are graphing more or less well behaved mathematical functions, we can assume that our data is completely described by a rectangle in Cartesian space within which we wish to plot a function. The display, on the other hand, is described by a rectangle of discrete points of fixed size and width. We need to be able to calculate in the general Cartesian plane and display in the particular applet window.
We'll need a method that will convert a point in the applet window into a point in the Cartesian plane, and one that will convert it back. Here it is:
import java.applet.*;
import java.awt.*;
public class GraphApplet extends Applet {
int x0, xN, y0, yN;
double xmin, xmax, ymin, ymax;
int AppletHeight, AppletWidth;
public void init() {
// How big is the applet?
Dimension d = size();
AppletHeight = d.height;
AppletWidth = d.width;
x0 = 0;
xN = AppletWidth-1;
y0=0;
yN=AppletHeight-1;
xmin = -10.0;
xmax = 10.0;
ymin = -1.0;
ymax = 1.0;
}
public void paint(Graphics g) {
double x1,y1,x2,y2;
int i, j1, j2;
j1 = yvalue(0);
for (i = 0; i < AppletWidth; i++) {
j2 = yvalue(i+1);
g.drawLine(i, j1 ,i+1, j2);
j1 = j2;
}
}
private int yvalue(int ivalue) {
// Given the xpoint we're given calculate the Cartesian equivalent
double x, y;
int jvalue;
x = (ivalue * (xmax - xmin)/(AppletWidth - 1)) + xmin;
// Take the sine of that x
y = Math.sin(x);
// Scale y into window coordinates
jvalue = (int) ((y - ymin)*(AppletHeight - 1)/(ymax - ymin));
// Switch jvalue from cartesian coordinates to computer graphics coordinates
jvalue = AppletHeight - jvalue;
return jvalue;
}
}
Run this applet. Isn't that a much nicer looking sine wave? There are still a number of things we can add to make this a more complete applet though. The most important would be to add some parameters so that we can define the size of the applet in HTML. The following modification of the init and paint methods looks for xmin, xmax, ymin, and ymax to be specified via parameters. However for robustness if the author of the HTML forgets to specify them we supply some reasonable default values.
import java.applet.*;
import java.awt.*;
public class GraphApplet extends Applet {
int x0, xN, y0, yN;
double xmin, xmax, ymin, ymax;
int AppletHeight, AppletWidth;
public void init() {
String ParamString;
// How big is the applet?
Dimension d = size();
AppletHeight = d.height;
AppletWidth = d.width;
x0 = 0;
xN = AppletWidth-1;
y0=0;
yN=AppletHeight-1;
ParamString = getParameter("xmin");
if (ParamString != null) {
xmin = Double.valueOf(ParamString).doubleValue();
}
else {
xmin = -1.0;
}
ParamString = getParameter("xmax");
if (ParamString != null) {
xmax = Double.valueOf(ParamString).doubleValue();
}
else {
xmax = 1.0;
}
ParamString = getParameter("ymax");
if (ParamString != null) {
ymax = Double.valueOf(ParamString).doubleValue();
}
else {
ymax = 1.0;
}
ParamString = getParameter("ymin");
if (ParamString != null) {
ymin = Double.valueOf(ParamString).doubleValue();
}
else {
ymin = -1.0;
}
}
public void paint(Graphics g) {
double x1,y1,x2,y2;
int i, j1, j2;
j1 = yvalue(0);
for (i = 0; i < AppletWidth; i++) {
j2 = yvalue(i+1);
g.drawLine(i, j1 ,i+1, j2);
j1 = j2;
}
}
private int yvalue(int ivalue) {
// Given the xpoint we're given calculate the Cartesian equivalent
double x, y;
int jvalue;
x = (ivalue * (xmax - xmin)/(AppletWidth - 1)) + xmin;
// Take the sine of that x
y = Math.sin(x);
// Scale y into window coordinates
jvalue = (int) ((y - ymin)*(AppletHeight - 1)/(ymax - ymin));
// Switch jvalue from cartesian coordinates to computer graphics coordinates
jvalue = AppletHeight - jvalue;
return jvalue;
}
}
Now we can adjust the range over which we graph without modifying our code!
So far we've only graphed sine functions. It should be obvious how to modify the code to graph cosines or many other kinds of functions. However what if we want to define the function at runtime?
Exercises
1. Add labeled coordinate axes to the graph.
2. Our graph method handled mathematical functions. How would you need to change it and what features would you add to make it suitable for plotting discrete experimental data?
An infinite set that with zero length
We're now going to use Java to implement some classic examples of fractal geometry. We'll do three of these. We begin with a one-dimensional set with an infinite number of points that covers zero length. Then we'll investigate the Koch snowflake. Finally in the next chapter we'll delve into the most famous fractal of all, the Mandelbrot set.
The middle third set is defined by starting with all the real numbers between zero and one inclusive. Then we cut out the middle third of that set (exclusive of the endpoints). i.e. everything between one third and two thirds exclusive.
Next we cut the middle third of the two line segments that remain, i.e. everything between one ninth and two ninths and between seven ninths and eight ninths. We continue this process indefinitely.
Was that confusing? Good. A picture is worth a thousand words and a good Java program is worth a thousand pictures. We now proceed to show you a Java program that draws successive pictures to demonstrate the middle third set.
import java.applet.Applet;
import java.awt.*;
import java.util.Vector;
public class MiddleThird extends Applet {
int AppletWidth;
int AppletHeight;
Vector endpoints = new Vector();
public void init() {
Dimension d = size();
AppletHeight = d.height;
AppletWidth = d.width;
endpoints.addElement(new Float(0.0f));
endpoints.addElement(new Float(1.0f));
}
public void paint(Graphics g) {
float x1, x2;
Float tempFloat;
for (int i = 0; i < AppletHeight; i+= 5) {
// draw the lines
for (int j=0; j < endpoints.size(); j += 2) {
tempFloat = (Float) endpoints.elementAt(j);
x1 = tempFloat.floatValue();
tempFloat = (Float) endpoints.elementAt(j+1);
x2 = tempFloat.floatValue();
g.drawLine( Math.round(x1*AppletWidth), i, Math.round(x2*AppletWidth), i);
}
//remove the middle third of the lines
CutSegments();
// Now check to see if we've exceeded the resolution of our screen
tempFloat = (Float) endpoints.elementAt(0);
x1 = tempFloat.floatValue();
tempFloat = (Float) endpoints.elementAt(1);
x2 = tempFloat.floatValue();
if (Math.round(x1*AppletWidth) == Math.round(x2*AppletWidth)) break;
}
}
private void CutSegments() {
int index = 0;
float gap;
float x1, x2;
Float tempFloat1, tempFloat2;
int stop = endpoints.size();
for (int i=0; i < stop; i+=2) {
CutMiddleThird(index, index+1);
index += 4;
}
}
private void CutMiddleThird(int left, int right) {
float gap;
float x1, x2;
Float tempFloat1, tempFloat2;
tempFloat1 = (Float) endpoints.elementAt(left);
tempFloat2 = (Float) endpoints.elementAt(right);
gap = tempFloat2.floatValue() - tempFloat1.floatValue();
x1 = tempFloat1.floatValue() + gap/3.0f;
x2 = tempFloat2.floatValue() - gap/3.0f;
endpoints.insertElementAt(new Float(x2), right);
endpoints.insertElementAt(new Float(x1), right);
}
}
Compile and load this applet. Is that clearer? Of course this isn't a perfect representation of the middle third set since we have to deal with points of finite size rather than with genuine mathematical points. Depending on how large a window you give your applet, you will probably only see about six to twelve iterations before we need to start working with fractional pixels.
Flying Lines
The next example is harder to describe than it is to code. Like Mondrian it runs in an infinite loop but it's a little more than random images. Compile the following code, run it and then look over the code to see if you can understand the algorithm.
//Bounce lines around in a box
import java.applet.Applet;
import java.awt.*;
public class FlyingLines extends Applet {
int NUM_LINES = 25;
int gDeltaTop=3, gDeltaBottom=3;
int gDeltaLeft=2, gDeltaRight=6;
int AppletWidth, AppletHeight;
int gLines[][] = new int[NUM_LINES][4];
public void init() {
AppletWidth = size().width;
AppletHeight = size().height;
}
public void start() {
gLines[0][0] = Randomize(AppletWidth);
gLines[0][1] = Randomize(AppletHeight);
gLines[0][2] = Randomize(AppletWidth);
gLines[0][3] = Randomize(AppletHeight);
for (int i=1; i < NUM_LINES; i++ ) {
LineCopy(i, i-1);
RecalcLine(i);
}
repaint();
}
public void paint(Graphics g) {
while (true) {
for (int i=NUM_LINES - 1; i > 0; i–) {
LineCopy(i, i-1);
}
RecalcLine(0);
g.setColor(Color.black);
g.drawLine(gLines[0][0], gLines[0][1], gLines[0][2], gLines[0][3]);
g.setColor(getBackground());
g.drawLine(gLines[NUM_LINES-1][0], gLines[NUM_LINES-1][1],
gLines[NUM_LINES-1][2], gLines[NUM_LINES-1][3]);
}
}
private void LineCopy (int to, int from) {
for (int i = 0; i < 4; i++) {
gLines[to][i] = gLines[from][i];
}
}
public int Randomize( int range ) {
double rawResult;
rawResult = Math.random();
return (int) (rawResult * range);
}
private void RecalcLine( int i ) {
gLines[i][1] += gDeltaTop;
if ((gLines[i][1] < 0) || (gLines[i][1] > AppletHeight)) {
gDeltaTop *= -1;
gLines[i][1] += 2*gDeltaTop;
}
gLines[i][3] += gDeltaBottom;
if ( (gLines[i][3] < 0) || (gLines[i][3] > AppletHeight) ) {
gDeltaBottom *= -1;
gLines[i][3] += 2*gDeltaBottom;
}
gLines[i][0] += gDeltaLeft;
if ( (gLines[i][0] < 0) || (gLines[i][0] > AppletWidth) ) {
gDeltaLeft *= -1;
gLines[i][0] += 2*gDeltaLeft;
}
gLines[i][2] += gDeltaRight;
if ( (gLines[i][2] < 0) || (gLines[i][2] > AppletWidth) ) {
gDeltaRight *= -1;
gLines[i][2] += 2*gDeltaRight;
}
} //RecalcLine ends here
} // FlyingLines ends here