DevCity.NET -
Chart Success: Second Helpings of Pie. Part 3
Ged Mead

Ged Mead (XTab) is a Microsoft Visual Basic MVP who has been working on computer software and design for more than 25 years. His journey has taken him through many different facets of IT. These include training as a Systems Analyst, working in a mainframe software development environment, creating financial management systems and a short time spent on military laptop systems in the days when it took two strong men to carry a 'mobile' system.

Based in an idyllic lochside location in the West of Scotland, he is currently involved in an ever-widening range of VB.NET, WPF and Silverlight development projects. Now working in a consultancy environment, his passion however still remains helping students and professional developers to take advantage of the ever increasing range of sophisticated tools available to them.

Ged is a regular contributor to forums on vbCity and authors articles for DevCity. He is a moderator on VBCity and the MSDN Tech Forums and spends a lot of time answering technical questions there and in several other VB forum sites. Senior Editor for DevCity.NET, vbCity Developer Community Leader and Admin, and DevCity.NET Newsletter Editor. He has written and continues to tutor a number of free online courses for VB.NET developers.

by Ged Mead
Published on 3/23/2005

The first two articles in this series introduced some basic graphics skills which were used to create a Pie Chart in Part 1 and a Bar Chart in Part 2. Both charts were elementary, but served their purpose. As the series progresses we will look at ways of building on these early steps and creating more challenging displays. In both of those articles the data was created in advance at design time and hard coded  into the project. In this article,  data will be taken from the user and used to create a pie chart. This project also introduces HatchStyles - a range of predefined patterns - to replace the solid colors used in the first Pie Chart example.

Setting Out The Form

Skeleton Layout 
   Because these articles are aimed at showing you how to use the Graphics Classes, I will skim over most of the non-graphics stuff wherever I can. So, to help shortcut the process of creating the Windows Form and its controls, I have included a project containing a skeleton form (i.e. it contains the controls, but none of the graphics code). You will find it in the folder named "Skeleton" in the attached zip file.

  The form and its controls looks like this:


Of course, if you prefer to build the form yourself, go ahead. I will let you know the names used and other details for the controls where these are significant to the code as we come to them.

Let's get Coding
Insert these statements at the top of the form:

    Option Strict On
    Imports System.Drawing.Drawing2D
    Imports System.Collections

As in the previous articles, a Structure is used to compartmentalise the data and an arraylist to store it. It's quick and easy and should be very familiar by now.

  Structure GraphData
    Dim Name As String
    Dim Amount As Decimal
    Dim Clr As Color
    Dim Pattern As HatchStyle
  End Structure

    ' Arraylist to hold data as it is input by user
    Dim UserData As New ArrayList

    This Structure is similar to the one created in Part 1 - the first three fields (Name, Amount and Clr) representing the same elements. The additional item (Pattern) will hold the user's choices of HatchStyles.

    The framework offers a range of more than 50 HatchStyles - basically, they offer a choice of coloured shadings. The image below shows a few sample styles:



Temporary Variables
    From the work we did in Parts 1 and 2, you will know that we will create instances of our User Defined Value Type (GraphData) and assign values to each of the four fields. In this version we will let the user do this at run time.

    We also need a couple of variables to hold the values of the Color and HatchStyle elements temporarily while the user is choosing the rest of the settings. Add these to the declarations area where you instantiated the arraylist:

    Dim clrPicked As Drawing.Color = Color.Black
    Dim hatchPicked As HatchStyle = HatchStyle.DarkHorizontal

    You will see that default values (Black and DarkHorizontal) are included in the code above. Doing this avoids errors which would otherwise arise if the user forgets to select either of these elements.



The User Input Controls


    Ignoring labels, the first control on the form is a TextBox. If creating your own project, you should name it txtName.

    The next control is a NumericUpDown, named nudValue. You can set the Min, Max and Value settings to whatever values you like.

"Select Color" Button
    Next down the form is a Button. I have named it btnColor, but the name isn't important.

    The button's click event will open a ColorDialog from which the user can select a forecolor for the HatchStyle.
