Introduction
The Silverlight Toolkit is a collection of Silverlight controls, components, and utilities made available outside the normal Silverlight release cycle. It adds new functionality quickly for designers and developers, and provides the community an efficient way to help shape product development by contributing ideas and bug reports. It includes full source code, Unit Tests, samples, and documentation for 12 new controls covering charting, styling, layout, and user input.
The Silverlight Toolkit gives us an incredibly powerful charting control for Silverlight! The one type of chart that is still relatively difficult to create using this toolkit is stock charts (OHLC or Candlestick).
A candlestick chart is a style of bar-chart used primarily to describe price movements of an equity over time.
It is a combination of a line-chart and a bar-chart, in that each bar represents the range of price movement over a given time interval. It is most often used in technical analysis of equity and currency price patterns. They appear superficially similar to error bars, but are unrelated.
Luckily the Silverlight Toolkit is also very customizable. This article will show a method of extending the Silverlight Toolkit to allow for the creation of stock charts.
The Basics
Each chart can contain multiple series. The Silverlight Toolkit has some common series:
BarSeries
BubbleSeries
ColumnSeries
LineSeries
PieSeries
ScatterSeries
We will be creating our own CandlestickSeries
by deriving from DataPointSingleSeriesWithAxes
:
public sealed partial class CandlestickSeries : DataPointSingleSeriesWithAxes
{
}
DataPointSingleSeriesWithAxes
is a dynamic series with axes, and only one legend item and style for all data points.
Each series contains multiple data points:
public sealed partial classCandlestickDataPoint : DataPoint
{
}
DataPoint
represents a control that displays a data point.
Now that we have the basics (a chart has a series, and each series has data points), let's dig deeper to see how it works…
CandlestickDataPoint
The CandlestickDataPoint
will be responsible for rendering the candlestick. Each candlestick has a shadow and body. Here is the default control template for the CandlestickDataPoint
:
<ControlTemplate TargetType="charting:CandlestickDataPoint">
<Border x:Name="Root" Opacity="0"
BorderBrush="{x:Null}" BorderThickness="0"
RenderTransformOrigin="0.5,0.5">
-->
<Grid>
<Grid x:Name="PART_Shadow">
<Rectangle Fill="Black" Width="1" />
</Grid>
<Grid x:Name="PART_Body"
Background="{TemplateBinding Background}"
Margin="0,5,0,5">
<Rectangle Fill="{TemplateBinding Background}" />
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#64686868" />
<GradientStop Color="#FF4D4C4C" Offset="0.996" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle RenderTransformOrigin="0.5,0.5"
Margin="19,19,17,17" Opacity="0.245">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF919191" />
<GradientStop Color="#FFFFFFFF" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="0.5" Y="0.5" />
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle x:Name="MouseOverHighlight" Opacity="0"
Fill="#FF9B1C1C" Stroke="#FF000000" />
<Rectangle x:Name="SelectionHighlight"
Opacity="0" Fill="#3EF0F0F0" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
Each CandlestickDataPoint
also has the following DPs (these are needed for the calculation of the body size):
The only “tricky” thing about the CandlestickDataPoint
is how it sizes the body. CandlestickDataPoint
has a method called UpdateBody()
which will resize the body.
CandlestickSeries
To make the CandlestickSeries
“work”, we need to override CreateDataPoint
, PrepareDataPoint
, and UpdateDataPoint
.
protected override DataPoint CreateDataPoint()
{
return new CandlestickDataPoint();
}
CreateDataPoint
adds an object to the series host by creating a corresponding data point for it.
protected override void PrepareDataPoint(DataPoint dataPoint, object dataContext)
{
base.PrepareDataPoint(dataPoint, dataContext);
CandlestickDataPoint candlestickDataPoint = (CandlestickDataPoint)dataPoint;
candlestickDataPoint.SetBinding(CandlestickDataPoint.OpenProperty, OpenValueBinding);
candlestickDataPoint.SetBinding(CandlestickDataPoint.CloseProperty, CloseValueBinding);
candlestickDataPoint.SetBinding(CandlestickDataPoint.HighProperty, HighValueBinding);
candlestickDataPoint.SetBinding(CandlestickDataPoint.LowProperty, LowValueBinding);
}
PrepareDataPoint
prepares a data point by extracting and binding it to a data context object.
protected override void UpdateDataPoint(DataPoint dataPoint)
{
CandlestickDataPoint candlestickDataPoint = (CandlestickDataPoint)dataPoint;
double PlotAreaHeight =
ActualDependentRangeAxis.GetPlotAreaCoordinate(
ActualDependentRangeAxis.Range.Maximum);
double dataPointX =
ActualIndependentRangeAxis.GetPlotAreaCoordinate(
ValueHelper.ToComparable(dataPoint.ActualIndependentValue));
double highPointY =
ActualDependentRangeAxis.GetPlotAreaCoordinate(
ValueHelper.ToDouble(candlestickDataPoint.High));
double lowPointY =
ActualDependentRangeAxis.GetPlotAreaCoordinate(
ValueHelper.ToDouble(candlestickDataPoint.Low));
double openPointY =
ActualDependentRangeAxis.GetPlotAreaCoordinate(
ValueHelper.ToDouble(candlestickDataPoint.Open));
double closePointY =
ActualDependentRangeAxis.GetPlotAreaCoordinate(
ValueHelper.ToDouble(candlestickDataPoint.Close));
candlestickDataPoint.UpdateBody(ActualDependentRangeAxis);
if (ValueHelper.CanGraph(dataPointX))
{
dataPoint.Height = Math.Abs(highPointY - lowPointY);
dataPoint.Width = 5.0;
if (dataPoint.ActualWidth == 0.0 || dataPoint.ActualHeight == 0.0)
dataPoint.UpdateLayout();
Canvas.SetLeft(dataPoint,
Math.Round(dataPointX - (dataPoint.ActualWidth / 2)));
Canvas.SetTop(dataPoint,
Math.Round(PlotAreaHeight - highPointY));
}
}
UpdateDataPoint
updates the visual representation of a single data point in the plot area.
And that is it…
<charting:Chart Title="Typical Use">
<charting:Chart.Series>
<charting:CandlestickSeries
Title="MSFT"
ItemsSource="{Binding Microsoft, Source={StaticResource FinancialData}}"
IndependentValueBinding="{Binding Date}"
DependentValueBinding="{Binding High}"
OpenValueBinding="{Binding Open}"
CloseValueBinding="{Binding Close}"
LowValueBinding="{Binding Low}"
HighValueBinding="{Binding High}"/>
</charting:Chart.Series>
</charting:Chart>
Please also visit my blog for more information...