In this lesson we'll address the issue of encapsulation (encapsulate: "to enclose in or as if in a capsule," Merriam-Webster). The subject of encapsulation can be addressed at many different levels. In Java, it can refer to breaking systems into packages, packages into objects, objects into properties and methods, methods into sub-methods, etc. In this discussion we are mainly looking at properties and methods.
GitHub repository: Cartesian Plane Part 3
Previous lesson: Cartesian Plane Lesson 2: Drawing the Grid Lines
Lesson 3: Encapsulation
1. Pieces and Properties
Figure 1 shows what our final version of the Cartesian Plane graphic might look like, before we plot any equations. Let's break it down into pieces:
grid (this piece includes the property pixels-per-unit)
main window
+-- margins
| +-- top
| +-- right
| +-- bottom
| +-- left
+-- grid lines
+-- axes
+-- tic marks (the lines across the x- and y-axes)
| +-- minor (the shorter lines)
| +-- major (the longer lines)
+-- labels (on the tic marks)
+-- text (in the margins)
+-- top
+-- right
+-- bottom
+-- left
A couple of notes on the above:
- The grid lines, axes and tic marks could be further broken into categories of horizontal and vertical, but let's not distinguish between orientation.
- We still have to talk about the bits of the graphic that represent plots, but let's leave that for later.
- For now, let's also leave out a discussion of marginal text.
So, leaving out text and plot, we have six categories of beast that have to be described. Each category is going to have certain properties. A list of the properties of each category follows:
- General grid properties
- Pixels-per-unit (the grid unit)
- Main window properties
- Width
- Height
- Background color
- Top margin properties
Note: A horizontal margin extends the full width of the component that contains it (the main window in this case); its width describes the distance between its minimum and maximum y-coordinates. A vertical margin extends the full height of the component that contains it; its width is the distance between its minimum and maximum x-coordinates.- Width
- Background color
- Right margin properties
- Width
- Background color
- Bottom margin properties
- Width
- Background color
- Left margin properties
- Width
- Background color
- Grid line properties
Note 1: horizontal and vertical lines have the same properties (the same color, weight and spacing).
Note 2: horizontal lines have a length that spans the width of the rectangle that contains the grid; vertical lines have a length that spans the height of the rectangle that contains the grid.- Weight (the thickness of the line)
- Color
- Lines per unit
- Draw grid lines (true or false; false means the grid lines won't be visible)
- Axis properties
Note 1: the horizontal and vertical axes have the same properties (the same color and weight).
Note 2: the horizontal axis has a length that spans the width of the rectangle that contains the grid; the vertical axis has a length that spans the height of the rectangle that contains the grid.- Weight (the thickness of the axis)
- Color
- Minor tic properties
Note: the horizontal and vertical tics have the same properties (the same color, weight, length and spacing).
- Weight (the thickness of the tic)
- Color
- Length
- Marks per unit
- Draw tic marks (true or false; false means the minor tic marks won't be visible)
- Major tic properties
- Weight (the thickness of the tic)
- Color
- Length
- Marks per unit
- Draw tic marks (true or false; false means the major tic marks won't be visible)
- Label properties
Note 1: for the sake of simplicity, the position of a label will always correspond to the position of a major tic mark; minor tic marks will not be labeled.- Font name
- Font style (bold, italic, plain, etc.
- Font size
- Draw labels (true or false; false means the labels won't be visible)
To encapsulate the above properties, we will have an instance variable for each, the types of which will be:
- Main window width and height: int
- Grid units: float
- All other width, height, length, spacing and weight properties: float
- All colors: Color
- Font name: String
- Font style: int (as determined by the Font class, for example, Font.BOLD)
- Font size: float
- Draw (e.g. Should we draw the labels? Should we draw the minor tic marks?): boolean
Before we start creating variable names let's establish some naming conventions:
Don't forget!!
Speaking of naming conventions, don't forget that there are some general Java naming conventions that you should be following. They include:
- Names should not begin with an underscore (_).
- Package name should consist of lowercase characters and underscores.
- Class and interface names should begin with a capital letter.
- Method names should begin with a lowercase character.
- Non-constant variable names should begin with a lowercase letter.
- Constant variable names should be written in uppercase with underscores separating name components (more about "constant variables," below).
- Variable names associated directly with grid properties (so far there's only grid units) begin with grid or GRID.
- Variable names associated with the main window (background color, for example) begin with mw or MW.
- Variable names associated with the margin begin with margin or MARGIN.
- Variable names associated with a specific margin contain a reference to their orientation immediately following margin (e.g., marginTop or MARGIN_TOP).
- Variable names associated with minor tic marks begin with ticMinor or TIC_MINOR.
- Variable names associated with major tic marks begin with ticMajor or TIC_MAJOR.
- Variable names associated with grid lines begin with gridLine or GRID_LINE.
- Variable names associated with the x- or y-axis begin with axis or AXIS.
- Variable names associated with labels begin with label or LABEL.
- "Background" is abbreviated bg or BG.
- "Default value" is abbreviated DV.
- "Lines per unit" is abbreviated LPU.
- "Marks per unit" is abbreviated MPU
2. Default Values; Constant Variables
Each of our properties will require a default value. We will encapsulate each default value in a constant variable. Now, where to put our constant variable declarations? One common strategy is to declare them in the class that contains their associated properties, so, for example, BOLD, ITALIC and MONOSPACED are declared in the java.awt.Font class. If you have many such constants, associated with properties across many classes, you might also collect all of them in a single class, as is the case with javax.swing.SwingConstants. We are going to put all of our constants into a class called CPConstants. In later lessons we'll introduce a lot more constants, and we'll put them all in this class. Since they are constant variables we'll follow the convention of giving them all names consisting of nothing but underscores and uppercase characters.
What is a constant variable?
A constant variable is a public class variable (declared static) that cannot be changed after being initialized (declared final). Constant variables serve a special purpose: their values can be extracted from a class without first loading the class (a time-consuming operation).
Are constant variables ever private?
Constants can certainly be private. In fact, private constants are an excellent idea, since they can be used to replace so-called "magic numbers." For example, instead of writing code like this:
renderAt( 3.3 )
You can write more readable code like this:
private static final float MAXIMUM_PSI = 3.3;
renderAt( MAXIMUM_PSI )
But, if its private, is it a constant variable with a name spelled in uppercase? Well, if it's private, it makes no sense to be able to extract its value from a class without first loading the class. I tell my students if it's public, static and final it's a constant variable, and must be spelled in uppercase. If it's private and feels like a constant (MAXIMUM_PSI, for instance), spell it in uppercase, but feel free to follow the naming convention for non-constant variables.
One more thing about the default values: they will all be implemented as Strings. For example, the default value for font size, a float value, will be implemented as:
String LABEL_FONT_SIZE_DV = "8";
This may seem at first non-intuitive, but it will make more sense later, when we extend our treatment of default values to places like property files, command line arguments and environment variables. To facilitate use, the CPConstants class will contain class methods for converting strings to appropriate types. Here are the string-to-X conversion methods.
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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | /** * Convert a String to an int and return the int. * * @param sVal the String to convert * * @return the converted int * * @throws NumberFormatException if sVal * cannot be converted to an int */ public static int asInt( String sVal ) { int iVal = Integer.parseInt( sVal ); return iVal; } /** * Convert a String to an float and return the float. * * @param sVal the String to convert * * @return the converted float * * @throws NumberFormatException if sVal * cannot be converted to a float */ public static float asFloat( String sVal ) { float fVal = Float.parseFloat( sVal ); return fVal; } /** * Convert a String to a boolean and return the boolean. * The operation is case-insensitive. * Any value other than "true" is converted to false. * * @param sVal the String to convert * * @return the converted boolean */ public static boolean asBoolean( String sVal ) { boolean bVal = Boolean.parseBoolean( sVal ); return bVal; } /** * Convert a String to a Color and return the Color. * The String must be encoded as an integer value. * Decimal integer and Hexadecimal integer values * are accepted. * (A hexadecimal string value begins with "0x" or "#".) * * @param sVal the String to convert * * @return the converted Color * * @throws NumberFormatException if sVal * cannot be converted to an integer */ public static Color asColor( String sVal ) { int iVal = Integer.decode( sVal ); Color cVal = new Color( iVal ); return cVal; } /** * Convert a String to a font style and return the result. * Integer values for font styles are defined in the Font class. * Input is case-insensitive; valid values are * PLAIN, BOLD and ITALIC. * * @param sVal the String to convert * * @return the converted Color * * @throws IllegalArgumentException if sVal * cannot be converted to a font style. */ public static int asFontStyle( String sVal ) { String cisVal = sVal.toUpperCase(); int iVal = -1; switch ( cisVal ) { case "PLAIN": iVal = Font.PLAIN; break; case "BOLD": iVal = Font.BOLD; break; case "ITALIC": iVal = Font.ITALIC; break; default: String err = "\"" + sVal + "\"" + "is not a valid font style"; throw new IllegalArgumentException( err ); } return iVal; } |
And here are the (many) default value declarations.
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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | ///////////////////////////////////////////////// // General grid properties ///////////////////////////////////////////////// /** Grid units (pixels-per-unit) default value: float. */ public static final String GRID_UNIT_DV = "65"; ///////////////////////////////////////////////// // Main window properties ///////////////////////////////////////////////// /** Grid units (pixels-per-unit) default value: float. */ /** Main window width default value: int. */ public static final String MW_WIDTH_DV = "500"; /** Main window height default value: int. */ public static final String MW_HEIGHT_DV = "500"; /** Main window background color default value: int. */ public static final String MW_BG_COLOR_DV = "0xE6E6E6"; ///////////////////////////////////////////////// // Margin properties ///////////////////////////////////////////////// /** Top margin width default value: float. */ public static final String MARGIN_TOP_WIDTH_DV = "20"; /** Top background color default value: int. */ public static final String MARGIN_TOP_BG_COLOR_DV = "0x008080"; /** Right margin width default value: float. */ public static final String MARGIN_RIGHT_WIDTH_DV = "20"; /** Right margin background color: int. */ public static final String MARGIN_RIGHT_BG_COLOR_DV = "0x008080"; /** Bottom margin width default value: float. */ public static final String MARGIN_BOTTOM_WIDTH_DV = "60"; /** Bottom margin background color: int. */ public static final String MARGIN_BOTTOM_BG_COLOR_DV = "0x008080"; /** Left margin width default value: float*/ public static final String MARGIN_LEFT_WIDTH_DV = "60"; /** Left margin background color: int. */ public static final String MARGIN_LEFT_BG_COLOR_DV = "0x008080"; ///////////////////////////////////////////////// // Tic mark properties ///////////////////////////////////////////////// /** Minor tic mark color default value default value: int. */ public static final String TIC_MINOR_COLOR_DV = "0X000000"; /** Minor tic mark weight default value: float. */ public static final String TIC_MINOR_WEIGHT_DV = "3"; /** Minor tic mark length default value: float. */ public static final String TIC_MINOR_LEN_DV = "7"; /** Minor tic marks per unit default value: float. */ public static final String TIC_MINOR_MPU_DV = "10"; /** Draw minor tic marks default value: boolean */ public static final String TIC_MINOR_DRAW_DV = "true"; /** Minor tic mark color default value: int. */ public static final String TIC_MAJOR_COLOR_DV = "0X000000"; /** Major tic mark weight default value: float. */ public static final String TIC_MAJOR_WEIGHT_DV = "3"; /** MAJOR tic mark length default value: float. */ public static final String TIC_MAJOR_LEN_DV = "7"; /** Major tic marks per unit default value: float. */ public static final String TIC_MAJOR_MPU_DV = "1"; /** Draw major tic marks default value: boolean */ public static final String TIC_MAJOR_DRAW_DV = "true"; ///////////////////////////////////////////////// // Grid line properties ///////////////////////////////////////////////// /** Grid line weight default value: float. */ public static final String GRID_LINE_WEIGHT_DV = "1"; /** Grid lines per unit default value: float. */ public static final String GRID_LINE_LPU_DV = TIC_MAJOR_MPU_DV; /** Left margin background color: int. */ public static final String GRID_LINE_COLOR_DV = "0x4B4B4B"; /** Draw grid lines default value: boolean */ public static final String GRID_LINE_DRAW_DV = "true"; ///////////////////////////////////////////////// // Axis properties ///////////////////////////////////////////////// /** Axis color default value: int. */ public static final String AXIS_COLOR_DV = "0X000000"; /** Axis weight default value: float. */ public static final String AXIS_WEIGHT_DV = "3"; ///////////////////////////////////////////////// // Label properties ///////////////////////////////////////////////// /** Label font color default value: int. */ public static final String LABEL_FONT_COLOR_DV = "0X000000"; /** Label font name default value: String. */ public static final String LABEL_FONT_NAME_DV = "Monospaced"; /** * Label font style default value: String. * One of the Font class constants: * BOLD, ITALIC or PLAIN */ public static final String LABEL_FONT_STYLE_DV = "PLAIN"; /** Label font size default value: float. */ public static final String LABEL_FONT_SIZE_DV = "8"; |
3. Implementing the Properties
So almost all of our properties will be implemented as instance variables. The two exceptions are the main window default width and height. These are only used in a constructor, and never changed. We'll implement them as class variables. Speaking of constructors, we will also add a default constructor which makes use of the default values (the defalt contructor is a constructor that has parameters; also called a no-argument constructor because you don't have to provide an argument when invoking it). Here are the declarations and the new constructor.
1 2 3 4 5 6 7 8 9 10 11 | private static final int mainWindowWidthDV = CPConstants.asInt( CPConstants.MW_WIDTH_DV ); private static final int mainWindowHeightDV = CPConstants.asInt( CPConstants.MW_HEIGHT_DV ); // ... public CartesianPlane() { this( mainWindowWidthDV, mainWindowHeightDV ); } // ... } |
Note that the new constructor makes use of constructor chaining. The code:
this( mainWindowWidthDV, mainWindowHeightDV );
Causes the overloaded constructor CartesianPlane(int, int) to be invoked. Constructor chaining is a convenient way to avoid have to write and maintain the same code in many constructors. If you're going to use constructor chaining the this(...) invocation must be the first line of code in the constructor. Here's a slightly more complex example of constructor chaining:
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 | public class SmartRect { private static final Color DEFAULT_COLOR = Color.BLUE; private static final float DEFAULT_WIDTH = 127.3f; private static final float DEFAULT_HEIGHT = 99.6f; private final Color color; private final float width; private final float height; public SmartRect() { this( DEFAULT_COLOR, DEFAULT_WIDTH, DEFAULT_HEIGHT ); } public SmartRect( Color color ) { this( color, DEFAULT_WIDTH, DEFAULT_HEIGHT ); } public SmartRect( float width, float height ) { this( DEFAULT_COLOR, width, height ); } public SmartRect( Color color, float width, float height ) { this.color = color; this.width = width; this.height = height; } // ... } |
Next come the declarations of the instance variables to hold all the other properties:
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 78 79 80 81 82 83 84 85 86 87 | ///////////////////////////////////////////////// // Main window properties // Note: "width" and "height" are included as // main window properties in CPConstants, // but it is not necessary to encapsulate // their values in instance variables. // See the default constructor. ///////////////////////////////////////////////// private Color mwBGColor = CPConstants.asColor( CPConstants.MW_BG_COLOR_DV ); ///////////////////////////////////////////////// // Margin properties ///////////////////////////////////////////////// private float marginTopWidth = CPConstants.asFloat( CPConstants.MARGIN_TOP_WIDTH_DV ); private Color marginTopBGColor = CPConstants.asColor( CPConstants.MARGIN_TOP_BG_COLOR_DV ); private float marginRightWidth = CPConstants.asFloat( CPConstants.MARGIN_RIGHT_WIDTH_DV ); private Color marginRightBGColor = CPConstants.asColor( CPConstants.MARGIN_RIGHT_BG_COLOR_DV ); private float marginBottomWidth = CPConstants.asFloat( CPConstants.MARGIN_BOTTOM_WIDTH_DV ); private Color marginBottomBGColor = CPConstants.asColor( CPConstants.MARGIN_BOTTOM_BG_COLOR_DV ); private float marginLeftWidth = CPConstants.asFloat( CPConstants.MARGIN_LEFT_WIDTH_DV ); private Color marginLeftBGColor = CPConstants.asColor( CPConstants.MARGIN_LEFT_BG_COLOR_DV ); ///////////////////////////////////////////////// // Tic mark properties ///////////////////////////////////////////////// private Color ticMinorColor = CPConstants.asColor( CPConstants.TIC_MINOR_COLOR_DV ); private float ticMinorWeight = CPConstants.asFloat( CPConstants.TIC_MINOR_WEIGHT_DV ); private float ticMinorLen = CPConstants.asFloat( CPConstants.TIC_MINOR_LEN_DV ); private float ticMinorMPU = CPConstants.asFloat( CPConstants.TIC_MINOR_MPU_DV ); private boolean ticMinorDraw = CPConstants.asBoolean( CPConstants.TIC_MINOR_DRAW_DV ); private Color ticMajorColor = CPConstants.asColor( CPConstants.TIC_MAJOR_COLOR_DV ); private float ticMajorWeight = CPConstants.asFloat( CPConstants.TIC_MAJOR_WEIGHT_DV ); private float ticMajorLen = CPConstants.asFloat( CPConstants.TIC_MAJOR_LEN_DV ); private float ticMajorMPU = CPConstants.asFloat( CPConstants.TIC_MAJOR_MPU_DV ); private boolean ticMajorDraw = CPConstants.asBoolean( CPConstants.TIC_MAJOR_DRAW_DV ); ///////////////////////////////////////////////// // Grid line properties ///////////////////////////////////////////////// private Color gridLineColor = CPConstants.asColor( CPConstants.GRID_LINE_COLOR_DV ); private float gridLineWeight = CPConstants.asFloat( CPConstants.GRID_LINE_WEIGHT_DV ); private float gridLineLPU = CPConstants.asFloat( CPConstants.GRID_LINE_LPU_DV ); private boolean gridLineDraw = CPConstants.asBoolean( CPConstants.GRID_LINE_DRAW_DV ); ///////////////////////////////////////////////// // Axis properties ///////////////////////////////////////////////// private Color axisColor = CPConstants.asColor( CPConstants.AXIS_COLOR_DV ); private float axisWeight = CPConstants.asFloat( CPConstants.AXIS_WEIGHT_DV ); ///////////////////////////////////////////////// // Label properties (these are the labels that // go on the x- and y-axes, e.g., 1.1, 1.2) ///////////////////////////////////////////////// private Color labelFontColor = CPConstants.asColor( CPConstants.LABEL_FONT_COLOR_DV ); private String labelFontName = CPConstants.LABEL_FONT_NAME_DV; private int labelFontStyle = CPConstants.asFontStyle( CPConstants.LABEL_FONT_STYLE_DV ); private float labelFontSize = CPConstants.asFloat( CPConstants.LABEL_FONT_SIZE_DV ); |
Occasionally our users are going to want to ask what the current value of a property is, or even change the value of a property. Since our instance variables are private (as they should be!) we will need methods to provide access to them. By convention, a setter is a method used to change a property value, and a getter is a method used to get the value (collectively these are called accessors). The format of setters and getters follows a simple a simple pattern. Given a property prop of a given type (int, float, etc.) a setter has this declaration:
public void setProp( type prop )
and a getter looks like this:
public type getProp()
Here are examples based on our top margin width property:
1 2 3 4 5 6 7 8 9 | public float getMarginTopWidth() { return marginTopWidth; } public void setMarginTopWidth(float marginTopWidth) { this.marginTopWidth = marginTopWidth; } |
Exceptions to the pattern can be made for boolean property getters. While the getProperty pattern is acceptable, you can substitute is for get. Here are the setter and getter for one of our boolean properties.
1 2 3 4 5 6 7 8 9 | public boolean isGridLineDraw() { return gridLineDraw; } public void setGridLineDraw(boolean gridLineDraw) { this.gridLineDraw = gridLineDraw; } |
A couple of more notes about properties:
Note 1: Not every property is encapsulated in an instance variable.
While it is true that every property in this project (at least so far) is associated with an instance variable, that is not always true. For example area and perimeter are properties of a rectangle, but they are not typically stored in variables:
1 2 3 4 5 6 7 8 9 10 11 | public float getArea() { float area = width * height; return area; } public float getPerimeter() { float perimeter = 2 * width + 2 * height; return perimeter; } |
Note 2: Not every property has a getter and a setter.
There are read-only properties that have only getters (such as area and perimeter in the above example). It is also possible (if rare) to have a write-only property that has s setter but no getter.
Note 3: Not every instance (or class) variable represents a public property.
Some of the instance variables we have so far, for example, currWidth and currHeight, exist purely for the convenience of out code and are of no interest to our users.
Note 4: Completing the setter/getter pattern.
There's one more bit about setters and getters that we haven't discussed: setters and getters for properties implemented as arrays. Such properties often have 2 pairs of setters and getters: one pair to set/get the entire array, and another pair to set/get an element in the array. The setter for an array element has a second parameter that describes the index of the element to set; the getter has a parameter that describes the element to get. Here is an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | private double[] results; public double[] getResults() { return results; } public void setResults(double[] results) { this.results = results; } public double getResult( int index ) { return results[index]; } public void setResult( double value, int index ) { results[index] = value; } |
Here are some of the results for our Cartesian Plane project.
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 78 79 80 81 | // ... /** * @return the ticMajorColor */ public Color getTicMajorColor() { return ticMajorColor; } /** * @param ticMajorColor the ticMajorColor to set */ public void setTicMajorColor(Color ticMajorColor) { this.ticMajorColor = ticMajorColor; } /** * @return the ticMajorWeight */ public float getTicMajorWeight() { return ticMajorWeight; } /** * @param ticMajorWeight the ticMajorWeight to set */ public void setTicMajorWeight(float ticMajorWeight) { this.ticMajorWeight = ticMajorWeight; } /** * @return the ticMajorLen */ public float getTicMajorLen() { return ticMajorLen; } /** * @param ticMajorLen the ticMajorLen to set */ public void setTicMajorLen(float ticMajorLen) { this.ticMajorLen = ticMajorLen; } /** * @return the ticMajorMPU */ public float getTicMajorMPU() { return ticMajorMPU; } /** * @param ticMajorMPU the ticMajorMPU to set */ public void setTicMajorMPU(float ticMajorMPU) { this.ticMajorMPU = ticMajorMPU; } /** * @return the ticMajorDraw */ public boolean isTicMajorDraw() { return ticMajorDraw; } /** * @param ticMajorDraw the ticMajorDraw to set */ public void setTicMajorDraw(boolean ticMajorDraw) { this.ticMajorDraw = ticMajorDraw; } // ... |
4. Breaking a Task into Manageable Units
You never want to mash hundreds (or tens or thousands) of lines of code into a small space in order to accomplish multiple tasks at once. Suppose you go to your paintComponent method and try to write one long stream of code to draw every bit of your plane. Afterward you find it doesn't work quite right. Do you have an error in your code to draw the grid lines? Or maybe the tic marks? If your labels don't look right how are you going to refine the label drawing code if it's mashed together with all the rest of your code?
We've already encapsulated some of our tasks in helper methods, for example, to draw the grid lines and paint the margins. Eventually we will also break out the code for drawing the axes, tic marks, labels and (several lessons from now) the user's graphs. For now, let's write the code for drawing the axes (at last! some visible progress!). We will also have to adapt the existing code for drawing grid lines and painting margins to use our new variable names. Here's the code to do that.
It's a common saying among object-oriented programmers that "a class should do one thing and do it well." But that can be applied to every other level of programming, too. A system should do one thing and do it well; a package should do one thing and do it well; a method, even a simple loop should do one thing and do it well.
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 | public void paintComponent( Graphics graphics ) { // ... drawGrid(); drawAxes(); paintMargins(); // ... } private void drawGrid() { gtx.setColor( gridLineColor ); gtx.setStroke( new BasicStroke( gridLineWeight ) ); float gridSpacing = gridUnit / gridLineLPU; float numLeft = (float)Math.floor( gridWidth / 2 / gridSpacing ); float leftXco = centerXco - numLeft * gridSpacing; for ( float xco = leftXco ; xco <= maxXco ; xco += gridSpacing ) { Line2D gridLine = new Line2D.Float( xco, minYco, xco, maxYco ); gtx.draw( gridLine ); } float numTop = (float)Math.floor( gridHeight / 2f / gridSpacing ); float topYco = centerYco - numTop * gridSpacing; for ( float yco = topYco ; yco <= maxYco ; yco += gridSpacing ) { Line2D gridLine = new Line2D.Float( minXco, yco, maxXco, yco ); gtx.draw( gridLine ); } } private void drawAxes() { gtx.setColor( axisColor ); gtx.setStroke( new BasicStroke( axisWeight ) ); Line2D line = new Line2D.Float(); // x axis line.setLine( centerXco, minYco, centerXco, maxYco ); gtx.draw( line ); // y axis line.setLine( minXco, centerYco, maxXco, centerYco ); gtx.draw( line ); } private void paintMargins() { Rectangle2D rect = new Rectangle2D.Float(); // top margin rect.setRect( 0, 0, currWidth, marginTopWidth ); gtx.setColor( marginTopBGColor ); gtx.fill( rect ); // right margin float marginRightXco = currWidth - marginRightWidth; rect.setRect( marginRightXco, 0, marginRightWidth, currHeight ); gtx.setColor( marginRightBGColor ); gtx.fill( rect ); // bottom margin float marginBottomXco = currHeight - marginBottomWidth; rect.setRect( 0, marginBottomXco, currWidth, marginBottomWidth ); gtx.setColor( marginBottomBGColor ); gtx.fill( rect ); // left margin rect.setRect( 0, 0, marginLeftWidth, currHeight ); gtx.setColor( marginLeftBGColor ); gtx.fill( rect ); } |
No comments:
Post a Comment