Cartesian Plane Lesson 8 Page 2: Points on the Graph, Properties

Cartesian plane user interface, custom events, functional interfaces, lambdas, streams

On this page, we will begin our discussion of how the user will plot points on the Cartesian plane that we have worked so hard to generate. The user will want to specify the coordinates of a point and also customize other properties, such as its color and, in particular, its shape; for this purpose, we will develop the PlotShape facility.

Plotting is an operation that lends itself well to streaming. When we introduce plotting into the CartesianPlane class, we will use a stream of commands that follows a pattern suggested by the following example:

  • Set point color;
  • Set point shape;
  • Draw point at (x1,y1);
  • Draw point at (x2,y2);
  • Draw point at (xn,yn);
  • Change point color;
  • Change point shape;
  • Draw point at (xn+1,yn+1);
  • etc.

To support this streaming strategy, we will develop the PlotCommand facility.

We’ll start by defining the PlotShape and PlotCommand facilities, both of which will reside in the com.acmemail.judah.cartesian_plane package.

GitHub repository: Cartesian Plane Part 8

Previous page: Cartesian Plane Lesson 8 Page 1: Notification Manager

The PlotShape Facility

The foundation of the PlotShape facility is the PlotShape interface. This is a functional interface whose abstract method is Shape getShape(double xco, double yco). The coordinates are the x- and y-coordinates of the pixel at which a point is drawn (I anticipate that this will be used as the center point of the shape, but that’s up to the implementer). Users can choose an implementation of the interface we supply, or write their own. The declaration of the interface itself is straightforward:

Review: Shape is an interface in the Java AWT that describes a geometric figure. Classes in the AWT that implement Shape include Arc2D, CubicCurve2D, Ellipse2D, Line2D, Path2D, Polygon, and Rectangle2D.

public interface PlotShape
{
    Shape getShape( double xco, double yco );
}

We will supply four pre-built implementations:

  • PointShape: this class will encapsulate a single pixel drawn at the given coordinates. A single pixel is represented by a Line2D 1 pixel in length*. It has no constructors.
  • CircleShape: this class encapsulates a circle of given radius whose center is at the given pixel coordinates. A circle is represented by an Ellipse2D bounded by a square. It has a constructor that sets the circle’s radius, and a default constructor that sets the radius to the spacing between minor tick marks (recall that spacing is computed using GRID_UNIT / TICK_MINOR_MPU).
  • SquareShape: this class represents a square with a center at the given coordinates. It is represented by a Rectangle2D with equal width and height. It has a constructor that sets the side length, and a default constructor that sets it to twice the spacing between minor tick marks.
  • DiamondShape: this class encapsulates a four-sided polygon, enclosed in a rectangle, with vertices at the center of each side of the rectangle. It has a constructor that sets the width and height of the enclosing rectangle, and a default constructor that sets them to 2 times the minor-tick spacing.

*It’s worth noting that, while a 1 pixel line is the traditional way to represent a point, the results may vary depending on the implementation of the graphics context and the current values of properties contained in it. It might be worth exploring an alternative, such as a 1×1 rectangle.

