Blog

Data Binding with Visiblox Charts

Data binding is one of the most powerful features in WPF and Silverlight - allowing you to connect your business logic to a user interface with minimal effort. Visiblox Charts is lightweight as it is built from the ground-up to support data binding. This blog post is a tutorial on how to integrate Visiblox Charts into your application using data binding.

Contents


Overview

In Visiblox Charts, there are two definitions of a series. A data series (defined by the IDataSeries interface) holds data that is to be plotted onto a chart. A chart seriesĀ  (defined by the IChartSeries interface) is responsible for the visual representation of a data series. Setting the DataSeriesĀ  property on a chart series supplies its underlying data. This can be summarised in the following diagram:

It is also possible to bind business objects directly to a chart series, removing the necessity to create a data point for each business object manually. Visiblox supports two methods of data binding:

  • BindableDataSeries
  • AutoSeriesGenerator

I will now cover each of these methods of data binding individually. This post is written against version 2.08 of Visiblox Charts, which at the time of writing is the most recent version. Data binding with Visiblox is possible both in XAML and code-behind. For brevity I will provide all of my sample solutions in XAML-only, although the attached example solution file will also contain code-behind samples. So, let's begin!

The BindableDataSeries class

Introduction

BindableDataSeries is an implementation of the IDataSeries interface. With this class it is not necessary to create DataPoint objects and add them to its internal collection, as is needed with the DataSeries class. Instead, using the ItemsSource property, a collection of business objects is identified. The X and Y values are defined by creating a binding onto properties exposed by the business object type. The BindableDataSeries class will create a BindableDataPoint object for each business object using these X and Y value bindings, which are then plotted onto a chart. If the collection of business objects implements the INotifyCollectionChanged interface, then Bindable Series will also subscribe to the appropriate CollectionChanged event and respond appropriately to insertions, deletions and updates to the collection.

BindableDataSeries exposes the following properties:

  • ItemsSource: Identifies the source collection.
  • XValueBinding: Identifies the property to be used for the X value, as a Binding.
  • YValueBinding: Identifies the property to be used for the Y value, as a Binding.
  • YValueBindings: To be used instead of YValueBinding when working with a multi-valued chart series. Identifies the properties to use as Y values, as a collection of Binding objects.

DataBinding - Standard Approach

Binding to a simple Visiblox chart series, such as a LineSeries is rather simple, both in XAML and Code-Behind. To display a line series plotting the temperature over a day at a weather station, I have set up the following simple data models:

public class HourTemperature
{
  public int Hour { get; set; }
  public int Temperature { get; set; }
}

public class WeatherStationTemperature : ObservableCollection<HourTemperature> { }

I define a collection in this manner so that it can be declared and populated in XAML, which doesn't seem to like generics!

The next thing to do is to define a chart. I then create and populate a collection of business objects. This is linked to the chart by creating a BindableDataSeries, with the ItemsSource property pointing to this collection. I identify a binding to the Date property on DayTemperature as the XValueBinding, and the Temperature property as the YValueBinding property. Once this is done, the BindableDataSeries can be added to the LineSeries and the LineSeries can be added to the Chart. This is possible because internally, the DataContext of the DataSeries is set to the IDataPoint.ModelĀ  property.

<UserControl x:Class="..."
    xmlns:charts="Visiblox.Charts"
    xmlns:local="..."
 
    <UserControl.Resources>
      <local:WeatherStationTemperature x:Key="dataCollection">
        <local:HourTemperature Hour="1" Temperature="20" />
        <local:HourTemperature Hour="2" Temperature="21" />
      </local:WeatherStationTemperature>
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot">
      <charts:Chart x:Name="Chart">
        <charts:Chart.XAxis>
            <charts:LinearAxis />
          </charts:Chart.XAxis>
          <charts:Chart.YAxis>
            <charts:LinearAxis />
        </charts:Chart.YAxis>
        <charts:Chart.Series>
          <charts:LineSeries ShowPoints="true">
            <charts:LineSeries.DataSeries>
              <charts:BindableDataSeries  ItemsSource="{StaticResource dataCollection}"
                                          XValueBinding="{Binding Hour}"
                                          YValueBinding="{Binding Temperature}" />
            </charts:LineSeries.DataSeries>
          </charts:LineSeries>
        </charts:Chart.Series>
      </charts:Chart>
    </Grid>
</UserControl>

Data Binding with Multi-Valued Charts

