Blog

Back to Blog

Getting Started with ShinobiControls and MonoTouch – A Stock Charting App

Posted on 8 Aug 2012 Written by Colin Eberhardt

Last week we announced the release of the ShinobiControls MonoTouch Bindings (beta), which allow you to use our charts and grids within MonoTouch (C#) applications. In this blog post I’ll show just how easy it is to create a simple application that displays the stocks in the UK FTSE-100 index and renders a chart for each.

Mono Touch Stock Chart

In order to get started with MonoTouch development, you need a Mac, Xcode and MonoDevelop (the graphical IDE for MonoTouch development). You can find  information regarding the required tools and where to find them on the Xamarin website. You’ll also need a copy ShinobiControls, which are available as a trial download.

Displaying the List of Stocks

Before we get onto the fun part (i.e. charts!), we’ll first need to render a list of stocks from the FTSE-100. This view is a classic, list-based layout, which is rendered using a UITableView.

The items within the list are represented by simple data objects:

// a data item that represents a single stock
private class StockItem
{
  public StockItem(string symbol)
  {
    Price = double.NaN;
    Symbol = symbol;
  }
      
  public string Symbol { get; private set; }

  public double Price { get; set; }
      
  public double Change { get; set; }
}

The view controller creates a collection of StockItems, one for each of the FTSE-100 constituents, within its constructor.

In order to render items within a UITableView you need to create a data-source which provides the cell (i.e. the UI element) for each row of the table. This is covered in great detail on the Xamarin “Working with Tables and Cells” tutorial pages, so I will not cover it here.

In order to create a custom cell for the table, I followed the approach detailed in  Simon Guindon’s custom UITableViewCell tutorial, where you create a view controller (with associated xib file), replacing the ‘root’ UIView with a UITableViewCell. This allows you to design the cell using Interface Builder, whilst still allowing MonoTouch to generate the C# outlets for the various elements that are added to the interface:

Table View Cell Design

As described in Simon’s tutorial, the view controller is used to ‘host’ the cell and provide methods for changing its UI state. The UITableView datasource recycles cells, rather than view controllers, so you need to manually maintain references to the view controller that host each cell. The datasource implementation is shown below:

// a table source that renders our list of stocks
private class TableSource : UITableViewSource
{
  private static int _sequence;
  private static readonly string _cellIdentifier = "TableCell";
      
  private Dictionary<int, StockItemTableCellView> _cellControllers;      
  private List<StockItem> _tableItems;
  private UINavigationController _navigationController;
 
  public TableSource (UINavigationController navigationController, List<StockItem> items)
  {
    _tableItems = items;
    _navigationController = navigationController;
    _cellControllers = new Dictionary<int, StockItemTableCellView>();
  }
       
  public override int RowsInSection (UITableView tableview, int section)
  {
    return _tableItems.Count;
  }
 
  public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
  {
    UITableViewCell cell = tableView.DequeueReusableCell (_cellIdentifier);
        
    // use the method described in this blog post to wrap a cell in a view controller
    // http://simon.nureality.ca/?p=91
    StockItemTableCellView cellController = null;        
    if (cell == null) {
      cellController = new StockItemTableCellView ();
      NSBundle.MainBundle.LoadNib ("StockItemTableCellView", cellController, null);
      cell = cellController.Cell;
          
      cell.Tag = _sequence++;
      _cellControllers.Add (cell.Tag, cellController);
    } else {
      cellController = _cellControllers [cell.Tag];
    }
        
    // set the state of this cell
    var stockDataItem = _tableItems [indexPath.Row];
    cellController.Symbol = stockDataItem.Symbol;
    cellController.Price = stockDataItem.Price; 
        
    if (stockDataItem.Change > 0.0) {
      cellController.Direction = PriceDirection.Rising;
    } else if (stockDataItem.Change < 0.0) {
      cellController.Direction = PriceDirection.Falling;
    } else {
      cellController.Direction = PriceDirection.NonMover;
    }
    return cell;
  }      
}

The code above will render our list of stocks, but we still need to find a way to obtain their prices. Thankfully there are a few online services that can be used for obtaining stock quotes. One of the simplest and most popular is the Yahoo Finance API, which returns a list of quotes in CSV format. The code below performs a single request to retrieve the prices for all of our stocks, parses the returned CSV, updates the state of the StockItem instance and forces the table to re-render:

private void FetchQuotes ()
{
  string url = "http://finance.yahoo.com/d/quotes.csv?f=sac1k&s=";      
  url += string.Join ("+", _stocks.Select (s => s.Symbol));
      
  WebClient client = new WebClient ();
  client.DownloadStringCompleted += (s,e) => ParseStockQuotes(e.Result);     
  client.DownloadStringAsync (new Uri (url));
}
    
    
private void ParseStockQuotes (string quotesCSV)
{
  // split each line
  var lines = quotesCSV.Split ('\n');
  foreach (var line in lines) {
    // split each item within the line
    var components = line.Split (',');
    if (components.Length > 1) {
      try {
        // extract the symbol, price and change
        string symbol = components [0].Replace ("\"", "");
        double quote = double.Parse (components [1]);
        double change = double.Parse (components [2]);
            
        // locate the respective data item and update its state
        var stockItem = _stocks.SingleOrDefault (s => s.Symbol == symbol);
        if (stockItem != null) {
          stockItem.Price = quote;
          stockItem.Change = change;
        }
      } catch {
      }
    }
  }
      
  // re-render the list
  stockListTable.ReloadData ();
}

Navigating to the chart for each stock is achieved by implementing the RowSelected method on the datasource (MonoTouch combines the datasource and delegate into a single class), to detect when a user has tapped on a row. The implementation of this method, creates the chart view controller and initiates the navigation as detailed in a previous tutorial I wrote.

Rendering a Chart

Enough of UITableViews – it’s time for the fun to begin!

Now before we dive into creating the chart itself, we need to correctly configure our MonoTouch project. By default MonoDevelop select the ARMv6 architecture, which is for devices supporting iOS 4.3 and below. ShinobiControls uses iOS 4.3.3 and above (i.e. iPhone 3GS and above, and all iPads). In order to deploy to a real device you must configure the project to target ARMv7 processors:

Architecture

Now we’re good to go …

The view controller for the chart has a few different elements; it contains a UIView which we will use to host the chart, a UIView with a label and activity indicator, which are rendered while data is being fetched from Yahoo and a label which forms the page title. Each of these items have been created via Interface Builder and suitable outlets created.

Chart View

In the override for the controllers ViewDidLoad method we create the chart, add a datasource, configure the axes and add it to the view:

public override void ViewDidLoad ()
{
  base.ViewDidLoad ();
      
  _chart = new ShinobiChart (chartHostView.Bounds);
      
  // set the datasource
  _charDataSource = new StockChartDataSource ();
  _chart.DataSource = _charDataSource;
      
  // apple a theme
  _chart.Theme = new SChartMidnightTheme ();
 
  // add a couple of axes
  _chart.XAxis = new SChartDateTimeAxis ();
  _chart.YAxis = new SChartNumberAxis ();
  ConfigureAxis (_chart.XAxis);
  ConfigureAxis (_chart.YAxis);
      
  chartHostView.Hidden = true;
  chartHostView.InsertSubview (_chart, 0);
}

private void ConfigureAxis (SChartAxis axis)
{
  axis.EnableGesturePanning = true;
  axis.EnableGestureZooming = true;
  axis.EnableMomentumPanning = true;
  axis.EnableMomentumZooming = true;
}

Because we want the user to be able to pan and pinch the chart we enable these gestures for both axes.

The ShinobiChart has a datasource (for supplying data) and delegate (for styling and reacting to user-interactions), just like the UITableView. The MonoTouch bindings combine these methods into a single base class implementation, just as Xamarin have done for UITableView.

The datasource implementation is pretty trivial:

private class StockChartDataSource : SChartDataSource
{
  private List<SChartData> _dataPoints;
      
  public StockChartDataSource ()
  {
    _dataPoints = new List<SChartData> ();      
  }
      
  public List<SChartData> DataPoints {
    set {
      _dataPoints = value;
    }
  }
      
  public override SChartData GetDataPoint (ShinobiChart chart, int dataIndex, int seriesIndex)
  {
    return null;
  }
      
  public override SChartData[] GetDataPoints (ShinobiChart chart, int seriesIndex)
  {
    return _dataPoints.ToArray ();
  }
      
  public override int GetNumberOfSeries (ShinobiChart chart)
  {
    return 1;
  }
      
  public override int GetNumberOfDataPoints (ShinobiChart chart, int seriesIndex)
  {
    return _dataPoints.Count;
  }
      
  public override SChartSeries GetSeries (ShinobiChart chart, int index)
  {
    var lineSeries = new SChartLineSeries ();         
    lineSeries.Style.LineColor = UIColor.FromRGB (166, 166, 166);
    lineSeries.Style.AreaColor = UIColor.FromRGB (16, 99, 123);
    lineSeries.Style.AreaColorLowGradient = UIColor.FromRGB (0, 0, 41);
    lineSeries.Style.ShowFill = true;        
    lineSeries.CrosshairEnabled = true;        
    return lineSeries;
  }
}

 

Note that GetDataPoints has been implemented, which provides the series data all in one go, so we do not have to implement the GetDataPoint method which requests each point individually.

So, how do we obtain the data? Simple … another request to Yahoo. Following a similar pattern to the request for stock quotes:

private void FechPriceData ()
{
  string url = "http://ichart.finance.yahoo.com/table.csv?d=0&e=28&f=2013&g=d&a=3&b=12&c=1996&ignore=.csv&s="
    + _symbol;
      
  WebClient client = new WebClient ();
  client.DownloadStringCompleted += (s,e) => {
    ParseCSVStockPrices (e.Result); 
    progressIndicatorView.Hidden = true;
    chartHostView.Hidden = false;
  };
  client.DownloadStringAsync (new Uri (url));
}
        
private void ParseCSVStockPrices (string csvData)
{
  var seriesData = new List<SChartData> ();
      
  var lines = csvData.Split ('\n');
  foreach (var line in lines.Skip(1)) {
    var components = line.Split (',');
    if (components.Length > 1) {
          
      DateTime date = DateTime.Parse (components [0]);
      double value = double.Parse (components [1]);
      seriesData.Add (new SChartDataPoint (){
        XValue = date.ToNSDate(),
        YValue = new NSNumber(value)
      }
      );
    }
  }
      
  InvokeOnMainThread (() => {
    _charDataSource.DataPoints = seriesData;
    _chart.ReloadData ();
    _chart.RedrawChart ();
  });
}

When the data has been returned by Yahoo, the progress indicator is hidden and the chart revealed. Note, iOS has UI thread affinity, just like the .NET family of UI frameworks, and while the .NET framework marshals the DownloadStringCompleted event onto the UI thread, it would appear that MonoTouch does not. So, before updating the datasource with the datapoints and re-drawing the chart, we need to ‘switch’ to the main thread.

Stock Chart With Progress

And there you have it, a simple example of plotting stock charts with ShinobiControls and MonoTouch!

You can download the full sourcecode for this demo here: ShinobiStockChart.zip

If you have any questions, or comments, please use the form below.

Back to Blog