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:
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.
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{}
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.
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:
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.
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); } }}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()); } }}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" ?><ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 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; } }}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!