Multi-valued charts, where each data point has more than one Y value, are commonly used within the financial domain. The premium version of Visiblox Charts includes Candlestick, HLC and HLOC chart series types, which all derive from MultiValuedSeriesBase. In addition, the MultiValuedDataPoint implementation of IDataPoint accepts a Dictionary of Y values, rather than a singular Y value. The key to this dictionary is of type object, and it is typical for a series to declare a string constant for each type of Y value it accepts. So, the CandlestickSeries class has four of such constants: High, Low, Open and Close.

The BindableDataSeries supports multi-valued chart series using the YValueBindings property, instead of the YValueBinding property. This property is an ObservableCollection<YValueBinding> - with one YValueBinding instance for each Y value. The YValueBinding class itself has two properties:

  • YValueKey: Identifies the 'key' in the Y Value dictionary for a MultiValuedDataPoint.
  • Binding: Identifies the property to be used for this particular Y value, as a Binding.

An example use of this binding method is the presenting of stock information as a Candlestick series. A stock has the following data model:

public class Stock
{
  public int Day { get; set; }
  public double High { get; set; }
  public double Low { get; set; }
  public double Open { get; set; }
  public double Close { get; set; }
  public string Isin { get; set; }
}

public class StockHistory : ObservableCollection<Stock> { }

public class YValueBindingsCollection : ObservableCollection<YValueBindings> { }

NOTE: The YValueBindingsCollection has been created so that it can be created and populated in XAML. In the next version of Visiblox, this type will be provided within the Visiblox.Charts namespace, so it will not be necessary to manually create this class.

Data binding to this data model is similar to the temperature example given above, except this time the YValueBindings collection is populated and bound to.

<UserControl x:Class="..."
    xmlns:charts="Visiblox.Charts"
    xmlns:local="..."
 
    <UserControl.Resources>
      <local:StockHistory x:Key="dataCollection">
        <local:Stock Day="1" High="10" Low="8" Open="8" Close="9" Isin="000" />
        <local:Stock Day="2" High="12" Low="7" Open="9" Close="12" Isin="000" />
      </local:StockHistory>
      <local:YValueBindings x:Key="YValues">
        <charts:YValueBinding YValueKey="High" Binding="{Binding High}" />
        <charts:YValueBinding YValueKey="Low" Binding="{Binding Low}" />
        <charts:YValueBinding YValueKey="Open" Binding="{Binding Open}" />
        <charts:YValueBinding YValueKey="Close" Binding="{Binding Close}" />
      </local:YValueBindings>
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot">
      <charts:Chart x:Name="Chart">
        <charts:Chart.XAxis>
          <charts:LinearAxis />
        </charts:Chart.XAxis>
        <charts:Chart.YAxis>
          <charts:LinearAxis />
        </charts:Chart.YAxis>
        <charts:Chart.Series>
          <charts:CandlestickSeries>
            <charts:CandlestickSeries.DataSeries>
              <charts:BindableDataSeries  ItemsSource="{StaticResource dataCollection}"
                                          XValueBinding="{Binding Day}"
                                          YValueBindings="{StaticResource YValues}" />
            </charts:CandlestickSeries.DataSeries>
          </charts:CandlestickSeries>
        </charts:Chart.Series>
      </charts:Chart>
    </Grid>
</UserControl>

Data Binding with Stacked Charts

The final type of chart series supported by Visiblox is a multiple series, which is defined by the IChartMultipleSeries interface. Chart series of this type have their own collection of chart series, to provide support for stacked charts. This represents that the nested charts are dependent upon each other for their visual representations, with the IChartMultipleSeries co-ordinating this rendering. The IChartMultipleSeries does not use its own DataSeries property, instead using the data series on each nested chart series. Using the BindableDataSeries, it is possible to bind to each individual nested chart series within an IChartMultipleSeries.

Stacked charts are commonly-used in sales, and so for this example I will use the following data model:

public class EmployeeSales
{
  public string Employee { get; set; }
  public int ProductsSold { get; set; }
}

