A WP7 databound graph control

For a project we are working on, we needed a simple (and free) WP7 graph usercontrol. The data which the usercontrol visualizes needs to be databound and each time new data arrives, the graph should update itself on-the-fly .In the following tutorial we’ll show how to create such a control from scratch (make note that normally this should also work in Silverlight or WPF projects).

A demo-solution of this tutorial can be downloaded here.

Graph User control: Xaml-code

First, we create a new UserControl. The xaml-part is pretty empty, we’ll add two things:

  1. A style-resource which we’ll use when drawing the graph. In this example, we want the graphlines to have the same color as the accent color of the phone.
  2. An empty gridcontrol in which the graph will be drawn












If you’d like a more fancy usercontrol, you can add a nice border or any other stuff you’d like. As long as there’s somewhere a gridcontrol named “GraphGrid” everything will be just fine.

Graph User control: Code-behind, overview

The code-behind of our usercontrol has two important parts:

  1. A DrawGraph() method which will…wait for it… draw the graph inside the gridcontrol
  2. A dependency property (DP) containing the collection of values (ints) we want to have a graph drawn of.
public partial class GraphControl : UserControl
    {
        public GraphControl()
        {
            InitializeComponent();
            Loaded += (s,e) => DrawGraph();
        }

        public void DrawGraph()
        {
            //...
        }

       // GraphValue DependencyProperty
    }

Also notice that we explicitly call the DrawGraph() method once the usercontrol is loaded (line 6). If we don’t do this, our graph want be drawn from the start, even if the dependency property already has a collection bound to it.

Graph User control: Code-behind, drawing the graph

To draw the actual graph, we’ll copy and edit a very straightforward solution by “stoneTip” who answer this StackOverflow question. Make sure to check out the original code on StackOverflow if you need more details about what is actually happening.

We split the DrawGraph() method in two parts: one which will draw the graph, the other part is used if no data was bound to the control which will then simply show ‘No data’ inside the control:

        public void DrawGraph()
        {

            if (GraphValues != null && GraphValues.Count(i => true) > 0)
            {
                //Draw graph lines
            }
            else
            {
                GraphGrid.Children.Clear();
                GraphGrid.Children.Add(new TextBlock()
                                           {
                                               Text = "No data",
                                               VerticalAlignment = VerticalAlignment.Center,
                                               HorizontalAlignment = HorizontalAlignment.Center
                                           });
            }
        }

The actual drawing part (inside the if) is as follows:

int maxGraphVal = GraphValues.Max(p => p);
int minGraphVal = GraphValues.Min(p => p);
int gridUnit =
    (int)GraphGrid.ActualWidth / GraphValues.Count(i => true);
int graphHeight =
    (int)GraphGrid.ActualHeight;

var sparkLine = new Polyline()
                    {
                        Style = (Style)Resources["graphLine"]
                    };

//Process each value and compute place in graph
int currentX = 0;
decimal graphValRange = maxGraphVal - minGraphVal;
foreach (int graphVal in GraphValues)
{

    decimal graphY2Val =
        (graphVal - minGraphVal) / graphValRange;

    double graphY2ValDouble =
        Convert.ToDouble(graphHeight - (graphY2Val * graphHeight));

    sparkLine.Points.Add(new Point(currentX, graphY2ValDouble));
    currentX += gridUnit;
}

// Add the spark line to the graph
GraphGrid.Children.Clear();
GraphGrid.Children.Add(sparkLine);

Graph User control: Code-behind, adding the source data dependency property

Lastly, we need to define a dependency property to which users of our control can bind the graph points. We’ll use an ObservableCollectionfor this (though I’m quite sure that’s not the most lightweight solution):

        public ObservableCollection GraphValues
        {
            get { return (ObservableCollection)GetValue(GraphDataProperty); }
            set { SetValue(GraphDataProperty, value); DrawGraph(); }
        }

        public static readonly DependencyProperty GraphDataProperty =
            DependencyProperty.Register("GraphValues",
                typeof(ObservableCollection),
                typeof(GraphControl),
                new PropertyMetadata(null, OnGraphValuesChanged));