Here’s the code for the four classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class PointShape implements PlotShape
{
    /** Shape to use to plot a point. */
    private final Line2D    shape   = new Line2D.Float();
    
    @Override
    public Shape getShape( double xco, double yco )
    {
        shape.setLine( xco, yco, xco, yco );
        return shape;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class CircleShape implements PlotShape
{
    /** PropertyManager singleton; declared here for convenience. */
    private static final PropertyManager    PMGR            = 
        PropertyManager.INSTANCE;
    private static final String             GRID_UNIT       =
        CPConstants.GRID_UNIT_PN;
    private static final String             TIC_MINOR_MPU   =
        CPConstants.TIC_MINOR_MPU_PN;
    
    /** Shape to use to plot a point. */
    private final Ellipse2D shape   = new Ellipse2D.Float();
    
    /** Radius of the circle. */
    private final float     radius;
    /** 
     * Length of a side of the bounding rectangle
     * that describes the circle.
     */
    private final float     side;
    
    /**
     * Default constructor.
     * Instantiate a circle with radius
     * equal to the spacing between minor ticks.
     */
    public CircleShape()
    {
        this( getSpacing() );
    }
    
    /**
     * Constructor.
     * Sets the radius of the circle
     * to a given value.
     * 
     * @param radius    the given value
     */
    public CircleShape( float radius )
    {
        this.radius = radius;
        side = 2 * radius;
    }
    
    @Override
    public Shape getShape( double xco, double yco )
    {
        double  cornerXco   = xco - radius;
        double  cornerYco   = yco - radius;
        shape.setFrame( cornerXco, cornerYco, side, side );
        return shape;
    }
    
    /**
     * Calculate the spacing between minor ticks
     * based on the current grid unit and 
     * tick minor MPU.
     * 
     * @return  the current spacing between minor ticks
     */
    private static float getSpacing()
    {
        float   gridUnit    = PMGR.asFloat( GRID_UNIT );
        float   mpu         = PMGR.asFloat( TIC_MINOR_MPU );
        float   spacing     = gridUnit / mpu;
        return spacing;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class SquareShape implements PlotShape
{
    /** PropertyManager singleton; declared here for convenience. */
    private static final PropertyManager    PMGR            = 
        PropertyManager.INSTANCE;
    /** Grid unit property name; declared here for convenience. */
    private static final String             GRID_UNIT       =
        CPConstants.GRID_UNIT_PN;
    /** Minor tick/LPU property name; declared here for convenience. */
    private static final String             TIC_MINOR_MPU   =
        CPConstants.TIC_MINOR_MPU_PN;

    /** Shape to use to plot a point. */
    private final Rectangle2D   shape   = new Rectangle2D.Float();
    
    /** Length of a side of the square. */
    private final float     side;
    /** 
     * Offset to position the upper-left corner of the square
     * so that the center of the square
     * is at the coordinates passed to the getShape method.
     */
    private final float     cornerOffset;
    
    /**
     * Default constructor.
     * Sets the side of the square
     * to the spacing between minor tick marks.
     */
    public SquareShape()
    {
        this( getSpacing() * 2 );
    }
    
    /**
     * Constructor.
     * Sets the side of the square
     * to a given value.
     * 
     * @param side    the given value
     */
    public SquareShape( float side )
    {
        this.side = side;
        cornerOffset = side / 2;
    }
    
    @Override
    public Shape getShape( double xco, double yco )
    {
        double  cornerXco   = xco - cornerOffset;
        double  cornerYco   = yco - cornerOffset;
        shape.setFrame( cornerXco, cornerYco, side, side );
        return shape;
    }
    
    /**
     * Calculate the spacing between minor ticks
     * based on the current grid unit and 
     * tick minor MPU.
     * 
     * @return  the current spacing between minor ticks
     */
    private static float getSpacing()
    {
        float   gridUnit    = PMGR.asFloat( GRID_UNIT );
        float   mpu         = PMGR.asFloat( TIC_MINOR_MPU );
        float   spacing     = gridUnit / mpu;
        return spacing;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
 * Encapsulates a Shape to use
 * to plot points
 * on the CartesianPlane grid.
 * This Shape is constituted by
 * a rotated rectangle with a given width and height.
 * 
 * @author Jack Straub
 *
 */
public class DiamondShape implements PlotShape
{    
    /** 
     * PropertyManager instance.
     * Declared here for convenience.
     */
    private static final PropertyManager    pmgr    = 
        PropertyManager.INSTANCE;
    
    /** Shape to use to plot a point. */
    private final Path2D    shape           = new Path2D.Float();
    
    /** Width of bounding rectangle. */
    private final float     rectWidth;
    
    /** Height of bounding rectangle. */
    private final float     rectHeight;
    
    
    /**
     * Default constructor.
     * Set the width of the bounding rectangle
     * to 2 * minor tic spacing, 
     * and height to major tic length.
     */
    public DiamondShape()
    {
        this(
            pmgr.asFloat( CPConstants.TIC_MINOR_MPU_PN ) * 2,
            pmgr.asFloat( CPConstants.TIC_MAJOR_LEN_PN )
        );
    }
    
    /**
     * Constructor.
     * Sets the width and height of the rectangle
     * to the given values.
     * 
     * @param rectWidth     the given width
     * @param rectHeight    the given height
     */
    public DiamondShape( float rectWidth, float rectHeight )
    {
        float   rWidth  = rectWidth;
        float   rHeight = rectHeight;
        
        this.rectWidth = rWidth;
        this.rectHeight = rHeight;
    }
    
    @Override
    public Shape getShape( double xco, double yco )
    {
        double  leftXco     = xco - rectWidth / 2f;
        double  rightXco    = leftXco + rectWidth;
        double  topYco      = yco - rectHeight / 2;
        double  bottomYco   = topYco + rectHeight;
        
        shape.reset();
        shape.moveTo( leftXco, yco );
        shape.lineTo( xco, topYco );
        shape.lineTo( rightXco, yco );
        shape.lineTo( xco, bottomYco );
        shape.closePath();
        return shape;
    }
}

The PlotCommand Facility

This facility allows command objects to be instantiated and then streamed to a utility that plots points on a graph. Examples of commands include “set color to red” and “plot a point in a given CartesianPlane.” It is built around the PlotCommand functional interface, whose abstract method is void execute(). The declaration of the interface looks like this:

public interface PlotCommand
{
    void execute();
}

The facility is designed so that users can either create their own commands or use a prebuilt command. There are three prebuilt implementations of this interface:

  • PlotPointCommand: this command directs the CartesianPlane object to plot a point at a given coordinate (note this is not a pixel location; CartesianPlane maps the given Cartesian coordinate to the appropriate pixel coordinate). It has one constructor: PlotPointCommand( CartesianPlane plane, float xco, float yco ), where plane is the Cartesian plane in which to draw the given point, and xco and yco are the coordinates of the given point.
  • PlotShapeCommand: this command directs the CartesianPlane object to change the shape used to draw a point. It has one constructor, PlotShapeCommand(CartesianPlane plane, PlotShape shape), where plane is the target CartesianPlane object and shape is the shape to change to.
  • PlotColorCommand: this command directs the CartesianPlane object to change the color used to draw a point. It has one constructor, PlotColorCommand(CartesianPlane plane, Color color), where plane is the target CartesianPlane object and color is the new color to apply.

This facility will require new methods in the CartesianPlane class:

  • public void plotPoint( float userXco, float userYco )
    Draw a point at the given Cartesian coordinates.
  • public void setPlotColor( Color color )
    Set the drawing color to the given value.
  • public void setPlotShape( PlotShape plotShape )
    Set the shape of the next point(s) to the given object.

We’ll discuss detailed changes to the CartesianPlane class on the next page. Here is the source code for the three prebuilt classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class PlotPointCommand implements PlotCommand
{
    private static final String     format  = "PlotCommand: (%4.2f,%4.2f)";
    
    private final CartesianPlane    plane;
    private final float             xco;
    private final float             yco;
    
    /**
     * Constructor.
     * Instantiates a command to plot a point in a 
     * {@linkplain CartesianPlane}.
     * 
     * @param plane the plane in which the plot is to be plotted
     * @param xco   the x-coordinate of the point
     * @param yco   the y-coordinate of the point
     */
    public PlotPointCommand( CartesianPlane plane, float xco, float yco )
    {
        this.plane = plane;
        this.xco = xco;
        this.yco = yco;
    }
    
    @Override
    public String toString()
    {
        String  str = String.format( format, xco, yco );
        return str;
    }
    
    @Override
    public void execute()
    {
        plane.plotPoint( xco, yco );
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class PlotColorCommand implements PlotCommand
{
    private final CartesianPlane    plane;
    private final Color             color;
    
    /**
     * Constructor.
     * Determines the plane in which the plot color 
     * is to be set and the color to set it to.
     * 
     * @param plane the plane in which the plot color is to be set
     * @param color the color to set the plot color to.
     */
    public PlotColorCommand( CartesianPlane plane, Color color )
    {
        this.plane = plane;
        this.color = color;
    }
    
    @Override
    public void execute()
    {
        plane.setPlotColor( color );
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class PlotShapeCommand implements PlotCommand
{
    private final CartesianPlane    plane;
    private final PlotShape         shape;
    
    /**
     * Constructor.
     * Determines the shape to be used
     * when plotting a point
     * in the Cartesian plane.
     * 
     * @param plane the Cartesian plane in which the shape is to be used
     * @param shape the shape to use
     */
    public PlotShapeCommand( CartesianPlane plane, PlotShape shape )
    {
        this.plane = plane;
        this.shape = shape;
    }
    
    @Override
    public void execute()
    {
        plane.setPlotShape( shape );

    }
}

Summary

On this page, we discussed two new facilities to support plotting in the Cartesian plane:

  • PlotShape is a functional interface that allows users to specify the shape of a point drawn on the plane. Users can utilize a prebuilt shape or implement their own.
  • PlotCommand is a functional interface that allows users to control a stream of commands to a plotting facility. Examples of commands include changing the color to draw with, changing the shape of the drawing, and drawing a point at coordinates (x, y).

On the next page, we will extend the CartesianPlane class to execute a stream of commands that control a plot on the plane.

Next: Cartesian Plane Lesson 8 Page 3: Plotting Points on a Graph