Colin Eberhardt's Technology Adventures

WPF Charting Performance Comparisons (the Battle Continues)

April 24th, 2012

This blog post presents a thorough analysis of the performance of various WPF Charting components. The results show that a new class of charting solutions, which use raster-based graphics as opposed to retained mode vector graphics, provide a considerable performance advantage.

Introduction

Readers of my blog will know that charting and performance are two things that interest me greatly. One of the first things I did back in 2009 when I started working with WPF and Silverlight was have a go with the Microsoft ‘toolkit’ charts. Unfortunately I found these charts, and the various third party competitors, to be painfully slow when rendering just a few hundred datapoints. As a result of these findings I set about creating a high performance chart for Silverlight and WPF, which eventually became Visiblox charts.

In December 2010 I published a performance comparison that demonstrated that Visiblox was approximately 50-100 times faster than the Toolkit charts and 10 times faster than Visifire charts. At the time the only charts which came close were those developed as part of Microsoft’s now abandoned Dynamic Data Display project. Since publishing these figures, a number of the WPF and Silverlight charting vendors have started to take performance much more seriously, with Visifire more than doubling performance amid claims of being the fastest chart, amCharts have also publishing some very good performance figures as have Telerik.

Much has changed in the year and a half since my original performance blog post and I thought it would be a good idea to repeat the tests. However, this time I wanted to be more thorough, including a few different types of tests and also using WPF instead of Silverlight, due to the number of email requests I have had for a WPF charting comparison.

The Test Suite

In order to allow me to collect performance data for a range of WPF charts across a range of tests, I have written an automated performance test suite. The application uses MEF in order to allow me to easily slot in new tests and new charting providers. If you want a copy of the code, please get in touch.

The suite executes the following tests:

  1. Line Series Frame Rate – this test initially renders a ‘streaming’ line series which a fixed number of datapoints. New data is added to the chart as quickly as possible, and each time a new point is added to the front and old point is removed in order to keep a fixed number of points in view. The chart axes are auto-scaled (API permitting). The test initially starts with 50 datapoints, with each test run doubling this number.
  2. Scatter Series Frame Rate – a number of points are added to the chart. The location of these points is then animated in a manner that emulates Brownian motion. The test initially starts with 50 datapoints, with each test run doubling this number.
  3. Scatter Series Memory Usage – the above test is re-run, but this time rather than measuring render frame rate, the memory consumed as a result of adding the datapoints is recorded.

Initially I used a timer to measure framerate, followed by an attempt to measure via CompositionTarget.Rendering. However, I found that neither method gave an accurate frame rate measure, indeed the WPF team report that the CompositionTarget.Rendering can fire multiple times per frame. Instead, I opted for a more manual measurement using the WpfPerf tool, which makes use of the ETW events emitted by WPF.

Line Series Results

The results of the line series framerate tests are shown below:

NOTE: I am using a log-log scale for the above chart in order to make it easier to see the shape of the various curves.

Regarding which charting vendors I used for comparison, I tried to include all established and well known vendors in my comparison. For those which have already published performance figures or comments relating to Visiblox, I have included their names. For the others I have simply given them a token name ‘Vendor X’, in order to comply with the trial licencing terms which typically prohibit the publishing of performance figures.

One thing worth noting is that some charting vendors use sampling in order to improve framerates. I found that Vendor A (not included above) uses sampling without providing a mechanism for turning it off. Whereas most vendors that do support sampling allow you to turn it on or off and configure the sampling algorithm used. In order to provide a fair comparison, sampling was turned off on all the charts.

The chart above reveals some interesting patterns, due to the two very different classes of chart under test.

Vector based charts – WPF UIs are rendered via retained mode vector graphics. Whilst this approach is what has given rise to the very rich UI framework that WPF applications enjoy, it has been found that this approach just doesn’t perform as well as the bitmap-based approach used by Windows Forms when faced with complex UIs. This has led some people to use WinForms charts within their WPF applications! We can see that the faster vector charts from Microsoft Toolkit, Vendor B, the updated Visifire and Visiblox charts are all close to the limit of what is possible with this style of rendering.