What now only lacks is the part that will automatically update our graph every time new values are added to the collection or the binding changes. This part is inspired on the solution by “Josh G” who answers this SO question.

        private static void OnGraphValuesChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
        {

            GraphControl me = d as GraphControl;
            if (me != null)
            {
                var old = e.OldValue as ObservableCollection;
                if (old != null)
                    old.CollectionChanged -= me.OnGraphValueChanged;

                var n = e.NewValue as ObservableCollection;
                if (n != null)
                    n.CollectionChanged +=  me.OnGraphValueChanged ;
            }
        }

        private void OnGraphValueChanged
            (object sender, NotifyCollectionChangedEventArgs e)
        {
            DrawGraph();
        }

Using the control

With the UserControl ready and waiting. We can now easily add our shiny new ‘GraphControl’ anywhere we like.

First add the namespace to the page on which we want to add the control:

xmlns:myGraph="clr-namespace:GraphControlDemo"

Next, add the control:


In the code-behind we now only need to define some ObservableCollectioncalled SampleData which contains the points to be drawn on the graph. For example:

        public ObservableCollection SampleData { get; set; }

        public MainPage()
        {
            InitializeComponent();

            SampleData = new ObservableCollection() { 1, 2, 4 };

            LayoutRoot.DataContext = this;
        }

To test if the auto-updating works. You could add a button that adds a random sample to the collection every time it is clicked:

        private void AddRandSampleBtnClick(object sender, RoutedEventArgs e)
        {
            Random r= new Random();
            SampleData.Add(r.Next(-10, 50));
        }

And that’s a wrap!

Update: A ‘point-with-line’ graph

If you rather want a non-connected graph, use the following code (quick’n dirty version):


            int maxGraphVal = GraphValues.Max(p => p);
            int minGraphVal = GraphValues.Min(p => p);
            int gridUnit =
                (int)GraphGrid.ActualWidth / GraphValues.Count(i => true);
            int graphHeight =
                (int)GraphGrid.ActualHeight;

            var graphCanvas = new Canvas();
            graphCanvas.Width = GraphGrid.ActualWidth;
            graphCanvas.Height = GraphGrid.ActualHeight;

            //Process each value and compute place in graph
            int currentX = 0;
            decimal graphValRange = maxGraphVal - minGraphVal;
            foreach (int graphVal in GraphValues)
            {
                decimal graphY2Val =
                    (graphVal - minGraphVal) / graphValRange;

                double graphY2ValDouble =
                    Convert.ToDouble(graphHeight - (graphY2Val * graphHeight));

                var pointtoAdd = new Ellipse() {  Style = (Style)Resources["ellipseLine"], Width = 14, Height = 14 };
                var linetoAdd = new Line()
                                    {
                                        Stroke = new SolidColorBrush(Colors.Red),
                                        StrokeThickness = 3,
                                        X1 = 7,
                                        X2 = 7,
                                        Y1 = 7,
                                        Y2 = (double)graphHeight-graphY2ValDouble

                                    };
                Canvas.SetLeft(pointtoAdd,currentX);
                Canvas.SetTop(pointtoAdd, (int)graphY2ValDouble);

                Canvas.SetLeft(linetoAdd, currentX);
                Canvas.SetTop(linetoAdd, (int)graphY2ValDouble);

                graphCanvas.Children.Add(pointtoAdd);
                graphCanvas.Children.Add(linetoAdd);
                currentX += gridUnit;
            }
            GraphGrid.Children.Clear();
            GraphGrid.Children.Add(graphCanvas);

Here’s both type of types of graph together:

3 gedachten over “A WP7 databound graph control

  1. would you mind if you upload the source file 🙂

    Like

Plaats een reactie

Deze site gebruikt Akismet om spam te bestrijden. Ontdek hoe de data van je reactie verwerkt wordt.

search previous next tag category expand menu location phone mail time cart zoom edit close