Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A WP7 Databound Graph Control

DZone's Guide to

A WP7 Databound Graph Control

· Mobile Zone
Free Resource

Launching an app doesn’t need to be daunting. Whether you’re just getting started or need a refresher on mobile app testing best practices, this guide is your resource! Brought to you in partnership with Perfecto

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
<UserControl x:Class="GraphControlDemo.GraphControl">

    <UserControl.Resources>
        <Style TargetType="Polyline" x:Key="graphLine">
            <Setter Property="Stroke" Value=
                    "{StaticResource PhoneAccentColor}" />
            <Setter Property="StrokeThickness" Value="2" />
        </Style>
    </UserControl.Resources>

    <Grid x:Name="GraphGrid"  />

</UserControl>

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<int> GraphValues
{
    get { return (ObservableCollection<int>)GetValue(GraphDataProperty); }
    set { SetValue(GraphDataProperty, value); DrawGraph(); }
}


public static readonly DependencyProperty GraphDataProperty = 
    DependencyProperty.Register("GraphValues",
        typeof(ObservableCollection<int>),
        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<int>;
        if (old != null)
            old.CollectionChanged -= me.OnGraphValueChanged;

        var n = e.NewValue as ObservableCollection<int>;
        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:

<myGraph:GraphControl   GraphValues="{Binding SampleData}"/> 

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<int> SampleData { get; set; }

public MainPage()
{
    InitializeComponent();

    SampleData = new ObservableCollection<int>() { 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!

Keep up with the latest DevTest Jargon with the latest Mobile DevTest Dictionary. Brought to you in partnership with Perfecto.

Topics:

Published at DZone with permission of Tim Dams, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}