Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simple and Easy-to-Use Pie Chart Controls in WPF

0.00/5 (No votes)
15 Mar 2013 1  
This article tries to make it very easy for you to create Pie charts in WPF.

Introduction 

The purpose of this article is to build a couple of simple pie chart controls. I am not trying to create anything fancy; just something which is simple to use and easy to learn.

Here is how the sample application, showing the use of the provided Pie Controls, looks like:

Part-1: Using the Controls

Using the controls is straightforward. Two controls have been provided: PieControl, which shows only the pie, and PieChart, which contains both the pie and, additionally, the legend.

Creating the PieControl (i.e., pie without the legend) and PieChart is almost identical. You can create either of these by performing the following steps:

xmlns:pie="clr-namespace:PieControls;assembly=PieControls"
<StackPanel>
    <pie:PieControl x:Name="pie1" Width="120" Height="120"/>
    <pie:PiChart x:Name="chart1" Width="260" Height="140" PieWidth="120" PieHeight="120"/>
</StackPanel>

Be careful to use the same Width and Hight values for the PieControl. For the PieChart, PieWidth and PieHeight should be the same.

using PieControls;
ObservableCollection<PieSegment> pieCollection = new ObservableCollection<PieSegment>();
pieCollection.Add(new PieSegment { Color = Colors.Green, Value = 5, Name = "Dogs" });
pieCollection.Add(new PieSegment { Color = Colors.Yellow, Value = 12, Name = "Cats" });
pieCollection.Add(new PieSegment { Color = Colors.Red, Value = 20, Name = "Mice" });
pieCollection.Add(new PieSegment { Color = Colors.DarkCyan, Value = 22, Name = "Lizards" });
pie1.Data = pieCollection;
chart1.Data = pieCollection;
  1. Add references to the PieControls and Microsoft.Expression.Drawing assemblies provided in the download.
  2. Include the PieControls namespace in your XAML file.
  3. Add the PieControl and/or the PieChart to your Window/Control using something like this:
  4. Include the 'using' statement in your code behind file
  5. 5. Populate the data for the pie chart in your code behind file and assign it to the pie controls like this:
  6. Make any changes you like to the pieCollection or any of its items anytime. They will be automatically reflected in the resulting Pie!

1.1. Customization Options

You can use the following to customize the controls:

  1. Properties of the class PieSegment. They are all designed to immediately change the pie view.
  2. Width and Height Properties of PieControl and PieChart.
  3. PieWidth and PieHeight Properties of PieChart (these represent the width/height of the PieControl contained within the PieChart).
  4. PopupBrush Property of PieControl and PieChart classes. This Property is used to paint the background of the popup that shows when you move your mouse cursor over the pie.
  5. I have only used integral values in the sample just to keep it simple. The controls, however, internally use double precision floating point values; so your option is not limited to integers only.

Part-2: Understanding the Source Code and the Math Behind

The file PieControl.cs in the attached source code contains the definition of our Pie Control. The other classes of interest are PieChart and PieSegment. PieChart makes use of PieControl and provides additional facility of a legend. The class PieSegment is the data carrier used to communicate pie values to PieChart and PieControl.  

A basic Pie is just a circle divided into smaller sectors according to the percentage of each data category. Each category having more than 0% share gets exactly one circle sector. 0 gets nothing, although we will keep the category there for possible future share it gets.

