Silverlight 2 Custom Stock Charts With Silverlight Toolkit

Since the Silverlight Toolkit came out, I've been trying to find a couple of hours to start experimenting with the incredible number and quality of new controls that are now available. Well, I had some time Sunday afternoon, and I thought I'd take a crack at a custom Stock chart using the LineSeries chart from the Toolkit. This isn't feature-complete, but I think it's a pretty good start.

I started out with a Silverlight-enabled WCF Service that would allow me to use some previous code I'd written to accept a Stock (or index) Symbol, a beginning date and an ending date, and which would call out to the appropriate Yahoo Finance historical data url to get the csv- formatted list of historical quotes. This older code massaged the list into a DataTable. For Silverlight, I created a StockData class and simply iterated over the list of DataRows, turning it into a List<T> of type StockData, which is perfect for Silverlight to consume on the client side:

public class StockData
    {
         //Date    Open    High    Low    Close    Volume    Adj Close
        public DateTime Date { get; set; }
        public double Open { get; set;   }
         public double High { get; set; }
        public double Low { get; set;}
        public double Close { get; set; }
        public long Volume { get; set; }
        public double AdjClose { get; set; }
        public StockData() {}
        public StockData( DateTime date, double open, double high, double low,
                                          double close, long volume, double adjClose)
        {
            Date = date;
            Open = open;
            High = high;
            Low = low;
            Close = close;
            Volume = volume;
            AdjClose = adjClose;
        }

    }



When this is returned to the Silverlight calling application, it is exposed as an ObservableCollection which makes it easy to databind to the Chart control. Here is the "guts" of the Downloader class:

 
using System;
using System.Data;
using System.Net;

namespace StockCharts.Web
{
    public class Downloader
    {
         private string urlTemplate =
             @"http://ichart.finance.yahoo.com/table.csv?s=[symbol]&a=" +
            "[startMonth]&b=[startDay]&c=[startYear]&d=[endMonth]&e=" +
            "[endDay]&f=[endYear]&g=d&ignore=.csv";

        public DataTable UpdateSymbol(string symbol, DateTime? startDate, DateTime? endDate)
         {
             if (!endDate.HasValue) endDate = DateTime.Now;
             if (!startDate.HasValue) startDate = DateTime.Now.AddYears(-2);
             if (String.IsNullOrEmpty(symbol))
                 throw new ArgumentException("Symbol invalid: " + symbol);
             // NOTE: Yahoo's scheme uses a month number 1 less than actual e.g. Jan. ="0"
            int strtMo = startDate.Value.Month - 1;
             string startMonth = strtMo.ToString();
             string startDay = startDate.Value.Day.ToString();
             string startYear = startDate.Value.Year.ToString();

             int endMo = endDate.Value.Month - 1;
             string endMonth = endMo.ToString();
             string endDay = endDate.Value.Day.ToString();
             string endYear = endDate.Value.Year.ToString();

            urlTemplate = urlTemplate.Replace("[symbol]", symbol);

            urlTemplate = urlTemplate.Replace("[startMonth]", startMonth);
            urlTemplate = urlTemplate.Replace("[startDay]", startDay);
            urlTemplate = urlTemplate.Replace("[startYear]", startYear);

            urlTemplate = urlTemplate.Replace("[endMonth]", endMonth);
            urlTemplate = urlTemplate.Replace("[endDay]", endDay);
            urlTemplate = urlTemplate.Replace("[endYear]", endYear);
             string history = String.Empty;
            var wc = new WebClient();
             try
            {
                history = wc.DownloadString(urlTemplate);
             }
             catch (WebException wex)
             {
                 throw;
            }
            finally
            {
                wc.Dispose();
            }
            var dt = new DataTable();
             // trim off unused characters from end of line
            history = history.Replace("\r", "");
            // split to array on end of line
            string[] rows = history.Split('\n');
            // split to colums
            string[] colNames = rows[0].Split(',');
            // add the columns to the DataTable
            foreach (string colName in colNames)
                dt.Columns.Add(colName);
            DataRow row = null;
            string[] rowValues;
             object[] rowItems;
             // split the rows
            for (int i = rows.Length - 1; i > 0; i--)
            {
                rowValues = rows[i].Split(',');
                row = dt.NewRow();
                rowItems = ConvertStringArrayToObjectArray(rowValues);
                 if (rowItems[0] != null && (string) rowItems[0] != "")
                {
                    row.ItemArray = rowItems;
                     dt.Rows.Add(row);
                 }
             }
             return dt;
        }


        private object[] ConvertStringArrayToObjectArray(string[] input)
        {
             int elements = input.Length;
            var objArray = new object[elements];
            input.CopyTo(objArray, 0);
             return objArray;
        }
     }
}

For most developers, the above should be self-explanatory. Note the use of nullable DateTime parameters to allow for a default chart span. Next, I need an OperationContract in my service that makes a call to the above method:

[OperationContract]
        public List<StockData> GetStockData(string symbol, DateTime startDate, DateTime endDate)
        {
            List<StockData> stockList = new List<StockData>();
            Downloader d = new Downloader();
            DataTable dt=  d.UpdateSymbol(symbol, startDate, endDate);
             // Date    Open    High    Low    Close    Volume    Adj Close
            foreach(DataRow row in dt.Rows)
            {
                DateTime date = Convert.ToDateTime(row["Date"]);
                 double open = Convert.ToDouble(row["Open"]);
                 double high = Convert.ToDouble(row["High"]);
                 double low = Convert.ToDouble(row["Low"]);
                 double close = Convert.ToDouble(row["Close"]);
                 long volume = Convert.ToInt64(row["Volume"]);
                 double adjClose = Convert.ToDouble(row["Adj Close"]);
                StockData sd = new StockData(date, open, high, low, close, volume, adjClose);
                 stockList.Add(sd);
            }
            return stockList;
        }


Finally, here is the XAML Markup in my Page, and then, following that, the codebehind code in Page.xaml.cs:

<UserControl xmlns:charting="clr-namespace:Microsoft.Windows.Controls.DataVisualization.Charting;assembly=Microsoft.Windows.Controls.DataVisualization"  x:Class="StockCharts.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="600" Height="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <charting:Chart x:Name="Chart1" Width="750" Height="350" VerticalAlignment="Top" >

            <charting:Chart.Series>

                <charting:LineSeries ItemsSource="{Binding}"

                              DependentValueBinding="{Binding Close}"

                              IndependentValueBinding="{Binding Date}" >

                    <charting:LineSeries.DataPointStyle>
                        <Style TargetType="Control">
                            <Setter Property="Visibility" Value="Collapsed"/>
                        </Style>
                    </charting:LineSeries.DataPointStyle>
                 </charting:LineSeries>

            </charting:Chart.Series>
                
        </charting:Chart>
        <TextBox x:Name="text1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Text="YHOO"></TextBox>
        <Controls:Button xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows"  Width="200"  Height="24" Content="Chart It!" VerticalAlignment="Bottom" Click="Button_Click"   ></Controls:Button>
         

    </Grid>
</UserControl>

 

And here is the codebehind that does the work:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using StockCharts.StockService;

namespace StockCharts
{
    public partial class Page : UserControl
    {
         public Page()
        {
             InitializeComponent();
        }

         void c_GetStockDataCompleted(object sender, GetStockDataCompletedEventArgs e)
        {  
            ObservableCollection stockDatas = e.Result;
            Chart1.LegendTitle = null;
            Chart1.Title = text1.Text;
            Dispatcher.BeginInvoke(() =>  this.Chart1.DataContext= stockDatas);
         }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            string symbol = this.text1.Text;
            StockService.stocksClient c = new stocksClient();
            c.GetStockDataCompleted += new EventHandler(c_GetStockDataCompleted);
            c.GetStockDataAsync(symbol, DateTime.Now.AddYears(-2), DateTime.Now);
         }
    }
}

 

The result, when you fill in a stock or index symbol and press the button, looks like this:


Obviously this is just a start - If I had time and really wanted to make a top-rate stock charting app in Silverlight, I'd add a lower chart for the volume, and a selection of technical analysis indicators such as moving averages, stochastics, Relative Strength, and more.  I'd probably also add an autocomplete control for selecting the desired stock symbol from a typeahead dropdown list.

And of course, if I really wanted to get "flashy" (no pun intended) I'd think about how I can animate the chart as it appears.

But as a proof-of-concept, I can safely say that it doesn't take much to create really stunning chart graphics in Silverlight- no matter what the business application may be.

Microsoft's stock chart doesn't look very pretty right now, but then neither does anybody else's. It could be a while before we bottom out and have a new bull market -- a long while, unfortunately. If you really want to see a picture of a disaster, type in FNM in the symbol textbox. That's -- you guessed it -- Fannie Mae.

This stock chart Silverlight Application should run fine in Internet Explorer, Firefox, or Google Chrome (with the Dev Channel enabled).

Silverlight has matured remarkably over the last year or so, and has become a quite exciting RIA platform for developers. Stock charting and analysis applications are just the tip of the iceberg.

You can download the complete Visual Studio 2008 solution here.

By Peter Bromberg   Popularity  (9719 Views)