
In this post, we'll create a pie chart, using Silverlight. You can see an example of the finished pie chart in the diagram to the left.
Below, I have listed the full page.xaml for the pie chart. It's quite simple - the chart itself is painted onto the canvas and there is a button to refresh the graph, using new data.
I also added a TextBlock, which I used to display any test output was needed.
<UserControl x:Class="piechart.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="400">
<Grid x:Name="LayoutRoot" Background="White">
<Canvas Background="#ffffff" Name="graphc">
<TextBlock Name="outtab" TextWrapping="Wrap" Canvas.Top="250"></TextBlock>
<Button Name="Button" Width="400" Height="30" Click="Button_Click" Canvas.Top="370" Content="New Graph"></Button>
</Canvas>
</Grid>
</UserControl>
Listed below is the Page.xaml.cs. The button click event refreshes the graph, using new randomly generated data.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace piechart
{
public partial class Page : UserControl
{
String[] hex = { "#ff336699", "#ff339966", "#ff9999cc", "#ffcc6699", "#ff6699cc" };
Path[] paths = new Path[5];
int r = 100; //the radius of the circle
piechart.Shape[] shapes = new piechart.Shape[5];
public Page()
{
InitializeComponent();
outtab.Text = draw();
}
public String draw()
{
String output = "";
Point start = new Point(r, 0);
Point centre = new Point(0, 0);
//randomly assign data
Double total = 0.0;
Random rnd = new Random();
int[] randoms = new int[shapes.Length];
for (int i = 0; i < shapes.Length; i++)
{
try
{
randoms[i] = rnd.Next();
}
catch { }
total += randoms[i];
}
Array.Sort(randoms);
double curangle = 0.0;
//Set up the array of shapes
for (int i = 0; i < shapes.Length; i++ )
{
shapes[i] = new Shape(randoms[i], r, start, centre, hex[i]);
shapes[i].SetAngle(shapes[i].GetData()/total * 360);
paths[i] = shapes[i].Pie(curangle);
graphc.Children.Add(paths[i]);
//increment the angle
curangle += shapes[i].GetAngle();
output += "" + "\n";
}
return output;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < shapes.Length; i++)
{
graphc.Children.Remove(paths[i]);
}
if (outtab.Text.Length > 0)
{
outtab.Text = draw();
}
}
}
}
Below is Shape.cs. This class is responsible for creating the pie slices:
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace piechart
{
public class Shape
{
int data = 0;
public void SetData(int data)
{
this.data = data;
}
public int GetData()
{
return data;
}
Point point;
public void SetPoint(Point point)
{
this.point = point;
}
public Point GetPoint()
{
return point;
}
Double angle;
public void SetAngle(Double angle)
{
this.angle = angle;
}
public Double GetAngle()
{
return angle;
}
String output;
public void SetOutput(String output)
{
this.output = output;
}
public String GetOutput()
{
return output;
}
int r = 0;
Point start;
Point centre;
String hex = "";
public Shape(int data, int r, Point start, Point centre, String hex)
{
this.data = data;
this.r = r;
this.start = start;
this.centre = centre;
this.hex = hex;
}
public Path Pie(double rotangle)
{
Utils utils = new Utils();
Path path1 = new Path();
SolidColorBrush fb = new SolidColorBrush(utils.HexColor(hex));
PathGeometry pg1 = new PathGeometry();
PathFigure pf1 = new PathFigure();
pf1.StartPoint = start;
//calculate point
double theta = (2 * Math.PI) / 360 * angle;
double endX = (Math.Cos(theta) * r);
double endY = (Math.Sin(theta) * r);
SetPoint(new Point(endX, endY));
output += theta + " " + endX + " " + endY;
PathSegmentCollection segments = new PathSegmentCollection();
//arc1
ArcSegment arc1 = new ArcSegment();
arc1.Point = point;
arc1.Size = new Size(r, r);
if (angle > 180)
{
arc1.IsLargeArc = true;
}
else
{
arc1.IsLargeArc = false;
}
arc1.SweepDirection = SweepDirection.Clockwise;
segments.Add(arc1);
//LineSegments
LineSegment ls1 = new LineSegment();
ls1.Point = start;
segments.Add(ls1);
LineSegment ls2 = new LineSegment();
ls2.Point = centre;
segments.Add(ls2);
LineSegment ls3 = new LineSegment();
ls3.Point = point;
segments.Add(ls3);
LineSegment ls4 = new LineSegment();
ls4.Point = start;
segments.Add(ls4);
TransformGroup trans = new TransformGroup();
//rotate
RotateTransform rt = new RotateTransform();
rt.Angle = rotangle;
trans.Children.Add(rt);
path1.RenderTransform = trans;
//translate
TranslateTransform tt = new TranslateTransform();
tt.X = 100;
tt.Y = 100;
trans.Children.Add(tt);
path1.RenderTransform = trans;
pf1.Segments = segments;
pg1.Figures.Add(pf1);
path1.Data = pg1;
path1.Fill = GetBrush(hex);
path1.Stroke = fb;
path1.SetValue(Canvas.TopProperty, 0.0);
path1.SetValue(Canvas.LeftProperty, 0.0);
path1.StrokeThickness = 0;
path1.MouseEnter += path_MouseEnter;
path1.MouseLeave += path_MouseLeave;
return path1;
}
public Brush GetBrush(String hexcolor)
{
LinearGradientBrush fillBrush = new LinearGradientBrush();
Color col = new Utils().HexColor(hexcolor);
fillBrush.StartPoint = new Point(0, 0.0);
fillBrush.EndPoint = new Point(0, 1.0);
// First Gradient stop
GradientStop gs1 = new GradientStop();
gs1.Color = Color.FromArgb(0, col.R, col.G, col.B);
gs1.Offset = 0.0;
fillBrush.GradientStops.Add(gs1);
// Second Gradient stop
GradientStop gs2 = new GradientStop();
gs2.Color = Color.FromArgb(col.A, col.R, col.G, col.B);
gs2.Offset = 0.1;
fillBrush.GradientStops.Add(gs2);
// Third Gradient stop
GradientStop gs3 = new GradientStop();
gs3.Color = col;
gs3.Offset = 1.0;
fillBrush.GradientStops.Add(gs3);
return fillBrush;
}
private void path_MouseEnter(object o, MouseEventArgs e)
{
Path path = (Path)o;
path.Fill = GetBrush("#6666cc");
}
private void path_MouseLeave(object o, MouseEventArgs e)
{
Path path = (Path)o;
path.Fill = GetBrush(hex);
}
}
}
Finally, note that I used the hex utility in a Utils.cs class. This just makes it convenient to convert from hex colours. You can read about this utility class in my post about Converting from a Hex String to a Color object

2 comments:
Silverlemma,
Great post - it's nice to be able to see exactly how you've put this together!
By the way, if you're seriously interested in Charting, you might want to consider the Charting package that's available as part of the Silverlight Toolkit. I'm part of that project and have blogged a decent amount about what it is and how it works. For example, here's a link to the latest release notes with links to some other relevant stuff: http://blogs.msdn.com/delay/archive/2009/03/19/silverlight-charting-is-faster-and-better-than-ever-silverlight-toolkit-march-09-release-now-available.aspx.
There's lots of similar math to what you've just shown going on in *our* code, so maybe have a look if that's of interest to you! :)
O wow - thanks :))
You have some great looking charts :)
Post a Comment