Step 4a: Graphics and Bitmap Objects
We touched on the role of the Graphics object in Part 1 and saw that it can be thought of as a canvas on which we can draw what we want to see on the surface of the form or other control. We drew directly on to it for our Pie Chart.
As we are dealing with a PictureBox in this article, the approach will be slightly different.
This is what we do:
- Declare a Graphics object
- Also create a Bitmap object. This Bitmap will be same size as the PictureBox and will be assigned the PictureBox’s Graphics object.
- To give us access to the drawing methods of the Graphics class, we assign the Bitmap to the Graphics object.
- Once the drawing is complete we take the Bitmap that we have edited with the graphics tools and use that as the PictureBox’s Image.
Confused? Don't worry! It’s tricky stuff when you first meet it, but after a while it does begin to fall into place. Essentially, we have created a Graphics object which is the same shape, size and resolution as the PictureBox. We can draw on this and later we will use our completed drawing as the image for the PictureBox.
All the drawing code goes into one procedure named DrawChart. Here is the code for the above actions (This is of course just the first part of the complete procedure):
Private Sub DrawChart()
Dim g As Graphics
Dim bmap As Bitmap
bmap = New Bitmap(PBBarChart.Width, PBBarChart.Height, _
g = Graphics.FromImage(bmap)
Step 4b: Vertical Axis
The vertical axis of the chart (technically known as the Y-Axis) will be drawn inside the PictureBox. We move it in from the boundaries, using the values we have assigned to the TopMargin. LeftMargin and BaseMargin variables.
This axis will have small tick marks evenly spaced, each space representing 100 sales. In addition, the value of each tick mark will be written at the side of mark. The image below shows a sample part of the vertical axis. You can see the full thing in the picture at the start of this article:
The following code sets the start point and end point of the vertical axis:
Dim StartPoint As New Point(LeftMargin, PBBarChart.Height - BaseMargin)
Dim EndPoint As New Point(LeftMargin, TopMargin)
The names of the variables StartPoint and EndPoint need no explanation but the concept of a Point might be worth a mention. We are all familiar with the idea of a point – that is, a specific location which can be identified in some way. In .Net graphics, a Point is identified by its x position (how far it is from the left hand side) and its y position (how far down it is from the top).
In the current project we measure these two values in pixels, because that is the unit used to track positions on the screen. It is important to understand that the Point isn’t just a position in space, it is in fact an Object, complete with its own properties and methods.
You see that the StartPoint Point has its x position set in line with the left margin we set earlier; its y position is calculated by moving up from the bottom of the picture box by the distance equal to the BaseMargin.
The EndPoint of the axis has an x value the same as the StartPoint, as you would expect (it is a vertical line, after all). Its y value is set to a point which is shy of the top of the PictureBox by the value of the TopMargin.
Armed with our start and end points, we take a pen and draw the line:
Dim LinePen As New Pen(Color.Black, 2)
g.DrawLine(LinePen, StartPoint, EndPoint)
If you are entering the code as you read the article, you may be wondering why you haven't see any drawing appear. The reason is that we have drawn the line on an object that isn’t visible yet to the user. Effectively we are using a technique known as double buffering, where we create a bitmap object the same size as the picturebox and behind the scenes we draw on this bitmap. At this stage though we haven't assigned this bitmap back into the PictureBox as its image.
Step 4c: Tick Marks and Text
Because we generated the data ourselves at design time, we know that the maximum sales figure is 942. So we know in advance that it will be safe to draw a vertical axis which stretches from 0 to 1000 and all the sales data will fit inside those two extremes. In a later article we will take user data at runtime and calculate dynamically how many tick marks will be needed. But for now we’ll do it the easy (well, easier) way.
To decide how to space out the ten Tick marks we first of all need to know the length of the vertical axis:
Dim VertLineLength As Integer = PBBarChart.Height - (BaseMargin + TopMargin)
This will enable us to calculate the correct gap between each of the Ticks. The layout is from 0 to 1000 in intervals of 100, remember:
Dim VertGap As Integer = CInt(VertLineLength / 10)
I have arbitrarily chosen to set the (horizontal) length of each Tick mark as 5 pixels. It follows then that the start point of each Tick must be 5 pixels to the left of the vertical axis. The end point of each Tick will be on the vertical axis itself. Those are the x positions needed and are the same for each Tick.
The y positions (which you will recall are the number of pixels counting down from the top) must change for each Tick mark, because we are drawing ten of them equally spaced all the way up the vertical axis. The spacing between them therefore must be the value we have just calculated – VertGap.
Dim TickSP As New Point(LeftMargin - 5, StartPoint.Y - VertGap)
Dim TickEP As New Point(LeftMargin, StartPoint.Y - VertGap)
We have all the information needed to create the Tick marks and the 100, 200, 300, etc text. Let’s set up a Font for the text:
Dim ValueFont As New Font("Arial", 8, FontStyle.Regular)
And now we can loop 10 times through a code block which will draw out Tick marks from bottom to top and add the 100s as text at the same time:
For i As Integer = 1 To 10
' Tick mark
g.DrawLine(New Pen(Color.Black), TickSP, TickEP)
' Tick Values as text
g.DrawString(CStr(i * 100), ValueFont, Brushes.Black, 2, TickSP.Y - 5)
' Reset y positions, moving 10% up vertical line
TickSP.Y -= VertGap
TickEP.Y -= VertGap
The only code which might need additional explanation is the one which uses DrawString to draw the text next to the Tick marks. You may have noticed that the y-position of the start of the text has been shifted 5 pixels higher than the start of the Tick mark itself (TickSP.Y – 5). This is simply because the x,y position (the point of origin) of the drawn string is the very top left point of the imaginary rectangle in which the text is drawn. So, to bring the middle of the text approximately in line with the tick mark, that point of origin has been nudged five pixels further up the page.
Step 4d: Time For A Test
Although we have more drawing code to write before we are done, it might be useful to fast-forward and have a look at the results so far. To do this, add the following code to the very end of the DrawChart Sub:
PBBarChart.Image = bmap
I will explain this code later, but for now it will enable you to run the project and see the vertical axis and the Tick marks. Just be sure to remember that the code we create in the following pages is entered above those three lines, otherwise there will be problems.
The button is used to fire the drawing code, so the button_click event of btnDraw needs to call the two procedures we have created:
Private Sub btnDraw_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDraw.Click
' Generate the Data
' Then Draw the Chart
You may be thinking that this isn’t much to show for several pages of explanation. Bear in mind though that the actual code used is really quite short. Because this series of articles is aimed squarely at graphics newbies, I’ve erred on the side of too much explanation, rather than too little.