“ … the major ‘partners’ cannot create a WPF chart that renders 2000 floating points of data in less that 5 seconds. I have tried them all and they are slow so we have had to regress to a WinForms version that renders the same data in milliseconds.” – from the channel 9 forums.

Raster based charts – Because of the limits of the WPF rendering system the two fastest charts in this test, Visiblox (via the new raster series type as part of the Ultimate Editition) and SciChart (a newcomer that exclusively uses this approach) both use a raster / bitmap graphics approach, which the WPF and Silverlight frameworks both have support for via the WriteableBitmap class. This approach yields results which are more than 100 times faster than what is possible with vector graphics.

One of the other common complaints made about WPF charts is the initial render time, with charts containing just a few thousands datapoints taking seconds to show on screen. Whilst this test focusses in framerates, it is also gives a good indication of the initial render times, with raster based charts appearing almost instantly when loaded with tens of thousands of points.

Scatter Series Results

The results of the Scatter series tests are shown below:

Again, the same pattern emerges, with the raster based approach yielding a significant increase in frame rate over the vector based.

Scatter Series Memory Usage

The final test looks at the memory consumption of the various charting providers:

The above shows that the memory consumption is directly related to the number of datapoints present in the chart, however, the scale of this relationship is very different for the charts under test. I have a feeling that the Toolkit charts have a memory leak which contributes to the poor performance in this test.

Conclusions

The overall performance of the charting solutions available to WPF (and Silverlight) developers is improving. However, the performance of the WPF rendering system appears to create an upper limit which constrains what can be achieved. The raster-based approach is not subject to these same constraints and as a result provides a considerable improvement in performance.

If you want to have access to the code I used to make these tests, please get in touch. Also, if you are a charting vendor and think you might be Vendor A, B or C in these tests, and wish to be named, let me know.

Adding Error Bars to Visiblox Silverlight Charts

October 21st, 2011

Having spent a number of years studying Physics at university, I have had the importance of error bars well and truly drummed into me! Within physics, or any experimental science, there are always going to be errors in the measurements you make. The more repeat measurements you make, the more confident you can be in the mean value, however you cannot remove the errors altogether. Error bars provide a way to represent the spread of experimental observations graphically, without them, it is hard to have any confidence in the conclusions drawn from the observations!

In this blog post I will show how to implement a custom Visiblox chart series to render error bars:

(The data in the above chart is from a page which details how to calculate the standard error from experimental results).

Creating a Custom Series

As described in my previous blog post on creating a spline series, to create a new series type, you sub-class one of the Visiblox base-classes, in this case MultiValueSeriesBase is a suitable starting point:

public class ErrorBarSeries : MultiValueSeriesBase
{ 
  protected override FrameworkElement CreatePoint(IDataPoint dataPoint)
  {
    throw new NotImplementedException();
  }
 
  protected override void RenderDataLabels()
  {
    throw new NotImplementedException();
  }
}

I don’t want data labels, so the only method I need to implement is CreatePoint, which takes the (multi-valued) point to be rendered as its only argument. The lifecycle of point creating and destruction is taken care of by the base-class.

The IDataPoint has a string indexer which is used to retrieve multiple Y values for multi-valued series. It is a good idea to define these in a single place, here we define the three y-values required for an error-bar series:

public static readonly string ErrorUp = "ErrorUp";
 
public static readonly string ErrorDown = "ErrorDown";
 
public static readonly string Value = "Value";

The CreatePoint implementation for this series creates a Path as follows:

protected override FrameworkElement CreatePoint(IDataPoint dataPoint)
{
  var lineGeometry = BuildGeometry(dataPoint);
 
  Path line = new Path();
  line.Stroke = new SolidColorBrush(Colors.Black);
  line.Fill = new SolidColorBrush(Colors.Gray);
  line.StrokeThickness = 1.0;
  line.StrokeLineJoin = PenLineJoin.Bevel;
  line.Data = lineGeometry;
  line.SetValue(ZoomCanvas.IsScaledPathProperty, true);
 
  return line;
}

The BuildGeometry method does most of the work, extracting the values from the IDataPoint, transforming them (via the axis) to the required coordinate system, then creating a suitable geometry:

