Search code examples
wpfdata-visualizationpie-chart

chartingToolkit:Chart how to show value of each pie


I'm using System.Windows.Controls.DataVisualization.Charting to draw PieChart. But I don' know how to show the percentage/Value of each Pie Slice on the chart. Is that possible?


Solution

  • Adding percentage to a System.Windows.Controls.DataVisualization.Charting PieChart is not so immediate, since there are no properties for managing labels.

    Anyway there are some ways to reach the goal. I wrote an article on my blog to describe the one I used.

    The first step is to create a custom PieDataPoint class:

    public class PieDataPoint : System.Windows.Controls.DataVisualization.Charting.PieDataPoint
    {
        public static readonly DependencyProperty TextedGeometryProperty =
            DependencyProperty.Register("TextedGeometry", typeof(Geometry), typeof(PieDataPoint));
    
        public Geometry TextedGeometry
        {
            get { return (Geometry)GetValue(TextedGeometryProperty); }
            set { SetValue(TextedGeometryProperty, value); }
        }
    
        static PieDataPoint()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PieDataPoint),
                new FrameworkPropertyMetadata(typeof(PieDataPoint)));
        }
    
        public PieDataPoint()
        {
            DependencyPropertyDescriptor dependencyPropertyDescriptor
                = DependencyPropertyDescriptor.FromProperty(GeometryProperty, GetType());
    
            dependencyPropertyDescriptor.AddValueChanged(this, OnGeometryValueChanged);
        }
    
        private double LabelFontSize
        {
            get
            {
                FrameworkElement parentFrameworkElement = Parent as FrameworkElement;
                return Math.Max(8, Math.Min(parentFrameworkElement.ActualWidth,
                    parentFrameworkElement.ActualHeight) / 30);
            }
        }
    
        private void OnGeometryValueChanged(object sender, EventArgs arg)
        {
            Point point;
            FormattedText formattedText;
    
            CombinedGeometry combinedGeometry = new CombinedGeometry();
            combinedGeometry.GeometryCombineMode = GeometryCombineMode.Exclude;
    
            formattedText = new FormattedText(FormattedRatio, 
                CultureInfo.CurrentCulture,
                FlowDirection.LeftToRight,
                new Typeface("Arial"), 
                LabelFontSize, 
                Brushes.White);
    
            if (ActualRatio == 1)
            {
                EllipseGeometry ellipseGeometry = Geometry as EllipseGeometry;
    
                point = new Point(ellipseGeometry.Center.X - formattedText.Width / 2,
                    ellipseGeometry.Center.Y - formattedText.Height / 2);
            }
            else if (ActualRatio == 0)
            {
                TextedGeometry = null;
                return;
            }
            else
            {
                Point tangent;
                Point half;
                Point origin;
    
                PathGeometry pathGeometry = Geometry as PathGeometry;
                pathGeometry.GetPointAtFractionLength(.5, out half, out tangent);
                pathGeometry.GetPointAtFractionLength(0, out origin, out tangent);
    
                point = new Point(origin.X + ((half.X - origin.X) / 2) - formattedText.Width / 2,
                    origin.Y + ((half.Y - origin.Y) / 2) - formattedText.Height / 2);
    
            }
    
            combinedGeometry.Geometry1 = Geometry;
            combinedGeometry.Geometry2 =  formattedText.BuildGeometry(point);
    
            TextedGeometry = combinedGeometry;
        }
    }
    

    As you can see it adds a FormattedText geometry (with the percentage) to the original Geometry. Then you need to create a default style (in a generic.xaml dictionary) for using the new geometry property (named TextedGeometry).

    The style has to contain - at least - something like that:

    <Path Name="Slice" Data="{TemplateBinding local:PieDataPoint.TextedGeometry}" 
                Fill="{TemplateBinding Control.Background}" 
                Stroke="{TemplateBinding Control.BorderBrush}" 
                StrokeMiterLimit="1">
        <ToolTipService.ToolTip>
            <StackPanel>
                <ContentControl Content="{TemplateBinding chartingToolkit:DataPoint.FormattedDependentValue}" />
                <ContentControl Content="{TemplateBinding chartingToolkit:PieDataPoint.FormattedRatio}" />
            </StackPanel>
        </ToolTipService.ToolTip>
    </Path>
    

    As you can see the "Slice" path has its Data property binded to TextedGeometry.

    Now with a custom PieSeries we can force the Chart control to use our PieDataPoint:

    public class PieSeries : System.Windows.Controls.DataVisualization.Charting.PieSeries
    {
        protected override DataPoint CreateDataPoint()
        {
            return new PieDataPoint();
        }
    }
    

    so in your XAML you can use:

    <chartingToolkit:Chart Name="pieChart" Title="Pie Series Demo">
        <local:PieSeries DependentValuePath="Value" IndependentValuePath="Key"
                            ItemsSource="{Binding}" IsSelectionEnabled="True" />
    
    </chartingToolkit:Chart>
    

    Where local refers to your custom namespace. I hope it can help you.