Posted by: beatkiener | February 7, 2010

Databinding Multi-Series Charts

I am working on a Silverlight project, where we doing straight MVVM. Binding the Chart control included in the Silverlight Toolkit (www.codeplex.com/Silverlight) to a ViewModel is quite simple. But what when I want to bind a collection of different “graphs” each with an own collection of data-points to a single chart?

Suppose a line-chart with dynamically changing the numbers of lines.

image

 

I decided to use a DataTemplate similar to the ItemsTemplate in the ItemsControl class.
So, I created the MultiChart control as sub class of the Silverlight Toolkit Chart control.

public class MutliChart : System.Windows.Controls.DataVisualization.Charting.Chart
{
}
 

Then I added two dependency properties (full source code is attached)

public IEnumerable SeriesSource
{
    get { return (IEnumerable)GetValue(SeriesSourceProperty); }
    set { SetValue(SeriesSourceProperty, value); }
}

public DataTemplate SeriesTemplate
{
    get { return (DataTemplate)GetValue(SeriesTemplateProperty); }
    set { SetValue(SeriesTemplateProperty, value); }
}

 

Now, I’m able to databind my multi-series datasource to the SeriesSource property similar as one binds the ItemsSource to an ItemsControl or a DataGrid control. The MultiChart control generates DataSeries items based on the SeriesTemplate. The series are now full bindable to a dynamic list in the ViewModel.

The XAML locks like this:

    <local:MultiChart SeriesSource="{Binding MySalesData}" >
        <local:MultiChart.SeriesTemplate >
            <DataTemplate >
                <chartingToolkit:LineSeries
			       Title="{Binding Title}"
			       ItemsSource="{Binding Sales}"
			       IndependentValueBinding="{Binding SalesName}"
			       DependentValueBinding="{Binding SalesTotal}" />
            </DataTemplate>
        </local:MultiChart.SeriesTemplate>
    </local:MultiChart>

This implementation is similar to the content model in other controls i.e., Content and ContentTemplate, Header and HeaderTemplate, Items or ItemsSource and ItemTemplate, etc.

But what if I want to have different chart-series in the same chart such as column-series and line-series databound to the ViewModel?

image

What I need is a different DataTemplate per item type in the data source. Unfortunately Silverlight does not support typed data-templates natively. WPF answers this problem with DataTemplateSelector (and the ContentTemplateSelector, HeaderTemplateSelector, and ItemTemplateSelector properties).  Silverlight doesn’t yet support it, but we can achieve the same effect after a little more code.

So, I added another dependency property called SeriesTemplateSelector.

public DataTemplateSelector SeriesTemplateSelector
{
    get { return (DataTemplateSelector)GetValue(SeriesTemplateSelectorProperty); }
    set { SetValue(SeriesTemplateSelectorProperty, value); }
}

 

The class DataTemplateSelector does not exists in Silverlight. So, I just copied the DataTemplateSelector base class from the WPF assembly (via Reflector).

// code from WPF

using System;
using System.Windows;

namespace System.Windows.Controls
{
    // Summary: 
    // Provides a way to choose a System.Windows.DataTemplate based on the data 
    // object and the data-bound element. 
    public class DataTemplateSelector
    {
        // Summary: 
        // Initializes a new instance of the System.Windows.Controls.DataTemplateSelector 
        // class. 
        public DataTemplateSelector()
        {
        }

        // Summary: 
        // When overridden in a derived class, returns a System.Windows.DataTemplate 
        // based on custom logic. 
        // 
        // Parameters: 
        // item: 
        // The data object for which to select the template. 
        // 
        // container: 
        // The data-bound object. 
        // 
        // Returns: 
        // Returns a System.Windows.DataTemplate or null. The default value is null. 
        public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            return null;
        }
    }
}

 

If the SeriesTemplateSelector is set then the MultiChart control selects its data-template with this instance.

At the end my XAML looks like this:

<UserControl>
    <UserControl.Resources >
        <local:SeriesTemplateSelector x:Key="chartTemplateSelector">
            <local:SeriesTemplateSelector.SalesTemplate>
                <DataTemplate >
                    <chartingToolkit:LineSeries
                                    Title="{Binding SalesName}"
                                    ItemsSource="{Binding SalesTotals}"
                                    IndependentValueBinding="{Binding Date}"
                                    DependentValueBinding="{Binding SalesTotal}" />
                </DataTemplate>
            </local:SeriesTemplateSelector.SalesTemplate>
            <local:SeriesTemplateSelector.MedianTemplate>
                <DataTemplate >
                    <chartingToolkit:ColumnSeries
                                    Title="{Binding SalesName}"
                                    ItemsSource="{Binding SalesTotals}"
                                    IndependentValueBinding="{Binding Date}"
                                    DependentValueBinding="{Binding SalesTotal}" />
                </DataTemplate>
            </local:SeriesTemplateSelector.MedianTemplate>
        </local:SeriesTemplateSelector>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot">
            <local:MultiChart SeriesSource="{Binding SalesDataWithMedian}"
                              SeriesTemplateSelector="{StaticResource chartTemplateSelector}"
                              Title="Dynamic Multi Lines with different DataTemplates">

            </local:MultiChart>
    </Grid>
</UserControl>

 

Here the implementation of SeriesTemplateSelector class:

   public class SeriesTemplateSelector : DataTemplateSelector
    {
        public DataTemplate SalesTemplate { get; set; }
        public DataTemplate MedianTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {

            if (item is SalesPerformance)
            {
                SalesPerformance salesPerf = item as SalesPerformance;

                if (salesPerf.SalesName == "Median")
                {
                    return MedianTemplate;
                }
                else
                {
                    return SalesTemplate;
                }
            }

            // default
            return null;

        }
    }
 

Finally I got a fully bindable mutli-series chart control which provides the same content model as other well-known control such the ItemsControl.

Here you can find the source code in a demo project.


Responses

  1. [...] behavior with native UIElement MouseWheel eventMemory leak with focusable UIElement in Silverlight?Databinding Multi-Series ChartsBe careful when using WeakReference.IsAliveBasicHttpBinaryBinding for [...]

  2. how do you inherit from a sealed class?
    i download the demo project and visual studio is telling me that, “MultiChart: cannot derive from a sealed class System.Windows.Controls.DataVisualization.Charting.Chart”.

    can you help me with this ?

  3. yeah, now this is working, in the changes of the last version says:
    “Supports more flexible subclassing scenarios of core classes”
    this solves my problem to derive the MultiChart from the Chart class,

    but, what if i want use it in a WPF Application, and not in silverlight application?

    is been quite a mess, because a need to get the WPFToolkit, and i am very confusing with the correct versions of the System.Windows.Controls.DataVisualization.Toolkit that i need to have to run it in a WPF Application.


Leave a response

Your response:

Categories