public SixMonthEmployeeSales : ObservableCollection<SalesQuarter> { }
<UserControl x:Class="..."
    xmlns:charts="Visiblox.Charts"
    xmlns:local="..."
 
    <UserControl.Resources>
      <local:SixMonthEmployeeSales x:Key="dataCollectionOne">
        <local:EmployeeSales Employee="Sam" ProductsSold="20" />
        <local:EmployeeSales Employee="Jesse" ProductsSold="25" />
      </local:SixMonthEmployeeSales>
 
      <local:SixMonthEmployeeSales x:Key="dataCollectionTwo">
        <local:EmployeeSales Employee="Sam" ProductsSold="19" />
        <local:EmployeeSales Employee="Jesse" ProductsSold="17" />
      </local:SixMonthEmployeeSales>
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot">
      <charts:Chart x:Name="Chart">
        <charts:Chart.Series>
          <charts:StackedLineSeries>
            <charts:StackedLineSeries.Series>
              <!-- Line Series One -->
              <charts:LineSeries>
                <charts:LineSeries.DataSeries>
                  <charts:BindableDataSeries ItemsSource="{StaticResource dataCollectionOne}"
                                              XValueBinding="{Binding Employee}"
                                              YValueBinding="{Binding ProductsSold}" />
                </charts:LineSeries.DataSeries>
              </charts:LineSeries>
              <!-- Line Series Two -->
              <charts:LineSeries>
                <charts:LineSeries.DataSeries>
                  <charts:BindableDataSeries ItemsSource="{StaticResource dataCollectionTwo}"
                                              XValueBinding="{Binding Employee}"
                                              YValueBinding="{Binding ProductsSold}" />
                </charts:LineSeries.DataSeries>
              </charts:LineSeries>
            </charts:StackedLineSeries.Series>
          </charts:StackedLineSeries>
        </charts:Chart.Series>
      </charts:Chart>
    </Grid>
</UseControl>

Whilst this solution may be acceptable in situations with a small amount of data, it can become rather verbose with larger amounts of data. This can also place constraints upon your data model, in that the nested chart series must be available as its own resource. This cannot always be the case, as this itself can introduce scaling problems with an application. For this reason we began looking at ways in which we could programmatically create BindableDataSeries instances, rather than having the end-user doing this work. Enter the AutoSeriesGenerator.

The AutoSeriesGenerator class

Introduction

The idea behind the AutoSeriesGenerator class is that attached properties are used to specify a source of data, bindings to X and Y properties (similarly to the BindableDataSeries approach), and what type of chart series to create. AutoSeriesGenerator then instantiates as many chart series as needed, and sets up a BindableDataSeries for each of these chart series. AutoSeriesGenerator supports all chart series within the Visiblox.Charts namespace-out-of-the-box, and is primarily suited for stacked series, or for when you want to minimize the amount of code-behind.

AutoSeriesGenerator - Standard Approach

AutoSeriesGenerator exposes the following attached properties:

  • ChartSeriesProvider: Identifies, through the IChartSeriesProvider interface, what type of IChartSeries to instantiate.
  • ItemsSource: Identifies the source collection of data.
  • XValuePath: Identifies the property to be used for the X value, as a string path.
  • YValuePath: Identifies the property to be used for the Y value, as a string path.
  • YValuePaths: To be used instead of YValuePath when working with a multi-valued chart series. Identifies the properties to use as Y values, as a collection of string paths.

The ChartSeriesProvider attached property is of type IChartSeriesProvider. This interface has only one method:

IChartSeries CreateSeries(object targetParent, object boundObject);

By default, Visiblox Charts uses a TypeConverter so that instead of passing an implementation of this type as a value to the property in XAML, you can instead pass a string identifying the name of the chart series to create. This is a similar approach to how colors can be specified as strings in XAML, rather than as Brush instances. This is then passed to the default implementation of IChartSeriesProvider, DefaultChartSeriesProvider, to instantiate the chart series. DefaultChartSeriesProvider looks within the Visiblox.Charts namespace for the correct chart series to instantiate. In code-behind, the ChartSeriesProviderProperty is set to a new instance of DefaultChartSeriesProvider. This takes one argument in its constructor - a string identifying the name of the chart series type.

The XValuePath and YValuePath properties are very similar to the XValueBinding and YValueBinding properties of the BindableDataSeries, but, rather than Bindings, they are instead simply string paths to the properties to use. String paths are supplied here as these are used by AutoSeriesGenerator to automatically create Binding objects on your behalf.

The ItemsSource property will identify a collection of business object collections. Each individual collection becomes its own IChartSeries, and the business objects in that collection become the data points of that chart series. This can be best visualised with an example. Returning to the temperature example from above, now imagine that the temperature is being measured at multiple weather stations, as modeled by this object:

public class WeatherStationsData : ObservableCollection<WeatherStationTemperature> { }

A WeatherStationsData resource is declared and populated in XAML, similarly to the examples above. Now, however, all we need to do to create chart series is to declare a Chart item, and add some attached properties. There is no need to declare a chart series or a data series! So, to get a line series, the ChartSeriesProvider property is set to "LineSeries". The ItemsSource property is supplied with the WeaterStationsData resource. As we want the "Hour" property to be the X value for a data point, and the "Temperature" property to be the Y value, the XValuePath and YValuePaths are set accordingly:

<UserControl x:Class="..."
    xmlns:charts="Visiblox.Charts"
    xmlns:local="..."
 
    <UserControl.Resources>
       <local:WeatherStationsData x:Key="dataCollection">
            <local:WeatherStationTemperature>
                <local:HourTemperature Hour="1" Temperature="10" />
                <local:HourTemperature Hour="2" Temperature="14" />
            </local:WeatherStationTemperature>
            <local:WeatherStationTemperature>
                <local:HourTemperature Hour="1" Temperature="20" />
                <local:HourTemperature Hour="2" Temperature="19" />
            </local:WeatherStationTemperature>
        </local:WeatherStationsData>
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot">
      <charts:Chart x:Name="Chart"
                    charts:AutoSeriesGenerator.XValuePath="Hour"
                    charts:AutoSeriesGenerator.YValuePath="Temperature"
                    charts:AutoSeriesGenerator.ChartSeriesProvider="LineSeries"
                    charts:AutoSeriesGenerator.ItemsSource="{StaticResource dataCollection}">
                    <charts:Chart.XAxis>
                <charts:LinearAxis />
            </charts:Chart.XAxis>
            <charts:Chart.YAxis>
                <charts:LinearAxis />
            </charts:Chart.YAxis>
        </charts:Chart>
    </Grid>
</UserControl>

AutoSeriesGenerator with Multi-Valued Charts

The AutoSeriesGenerator class also supports multi-valued chart series, such as Candlestick Series, in a very similar way to BindableDataSeries. Recall that BindableDataSeries has a YValueBindings property in addition to the YValueBinding property, where you can specify each individual Y binding. AutoSeriesGenerator has a similar principle - the YValuePaths property accepts a YValuePathsCollection collection. This is actually a List<YValuePath> at heart.

The YValuePath object has two properties:

  • Key: Y values in MultiValueSeriesBase are identified by a Key. This string represents the key to a particular Y value.
  • Path: The string path to the property on the business object to be used for this particular Y value.

Let's return to the stocks example I used in the BindableDataSeries Multi-Valued example. Firstly, I want to display not just a history of a particular stock, but a history of multiple stocks. I can model this as a collection of StockHistory objects:

public class StockPortfolio : ObservableCollection<StockHistory> { }

So, similarly with the simple example above, the StockPortfolio resource is set up and populated, and then a Chart item is declared. A CandlestickSeries type is created by setting ChartSeriesProvider to that type. A YValuePathsCollection resource is also declared, in which the Y value types of the Candlestick Series are bound to the properties of the Stock object. So, instead of setting the YValuePath property, the YValuePathsproperty is set.

<UserControl x:Class="..."
    xmlns:charts="Visiblox.Charts"
    xmlns:local="..."
 
    <UserControl.Resources>
 
       <local:StockPortfolio x:Key="dataCollection">
            <local:StockHistory>
                <local:Stock Day="1" High="7" Low="2" Open="3" Close="6" Isin="000" />
                <local:Stock Day="2" High="9" Low="5" Open="6" Close="8" Isin="000" />
            </local:StockHistory>
            <local:StockHistory>
                <local:Stock Day="1" High="20" Low="14" Open="16" Close="19" Isin="001" />
                <local:Stock Day="2" High="21" Low="15" Open="19" Close="15" Isin="001" />
            </local:StockHistory>
        </local:StockPortfolio>
 
      <charts:YValuePathsCollection x:Key="yValues">
        <charts:YValuePath Key="High" Path="High" />
        <charts:YValuePath Key="Low" Path="Low" />
        <charts:YValuePath Key="Open" Path="Open" />
        <charts:YValuePath Key="Close" Path="Close" />
      </charts:YValuePathsCollection>
 
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot">
      <charts:Chart x:Name="Chart"
                    charts:AutoSeriesGenerator.XValuePath="Day"
                    charts:AutoSeriesGenerator.YValuePaths="{StaticResource yValues}"
                    charts:AutoSeriesGenerator.ChartSeriesProvider="CandlestickSeries"
                    charts:AutoSeriesGenerator.ItemsSource="{StaticResource dataCollection}">
                    <charts:Chart.XAxis>
                <charts:LinearAxis />
            </charts:Chart.XAxis>
            <charts:Chart.YAxis>
                <charts:LinearAxis />
            </charts:Chart.YAxis>
        </charts:Chart>
    </Grid>
