Introduction
Click here to view source code
Over the years I have been presented with many different situations while programming in WPF, which required a certain Control or class to be created to accommodate. Given all the various solutions I created throughout the years I thought it might be helpful to someone else. During this ongoing series I am going to post some of the more useful classes I have made in the past.
Simple Pie Chart
In one project I was assigned to redesign, there was data coming in that we wanted represented in the form of a pie chart. Initially, we simply displayed the information in the form of one out of many static pie chart images. A specific image would get selected based on what the percentage was closest. Although this solved our immediate needs I believed generating this with GeometryDrawing would make the chart much more accurate and should not be too difficult to create. My immediate goal was to try and represent some type of pie chart in XAML to get an idea of how it could be represented dynamically. Initial searching led to this solution involving dividing a chart into thirds. Following the example given will produce a subdivided geometric ellipse:
Programmatically Build Chart
Unfortunately, using strictly XAML will not work when attempting to create a pie chart dynamically. This is definitely a great starting point in how we could create this Control, but I needed a better understanding how to create geometric objects programmatically. Doing some more searching I came across this Code Project that describes how to create pie charts from code. My pie chart will be much simpler containing only two slices and taking in a percentage value to represent how the slices will subdivide. I still use an Image to represent how the geometry will be drawn and begin the creation of the root elements:
_pieChartImage.Width = _pieChartImage.Height = Width = Height = Size; var di = new DrawingImage(); _pieChartImage.Source = di; var dg = new DrawingGroup(); di.Drawing = dg; |
Since I know my starting point of the pie will always be at the top I then calculate where my line segment will end (the PieSliceFillers are brushes representing the fill color):
var angle = 360 * Percentage; var radians = ( Math.PI / 180 ) * angle; var endPointX = Math.Sin( radians ) * Height / 2 + Height / 2; var endPointY = Width / 2 - Math.Cos( radians ) * Width / 2; var endPoint = new Point( endPointX, endPointY ); dg.Children.Add( CreatePathGeometry( InnerPieSliceFill, new Point( Width / 2, 0 ), endPoint, Percentage > 0.5 ) ); dg.Children.Add( CreatePathGeometry( OuterPieSliceFill, endPoint, new Point( Width / 2, 0 ), Percentage <= 0.5 ) ); |
My CreatePathGeometry method creates both the inner and outer pie slices using a starting point, the point where the arc will end, and a boolean for ArcSegment to determine how the arc should get drawn if greater than 180 degrees.
private GeometryDrawing CreatePathGeometry( Brush brush, Point startPoint, Point arcPoint, bool isLargeArc ) { var midPoint = new Point( Width / 2, Height / 2 ); var drawing = new GeometryDrawing { Brush = brush }; var pathGeometry = new PathGeometry(); var pathFigure = new PathFigure { StartPoint = midPoint }; var ls1 = new LineSegment( startPoint, false ); var arc = new ArcSegment { SweepDirection = SweepDirection.Clockwise, Size = new Size( Width / 2, Height / 2 ), Point = arcPoint, IsLargeArc = isLargeArc }; var ls2 = new LineSegment( midPoint, false ); drawing.Geometry = pathGeometry; pathGeometry.Figures.Add( pathFigure ); pathFigure.Segments.Add( ls1 ); pathFigure.Segments.Add( arc ); pathFigure.Segments.Add( ls2 ); return drawing; } |
A better to visualize this is through a XAML representation:
<GeometryDrawing Brush="@Brush"> <GeometryDrawing.Geometry> <PathGeometry> <PathFigure StartPoint="@Size/2"> <PathFigure.Segments> <LineSegment Point="@startPoint"/> <ArcSegment Point="@arcPoint" SweepDirection="Clockwise" Size="@Size/2"/> <LineSegment Point="@Size/2"/> </PathFigure.Segments> </PathFigure> </PathGeometry> </GeometryDrawing.Geometry> </GeometryDrawing>
And with that we are able to create quick an easy pie charts as shown here:
Multi Pie Chart
Although this is suitable for a two sided pie chart, but what if you wanted more? That process is pretty straight forward based off what we already created. By including two dependency properties to represent our collection of data and brushes, we only need to rewrite how my segments are created:
var total = DataList.Sum(); var startPoint = new Point( Width / 2, 0 ); double radians = 0; for ( int i = 0; i < DataList.Count; i++ ) { var data = DataList[i]; var dataBrush = GetBrushFromList( i ); var percentage = data / total; Point endPoint; var angle = 360 * percentage; if ( i + 1 == DataList.Count ) { endPoint = new Point( Width / 2, 0 ); } else { radians += ( Math.PI / 180 ) * angle; var endPointX = Math.Sin( radians ) * Height / 2 + Height / 2; var endPointY = Width / 2 - Math.Cos( radians ) * Width / 2; endPoint = new Point( endPointX, endPointY ); } dg.Children.Add( CreatePathGeometry( dataBrush, startPoint, endPoint, angle > 180 ) ); startPoint = endPoint; } |
As you can see, the main difference is now we are accumulating the radians as we traverse the list to take into account any number of data objects. The result allows us to add any number of data items to our pie chart as shown here:
Conclusion
Although I did not get as much use for this class as I would have preferred, developing this helped me gain experience in manipulating geometry objects, which does not happen often enough.