Solution for
Programming Exercise 8.3
THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to
the following exercise from this on-line
Java textbook.
Exercise 8.3:
A polygon is a geometric figure
made up of a sequence of connected line segments. The points where the
line segments meet are called the vertices
of the polygon. The Graphics class includes commands for
drawing and filling polygons. For these commands, the coordinates of
the vertices of the polygon are stored in arrays. If g
is a variable of type Graphics then
- g.drawPolygon(xCoords, yCoords, pointCt)
will draw the outline of the polygon with vertices at
(xCoords[0],yCoords[0]), (xCoords[1],yCoords[1]), ...,
(xCoords[pointCt-1],yCoords[pointCt-1]). The third parameter,
pointCt, is an int that specifies the number of vertices
of the polygon. Its value should be 3 or greater. The first two parameters
are arrays of type int[]. Note that the polygon automatically
includes a line from the last point, (xCoords[pointCt-1],yCoords[pointCt-1]),
back to the starting point (xCoords[0],yCoords[0]).
- g.fillPolygon(xCoords, yCoords, pointCt)
fills the interior of the polygon with the current drawing color. The parameters
have the same meaning as in the drawPolygon() method. Note that it is OK for the sides of the polygon to cross each other, but the
interior of a polygon with self-intersections might not be exactly what you
expect.
Write a little applet that lets the user draw polygons. As the user clicks
a sequence of points, count them and store their x- and y-coordinates in two
arrays. These points will be the vertices of the polygon.
Also, draw a line between each consecutive pair of points to give
the user some visual feedback. When the user clicks near the starting
point, draw the complete polygon. Draw it with a red interior and
a black border. The user should then be able to start drawing a new
polygon. When the user shift-clicks on the applet, clear it.
There is no need to store information about the contents of the applet.
The paintComponent() method can just draw a border around the applet.
The lines and polygons can be drawn using a graphics context, g,
obtained with the command "g = getGraphics();".
You can try my solution. Note that as the user is drawing the
polygon, lines are drawn between the points that the user clicks.
Click within two pixels of the starting point to see a filled polygon.
Discussion
I put all the programming for my solution into a nested
class named Display. This class defines the drawing surface of the applet. The
only method in the main class is the init() method,
which creates the drawing surface and uses it as the content
pane of the applet.
The program needs several instance variables to store information
about the sequence of points that the user has clicked:
int[] xCoord, yCoord; // Arrays to hold he coordinates.
int pointCt; // Number of points in the arrays.
The arrays xCoord and yCoord are examples of
"partially full arrays", as covered in Section 8.3.
The variable pointCt keeps track of how many spaces in the
array are used. The instance variables are initialized in the
constructor of the Display with the commands:
xCoord = new int[500];
yCoord = new int[500];
pointCt = 0;
The size of the arrays allows for polygons with up to 500 vertices,
which should certainly be enough. When we start, there are not yet
any points in the array, so pointCt is zero. (The command
to set pointCt to zero is not really necessary, since pointCt,
as an instance variable, is automatically initialized to zero in any case.
However, including this command seemed to me to make my intentions clearer
to the reader. That should always be a consideration.)
In my solution, I included a subroutine to draw a line and
a subroutine to draw a polygon. These subroutines are called in
the mousePressed() routine. Since the lines and polygons
are drawn directly, rather than in the paintComponent()
method, these subroutines are responsible for obtaining graphics
contexts to do the drawing. The putLine method is simple:
private void putLine(int x1, int y1, int x2, int y2) {
// Draw a line from (x1,y1) to (x2,y2) directly onto this
// component, without going through the paintComponent()
// method.
Graphics g = getGraphics();
g.drawLine(x1,y1,x2,y2);
g.dispose();
}
The putPolygon() method uses the data for the polygon
that is stored in the instance variables xCoord, yCoord,
and pointCt. It is complicated a little by the fact that three
points are required to draw a polygon. If there are only two points
stored in the arrays, then I just draw a line between the two points.
Note that the two points are stored in the first two locations in
the xCoord and yCoord arrays. The coordinates
of the two points are (xCoord[0],yCoord[0]) and (xCoord[1],yCoord[1]).
If there are fewer than two points, I don't draw anything. If there
are three or more points, then I first draw the interior of the polygon
in red and then draw the outline of the polygon in black:
private void putPolygon() {
// Draw the polygon described by the arrays xCoord and yCoord
// and the integer pointCt. A filled polygon with a black
// outline is drawn. If pointCt is 0 or 1, nothing is drawn.
// If pointCt is 2, only a black line is drawn.
if (pointCt < 2)
return;
Graphics g = getGraphics();
if (pointCt == 2) {
g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]);
}
else {
g.setColor(Color.red);
g.fillPolygon(xCoord, yCoord, pointCt);
g.setColor(Color.black);
g.drawPolygon(xCoord, yCoord, pointCt);
}
g.dispose();
}
The main logic of the applet is in the mousePressed() method.
An algorithm for this method is:
if the user was holding down the Shift key:
Clear the applet, and start a new polygon
else if the user clicked near the first point:
Draw the current polygon and start a new one
else
Add the point (evt.getX(), evt.getY()) to the coordinate arrays
if this is not the first point in the arrays:
Draw a line between the previous point and this one
Actually, in my solution, I decided to add another case: If the user
right-clicks the applet or if the number of points reaches 500,
then I draw the current polygon and start a new one. Also, there
is a flaw in the algorithm as stated, where it tests "if the user
clicked near the first point". This test doesn't make sense unless
there actually is a first point, that is unless pointCt is
greater than zero. The test should really read "if pointCt > 0
and if the user clicked near (xCoord[0],yCoord[0])."
Where the algorithm says "start a new polygon", it is only necessary
to say "pointCt = 0;" since that will indicate that the coordinate
arrays contain no valid data. The command "draw the current polygon"
can be translated as "putPolygon()". Adding a point to
the coordinate arrays means putting the coordinates of the point in
the next available location of the arrays and incrementing pointCt
to record the fact that the number of valid data in the arrays has increased
by one. This is done with the commands
xCoord[pointCt] = evt.getX();
yCoord[pointCt] = evt.getY();
pointCt++;
Then, if pointCt is greater than 1, we have to draw a line
between the last two points in the array. The coordinates of these
points are (xCoord[pointCt-2],yCoord[pointCt-2]) and
(xCoord[pointCt-1],yCoord[pointCt-1]), so this can be done with
the command
putLine(xCoord[pointCt-2], yCoord[pointCt-2],
xCoord[pointCt-1], yCoord[pointCt-1] );
The only thing in the algorithm that still needs implementing is
to test whether the user clicks "near the starting point". The
starting point has coordinates (xCoord[0],yCoord[0]) and
the point where the user clicked has coordinates
(evt.getX(),evt.getY()). In my applet, I check whether
the x-coordinates of these points are two pixels or less apart
and the y-coordinates are also two pixels or less apart.
This is done by checking whether "Math.abs(xCoord[0] - evt.getX()) <= 2
&& Math.abs(yCoord[0] - evt.getY()) <= 2".
If you are uncomfortable with absolute values, you can use the
equivalent test "(evt.getX() - 2 <= xCoords[0])
&& (xCoords[0] <= evt.getX() + 2) &&
(evt.getY() - 2 <= yCoords[0])
&& (yCoords[0] <= evt.getY() + 2)".
The complete program for the applet is shown below.
(By the way, there are versions of the drawPolygon()
and fillPolygon() methods that work with objects belonging
to a class called Polygon, instead of with arrays. So,
you don't absolutely need arrays to work with polygons.)
The Solution
/*
This applet lets the user draw colored polygons.
The user inputs a polygon by clicking a series of points.
Clicking near the starting point (within 2 pixels) or
right-clicking (or Command-clicking) will complete the
polygon, so the user can begin a new one. Shift-clicking
will clear the screen. Up to 500 points are allowed in a
polygon. This applet does not keep a copy of the image
on the screen, so it will not reappear if the applet is
covered and then uncovered.
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimplePolygons extends JApplet {
public void init() {
// This simple init() method just creates a drawing surface
// belonging to the nested class Display and uses it for
// the content pane of the applet. (This is one case where
// it is rather silly to use a JApplet rather than a plain
// old Applet!)
setContentPane( new Display() );
}
class Display extends JPanel implements MouseListener {
/* Variables for implementing polygon input. */
private int[] xCoord, yCoord; // Arrays containing the points of
// the polygon. Up to 500 points
// are allowed.
private int pointCt; // The number of points that have been input.
private Color polygonColor = Color.red;
// Color that is used to draw the polygons.
public Display() {
// Initialize the applet. The applet listens for mouse events.
// Arrays are created to hold the points. A drawing surface
// belonging to the nested class Display is created and
// used as the drawing surface for the applet.
setBackground(Color.white);
addMouseListener(this);
xCoord = new int[500];
yCoord = new int[500];
pointCt = 0;
}
public void paintComponent(Graphics g) {
// The paintComponent() routine just draws a 1-pixel black
// border around the applet. Polygons drawn on the applet
// are not permanent.
super.paintComponent(g); // Fills with background color, white.
g.setColor(Color.black);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g.drawRect(1, 1, getWidth() - 3, getHeight() - 3);
}
private void putLine(int x1, int y1, int x2, int y2) {
// Draw a line from (x1,y1) to (x2,y2) directly onto this
// component, without going through the paintComponent()
// method.
Graphics g = getGraphics();
g.drawLine(x1,y1,x2,y2);
g.dispose();
}
private void putPolygon() {
// Draw the polygon described by the arrays xCoord and yCoord
// and the integer pointCt. A filled polygon with a black
// outline is drawn. If pointCt is 0 or 1, nothing is drawn.
// If pointCt is 2, only a black line is drawn.
if (pointCt < 2)
return;
Graphics g = getGraphics();
if (pointCt == 2) {
g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]);
}
else {
g.setColor(Color.red);
g.fillPolygon(xCoord, yCoord, pointCt);
g.setColor(Color.black);
g.drawPolygon(xCoord, yCoord, pointCt);
}
g.dispose();
}
public void mousePressed(MouseEvent evt) {
// Process a user mouse-click.
if (evt.isShiftDown()) {
// Clear the applet. (This only requires a repaint.)
// Also, set pointCt to zero to start a new polygon.
pointCt = 0;
repaint();
}
else if ( pointCt > 0 && (Math.abs(xCoord[0] - evt.getX()) <= 2)
&& (Math.abs(yCoord[0] - evt.getY()) <= 2) ) {
// User has clicked near the starting point.
// Draw the polygon and reset pointCt so that the
// user can start a new polygon.
putPolygon();
pointCt = 0;
}
else if (evt.isMetaDown() || pointCt == 500) {
// Draw the polygon and reset pointCt so that the
// user can start a new polygon. Note that there
// is a limit of 500 points for one polygon.
putPolygon();
pointCt = 0;
}
else {
// Add the point where the user clicked to the list of
// points in the polygon, and draw a line between the
// previous point and the current point. A line can
// only be drawn if there are at least two points.
xCoord[pointCt] = evt.getX();
yCoord[pointCt] = evt.getY();
pointCt++;
if (pointCt >= 2) {
putLine(xCoord[pointCt-2], yCoord[pointCt-2],
xCoord[pointCt-1], yCoord[pointCt-1]);
}
}
} // end mousePressed()
public void mouseReleased(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
} // end nested class Display
} // end class SimplePolygons
[ Exercises
| Chapter Index
| Main Index
]