/// <summary>
/// Creates the geometry for the given datapoint
/// </summary>
private PathGeometry BuildGeometry(IDataPoint dataPoint)
{
  var halfWidth = SuggestedPointWidth * WidthFactor;
 
  // obtain the data values
  var topDataValue = dataPoint[ErrorUp] as IComparable;
  var middleDataValue = dataPoint[Value] as IComparable;
  var bottomDataValue = dataPoint[ErrorDown] as IComparable;
 
  // convert to a the required render coordinates
  double topRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(topDataValue);
  double middleRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(middleDataValue);
  double bottomRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(bottomDataValue);
 
  double xMiddleRenderPos = XAxis.GetDataValueAsRenderPositionWithoutZoom(dataPoint.X);
  double xRightRenderPos = xMiddleRenderPos - halfWidth;
  double xLeftRenderPos = xMiddleRenderPos + halfWidth;
 
  // build a suitable gemoetry
  PathGeometry lineGeometry = new PathGeometry();
 
  PathFigure upperVerticalLine = CreateLineFigure(
    new Point(xMiddleRenderPos, middleRenderPos - halfWidth),
    new Point(xMiddleRenderPos, topRenderPos));
  lineGeometry.Figures.Add(upperVerticalLine);
 
  PathFigure lowerVerticalLine = CreateLineFigure(
    new Point(xMiddleRenderPos, bottomRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos + halfWidth));
  lineGeometry.Figures.Add(lowerVerticalLine);
 
  PathFigure upperBar = CreateLineFigure(
    new Point(xLeftRenderPos, topRenderPos),
    new Point(xRightRenderPos, topRenderPos));
  lineGeometry.Figures.Add(upperBar);
 
  PathFigure lowerBar = CreateLineFigure(
    new Point(xLeftRenderPos, bottomRenderPos),
    new Point(xRightRenderPos, bottomRenderPos));
  lineGeometry.Figures.Add(lowerBar);
 
  PathFigure center = CreateLineFigure(
    new Point(xMiddleRenderPos - halfWidth, middleRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos + halfWidth),
    new Point(xMiddleRenderPos + halfWidth, middleRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos - halfWidth)
  );
  lineGeometry.Figures.Add(center);
 
  return lineGeometry;
}
 
/// <summary>
/// Create a line figure that connects the given points
/// </summary>
private PathFigure CreateLineFigure(params Point[] points)
{
  // add all the points (except the first)
  var pointCollection = new PointCollection();
  foreach (var point in points.Skip(1))
  {
    pointCollection.Add(point);
  }
 
  // create a figure, using the first point as the StartPoint.
  return new PathFigure()
  {
    IsClosed = true,
    StartPoint = points.First(),
    Segments = new PathSegmentCollection()
    {
      new PolyLineSegment
      {
        Points = pointCollection
      }
    }
  };
}

We can now create an instance of this series in XAML:

<vis:Chart x:Name="chart">
  <vis:Chart.Series>
    <local:ErrorBarSeries/>
  </vis:Chart.Series>
</vis:Chart>

Supplying data to the chart via MultiValuedDataPoint as follows:

public MainPage()
{
  InitializeComponent();
 
  var data = new DataSeries<double, double>();
  data.Add(CreatePoint(-195, 1.4, 0.2));
  data.Add(CreatePoint(0, 62.2, 9.3));
  data.Add(CreatePoint(20, 70.4, 6.5));
  data.Add(CreatePoint(100, 77.4, 1.9));
 
  chart.Series[0].DataSeries = data;
}
 
private MultiValuedDataPoint<double, double> CreatePoint(double x, double y, double error)
{
  var point = new MultiValuedDataPoint<double, double>(x,
    new Dictionary<object, double>()
    {
      { ErrorBarSeries.ErrorUp, y + error },
      { ErrorBarSeries.ErrorDown, y - error },
      { ErrorBarSeries.Value, y }
    });
  return point;
}

This results in the following chart:

Binding to a Multi-valued Series

