2007-02-15
| Table of Contents: |
| Rate This Article: | Add This Article To: |
( Page 3 of 3 )
Now let's get to the code. Any drawing code must be put in or called from the control's Paint event procedure. Why Paint? This is one of the events that is built into .Net Framework controls (and forms as well), an event hat you can use without doing any special coding. Paint is called for a control whenever the control needs to be redrawn, such as when it is first displayed and when it is "uncovered" by another form. Seeing that this project does a lot of graphics output, I opted to put the various sections of drawing code each in their own procedure and then call each procedure as needed from the Paint event procedure. Otherwise, the Paint procedure would end up large and unwieldy.
When the Framework calls Paint, it passes an argument of type PaintEventArgs to the procedure. In turn, the PaintEventArgs object contains a reference to a Graphics object that is linked to the screen. The Graphics object provides a rich assortment of methods for performing any desired drawing operation, from lines and shapes to text and bitmaps. Your code will use this Graphics object to draw the chart elements on the screen. Here are the steps to follow. First, put the call to your procedure (which does not exist yet) in the Pain event procedure:
1. In the editing window, open the drop-down list at the top left and select XYChart Events.
2. In the list at the top right, select the Paint event. Visual Studio enters the shell of the Paint procedure (the first and last lines of code).
3. Enter the call to your axis-drawing procedure, passing it the Graphics object that is supplied to the Paint procedure. Because your procedure does not exist, Visual Studio flags the call as an error, but we'll fix that soon.
At this point the Paint event procedure will look like Listing 2.
Listing 2. The Paint event procedure with a call to the procedure for drawing the axes and tick marks.
Private Sub XYChart_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) _ Handles Me.Paint DrawBoxAndTickMarks(e.Graphics) End Sub
The next step is to code the procedure that does the drawing. The plan is as follows. Note the use of "pseudocode" to plan out what steps the procedure will need to perform. Pseudocode is nothing more than plain English statements detailing what actions will be taken. I often create the shell of the procedure and then add comments with the pseudocode. This is helpful to plan the sequence and nature of the actions that the code will take before you actually start writing the code. Anyway, the procedure will:
Draw a box around the plot area. The Graphics.DrawRectange method will be used for this. The coordinates will be determined from the control's width and height and the constants (MYTOP, etc.) that we defined earlier.
Draw tick marks on each axis:
a. Get the length of the axis.
b. Divide by the number of tick marks less one to get the distance between tick marks.
c. Use a loop to draw each tick mark (using Graphics.DrawLine) and then advance to the next one.
The reason for this, I discovered, was that the drawing is done in integer coordinates (that is, screen pixels) but when dividing the length of the entire axis into intervals it would not always come out even. For example, suppose you have 11 tick marks and therefore 10 intervals on the X axis, and the axis is 557 pixels long. This length cannot be exactly divided into 10 integer intervals, so the interval is rounded to 56 pixels. Using the original algorithm, the last tick mark is displayed at position 10 * 56 or 560 pixels, 3 pixels too far to the right.
To avoid this problem I modified the algorithm so that for the last tick mark on each axis, the plot coordinates are not calculated from the interval but are obtained from the position constants defined earlier. Thus, in this example, for the last (right-most) tick mark on each X axis, the X coordinate is calculated the same way as for the right Y axis as the width of the control minus the right Y axis offset: Me.Width - MYRIGHT. The result is the precise tick mark alignment that I wanted. For the intermediate tick marks there still might be a slight error in position but this does not really matter, being invisible to the user.
The complete procedure for drawing the axes and tick marks is presented in Listing 3. You should add this code to the control, placing it below the Paint event procedure.
Listing 3. The procedure for drawing the axes and tick marks.
Private Sub DrawBoxAndTickMarks(ByVal g As Graphics) Dim i As Integer Dim deltaX As Integer, deltaY As Integer Dim yStart As Integer, yEnd As Integer Dim xStart As Integer, xEnd As Integer ' Draw the plot area bounding box. g.DrawRectangle(Pens.Black, MYLEFT, MYTOP, Me.Width - MYRIGHT - MYLEFT, _ Me.Height - MYBOTTOM - MYTOP) ' Draw the tick marks. ' First draw on each horizontal axis. ' Get width of plot area and divide into (NUM_X_TICKS - 1) intervals. deltaX = (Me.Width - MYLEFT - MYRIGHT) / (NUM_X_TICKS - 1) ' Ending points as Y coordinates, first for the lower X axis. yStart = Me.Height - MYBOTTOM + ((TICK_MARK_LENGTH - 1) / 2) yEnd = Me.Height - MYBOTTOM - ((TICK_MARK_LENGTH - 1) / 2) For i = 0 To NUM_X_TICKS - 1 If i = NUM_X_TICKS - 1 Then g.DrawLine(Pens.Black, Me.Width - MYRIGHT, yStart, Me.Width - MYRIGHT, yEnd) Else g.DrawLine(Pens.Black, MYLEFT + (i * deltaX), yStart, MYLEFT + (i * deltaX), yEnd) End If Next ' Now for the upper X axis. yStart = MYTOP + ((TICK_MARK_LENGTH - 1) / 2) yEnd = MYTOP - ((TICK_MARK_LENGTH - 1) / 2) For i = 0 To NUM_X_TICKS - 1 If i = NUM_X_TICKS - 1 Then g.DrawLine(Pens.Black, Me.Width - MYRIGHT, yStart, Me.Width - MYRIGHT, yEnd) Else g.DrawLine(Pens.Black, MYLEFT + (i * deltaX), yStart, MYLEFT + (i * deltaX), yEnd) End If Next ' Now draw ticks on each vertical axis. ' Get height of plot area and divide into (NUM__TICKS - 1) intervals. deltaY = (Me.Height - MYTOP - MYBOTTOM) / (NUM_Y_TICKS - 1) ' Ending points as X coordinates, first for the right Y axis. xStart = Me.Width - MYRIGHT + ((TICK_MARK_LENGTH - 1) / 2) xEnd = Me.Width - MYRIGHT - ((TICK_MARK_LENGTH - 1) / 2) For i = 0 To NUM_Y_TICKS - 1 If i = NUM_Y_TICKS - 1 Then g.DrawLine(Pens.Black, xStart, Me.Height - MYBOTTOM, xEnd, Me.Height - MYBOTTOM) Else g.DrawLine(Pens.Black, xStart, MYTOP + (i * deltaY), xEnd, MYTOP + (i * deltaY)) End If Next ' Now for the left Y axis. xStart = MYLEFT + ((TICK_MARK_LENGTH - 1) / 2) xEnd = MYLEFT - ((TICK_MARK_LENGTH - 1) / 2) For i = 0 To NUM_Y_TICKS - 1 If i = NUM_Y_TICKS - 1 Then g.DrawLine(Pens.Black, xStart, Me.Height - MYBOTTOM, xEnd, Me.Height - MYBOTTOM) Else g.DrawLine(Pens.Black, xStart, MYTOP + (i * deltaY), xEnd, MYTOP + (i * deltaY)) End If Next End Sub
What's Next?
Our XY chart comnponent is starting to take shape. If you were to embed it on a form at this point it would look as shown in the figure below. (I'll cover how to test a control during development in the next article). The next article will also delve into an essential aspect of this component, the representation of the data to be plotted.
![]() |
|