Add a ColorDialog to your Form if necessary (one has already been included in the Skeleton Solution provided).

      The following code will take the user's color choice :

    Private Sub  btnColor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnColor.Click  
      '  Get user's choice of color      
      Dim  ColorPicker As New ColorDialog    
      Dim  Choice As DialogResult = ColorPicker.ShowDialog 
      If  Choice <> DialogResult.Cancel Then   
         clrPicked = ColorPicker.Color 
      End If       
    End Sub  

"Select Pattern" ComboBox       

          The fourth user input control is a ComboBox, which should be named cboPattern.

      This ComboBox  allows the user to select any of the HatchStyles for use in the pie chart.   We therefore need to populate the combo with that list.   Fortunately VB.Net gives us an extremely easy way of doing this.

       Put this code in the Form_Load event to get each  of the HatchStyle names and populate the ComboBox with them:

    Private Sub  Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load    
       '  Populate the combobox with all available HatchStyles    
          Dim  patts() As String   
          patts = System.Enum.GetNames(GetType(HatchStyle))     
    End Sub

         The ability to get at these Enumerations and manipulate them as a range can be very useful.   The alternative of manually typing  each of them in as an "Items.Add" code line isn't appealing!

   Now  the user can select a HatchStyle from the ComboBox and when this happens we note the user's choice in the variable we created for this purpose earlier on, named Hatchpicked.   This code in the ComboBox's SelectedIndexChanged event does exactly that:

   Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboPattern.SelectedIndexChanged 

      hatchPicked = CType(System.Enum.Parse(GetType(HatchStyle), _     
                cboPattern.Text), HatchStyle)     

    End Sub

       Effectively, it checks the user's choice - which is a string type in the combobox -   finds the equivalent HatchStyle enumeration and stores it.  




Getting the Data


Getting the Input

We invite the user to enter the four elements of data - Name, Amount, Color and Pattern - for any single slice of the pie. If they don't enter a choice, a default value is used.

When the user is happy with the "current set" of data (that is, what will become one segment of the pie) a click on the Confirm button will fire the following code. This takes those four pieces of data and stores them in the arraylist as one composite GraphData element.


  Private Sub btnConfirm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConfirm.Click
  ' Add latest item of data to the arraylist
   If txtName.Text = "" Then txtName.Text = "No Name"
    Dim gd As New GraphData
    gd.Name = txtName.Text
    gd.Amount = nudValue.Value
    gd.Clr = clrPicked
    gd.Pattern = hatchPicked
  End Sub

I'm sure you will have spotted the inclusion of the line which checks that the user has actually entered a Name and, if not, assigns some text for display.


Creating the Chart

Create the Chart

Draw the Chart

The Button to draw or redraw the chart is of course this one:

I have named it btnDraw, but the name isn't critical.
If you are not using the Skeleton solution, then don't forget to add a Panel control to your own project and name it pnlChart. In the screenshot shown at the start of this article, the panel is the white area at the right hand side of the form.

In order for the chart to be drawn or redrawn in the panel when the user clicks that button we need to write code that will carry out that action. This is refreshingly easy and the Invalidate method may soon become your new best friend.

  • Invalidate forces a control to be redrawn;
  • The redrawing calls the control's Paint method.
  • Put appropriate code in the control's Paint method and,
  • Hey presto, we create the chart with the data available so far.

The Invalidate method in the button's click event won't detain us long:-

    Private Sub btnDisplay_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDraw.Click
    End Sub

Paint the Panel
So, that just leaves the " engine" in the Panel's paint event which does the final drawing: I will only go into detail where the code or the logic is different from our earlier projects.
We met the Graphics object, SmoothingMode Property and Brush previously:

Private Sub pnlChart_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles pnlChart.Paint
  Dim g As Graphics = e.Graphics
  g.SmoothingMode = SmoothingMode.HighQuality
  Dim PattBrush As Brush

