I wanted to add an animated pie chart to my previous post. The samples from the Toolkit are terrific, but sometimes it is difficult to find the easiest, most cookbook like process; so for those of you who might want to do the same, here is an annotated walk-through of creating this animated pie chart:
I began by opening Visual Studio and creating a new Silverlight Application, and saying no to the offer to create a Web application.
The UI, created in Page.xaml consists of the header and the Chart, placed in a Grid. You can easily do this in Blend or, in this case the layout is so simple, I hard-coded it in Xaml:
<UserControl
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;
assembly=System.Windows.Controls.DataVisualization.Toolkit"
x:Class="Pi.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="350" Height="350">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Division of Effort Among Activities"
Margin="20,10,0,0"
Grid.Row="0"
FontFamily="Georgia"
FontSize="18"
Foreground="Blue" />
<chartingToolkit:Chart x:Name="ActivityChart"
Margin="20"
Grid.Row="1">
<chartingToolkit:Chart.Series>
<chartingToolkit:PieSeries Title="Fall Activity"
IndependentValueBinding="{Binding Name}"
DependentValueBinding="{Binding Value}" />
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
</Grid>
</UserControl>
The key Xaml here is the Chart control which contains a Series which in turn contains a PieSeries. The PieSeries has an Independent and a Dependent set of values, as explained in some detail here.
To have a set of values to bind to, I created a data class that I named Activity. The Activity class has three properties of interest:
- Name – used to hold the independent value
- Value – used to hold the dependent value
- Activities – returns a list of Activity objects
1: using System;
2: using System.Collections.Generic;
3:
4: namespace Pi
5: {
6: public class Activity
7: {
8: public string Name { get; set; }
9: public double Value { get; set; }
10:
11: // static property to retrieve
12: // List of Activity objects
13: public static List<Activity> Activities
14: {
15: get
16: {
17: // value of 5 and names of activities are hard coded
18: // Generalizing is left as an exercise for the ambitious
19: var percentages = FillPercentages(5);
20: var activitiesList = new List<Activity>()
21: {
22: new Activity() {Name = "RiaServices", Value = percentages[0]},
23: new Activity() {Name = "DataGrid", Value = percentages[1]},
24: new Activity() {Name = "Behaviors", Value = percentages[2]},
25: new Activity() {Name = "VSM", Value = percentages[3]},
26: new Activity() {Name = "SampleData", Value = percentages[4]}
27: };
28: return activitiesList;
29: }
30: }
31:
32: // fill List<Double> with n doubles that sum to 1.0
33: // where n = numDoubles
34: private static List<Double> FillPercentages(int numDoubles)
35: {
36: var pctgs = new List< Double >();
37: var r = new Random();
38: double total = 0.0;
39:
40: for (int i = 0; i < numDoubles-1;)
41: {
42:
43: double val = r.NextDouble();
44: if ( val + total < 1.0)
45: {
46: pctgs.Add(val);
47: ++i;
48: }
49: }
50: pctgs.Add( 1.0 - total ); // final value
51: return pctgs;
52: }
53:
54: } // end class
55: } // end namespace
On line 13 we start the definition of the Activities property (only a get accessor is implemented)
On line 19 we delegate to a helper method generating 5 random values between 0 and 1 that together sum to 1. These will be treated as the relative percentages reflected in the pie chart.
Lines 20-27 fill our five hard-coded activities with the generated percentages and on line 28 we return the List of Activity objects we just created.
The code-behind for MainPage uses a dispatch timer to call a helper method FillPie every 4 seconds.
The helper method sets the ItemSource property on the PieSeries to whatever is returned by the static Activities property of the Activity class. Retrieving that property causes the percentages to be regenerated, and the chart is redrawn.
1: using System;
2: using System.Windows.Controls;
3: using System.Windows.Controls.DataVisualization.Charting;
4: using System.Windows.Threading;
5:
6: namespace Pi
7: {
8: public partial class MainPage : UserControl
9: {
10: public MainPage()
11: {
12: InitializeComponent();
13: Animate();
14: }
15:
16: private void Animate()
17: {
18: var timer = new DispatcherTimer();
19: timer.Start(); // Run once for display
20: // lambda syntax - same as
21: // timer.Tick +=new EventHandler(FillPie);
22: // but then you'd need FillPie to take an object and event args
23: timer.Tick +=
24: ( ( s, args ) => FillPie() ); // every tick call FillPie
25:
26: // http://msdn.microsoft.com/en-us/library/cc316852.aspx
27: timer.Interval = new TimeSpan( 0, 0, 4 ); // 4 seconds
28: timer.Start();
29: }
30:
31:
32: private void FillPie()
33: {
34: var cs = ActivityChart.Series[0] as PieSeries;
35: if ( cs != null )
36: {
37: // generating the data is handled by the static property
38: cs.ItemsSource = Activity.Activities;
39: }
40: else
41: {
42: throw new InvalidCastException( "Expected Series[0] to be a column" );
43: } // end else
44: } // end method
45: } // end class
46: } // end namespace
Notice that on lines 23 and 24 we register the FillPie method with the event using lambda notation; this makes short work of using a method that does not happen to need the standard arguments (object, eventArgs).
Timer.Start is called on line 19 to cause an immediate drawing of the pie, and then again on line 28 to implement the new time interval.