In many situations the familiar pie chart is as good a way as any of presenting data in pictorial form. The Graphics Class in .Net offers us an easy way to create this style of chart in order to display facts and figures to the world.
The above chart can be created with very few lines of code.
However, it is obviously of very limited use on its own. The picture is colourful, but we need to know what the segments represent.
Let’s look at a basic but quite useful way of doing this – using a chart key:
With a key like the one shown above, it is very easy for the user to identify which part of the pie chart represents which of the companies. To make the data even more useful, each company’s individual total is also shown in the key.
As you will see, the Graphics Class in the .Net Framework makes it quite easy to create pie charts and keys of this kind. So we’ll use the built-in facilities of DrawPie and DrawString and avoid reinventing the wheel.
It’s obvious that we will need some data in order to create our example chart.Because this article is aimed at showing you ways of using GDI+ and the Graphics Class,I want to spend as little time as possible on the data gathering side of the project.
For this reason I have chosen to generate the data at design time.Although this may be suitable in a few real world situations, I am sure that most times you will need to get the data from other sources, such as data files, databases or directly input by the user.We plan to look at these other approaches in future articles.
For the time being, I have settled for using a simple Structure thatcreates a user-defined Type named GraphData.
This Type will contain three Fields – Amount, Clr and Description .We will be creating data for some fictional companies and the three fields represent:
Amount -the Annual Turnover in $K
Clr - the color used in the chart to represent the company
Description – the name of the company.
To create the GraphData Structure, put the following code in your form, making sure it is placed outside any procedures.
Structure GraphData
Dim Amount As Single
Dim Clr As Color
Dim Description As String
' Create a constructor
Sub New(ByVal amt As Integer, ByVal col As Color, _
ByVal desc As String)
Me.Amount = amt
Me.Clr = col
Me.Description = desc
End Sub
End Structure
The New constructor is only included to reduce the number of lines of code needed when we "manufacture" the data, which is our next step.
For convenience of handling, the data will be stored in an arraylist. The arraylist can hold any number of GraphData objects. In fact, we are only going to create four for current demonstration purposes.
Declare and instantiate the arraylist by putting the following line at the top of your code, again placing it outside any procedures.
Dim Companies As New System.Collections.ArrayList
Now to generate the sample data.We will create the four companies and assign values for their Amount, Col (Color) and Description fields.This code in the Form’s Load event is all that is needed:
Companies.Add(New GraphData(50, Color.Blue, "Muir Inc."))
Companies.Add(New GraphData(75, Color.Yellow, "Philmas Co."))
Companies.Add(New GraphData(62, Color.Red, "Xamco"))
Companies.Add(New GraphData(27, Color.LightGreen, "Wright plc"))
We have the data, so it can now be used as the source of the chosen graphics display - the Pie Chart.
Drawing and Persisting
One of the common problems for newcomers to graphics in .Net is the tricky business of persistence. Unless you get your drawing code correctly written and in the right place it may appear the first time, but will disappear partially or totally when the form is moved or if other windows are displayed on top of it and in fact in several other situations.
Different scenarios will call for different solutions to this problem. When drawing directly on the form you will often be able to avoid the “disappearing graphic” problem simply by placing your code in the form’s OnPaint event. This is the approach we will use for our Pie Chart demonstration.
If you haven’t accessed OnPaint before, you can locate it by selecting “(Overrides)” from the left hand dropdown list in the Code Window and then scrolling down, quite a long way, until you find OnPaint.
Controlling when and how often drawings are refreshed is a topic we will address many times in the coming series of articles. For now, we will try to keep it simple.
The Graphics Object
The form has a Graphics Object associated with it. Some people like to think of this object as a canvas on which everything that appears on the surface of the form is drawn. An overlay on the form, perhaps. And for our purposes in this article, that description is certainly accurate enough.
We write code for this Graphics Object, which opens the way for us to use the wide range of drawing methods and properties that subsequently become available. You will see a couple of examples of this very shortly.
Getting Started
First, we create a variable and assign the Form’s Graphics Object to it:
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
Dim g As Graphics = e.Graphics
If you are entering this code step by step using Visual Studio as you read this article, then try starting a new line of code and typing “g.” . Intellisense will immediately show you the very impressive range of methods and properties on hand for your use and pleasure.
For now though, we are going to limit ourselves to setting the SmoothingMode property. Selecting a SmoothingMode of HighQuality will reduce jagged lines effect to a minimum. Most quality improvements come at a price and very often, the price is reduced speed of display. However, our example is undemanding on resources, so this is unlikely to be a factor that you need worry about.
Add this line to the OnPaint event:
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
Location and Size
First, we have to decide two things:
1. Where we want to place the chart on the form
2. What size we want the chart to be.
Once these decisions are made, we employ another object from the .Net Graphics Class to make use of this information. That object is a Rectangle.
The key concept to keep in mind here is that a Rectangle is an actual object, and not merely the shape that it represents. It is easy to get confused with objects such as Rectangles, Points, Sizes and so on, because we often tend to see them in our mind’s eye as abstract rather than physical. But a Rectangle object in the Graphics Class is as much a real object as any other in the Framework.
Passing our location and size choices to the Rectangle object takes just one line of code:
Dim rect As Rectangle = New Rectangle(100, 105, 150, 150)
In the above code, the four values represent:
100 – the X Position of the Rectangle,
i.e. the number of pixels from the left hand side of the form to the left side of the Rectangle.
105 – the Y Position of the Rectangle.
The number of pixels from the top of the form to the top of the Rectangle.
150 – the Width of the Rectangle.
As the Pie Chart will take up all available space inside this Rectangle, it follows that this will also be the width of the pie.
150 – the Height of the Rectangle
Therefore also the height of the pie.
Although it is traditional to use a circle to display a pie chart, this isn’t necessary. You may for instance prefer to create an oval chart, in which case you need only change the Width or Height values to your preference.
Calculate the Total
Let’s pause here and check what it is we want to do. We want to display each company’s “share” of the pie chart so that each is allocated the correct proportion of chart real estate.
So, if for example the grand total of the four Amounts was 1000, and the first company had an individual Amount value of 250, then of course you would expect that company’s segment of the pie chart to take up exactly 25%, one quarter, of the available space. In real life, figures are rarely that user-friendly, but we can use some fairly basic math to get .Net to do the grunt work for us.
Calculating the grand total is easy enough. Enumerate through each of the individual amounts that we have stored in the arraylist, adding each Amount in turn to the total:
Dim TotalCount As Single
For Each gd As GraphData In Companies
TotalCount += gd.Amount
Next
This TotalCount value will be used very shortly in a formula that allocates the correct pie portion to each of the companies.
Calculate the Shares and Draw the Chart
We are going to write code which calculates those shares that represent each company in the chart and then draws the Colored segments accordingly.
A crucial piece of information that we have to pass to the drawing code is to tell it :
1. Where to begin drawing the next segment and
2. How big that segment should be
To do this we need to understand two concepts that sometimes cause difficulty for those new to GDI+. These are StartAngle and SweepAngle.
a. StartAngle . The PieChart is an ellipse, which as you know has 360 degrees. Although you might expect that the point at the very top of the ellipse is 0 degrees, this is not actually the case with GDI+. In fact, the 0 degree point is at the far right hand point of the ellipse, effectively on a horizontal line drawn exactly through the centre of the ellipse.
That sounds more complicated than it really is and to prove that a picture is really worth a thousand words, here is one:
The value of the StartAngle increases as you move in a clockwise rotation round the ellipse from the 0 degree start point. You can see this in the above graphic.
The StartAngle is simply the point in degrees on the ellipse where any particular arc begins. So, in terms of the Pie Chart we are creating here, we will be interested in the StartAngles (or starting point in degrees) of each of the companies’ segments.
b. SweepAngle. The SweepAngle is probably less confusing. An arc is measured in degrees. The SweepAngle is the number of degrees that any particular arc encompasses. In the example above, the highlighted segment has a SweepAngle of about 45 degrees (and a StartAngle of 200).
Again, dealing specifically with our Companies pie chart, the SweepAngle is the number of degrees in the pie segment that we allocate to each individual company . That is, the proportion of the 360 degrees each company should be allotted.
Draw The Chart
Armed with our figures, we may now draw our pie chart. The OnPaint continues with:
' Create variables to hold the changing values of Angles
Dim StartAngle As Single = 0
Dim SweepAngle As Single = 0
And we cycle through the data for each company, drawing its segment in the chosen color.
For Each gd As GraphData In Companies
SweepAngle = 360 * gd.amount / TotalCount
g.FillPie(New SolidBrush(gd.Clr), rect, StartAngle, SweepAngle)
StartAngle += SweepAngle
Next
The breakdown of the above code block goes like this:
1. Select the next company in the arraylist
2. Calculate how many of the 360 degrees it owns
3. Draw the Pie:
- Filling the segment with this company’s color,
- Containing the pie inside the Rectangle
- Starting the segment at the correct point on the ellipse
- Continuing for the number of degrees calculated for this company
4. Move the start position for the next segment by adding the number of degrees just used for the current segment.
Improve the Chart
We’ve already improved the look of the chart by setting the SmoothingMode to HighQuality, but I also quite like to finish off the display by putting edge lines round each of the segments.
This is refreshingly easy to do. Insert this additional code line just below the FillPie method in the code snippet above:
g.DrawPie(New Pen(Color.Brown), rect, StartAngle, SweepAngle)
You can alter the impact of the lines by changing the line color as you prefer.
Creating The Chart Key
The Heading
The text for the Chart’s Key is created using another Graphics method – DrawString.
We have already seen both the Pen and the Brush objects in action in the code above; the Brush to fill the segments, the Pen to draw the exterior lines. When it comes to drawing text with the DrawString method, you might expect to use a Pen object for this. By another of those quirks of the graphics class, you actually need a Brush to draw the text string, not a Pen.
You will recall that we created the Brush for the pie segments on the fly in this code line:
g.FillPie(New SolidBrush(gd.Clr), rect, StartAngle, SweepAngle)
We could do something similar with our text drawing code, but it would make it harder to read and analyze. So we will create the text brush separately. For the same reason, we will create the Font for the text separately too.
' Create a Brush to draw the text
Dim TextBrsh As Brush = New SolidBrush(Color.Black)
' Create a Font object instance for text display
Dim TextFont As New Font("Arial", 12, FontStyle.Bold)
Maybe it’s just me, but I have sometimes found setting the font arguments to be a bit tricky. Intellisense isn’t always your best friend in this particular situation, so take care to enter the arguments you really want, in the correct order.
Writing (drawing) the heading of the Key comes next:
g.DrawString("Chart Key", TextFont, TextBrsh, 310, 100)
The two values at the end of the above code line are the X and Y positions of the start of the text (i.e left and top positions on the Form).
Bullets and Company Info
Because we are now going to create several lines of text that we want to keep aligned vertically, the X position (pixel count from the left of the form) will stay the same. However, the Y position, counting from the top of the form will of course change as we move down the form displaying line after line. To keep track of this Y position we will use an Integer variable.
Dim pxFromTop As Integer = 135
I have placed the first line of company info 35 pixels below the Heading, 135 pixels below the top of the form.
Now we can again enumerate through the arraylist and use the information in there to create the detail of the chart key. You will see from the commenting included how we have achieved this.
For Each gd As GraphData In Companies
' Draw bullet
g.FillEllipse(New SolidBrush(gd.Clr), 310, pxFromTop, 15, 15)
' Draw line round bullet.
g.DrawEllipse(New Pen(Color.Black), 310, pxFromTop, 15, 15)
' Draw the text - color coded
g.DrawString(gd.Description & " (" & gd.Amount & ")", TextFont,
TextBrsh, 360, pxFromTop)
' Increase gap from Top for next line
pxFromTop += 30
Next
The only code which might need additional explanation are the values:
310 in the first two lines is the X Position of the circular bullet
15, 15 in the first two lines represent the width and height of the ellipse.
(Making their values equal will of course result in a circle)
360 in the third line is the X Position where we want the Company Name to begin.
I think we have covered variations of all the other settings in previous code snippets.
Dispose After Use
All that is left to do is the housekeeping - disposing of any disposable graphics objects that we specifically created as we were drawing.
TextBrsh.Dispose()
TextFont.Dispose()
Notice that we don’t try to dispose of any of the Brushes and Pens we created on the fly in code and also that we don’t dispose of the Graphics Object in this particular example. This is an area we will look at in more detail in future articles, as we need to.
All Done!
I have taken a lot of space to describe what is in fact not very much code. Hopefully, the extra detail and explanation will help you to see how you can create your own versions of this kind of pie chart and key.
There is a sample solution attached to this article if you would like to see it in action. However, there is no substitute for making your own mistakes as the best way to learn, so I do recommend that you try entering the code yourself in a new project and come back for the explanations if things don’t go quite as you expect.
The final version of the chart and key should look something like this:
Summary
In this first article, we have been introduced to the Graphics Object and the Rectangle. We used the DrawPie and FillPie Methods, and looked at how those methods use the Rectangle, StartAngle and SweepAngle settings to create the finished drawing we required.
We employed Brush objects to fill the coloured segments and also to draw the text; a Pen object was used to draw the enclosing lines round the pie segments and bullets.
We saw that the Font is also an object and how we can use its Constructor to create New instances based on our preferences of Font name, size and style.
The DrawString method was used to display text in the font and the various colors of our choosing. We used the FillEllipse and DrawEllipse methods to create circular Colored bullets in the Key.
We have seen that if we put our drawing code in the OnPaint event it will be redrawn whenever the form’s surface has been covered, hidden or otherwise visually affected. We learned that good housekeeping includes disposing of disposable objects when finished with.
So, although the amount of code used in this project is relatively short, it has included several key graphics techniques, including:-
• Brush objects
• DrawEllipse method
• FillEllipse
• Dispose
• DrawLine
• DrawPie
• DrawString
• FillPie
• Font object
• Persistence Using OnPaint
• Rectangle object
• SolidBrush
• StartAngle
• SweepAngle
• Using OnPaint event to Persist the drawing
What we’ve done here of course touches only the very tip of the .Net Graphics iceberg. The power, scope and potential of the graphics tools that are available to you will enable you to bring parts of your application to life in a way that would be difficult - if not impossible - in any other way.
In future articles we will continue to put some of this power to use. Along the way, I hope I will help demystify some of the difficult terms and arcane syntax that makes many developers see Graphics and GDI+ as something of a Black Art. There is so much potential in there, it would be shame not to use at least some it, and - who knows? - in time you may well succeed in graduating from Graphics Apprentice to fully qualified Wizard!
评论