In the previous example we created instances of MultiValuedDataPoint, a Visiblox type for representing multi-valued points. As an alternative, we can create model objects to represent each point, rendering them in the chart via databinding.

We first modify the code to create a collection of Measurement instances (a simple model object that implements INotifyPropertyChanged):

public MainPage()
{
  InitializeComponent();
 
  var data = new ObservableCollection<Measurement>();
  data.Add(CreateMeasurement(-195, 1.4, 0.2));
  data.Add(CreateMeasurement(0, 62.2, 9.3));
  data.Add(CreateMeasurement(20, 70.4, 6.5));
  data.Add(CreateMeasurement(100, 77.4, 1.9));
  this.DataContext = data;    
}
 
private Measurement CreateMeasurement(double x, double y, double error)
{
  return new Measurement()
    {
      XValue = x,
      YValue = y,
      YValueErrorUp = y + error,
      YValueErrorDown = y - error
    };
}

The markup for the chart is modified to use a BindableDataSeries, with bindings specified for the various component of the error bar series. Also, the ItemsSource of the BindableDataSeries is bound to the inherited DataContext:

<local:ErrorBarSeries>
  <local:ErrorBarSeries.DataSeries>
    <vis:BindableDataSeries ItemsSource="{Binding}"
                            XValueBinding="{Binding XValue}">
      <vis:BindableDataSeries.YValueBindings>
        <vis:YValueBinding YValueKey="Value" Binding="{Binding YValue}"/>
        <vis:YValueBinding YValueKey="ErrorUp" Binding="{Binding YValueErrorUp}"/>
        <vis:YValueBinding YValueKey="ErrorDown"  Binding="{Binding YValueErrorDown}"/>
      </vis:BindableDataSeries.YValueBindings>              
    </vis:BindableDataSeries>
  </local:ErrorBarSeries.DataSeries>
</local:ErrorBarSeries>

We can also display our data in a DataGrid, allowing us to manipulate the values (not that I condone manipulation of scientific data!), with the changes being reflected in the chart:

<sdk:DataGrid Grid.Row="1"
              x:Name="grid"
              ItemsSource="{Binding}"/>

This gives us the following application:

You can edit the values, with the changes reflected immediately in the chart above.

You can download the sourcecode for the above example here: VisibloxErrorBarSeries.zip

Regards, Colin E.

Visiblox MVP Giveaway

June 23rd, 2011

Readers of my blog will probably have noticed that I have a keen interest in both charting and performance. My friends over at Visiblox have combined these both to create what is one of the fastest charts for WPF, Silverlight and Windows Phone 7, see my recent benchmark blog post for details. What’s more, this performance has been achieved without sacrificing API clarity; the Visiblox charts can be instantiated with concise and clean XAML or manipulated from code-behind.

The guys at Visiblox told me that they are giving away free licences for their charting products to anyone who is a current Microsoft MVP, so I thought I’d share that with you in case you’re interested. For more details on this offer please contact Visiblox directly.

If you are an MVP, give these charts a whirl …

I’d better get back to work on the cross-platform (WPF, Silverlight, WP7) application that I will be demoing at the next Silverlight UK User Group meeting, and yes, it uses Visiblox charts!

Regards, Colin E.

MVVM Charting – Binding Multiple Series to a Visiblox Chart

March 6th, 2011

This post describes a method of using attached properties to bind a ViewModel which contains multiple data series to a Visiblox chart without any code-behind.




The Visiblox chart supports databinding in both WPF and Silverlight, where the X and Y values for each datapoint are bound to properties on an underlying model. However, there is no interface for binding a varying number of series (i.e a collection of collections). The solution provided here is similar to the one which Jeremiah Morrill published for binding multiple series to the Silverlight Toolkit charts, but with a few added extras, like series title binding and series type selection.

The solution is surprisingly simple, so I am going to dive straight into the code (note, I have collapsed the verbose attached property definitions so that just the interesting bits are shown below!)

public static class MultiSeries
{
  #region TitlePath attached property
 
  #region ItemsSourcePath attached property
 
  #region XValuePath attached property
 
  #region YValuePath attached property
 
  #region ChartTypeProvider attached property
 
  #region Source attached property
 