We don't want to make the mistake of trying to draw a chart with no data. So the next line tests that there is at least one completed element in the arraylist before the action is allowed to continue. Where data does exist, we can go ahead and start to process it: (This is the same code we used in Part 1, so I won't go though it in detail again.)

  If UserData.Count > 0 Then
  ' Create Rectangle to contain the Pie Chart
  Dim rect As Rectangle = New Rectangle(20, 10, 200, 200)
  ' Calculate the grand total
  Dim TotalCount As Single
  For Each gd As GraphData In UserData
  TotalCount += gd.Amount
  ' Create variables to hold the changing values of Angles
  Dim StartAngle As Single = 0
  Dim SweepAngle As Single = 0

The next block of code is much the same as that used in Part 1. The key difference being the nature of the Brush object .

We create a new HatchBrush and assign it the selected pattern and forecolor. I've gone for White as the backcolor in all cases. However, feel free to change this to a color of your choice or - if you're feeling adventurous - adding a further choice for the user, the HatchStyle Backcolor.

  ' Draw the Chart
  For Each gd As GraphData In UserData
   SweepAngle = 360 * gd.amount / TotalCount
   PattBrush = New HatchBrush(gd.Pattern, gd.clr, Color.White)
   g.FillPie(PattBrush, rect, StartAngle, SweepAngle)
   ' Optional: Draw lines round the segments:-
   g.DrawPie(New Pen(Color.Black), rect, StartAngle, SweepAngle)
   StartAngle += SweepAngle

The Key
Creating the key is a repeat of Part 1's code, except that I replaced the circular bullets with square ones. No particular reason: Just because I could.

  ' 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("Verdana", 9, FontStyle.Bold)
  ' Draw the Bullets and the Company info
  Dim pxFromTop As Integer = 235
  ' Draw the Header text
  g.DrawString("Chart Key", TextFont, TextBrsh, 35, pxFromTop)
  For Each gd As GraphData In UserData
   ' Increase gap from Top
   pxFromTop += 20
   ' Draw bullet
   PattBrush = New HatchBrush(gd.Pattern, gd.clr, Color.White)
   g.FillRectangle(PattBrush, 20, pxFromTop, 15, 15)
   ' Draw line round bullet.
   g.DrawRectangle(New Pen(Color.Black), 20, pxFromTop, 15, 15)
   ' Draw the text - color coded
   g.DrawString( & " (" & gd.Amount & ")", TextFont, TextBrsh, 60, pxFromTop)

Final housekeeping
Finally, dispose of disposable objects that are no longer in use:



Restart Option


The Oops! Option

 We’re almost done.  The user can enter as much data as they like and the chart can be refreshed each time to display the data and the pattern/color choices.  But there may come a time when the user wants to start over again.

  Rather than forcing them to close the application and fire it up again, let’s finish off by giving them a button to remove the data and clear the decks for another go.

  This code in the Click event of the Restart button is all that is needed:

   Private Sub btnRestart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles  btnRestart.Click
    End Sub

Our new friend, the Invalidate method, once again does most of the work for us, once we have removed all data from the arraylist.





In this article the original Pie Chart creation code moved closer to a real world scenario, one where the user inputs the data at run time.

     The Invalidate method was introduced and we saw how this forces a repaint of the control on which it is called.   

   A  HatchBrush was used to fill the pie segments with patterns selected from the HatchStyle enumeration.

 In summary, this article included coverage of the following :


  •  ArrayList
  •  Brush
  •  ColorDialog
  •  Dispose
  •  DrawPie
  •  DrawRectangle
  •  DrawString
  •  FillPie
  •  FillRectangle
  •  Font object
  •  HatchBrush
  •  HatchStyle
  •  Invalidate
  •  Pen
  •  StartAngle
  •  Structure
  •  SweepAngle
  •  System.Enum.GetNames
  •  System.Enum.Parse

If you have read all three articles in this series so far, I hope you will now be feeling very comfortable with several of the key basic Graphics Class methods, properties and techniques.   And I also hope that you will be looking forward to charting some new territory in Part 4 where we return to create a better bar chart.