Imaging what we would do if we needed to create Pie charts for showing the share of our household expenses divided into 4 categories: (1) Food, (2) Clothing (3) Rent and (4) Entertainment. Here's is what we do:

  1. Calculate the sum of all the categories.
  2. Calculate the share (i.e., angle) of each category by dividing the share by the total and multiplying the result with 360 (a circle's full inner angle is 360 degrees).
  3. Determine the coordinates and draw each sector.

The first two steps are straightforward. The last step depends on how we will use the WPF primitives to create the Pie. Details will be shortly in place. First let's put theory into practice and do an example to see how the above steps are actually performed.

Example

Suppose I spent the following amount of money on the given four categories in July, 2012.

  1. Food: $200
  2. Clothing: $160
  3. Rent: $280
  4. Entertainment $80

Here's how we will perform the earlier given three steps:

1. Sum of all the categories: 200 + 160 + 280 + 80 = $720. 

2. Share (angle) of each category:
    - Food: (200/720)*360=100 degrees
    - Clothing:(160/720)*360=80 degrees
    - Rent: (280/720)*360=140 degrees
    - Entertainment: (80/720)*360=40 degrees

    (100+80+140+40=360 degrees, the internal angle of the whole cirle/pie)

3. Draw the sectors:

 

This part is a bit involved. If you are not familiar with WPF drawing primitives, please first have a look at MSDN documentation for Geometry and Path classes; otherwise the following details will hardly make sense to you (I had a trouble understanding them while I was creating these controls. Only MSDN helped me out!). Follow this link for a good start. You may also need to have a look at the ArcSegment class before proceeding (click here).

 

We will create a Path object to represent each pie segment. The Path object will draw the circle sector representing the pie segment as well as do hit testing (and track Mouse events) for us. If a single category gets all the share, the Path will contain a single EllipseGeometry. But in most cases there will be more than one category with more than 0% share. In such cases, the Path will contain a PathGeometry, which in turn will contain a PathFigure and that PathFigure will contain 2 LineSegments and 1 ArcSegment as given below (these are all WPF classes):

LineSegment firstLineSegment = new LineSegment(startingPointOfArc, true);
ArcSegment arcSegment = new ArcSegment(endPointOfArc, pieSize, angleShare, angleShare > 180, SweepDirection.Clockwise, false);
LineSegment secondLineSegment = new LineSegment(centerPoint, true);
PathFigure pathFigure = new PathFigure(centerPoint, new PathSegment[] { firstLineSegment, arcSegment, secondLineSegment }, false);
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(pathFigure);
Path path = new Path();
path.Data = geometry;
myPanel.Children.Add(path);

Now we will try to understand what the code is doing. Remember that in the Constructors for LineSegement and ArcSegment the specified Point objects represent the End Point. The Start Point is always implicit and is determined by where the previous segment ended. The first LineSegment will get its Start Point from the containing PathFigure. To draw each Pie segment, we will always start the PathFigure at the center of the PieControl. For convenience we make sure that our entire PieControl is the size of the Pie. Also the Control must have the same Width and Height (our implementation does not support elliptical pie's). So the radius of the Pie will be Width/2 and the start point of pathFigure will be (radius, radius).

Now let's talk about calculating the coordinates of the line and arc segments. We need services of the Math class to do the calculations. The Math class uses Radian measure of angle while the WPF Geometry classes use Degrees. So we will need to convert between the two measures. Two simple methods are provided in the source code for this conversion (in PieControl class). Another twist is that, mathematically speaking, the angle starts at X-Axis; while we need to start the pie at 12'0 Clock (i.e. Right angle). We are drawing our figure clockwise, so we will adjust the coordinates by subtracting 90 degress while sweeping around the circumference.

Let's calculate the first sector representing Food (measuring 100 degrees). We will be ignoring round off errors to keep things simple. All the Point objects given below represent X and Y coordinates in screen pixels relative to the containing Control.  

(Remember the math starts at X-Axis, but we want to start our Pie at Right angle i.e. 90 degrees less, thinking clockwise)

  • centerpoint = (100, 100) - this is the center of the Control/Pie, presuming our Pie is 200 pixels wide.
  • Start angle = -90 degrees = -PI/2 Radians = -1.57 Radians
  • startingPointOfArc.X = Math.Cos(-1.57) * 100 + 100 = 100
  • startingPointOfArc.Y = Math.Sin(-1.57) * 100 + 100 = 0
  • Food angle = (200/720)*360= 100 degrees = 1.75 Radians.
  • Normalized Food angle: 100-90 degrees = 1.75-1.57 Radians=0.18 Radians (By normalizing we get a measure relative to the X-axis. The actual internal angle of the sector will, of course, remain 100 degrees, since we subtract 90 degrees from the start too).
  • endPoint.X = Math.Cos(0.18) * 100 + 100 = 198
  • endPoint.Y = Math.Sin(0.18) * 100 + 100 = 117

The second LineSegment joins the endPoint back to the centerpoint; thus closing the figure, which we will fill with the specified Brush for the sector. This completes the whole process of creating one Pie segment. The remaining ones are not different. They use the same calculation in a loop. The only point to be noted is that the next segment automatically starts at the end of the previous one in clockwise direction.

The following figure will further help clarify the above details:

 

Data Binding

We want to bind the pie data to the PieControl and ChartControl objects, so that the Pie view is always updated as the data changes. In order to do so, we put the PieSegment objects in an ObservableCollection. Additionally we make the PieSegment class implement INotifyPropertyChanged interface. Whenever any property of interest changes, we receive a notification in PieChart and/or PieControl classes and update the view accordingly. We do this by attaching to the PropertyChanged event and by recalculating and redrawing the charts whenever a change is notified.

Conclusion 

Please provide your feedback and suggestions regarding anything you think can improve the controls/article. Also if you find a bug, please let me know so that it can be removed in the next version. 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here