Financial Data Displays using FlexGrid

One of the more popular demos we have for C1FlexGrid control is the financial demo shown below. It shows a simulated live data feed of stock quotes for over 3,000 companies all in one flashy grid. Aside from the FlexGrid control being used, there are two other interesting aspects to this sample that may be useful for building other financial types of applications. One is the updating mechanism which can trigger numerous updates less than a second apart, and the second aspect is the spark line controls that show brief trends in price history. In this blog post I break down the sample and describe how these aspects work.

The FinancialDataList Class

First we’ll look at the FinancialDataList class. This class is responsible for storing the collection of stock symbols, their values and updating them on a regular frequency. It's a List of type FinancialData, which represents a single company and it even stores up to the 5 most recent values for each of its properties. This allows us to visualize historic data in a small sparkline. FinancialData also implements INotifyPropertyChanged, which means the UI will be notified of updates to our data values. In the sample we load a static list of stock symbols from a text file, and then we simulate the market by randomly generating values in the FinancialDataList class. This is where you can extend the sample by replacing the random values with real-time values from some service. The key to updating at a set frequency is the Timer control used within the FinancialDataList class. In the sample we use the ComponentOne utility Timer (included in the C1.WPF.FlexGrid dll), but you could use any regular timer class. Below is the complete FinancialDataList class with the timer functionality.


public class FinancialDataList : List<FinancialData>  
{  
    // fields  
    C1.Util.Timer _timer;  
    Random _rnd = new Random(0);  

    // object model  
    public int UpdateInterval  
    {  
        get { return (int)_timer.Interval.TotalMilliseconds; }  
        set { _timer.Interval = TimeSpan.FromMilliseconds(value); }  
    }  
    public int BatchSize { get; set; }  
    public bool AutoUpdate  
    {  
        get { return _timer.IsEnabled; }  
        set { _timer.IsEnabled = value; }  
    }  

    // ctor  
    public FinancialDataList()  
    {  
        _timer = new C1.Util.Timer();  
        \_timer.Tick += \_timer_Tick;  
        UpdateInterval = 100;  
        BatchSize = 100;  
    }  

    void \_timer\_Tick(object sender, EventArgs e)  
    {  
        if (this.Count > 0)  
        {  
            for (int i = 0; i < BatchSize; i++)  
            {  
                int index = _rnd.Next() % this.Count;  
                var data = this[index];  
                data.Bid = data.Bid * (decimal)(1 + (_rnd.NextDouble() * .11 - .05));  
                data.Ask = data.Ask * (decimal)(1 + (_rnd.NextDouble() * .11 - .05));  
                data.BidSize = _rnd.Next(10, 1000);  
                data.AskSize = _rnd.Next(10, 1000);  
                var sale = (data.Ask + data.Bid) / 2;  
                data.LastSale = sale;  
                data.LastSize = (data.AskSize + data.BidSize) / 2;  
                data.QuoteTime = DateTime.Now;  
                data.TradeTime = DateTime.Now.AddSeconds(-_rnd.Next(0, 60));  
            }  
        }  
    }  
}  

Then in our view model or code-behind we create a CollectionView out of our FinancialDataList class (this is possible because FinancialDataList is of type List). Then we set the CollectionView to the ItemsSource on the C1FlexGrid control and we are bound! The FlexGrid will show live data as it updates because our FinancialData class implements INotifyPropertyChanged and our FinancialDataList class has a timer that updates these values on each tick.


FinancialDataList _financialData;  

void PopulateFinancialGrid()  
{  
    _financialData = FinancialData.GetFinancialData();  
    var view = new ListCollectionView(_financialData);  
    _flexFinancial.ItemsSource = view;  
    ...  
}  

The static GetFinancialData method simply returns an instance of FinancialDataList after populating it with some initial values. This completes the data side of things, and next we’ll look at how we visualize our spark lines in C1FlexGrid using a Cell Factory.

The Financial Cell Factory

A while ago I blogged about creating custom cells in the C1FlexGrid control for XAML. There are two approaches: in XAML using the CellTemplate property, or in code using a CellFactory. It’s your call as to which you prefer and which is better for your application. The Financial demo uses a Cell Factory to render colored symbols and sparklines within the cells, so let’s look at that approach. To create custom cells (in code), you have to create a class that implements the ICellFactory interface and assign this class to the grid's CellFactory property. Like custom columns, custom ICellFactory classes can be highly specialized and application-specific, or they can be general, reusable, configurable classes. Below is the complete FinancialCellFactory class.


using C1.WPF.FlexGrid;  

public class FinancialCellFactory : CellFactory  
{  
    static Thickness _thicknessEmpty = new Thickness(0);  