</UserControl>

AutoSeriesGenerator with Stacked Charts

Finally, the AutoSeriesGenerator class also supports IChartMultipleSeries chart types- so is capable of producing stacked charts. In this case, the collection of business model collections represents a single stacked series - with each collection of business objects becoming an individual series within the 'stack'. Each Visiblox stacked series can only have one type of IChartSeries - so a StackedLineSeries may only contain LineSeries chart series.

Returning to the sales example from above, I have created a collection to nest instances of SixMonthEmployeeSales:

public class StoreSalesRecords : ObservableCollection<SixMonthSales> { }

So, within XAML, a StoreSalesRecords resource is set up and populated. Following that, a Chart item is created, and the XValuePath and YValuePath properties are set to the Employee and ProductsSold properties accordingly. Then, the ChartSeriesProvider property is set to StackedLineSeries. This setup is exactly the same pattern as the simple usage of AutoSeriesGenerator demonstrated above!

When the AutoSeriesGenerator creates chart series, it will automatically create a StackedLineSeries, and for each item in the StoreSalesRecords collection, it will create a LineSeries (with the appropriate bindings), and add this to the Seriescollection on the stacked series.

<UserControl x:Class="..."
    xmlns:charts="Visiblox.Charts"
    xmlns:local="..."
 
    <UserControl.Resources>
        <local:StoreSalesRecords x:Key="dataCollection">
            <local:SixMonthEmployeeSales>
                <local:EmployeeSales Employee="Sam" ProductsSold="20" />
                <local:EmployeeSales Employee="Jesse" ProductsSold="25" />
            </local:SixMonthEmployeeSales>
 
            <local:SixMonthEmployeeSales>
                <local:EmployeeSales Employee="Sam" ProductsSold="19" />
                <local:EmployeeSales Employee="Jesse" ProductsSold="17" />
            </local:SixMonthEmployeeSales>
        </local:StoreSalesRecords>
    </UserControl.Resources>
 
    <Grid x:Name="LayoutRoot">
        <charts:Chart x:Name="Chart"
                      Title="Auto Series Generator - Stacked Series Binding in XAML"
                      charts:AutoSeriesGenerator.XValuePath="Employee"
                      charts:AutoSeriesGenerator.YValuePath="ProductsSold"
                      charts:AutoSeriesGenerator.ChartSeriesProvider="StackedLineSeries"
                      charts:AutoSeriesGenerator.ItemsSource="{StaticResource dataCollection}">
            <charts:Chart.XAxis>
                <charts:CategoryAxis />
            </charts:Chart.XAxis>
            <charts:Chart.YAxis>
                <charts:LinearAxis />
            </charts:Chart.YAxis>
        </charts:Chart>
    </Grid>
</UserControl>

I'm sure that you can agree using AutoSeriesGenerator is a much more flexible solution than using the BindableDataSeries - especially when creating stacked series!

Summary

This post has introduced the two ways of data binding using Visiblox Charts - either by directly creating a BindableDataSeries, or letting Visiblox do all the hard work using the AutoSeriesGenerator class. Both methods support data binding to the three types of Visiblox chart series: a single series, a multiple series, and a multi-valued series. BindableDataSeries works out-of-the-box with your own custom chart series. In addition, the AutoSeriesGenerator class, through its IChartSeriesProvider interface, can also support custom chart series. This gives Visiblox Charts the ability to support data binding in high and low-data scenarios, with minimal code.

All of the sample code demonstrated throughout this tutorial is contained in a sample solution file, which you can download here. This includes the code-only versions of these examples too, as promised! Note that you will need the Premium Visiblox dll to run the MultiValuedChartSeries examples - although the Free Visiblox dll should suffice for all of the other examples - which you can get from the Visiblox downloads page. In a future blog post, we will explore the data binding capabilities of Visiblox Charts for use in MVVM applications.

Comments

hi - tx for the excellent product. just a qik question i am trying to bind multiple series data from a streaming service to a chart. I use the model-view-model approach and place each series into a dictionary of observable points, to which the chart is bound. Although this works its a somewhat slow (many updates per second) and reading the above i was wondering which binding to use. The questions is how to render many dynamically populated series using MVVM without performance degradation? Many thanks for your response to this...regards Craig
 
Posted by Craig
Not able to download the example code. Get a HTTP 404 error. Can you guys please fix this? I am working on a similar biding problem, will be helpful if I can get this example code.
 
Posted by Kaushik
Sorry about that - the examples download has been fixed and should work now.
 
Posted by Partha

Post a comment