Solution for Programming Exercise 6.9
This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.
Exercise 6.9:
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 the points (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 program 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. As the user is creating the polygon, you should just connect all the points with line segments. When the user clicks near the starting point, draw the complete polygon. Draw it with a red interior and a black border. Once the user has completed a polygon, the next click will clear the data and start a new polygon from scratch. All drawing should be done in the paintComponent() method.
Here is a picture of my solution after the user has drawn a fairly complex polygon:
This is an exercise in using arrays, but it is also an exercise in using instance variables to record the state of the program. The paintComponent() method needs enough information to correctly draw the picture. Obviously, that includes the coordinate arrays and the number of points that have been stored in the arrays. But the picture is different depending on whether or not the user has completed the polygon. If the polygon is complete, the picture shows a polygon; if not, the picture shows line segments connecting each point to the next. We need to record that basic distinction as part of the state. We can do that using a boolean variable, complete, which is true when a complete polygon should be drawn and false while the polygon is under construction. So, here are the necessary instance variables:
int[] xCoord, yCoord; // Arrays to hold the coordinates for up to 500 points. int pointCt; // Number of points in the arrays. boolean complete; // Set to true when a polygon is complete.
The arrays xCoord and yCoord are examples of partially full arrays, although there is only one counter variable that applies to both arrays.
Given these instance variables, the paintComponent() method can be written. To make the picture look nicer, I decided to use two Graphics2D features: antialiasing and wide lines. (See Subsection 6.2.5). There is also a question about what to draw when there is only one point. It's nice if the user can see that the point has been added to the data, but one point is not enough to draw a line or polygon. I decided to draw a small rectangle at the point, just to make it visible.
protected void paintComponent(Graphics g) { super.paintComponent(g); if (pointCt == 0) return; // Nothing at all to draw Graphics2D g2 = (Graphics2D)g; g2.setStroke( new BasicStroke(2) ); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (pointCt == 1) { // just one point -- draw a small dot there g.fillRect(xCoord[0], yCoord[0], 2, 2); } else if (complete) { // The polygon is complete; draw it, with a black border g.setColor(polygonColor); g.fillPolygon(xCoord, yCoord, pointCt); g.setColor(Color.BLACK); g.drawPolygon(xCoord, yCoord, pointCt); } else { // The polygon is not complete, just draw a series of lines. for (int i = 0; i < pointCt - 1; i++) g.drawLine( xCoord[i], yCoord[i], xCoord[i+1], yCoord[i+1]); } }
The rest of the program logic is in the mousePressed() routine. In that method, state variables have to change in response to the user's actions. It requires some care to do things in the right order. The three things that can happen are that the user starts a new polygon, the user completes the current polygon, or the user just adds a point to the current polygon. The conditions should be tested in that order:
if the current polygon is complete start a new polygon with the point where the user clicked else if the user clicked near the starting point complete the current polygon else add the point that the user clicked to the data call repaint() to make the change visible
Actually, in my solution, I decided to add another case: The polygon can also be completed by right-clicking, or -- to be very safe -- if the number of points has reached 500. Also, there is a bug 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])."
To complete a polygon just means setting the value of the variable complete to true. When repaint() is called, the data will be displayed as a polygon. When a new polygon is stated, the value of the variable complete has to be reset to false. Also, the first point on the polygon has to be put into the coordinate arrays, and pointCt must be set to 1 to indicate that there is only one point in the data. This much is pretty straightforward to implement.
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 program, I check whether the x-coordinates of these points are three 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()) <= 3 && Math.abs(yCoord[0] - evt.getY()) <= 3".
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * This program lets the user draw colored polygons. * The user inputs a polygon by clicking a series of points. * The points are connected with lines from each point to the * next Clicking near the starting point (within 3 pixels) or * right-clicking (or Command-clicking) will complete the * polygon, so the user can begin a new one. As soon as the * user begins drawing a new polygon, the old one is discarded. */ public class SimplePolygons extends JPanel implements MouseListener { /** * A main() routine to allow this program to be run as an application. */ public static void main(String[] args) { JFrame window = new JFrame("SimplePolygons"); SimplePolygons content = new SimplePolygons(); window.setContentPane(content); window.pack(); window.setLocation(100,100); window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); window.setResizable(false); window.setVisible(true); } /* 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 boolean complete; // Set to true when the polygon is complete. // When this is false, only a series of lines are drawn. // When it is true, a filled polygon is drawn. private final static Color polygonColor = Color.RED; // Color that is used to draw the polygons. /** * Initialize the panel and its data; add a black border and set * the panel to listen for mouse events on itself. Also sets * the preferred size of the panel to be 300-by-300. */ public SimplePolygons() { setBackground(Color.WHITE); setBorder(BorderFactory.createLineBorder(Color.BLACK,1)); setPreferredSize( new Dimension(300,300) ); addMouseListener(this); xCoord = new int[500]; yCoord = new int[500]; pointCt = 0; } protected void paintComponent(Graphics g) { super.paintComponent(g); if (pointCt == 0) return; Graphics2D g2 = (Graphics2D)g; g2.setStroke( new BasicStroke(2) ); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (pointCt == 1) { g.fillRect(xCoord[0], yCoord[0], 2, 2); } else if (complete) { // draw a polygon g.setColor(polygonColor); g.fillPolygon(xCoord, yCoord, pointCt); g.setColor(Color.BLACK); g.drawPolygon(xCoord, yCoord, pointCt); } else { // draw a series of lines for (int i = 0; i < pointCt - 1; i++) g.drawLine( xCoord[i], yCoord[i], xCoord[i+1], yCoord[i+1]); } } /** * Processes a mouse click. */ public void mousePressed(MouseEvent evt) { if (complete) { // Start a new polygon at the point that was clicked. complete = false; xCoord[0] = evt.getX(); yCoord[0] = evt.getY(); pointCt = 1; } else if ( pointCt > 0 && pointCt > 0 && (Math.abs(xCoord[0] - evt.getX()) <= 3) && (Math.abs(yCoord[0] - evt.getY()) <= 3) ) { // User has clicked near the starting point. // The polygon is complete. complete = true; } else if (evt.isMetaDown() || pointCt == 500) { // The polygon is complete. complete = true; } 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++; } repaint(); // in all cases, repaint. } // end mousePressed() public void mouseReleased(MouseEvent evt) { } public void mouseClicked(MouseEvent evt) { } public void mouseEntered(MouseEvent evt) { } public void mouseExited(MouseEvent evt) { } } // end class SimplePolygons