  /// <summary>
  /// Handles property changed event for the Source property
  /// </summary>
  private static void OnSourcePropertyChanged(DependencyObject d,
                                                      DependencyPropertyChangedEventArgs e)
  {
    Chart targetChart = d as Chart;
 
    SynchroniseChartWithSource(targetChart);
 
    IEnumerable Source = GetSource(targetChart);
    INotifyCollectionChanged incc = Source as INotifyCollectionChanged;
    if (incc != null)
    {
      incc.CollectionChanged += (s, e2) => SynchroniseChartWithSource(targetChart);
    }
 
  }
 
  private static void SynchroniseChartWithSource(Chart chart)
  {
    chart.Series.Clear();
 
    IEnumerable Source = GetSource(chart);
    if (Source == null)
      return;
 
    // iterate over each source series
    foreach (object seriesDataSource in Source)
    {
      // create a visiblox chart series
      IChartSeries chartSeries = GetChartTypeProvider(chart).GetSeries(seriesDataSource);
 
      // resolve the ItemsSource path (if present).
      var itemsSourcePath = GetItemsSourcePath(chart);
      IEnumerable itemsSource = null;
      if (!string.IsNullOrEmpty(itemsSourcePath))
      {
        itemsSource = seriesDataSource.GetPropertyValue<IEnumerable>(itemsSourcePath);
      }
      else
      {
        // if not present, assume this is a collection of collections
        itemsSource = seriesDataSource as IEnumerable;
      }
 
      // resolve the title path
      var titlePath = GetTitlePath(chart);
      var seriesTitle = "";
      if (!string.IsNullOrEmpty(titlePath))
      {
        seriesTitle = seriesDataSource.GetPropertyValue<string>(titlePath);
      }
 
      // create the data series, and add to the chart.
      chartSeries.DataSeries = new BindableDataSeries()
      {
        XValueBinding = new Binding(GetXValuePath(chart)),
        YValueBinding = new Binding(GetYValuePath(chart)),
        ItemsSource = itemsSource,
        Title = seriesTitle
      };
      chart.Series.Add(chartSeries);
    }
  }
 
  /// <summary>
  /// Gets the value of the named property.
  /// </summary>
  public static T GetPropertyValue<T>(this object source, string propertyName)
  {
    var property = source.GetType().GetProperty(propertyName);
    if (property == null)
    {
      throw new ArgumentException(string.Format("The property {0} does not exist on the type {1}",
        propertyName, source.GetType()));
    }
    return (T)property.GetValue(source, null);
  }
}

The MultiSeries class defines a number of attached properties, Source is used to bind the collection of series, this property must be an IEnumerable, but if it also implements INotifyCollectionChanged, we handle the CollectionChanged events to update the chart (adding or removing series). The optional ItemsSourcePath is used to provide the path to the nested collection (more on this later) and the optional TitlePath binds the chart title. The XValuePath and YValuePath properties are used to bind the X & Y values of the chart. Finally, ChartTypeProvider is used to determine the series type (Line, Bar, Column …) for each of the bound series. The provider must implement the following interface:

/// <summary>
/// An interface for providing Visiblox chart series.
/// </summary>
public interface IChartTypeProvider
{
  /// <summary>
  /// Creates a suitable chart series for the given data
  /// </summary>
  IChartSeries GetSeries(object boundObject);
}

In most MVVM chart binding applications, you will probably want all the series to have the same type. To achieve this, we can create a simple implementation of this interface which always returns the same chart type:

/// <summary>
/// A ChartTypeProvider that always returns the Visiblox series
/// type that was supplied in the constructor.
/// </summary>
public class DefaultChartTypeProvider : IChartTypeProvider
{
  private Type _seriesType;
 
  public DefaultChartTypeProvider(Type seriesType)
  {
    _seriesType = seriesType;
  }
 
  public IChartSeries GetSeries(object boundObject)
  {
    var ctr = _seriesType.GetConstructor(new Type[] { });
    return (IChartSeries)ctr.Invoke(new object[] { });
  }
}

