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!