[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 7.3


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 7.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

Write a panel class that lets the user draw polygons, and use your panel as the content pane in an applet (or standalone application). 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.

For this exercise, there is no need to store information about the contents of the applet. Do the drawing directly in the mousePressed() routine, and use the getGraphics() method to get a Graphics object that you can use to draw the line. (Remember, though, that this is considered to be bad style.) You will not need a paintComponent() method, since the default action of filling the panel with its background color is good enough.

You can try my solution here. 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

For my solution, I wrote a subclass of JApplet and used a nested class named Display to represent the panel on which the drawing is done. The only method in the main class is the init() method, which creates the drawing surface of type Display and uses it as the content pane of the applet. My discussion here is about Display.

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 the coordinates.
int pointCt;           // Number of points in the arrays.

The arrays xCoord and yCoord are examples of "partially full arrays", as covered in Subsection 7.3.1. The variable pointCt keeps track of how many spaces in the array are used. There are, of course, other ways to represent the data, such as using a single array of Point, or using an ArrayList. However, the statement of the problem asked for two arrays. 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 method to draw a line and a method to draw a polygon. These methods are called in the mousePressed() routine. Since the lines and polygons are drawn directly, rather than in the paintComponent() method, these methods 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:

/**
 * 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.
 */
private void putPolygon() {
   if (pointCt < 2)
      return;
   Graphics g = getGraphics();
   if (pointCt == 2) {
      g.setColor(Color.BLACK);
      g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]);
   }
   else {
      g.setColor(polygonColor);  // (polygonColor has value 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 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])."

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

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


/**
 * 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.  The drawing done in this
 * applet, using getGraphics() is considered to be bad style.
 *    Note that this class contains a main program, which
 * just opens a window that shows the same type of panel that
 * is shown by the applet. 
 */
public class SimplePolygons extends JApplet {
   
   /**
    * main() routine just shows a panel of type Display as its content pane.
    * This main routine has nothing to do with the function of this class
    * as an applet; it just makes it possible to run the class as a stand-
    * alone application.
    */
   public static void main(String[] args) {
      JFrame window = new JFrame("SimplePolygons");
      Display content = new Display();
      window.setContentPane(content);
      window.pack();
      window.setLocation(100,100);
      window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      window.setResizable(false);  
      window.setVisible(true);
   }
   
   /**
    * 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. 
    */
   public void init() {
      setContentPane( new Display() );
   }
   
   
   /**
    * This private class does all the work of the program.  It represents
    * the drawing surface where the user draws the polygons.  It listens
    * for mouse events on itself.
    */
   private static 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 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 Display() {
         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;
      }
      
         
      /**
       * Draw a line from (x1,y1) to (x2,y2) directly onto this
       * component, without going through the paintComponent() 
       * method.
       */
      private void putLine(int x1, int y1, int x2, int y2) {
         Graphics g = getGraphics();
         g.setColor(Color.BLACK);
         g.drawLine(x1,y1,x2,y2);
         g.dispose();
      }
      
      /**
       * 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.
       */
      private void putPolygon() {
         if (pointCt < 2)
            return;
         Graphics g = getGraphics();
         if (pointCt == 2) {
            g.setColor(Color.BLACK);
            g.drawLine(xCoord[0], yCoord[0], xCoord[1], yCoord[1]);
         }
         else {
            g.setColor(polygonColor);
            g.fillPolygon(xCoord, yCoord, pointCt);
            g.setColor(Color.BLACK);
            g.drawPolygon(xCoord, yCoord, pointCt);
         }
         g.dispose();
      }
      
      
      /**
       * Processes a mouse click.
       */
      public void mousePressed(MouseEvent evt) { 
         
         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 ]