In order to simplify the usage of this provider in XAML, we can provide a type converter which allows us to specify the required series type as a string, e.g. ChartTypeProvider="LineSeries", this makes use of the same framework mechanisms that allow you to specify a Fill as a string, e.g. Fill="Red", where the result will be to create the following, new SolidColorBrush() { Color = Colors.Red }, a suitable type converter is shown below:

[TypeConverter(typeof(StringToChartTypeProvider))]
public interface IChartTypeProvider
{
   ...
}
 
/// <summary>
/// A type converter that converts a string into a FixedChartTypeProvider. For example
/// "LineSeries" is converted into a FixedChartTypeProvider which
/// always returns Visblox.Chart.LineSeries instances
/// </summary>
public class StringToChartTypeProvider : TypeConverter
{
  public override object ConvertFrom(ITypeDescriptorContext context,
    CultureInfo culture, object value)
  {
    if (value is string)
    {
      var visibloxAssembly = typeof(Chart).Assembly;
      var seriesType = visibloxAssembly.GetType("Visiblox.Charts." + value.ToString());
      return new DefaultChartTypeProvider(seriesType);
    }
    return base.ConvertFrom(context, culture, value);
  }
 
  public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
  {
    if (sourceType == typeof(string))
      return true;
 
    return base.CanConvertFrom(context, sourceType);
  }
}

A Simple Example

We’ll start with a simple example, binding a collection of collections, in this case the harmonic vibrations of a string. The series are created as follows:

  public MainPage()
  {
    InitializeComponent();
 
    // a collection of collections
    var harmonics = new List<List<Point>>();
 
    // plot each harmonic frequency
    for (int frequency = 1; frequency < 5 ;frequency++)
    {
      // create the upper and lower component
      var upperHarmonic = new List<Point>();
      var lowerHarmonic = new List<Point>();
      for (double phase = 0; phase < Math.PI; phase+= (Math.PI / 100))
      {
        upperHarmonic.Add(new Point(phase, Math.Sin(phase * frequency) + frequency * 2.5));
        lowerHarmonic.Add(new Point(phase, Math.Sin(phase * frequency + Math.PI) + frequency * 2.5));
      }
 
      // add each to the collection
      harmonics.Add(upperHarmonic);
      harmonics.Add(lowerHarmonic);
    }
 
    this.DataContext = harmonics;      
  }
}

We can then use the attached properties defined above to bind this data to the chart. Note, because the bound data is a collection of collections (rather than a collection of items which expose the child collection via a property), the optional ItemsSourcePath is not required:

<UserControl.Resources>
  <vis:Palette x:Key="palette">
    <Style TargetType="vis:LineSeries">
      <Setter Property="LineStroke" Value="Black"/>
    </Style>
    <Style TargetType="vis:LineSeries">
      <Setter Property="LineStroke" Value="Black"/>
    </Style>
    <Style TargetType="vis:LineSeries">
      <Setter Property="LineStroke" Value="Red"/>
    </Style>
    <Style TargetType="vis:LineSeries">
      <Setter Property="LineStroke" Value="Red"/>
    </Style>
  </vis:Palette>
</UserControl.Resources>
 
<Grid x:Name="LayoutRoot" Background="White">
  <vis:Chart x:Name="chart"
              Palette="{StaticResource palette}"
              LegendVisibility="Collapsed"
              Title="Modes of a vibrating string"               
              local:MultiSeries.ChartTypeProvider="LineSeries"
              local:MultiSeries.XValuePath="X"
              local:MultiSeries.YValuePath="Y"
              local:MultiSeries.Source="{Binding}"/>
</Grid>

Whilst the MultiSeries class allows you to specify the chart type via ChartTypeProvider, it does not provide a mechanism for styling the various series which it produces based on the supplied Source. However, styling can be achieved using the chart’s Palette, which is a collection of Style instances which are applied to the series in order.

The resulting chart is shown below:

A More Complex MVVM Example

In this example, rather than binding a collection of collections, the following model is bound:

CompanySalesViewModel has a collection of SalesTeamViewModel instances, each of these has a name, TeamName, and a collection of SalesInRegionViewModel instances. Note, SalesTeamViewModel also has a string indexer property which will be used to bind this model to a DataGrid.