    // bind cell to ticker  
    public override void CreateCellContent(C1FlexGrid grid, Border bdr, CellRange range)  
    {  
        // create binding for this cell  
        var r = grid.Rows[range.Row];  
        var c = grid.Columns[range.Column];  
        var pi = c.PropertyInfo;  
        if (r.DataItem is FinancialData &&  
           (pi.Name == "LastSale" || pi.Name == "Bid" || pi.Name == "Ask"))  
        {  
            // create stock ticker cell  
            StockTicker ticker = new StockTicker();  
            bdr.Child = ticker;  
            bdr.Padding = _thicknessEmpty;  

            // to show sparklines  
            ticker.Tag = r.DataItem;  
            ticker.BindingSource = pi.Name;  

            // traditional binding  
            var binding = new Binding(pi.Name);  
            binding.Source = r.DataItem;  
            binding.Mode = BindingMode.OneWay;  
            ticker.SetBinding(StockTicker.ValueProperty, binding);  
        }  
        else  
        {  
            // use default implementation  
            base.CreateCellContent(grid, bdr, range);  
        }  
    }  

    // override horizontal alignment to make ticker cell stretch and fill the column width  
    public override void ApplyCellStyles(C1FlexGrid grid, CellType cellType, CellRange range, Border bdr)  
    {  
        var ticker = bdr.Child as StockTicker;  
        if (ticker != null)  
        {  
            ticker.HorizontalAlignment = HorizontalAlignment.Stretch;  
        }  
    }  
}  

We assign an instance of this to our FlexGrid by setting the CellFactory class.


_flexFinancial.CellFactory = new FinancialCellFactory();  

The first thing to do inside your cell factory is override the CreateCellContent method. This is fired for every cell as it’s created. So the next thing you need to do is check against the row or column information. For example, we only want to render our spark lines and symbols for three columns: LastSale, Bid and Ask. Lastly, we set content to the cell. In this case we are creating a StockTicker UserControl and binding it to our data values. We set this content to the cell by accessing the Border element parameter and we are done. Each cell in FlexGrid starts as an empty Border element. Think of it like a blank canvas you can fill with whatever you want! The StockTicker.xaml UserControl is defined as below.


<UserControl x:Class="Financial.StockTicker"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >  
    <UserControl.Resources>  
        <Storyboard x:Key="_sbFlash" >  
            <ColorAnimation  
                Storyboard.TargetName="_root"  
                Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)"  
                To="Transparent" Duration="0:0:1" />  
        </Storyboard>  
    </UserControl.Resources>  
    <Grid x:Name="_root" Background="Transparent" >  
        <Grid.ColumnDefinitions>  
            <ColumnDefinition Width="*" />  
            <ColumnDefinition Width="35" />  
            <ColumnDefinition Width="Auto" />  
            <ColumnDefinition Width="Auto" />  
        </Grid.ColumnDefinitions>  
        <Polyline Grid.Column="3"  
            x:Name="_sparkLine"  
            Points="0,1 .5,0 1,1"  
            Margin="3"  
            Width="24" Height="8"  
            Stretch="Fill"  
            Stroke="Blue" >  
            <Polyline.RenderTransform>  
                <ScaleTransform ScaleY="-1" CenterY="4" />  
            </Polyline.RenderTransform>  
        </Polyline>  
        <Polygon Grid.Column="2"  
            x:Name="_arrow"  
            Points="0,1 .5,0 1,1"  
            Margin="3"  
            Width="12" Height="8"  
            Stretch="Fill"  
            Fill="Green" >  
            <Polygon.RenderTransform>  
                <ScaleTransform x:Name="_stArrow" ScaleY="+1" CenterY="4" />  
            </Polygon.RenderTransform>  
        </Polygon>  
        <TextBlock  
            x:Name="_txtValue"  
            Text="0.00"  
            TextAlignment="Right"  
            VerticalAlignment="Center" />  
        <TextBlock Grid.Column="1"  
            x:Name="_txtChange"  
            Text="0.0%"  
            TextAlignment="Right"  
            Opacity="0.5"  
            FontSize="8.5"  
            VerticalAlignment="Center" >  
        </TextBlock>  
    </Grid>  
</UserControl>  

It consists of four elements: TextBlock for the value, another TextBlock for the % change, a Polygon (triangle) to show the change direction, and a Polyline which is our spark line. Updates to these elements are handled in code when the value is changed. The code below shows how we set the spark line (our Polyline element named _sparkLine) to visualize the historical data points. Once we obtain the FinancialData item from sender, we can add each historical point to the Polyline.Points collection. It’s that simple.


private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)  
{  
    var ticker = d as StockTicker;  
    var value = (double)e.NewValue;  
    var oldValue = (double)e.OldValue;  

...  

    // get historical data  
    var data = ticker.Tag as FinancialData;  
    var list = data.GetHistory(ticker.BindingSource);  
    if (list != null && list.Count > 1)  
    {  
        oldValue = (double)list[list.Count - 2];  
    }  

    // update sparkline  
    if (list != null)  
    {  
        var points = ticker._sparkLine.Points;  
        points.Clear();  
        for (int x = 0; x < list.Count; x++)  
        {  
            points.Add(new Point(x, (double)list[x]));  
        }  
    }  

...  
}  

You can download the complete demo source code below and check out the rest of the code. Download Financial_CS Download Financial_VB

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus