Whether or not a feature is supported in Xamarin Forms, if it's supported in the underlying native platforms, you can still make use of it. We walk through how you can utilize a Candlestick chart in your Xamarin Forms application.

In this post we will demonstrate that you can utilize financial series in the Telerik Chart for Xamarin Forms.

This type of series is not exposed in the current version of Telerik UI for Xamarin suite out of the box. However, the native platforms (Telerik UI for Android, Telerik UI for iOS and Telerik UI for Windows Universal) support such series. This means that despite the fact that the API is not unified for Xamarin Forms, you are still capable of using such visualizations in your applications with a bit of manual work. The final look should be similar: 

candlestickseries-xamarin-renderer

Requirements

You need to download the 2016.2.513 (2016 Q2) or later version of the Telerik UI for Xamarin Forms suite of controls and the latest Xamarin Forms version.

In order to control the visualization of the financial series among the different platforms, a customized RadCartesianChart renderer (one per platform) is required.
The next and actually last piece of the puzzle would be a level of abstraction representing the financial series. It should be used in the portable project of the solution. This abstraction would simply be a class deriving from the base series class included in our suite.

Creating Financial Series

This step is the easiest. In the portable project users can create a class deriving from the Telerik.XamarinForms.Chart.CartesianSeries. In order to follow the series naming convention it would be wise to call this class CandlesticskSeries since it will visualize candlesticks.

public class CandlestickSeries : CartesianSeries
{
}

Why Custom RadCartesianChart Renderer?

Customizing the renderer is required because this is the only place where you have access to the Telerik UI for XamarinForms (or XF for short) and the native control (Android, iOS and WinRT charts) at the same time.

The ItemsSource of the native component can be populated based on the ItemsSource of the XF control. For every newly created CandlestickSeries the respective native series will be created, populated with data and added to the native charting component.

Android Renderer

Creating a custom renderer is a straightforward operation. Simply create a new class deriving form Telerik.XamarinForms.ChartRenderer.Android.CartesianChartRenderer and you are ready to continue with the customization itself. The default renderer exposes the OnElementPropertyChanged() method which should be overridden for the purposes of our attempt. This is the entry point for the customization. 

public class FinancialSeriesChartRenderer : CartesianChartRenderer
{
     protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
     {
          base.OnElementPropertyChanged(sender, e);   
     }
}

Next we need to ensure that the default renderer has done its job, initializing the XF and the native controls. This can be done by simply implementing our code after the base logic.

This base logic will initialize for us the chart itself, its axes, grid lines, behaviors and all known types of series along with the settings that are applied to those components. If there is something unknown to the RadCartesianChart, it will ignore it. This is why our custom code should take care of the custom series that will be used.

public class FinancialSeriesChartRenderer : CartesianChartRenderer
{
     protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
     {
          base.OnElementPropertyChanged(sender, e);
          if (e.PropertyName.Equals("Renderer"))
          {
                
          }
     }
}

The easiest way to correctly create native series is to refer to the Telerik UI for Android documentation. According to the article you need to create two additional classes:

  • Java.Lang.Object representing a single data point
  • Customized DataPointBinding class mapping the properties of the newly created Java.Lang.Object to the properties defined in the business data object used in the portable project

For the purpose of this article we will create a CustomDataPointBinding which will use reflection to get the Java.Lang.Object through Xamarin. This will help you to fill the required data for the Android series. The implementation of the class should look like this:

public class CandlestickDataBinding : Com.Telerik.Widget.Chart.Engine.Databinding.DataPointBinding
{
     public CandlestickDataBinding(string name)
     {
          this.propertyName = name;
     }
     private MethodInfo getMethod;
     private string propertyName;
     public override Java.Lang.Object GetValue(Java.Lang.Object item)
     {
         var instance = item.GetType().GetProperty("Instance").GetValue(item);
         if (this.getMethod == null)
         {
              this.getMethod = instance.GetType().GetProperty(this.propertyName).GetGetMethod();
         }
         var value = this.getMethod.Invoke(instance, null);
         return value.ToJavaObject();
     }
}

The next step is to create, populate and add a Com.Telerik.Widget.Chart.Visualization.CartesianChart.Series.Categorical.CandlestickSeries for every custom CandlestickSeries (defined earlier in the post):

namespace ChartFinancialSeries.Android
{
     public class FinancialSeriesChartRenderer : CartesianChartRenderer
     {
         protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
         {
             base.OnElementPropertyChanged(sender, e);
             if (e.PropertyName.Equals("Renderer"))
             {
                 var chart = sender as RadCartesianChart;
                 if (chart != null)
                 {
                     foreach (var series in chart.Series)
                     {
                         if (series as Portable.CandlestickSeries == null
                         {
                             continue;
                         }
                             var androidSeries = new Com.Telerik.Widget.Chart.Visualization.CartesianChart.Series.Categorical.CandlestickSeries();
                             androidSeries.CategoryBinding = new CandlestickDataBinding("Category");
                             androidSeries.OpenBinding = new CandlestickDataBinding("Open");
                             androidSeries.HighBinding = new CandlestickDataBinding("High");
                             androidSeries.LowBinding = new CandlestickDataBinding("Low");
                             androidSeries.CloseBinding = new CandlestickDataBinding("Close");
                             if (series.ItemsSource != null)
                             {
                                 androidSeries.Data = new Java.Util.LinkedList(series.ItemsSource.OfType<object>().ToArray());
                             }
                             else
                             {
                                 androidSeries.Data = new Java.Util.LinkedList(new object[] { });
                             }
                             this.Control.Series.Add(androidSeries);                           }
                    }
               }
          }
     }
}

With this, the implementation of the custom renderer is done. It now should be able to properly handle the custom CandlestickSeries. 

The last step is to register and use the customized RadCartesianChart renderer instead of the default one. This can be done in the MainActivity.cs file:

[assembly: Xamarin.Forms.ExportRenderer(typeof(Telerik.XamarinForms.Chart.RadCartesianChart), typeof(ChartFinancialSeries.Android.FinancialSeriesChartRenderer))]
namespace ChartFinancialSeries.Android
{
}

The customization of the Android project is now finished and we can now move to the next platform.

iOS Renderer

Creating a custom iOS renderer in its essence is not different from creating an Android renderer. You need to derive a custom class from the default Telerik.XamarinForms.ChartRenderer.iOS.CartesianChartRenderer. The entry point of the customization is the same—the OnElementPropertyChanged() method:

namespace ChartFinancialSeries.iOS
{
     public class FinancialSeriesChartRenderer : CartesianChartRenderer
     {
         protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
         {
             base.OnElementPropertyChanged(sender, e);
             if (e.PropertyName.Equals("Renderer"))
             {
             }
         }
     }
}

In order to properly create the respective TKChartCandlestickSeries, you can refer to the Telerik UI for iOS documentation. The provided code in the article uses the TKChartFinancialDataPoint class as a data point of the candlestick series. Having this class provided in the Telerik suite means one less class to create.

You need to create one instance of this class for each candlestick defined as ItemsSource of the custom CandlestickSeries. This will allow the native charting component to visualize the financial data as expected.

public class FinancialSeriesChartRenderer : CartesianChartRenderer
{
     protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
     {
          base.OnElementPropertyChanged(sender, e);
          if (e.PropertyName.Equals("Renderer"))
          {
               var chart = sender as RadCartesianChart;
               if (chart != null)
               {
                    foreach (var sereis in chart.Series)
                    {
                         if (series as Portable.CandlestickSeries == null)
                         {
                             continue;
                         }
                             var financialDataPoints = new List<TKChartFinancialDataPoint>();
                             foreach (BusinessDataObject dataPoint in sereis.ItemsSource)
                             {
                                 if (dataPoint != null)
                                 {
                                    financialDataPoints.Add(TKChartFinancialDataPoint.DataPoint(
                                        new NSString(dataPoint.Category).ToNSObject(),
                                        new NSNumber(dataPoint.Open),
                                        new NSNumber(dataPoint.High),
                                        new NSNumber(dataPoint.Low),
                                        new NSNumber(dataPoint.Close)));
                                 }
                            }
                            var CandlestickSeries = new TKChartCandlestickSeries(financialDataPoints.ToArray());
                            this.Control.AddSeries(CandlestickSeries); 
                    }
               }
          }
     }
}

The issue that you can encounter is related to the required NSObject that will be used as category. In most cases the category is defined as string and converting it to NSObject does not seem to be a straightforward operation. The easiest solution is to use the ToNSObject() extension method that is defined in the Telerik.XamarinForms.Common.iOS.IOSTypeConversionExtensions class.

After having this implemented we are ready to register and use this renderer instead of the default one. This can be done in the AppDelegate class.

[assembly: Xamarin.Forms.ExportRenderer(typeof(Telerik.XamarinForms.Chart.RadCartesianChart), typeof(ChartFinancialSeries.iOS.FinancialSeriesChartRenderer))]
namespace ChartFinancialSeries.iOS
{
     [Register("AppDelegate")]
     public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
     {
         public override bool FinishedLaunching(UIApplication app, NSDictionary options)
         {
               new FinancialSeriesChartRenderer();
               global::Xamarin.Forms.Forms.Init();
               Telerik.XamarinForms.Common.iOS.TelerikForms.Init();
               LoadApplication(new Portable.App());
               return base.FinishedLaunching(app, options);
         }
    }
}

WinRT Renderer

The approach with the custom WinRT renderer is the same.

namespace ChartFinancialSeries.WinRT
{
    public class FinancialSeriesChartRenderer : CartesianChartRenderer
    {
        protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName.Equals("Renderer"))
            {
                
            }
        }
    }
}

The difference is how the native series are created and populated. In order to do this correctly, you can refer to the WinRT documentation. Fortunately, the type of the data required by the native CandlestickSeries is the same as the one used in our portable project—a list of objects. This means no additional type to type conversion will be needed. The only missing pieces are the bindings of the separate properties.

public class FinancialSeriesChartRenderer : CartesianChartRenderer
{
     protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
     {
          base.OnElementPropertyChanged(sender, e);
          if (e.PropertyName.Equals("Renderer"))
          {
               var chart = sender as XF.RadCartesianChart;
               if (chart != null)
               {
                    foreach (var series in chart.Series)
                    {
                         if (series as Portable.CandlestickSeries == null)
                         {
                              continue;
                         }
                         var winSeries = new CandlestickSeries();
                         winSeries.ItemsSource = series.ItemsSource;
                         androidSeries.CategoryBinding = new CandlestickDataBinding("category");
                         androidSeries.OpenBinding = new CandlestickDataBinding("open");
                            androidSeries.HighBinding = new CandlestickDataBinding("high");
                         androidSeries.LowBinding = new CandlestickDataBinding("low");
                         androidSeries.CloseBinding = new CandlestickDataBinding("close");
                         this.Control.Series.Add(winSeries);
                    }
               }
          }
     }
}

As always, the last step is to register the customized renderer. This can be done in the MainPage.xaml.cs file 

[assembly: Xamarin.Forms.Platform.WinRT.ExportRenderer(typeof(Telerik.XamarinForms.Chart.RadCartesianChart), typeof(ChartFinancialSeries.WinRT.FinancialSeriesChartRenderer))]
namespace ChartFinancialSeries.WinRT
{
     public sealed partial class MainPage
     {
          public MainPage()
          {
               Telerik.XamarinForms.Common.WinRT.TelerikForms.Init();
               this.InitializeComponent();
               LoadApplication(new Portable.App());
          }
     }
}

Using the Customized Renderers

After the renderers are prepared we are ready to continue with setting up their use case. For simplicity we will use a ContentPage with just one RadCartesianChart visualizing only one CandlestickSeries with hardcoded data.

<?xml version="1.0" encoding="utf-8" ?>
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:telerikChart="clr-namespace:Telerik.XamarinForms.Chart;assembly=Telerik.XamarinForms.Chart"
             xmlns:local="clr-namespace:Portable"
             x:Class="Portable.StartPage">
  <telerikChart:RadCartesianChart >
    <telerikChart:RadCartesianChart.BindingContext>
      <local:ViewModel/>
    </telerikChart:RadCartesianChart.BindingContext>
    <telerikChart:RadCartesianChart.HorizontalAxis>
      <telerikChart:CategoricalAxis PlotMode="BetweenTicks" LabelFitMode="MultiLine"/>
    </telerikChart:RadCartesianChart.HorizontalAxis>
    <telerikChart:RadCartesianChart.VerticalAxis>
      <telerikChart:NumericalAxis />
    </telerikChart:RadCartesianChart.VerticalAxis>
    <telerikChart:RadCartesianChart.Series>
      <local:CandlestickSeries ItemsSource="{Binding Items}"/>
    </telerikChart:RadCartesianChart.Series>
  </telerikChart:RadCartesianChart>
</ContentPage>

The business object holding the data representing one point is defined like this:

namespace Portable
{
     public class BusinessDataObject
     {
          public string Category { get;set;}
          public double Open { get; set; }
          public double High { get; set; }
          public double Low { get; set; }
          public double Close { get; set; }
          public BusinessDataObject(string category, double open, double close, double low, double high)
          {
               this.Category = category;
               this.Open = open;
               this.Close = close;
               this.Low = low;
               this.High = high;
          }
     }
}

The ViewModel holding the hardcoded business data looks like this:

namespace Portable
{
     public class ViewModel
     {
          public ICollection<BusinessDataObject> Items { get; set; }
          public ViewModel()
          {
               this.Items = this.GetData();
          }
          private ICollection<BusinessDataObject> GetData()
          {
               var result = new ObservableCollection<BusinessDataObject>();
               result.Add(new BusinessDataObject("2/29/2016", 72.75, 71.4, 71.4, 73));
               result.Add(new BusinessDataObject("2/26/2016", 71.0883, 71.52, 70.895, 71.7794));
               result.Add(new BusinessDataObject("2/25/2016", 69.68, 70.1, 69.68, 70.1));
               result.Add(new BusinessDataObject("2/24/2016", 66.5, 67.12, 65.99, 67.12));
               result.Add(new BusinessDataObject("2/23/2016", 68.26, 67.2, 66.832, 68.26));
               result.Add(new BusinessDataObject("2/22/2016", 70.391, 68.855, 68.855, 70.391));
               result.Add(new BusinessDataObject("2/19/2016", 67.6, 67.76, 67.6, 67.76));
               result.Add(new BusinessDataObject("2/18/2016", 67.57, 68.04, 67.57, 68.04));
               return result;
          }
     }
}

Let's Review

To sum up, in this blog post we provided guidelines on how to use CandlestickSeries in the RadCartesianChart, even though this series is not exposed in the Telerik UI for XamarinForms suite. We also took a look at the issues that are most likely to be encountered during implementation and resolved them. At the end we created a simple use case chart which utilized the customized renderers and series. 

Happy coding!


pavelrpavlov
About the Author

Pavel Pavlov

Pavel has been part of the Telerik team for three years. He has background in various XAML technologies, including WPF, Silverlight and WinRT. He is now part of the UI for Xamarin team at Telerik.

Related Posts

Comments

Comments are disabled in preview mode.