A custom ChartTypeProvider is used so that we can select chart types based on the bound SalesTeamViewModel:

public class SalesTypeProvider : IChartTypeProvider
{
  public IChartSeries GetSeries(object boundObject)
  {
    var viewModel = boundObject as SalesTeamViewModel;
    if (viewModel.TeamName == "Target")
      return new LineSeries();
    else
      return new ColumnSeries();
  }
}

The view model is bound to a chart and is also bound to a DataGrid as follows. Note this time the ItemsSourcePath is used to bind the TeamSales property of each SalesTeamViewModel:

<UserControl.Resources>
  <DropShadowEffect x:Key="shadow" Opacity="0.3"/>
 
  <Style TargetType="UIElement" x:Key="collapsedStyle">
    <Setter Property="Visibility" Value="Collapsed"/>
  </Style>
 
  <!-- a base style for column series -->
  <Style TargetType="vis:ColumnSeries" x:Key="columnBaseStyle">
    <Setter Property="PointStroke" Value="White"/>
    <Setter Property="PointStrokeThickness" Value="0.5"/>
    <Setter Property="Effect" Value="{StaticResource shadow}"/>
  </Style>
 
  <!-- a Visiblox palette -->
  <vis:Palette x:Key="palette">
    <Style TargetType="vis:ColumnSeries"
            BasedOn="{StaticResource columnBaseStyle}">
      <Setter Property="PointFill" Value="#4f81bd"/>
    </Style>
    <Style TargetType="vis:ColumnSeries"
            BasedOn="{StaticResource columnBaseStyle}">
      <Setter Property="PointFill" Value="#c0504d"/>
    </Style>
    <Style TargetType="vis:ColumnSeries"
            BasedOn="{StaticResource columnBaseStyle}">
      <Setter Property="PointFill" Value="#9bbb59"/>
    </Style>
    <Style TargetType="vis:LineSeries">
      <Setter Property="LineStroke" Value="#8064a2"/>
      <Setter Property="LineStrokeThickness" Value="4"/>
      <Setter Property="Effect" Value="{StaticResource shadow}"/>
    </Style>
  </vis:Palette>
</UserControl.Resources>
 
<Grid x:Name="LayoutRoot" Background="White"
      util:GridUtils.RowDefinitions=",Auto">
  <vis:Chart x:Name="chart"
              Title="Global Team Sales"
              LegendTitle="Team"
              Palette="{StaticResource palette}"
              local:MultiSeries.XValuePath="Region"
              local:MultiSeries.YValuePath="Sales"
              local:MultiSeries.TitlePath="TeamName"
              local:MultiSeries.ItemsSourcePath="TeamSales"
              local:MultiSeries.Source="{Binding SalesTeams}">
    <local:MultiSeries.ChartTypeProvider>
      <local:SalesTypeProvider/>
    </local:MultiSeries.ChartTypeProvider>
    <vis:Chart.XAxis>
      <vis:CategoryAxis GridlineStyle="{StaticResource collapsedStyle}" />
    </vis:Chart.XAxis>
  </vis:Chart>
 
  <sdk:DataGrid Grid.Row="1"
                ItemsSource="{Binding SalesTeams}"
                AutoGenerateColumns="False">
    <sdk:DataGrid.Columns>
      <sdk:DataGridTextColumn Binding="{Binding TeamName}"
                              Header="Team"
                              IsReadOnly="True"/>
      <sdk:DataGridTextColumn Binding="{Binding [US]}"
                              Header="US"/>
      <sdk:DataGridTextColumn Binding="{Binding [UK]}"
                              Header="UK"/>
      <sdk:DataGridTextColumn Binding="{Binding [Germany]}"
                              Header="Germany"/>
      <sdk:DataGridTextColumn Binding="{Binding [Japan]}"
                              Header="Japan"/>
    </sdk:DataGrid.Columns>
  </sdk:DataGrid>
</Grid>

The result is show below, note that updating the data in the grid causes the chart to update accordingly:

You can download the full sourcecode for this example: VisibloxMultiSeriesBinding.zip

Regards, Colin E.