In this post we look at plotting large amounts of data using candlestick series
The premium release of Visiblox Charts supports candlestick and HLOC series. Whilst there are examples provided on the examples page, this blog post describes a chart that plots substantially more data and with a few more interesting features also added in. The task here is that of plotting historical stock price data for a range of companies, going back several years. We're going to use candlesticks here to represent high, low, opening and closing prices for a day. We could equally easily use the HLOCSeries that Visiblox also supports. Whilst this might be fairly easy for small amounts of data we quickly end up with a large number of candlesticks to render if we're looking at an interestingly wide number of companies or time-span. Suppose we plot two years of prices for three companies - this entails rendering around 1500 candlesticks and so could prove challenging when it comes to maintaining performance.
We've based this chart on an earlier Code Project article that used Visiblox to display stock data with LineSeries: 
In that application the Google Finance API was used to get historical price data - given a stock code and a start and end date, a query was sent from the Silverlight application, via a proxy. The results came back in CSV format and contained all the information we need to plot a candlestick: for each date we have opening and closing as well as high and low prices for the date in question. That project also presented live stock data but this example uses only the historical data.
Using Candlesticks/HLOC with a BindableDataSeries
In order to get the data we've downloaded from the Google Finance API into our chart we represent it as an IList<HLOCHistoricStockData> and then set the DataSeries property of the CandlestickSeries to be a BindableDataSeries. In the event handler that is invoked once downloading is complete, the ItemSource of the BindableDataSeries is set to the downloaded IList<HLOCHistoricStockData>.
StockChart.Series.Add(
new CandlestickSeries()
{
IsInteractionEnabled = false,
ToolTipEnabled = true,
ToolTipTemplate = (ControlTemplate)Resources["CustomTooltipTemplate"],
DataSeries = new BindableDataSeries()
{
XValueBinding = new Binding("Date"),
YValueBindings = (YValueBindingsCollection)Resources["HLOCBindings"],
ItemsSource = e.Data,
Title = stockCode
}
});
HLOCHistoricStockData is simply a class that represents information about a day's trading of a given stock:
public class HLOCHistoricStockData
{
public string StockCode { get; set; }
public DateTime Date { get; set; }
public double Price { get; set; }
public double Open { get; set; }
public double Close { get; set; }
public double High { get; set; }
public double Low { get; set; }
public double Volume { get; set; }
}
The various properties of that class are bound to different elements of each data point using a YValueBindingsCollection defined as a static resource:
Navigating time series
Next, to aid navigation around the data, we add a navigation range controller. The control used is introduced in more detail in this blog post- we only make a couple of cosmetic changes in our use of it here:
- The dimensions of the thumb controls have been altered
- The regions that aren't being plotted in the main chart are shaded white rather than blue
Underneath the control we plot the price of stock using a LineSeries - the result looks like this:

Fixing x-axis height
The labels displayed against the x-axis of the chart would by default rearrange themselves such that they appear at a specified interval but don't collide with each other. This is achieved by either writing the label text sideways or alternating label positions, as shown below.

The labels reposition themselves whenever an axis range change would result in them colliding - whilst useful in some situations it does mean that the overall height of the plot area can change, which forces the chart to re-render.
To avoid that problem we fix the height to some sensible value and switch off collision detection with the LabelsAutoFit dependency property:
private IAxis _dateTimeAxis = new DateTimeAxis()
{
MajorTickInterval = 7,
MajorTickIntervalType = DateTimeAxisIntervalSpan.Days,
ShowMinorTicks = false,
LabelsAutoFit = false,
LabelFormatString = "dd MMM",
Height = 40
};
This leaves us with the problem that the label may still collide with each other. We resolve this by limiting the number of labels on the axis at the end of any range change operation:
private void UpdateXAxisMajorTickInterval()
{
var xAxis = StockChart.XAxis as DateTimeAxis;
if (xAxis != null && xAxis.ActualRange != null)
{
double axisSpan = ((DateTime)xAxis.ActualRange.EffectiveMaximum).Subtract(
(DateTime)xAxis.ActualRange.EffectiveMinimum).TotalDays;
// ensure there are _desiredNumberOfXAxisLabels labels on the XAxis
double interval = Math.Round(axisSpan / _desiredNumberOfXAxisLabels);
// make the interval equal to a whole number of weeks but always less than half the length of the axis
interval = Math.Min(7 * Math.Ceiling(interval / 7), axisSpan / 2);
((DateTimeAxis) StockChart.XAxis).MajorTickInterval = interval;
}
}
FilteredDataSeries
In order to avoid re-plotting the entire dataset every time a change is made to the chart - something that is now relatively expensive given the number of points being rendered - we use a FilteredDataSeries.
We use the WindowedFilterStrategy to preclude data outside a window around the visible range from the series. More details about filtering are given in an earlier blog post.
Rescaling the Y-axis
The navigation control we are using here supports an IsUpdateOnMouseUp dependency property that determines whether the bounds it specifies are updated continuously whilst the range is being dragged or only once when the drag operation is finished. Using that option avoids jerky updates to the main chart when changing the range by simply not updating it.
You can observe the difference that property makes by checking and unchecking the "Both" CheckBox in the "Mid-drag freeze" section of control panel.

One thing that can make updating the main chart during range changes slow is that the vertical axis rescales to fit the data currently within the effective range. This happens when the AutoScaleToVisibleData dependency property is true on the YAxis.
We can improve performance by only rescaling the YAxis once the range change operation has finished
private void UpdateStockChartRange()
{
...
if (_updateYAxisOnDragDelta)
{
UpdateStockChartYAxisRange();
}
}
private void UpdateStockChartYAxisRange()
{
StockChart.XAxis.AdoptZoomAsRange();
}
You can see the result of switching that behaviour on or off using the "Y" CheckBox in the "Mid-drag freeze" section.
Dealing with weekend gaps
Since stocks aren't traded during the weekend this chart seems like appropriate place to make use of a DiscontinuousDateTimeAxis. The example provides a CheckBox that will swap in a different axis as required.
The discontinuous axis is initialized using:
// Set up the DiscontinuityProvider so that is excludes weekends
ObservableCollection<DayOfWeek> excludeWeekend = new ObservableCollection<DayOfWeek>();
excludeWeekend.Add(DayOfWeek.Saturday);
excludeWeekend.Add(DayOfWeek.Sunday);
(_discontinuousDateTimeAxis as DiscontinuousDateTimeAxis).DiscontinuityProvider = new DailyHoursModeProvider()
{
ExcludeDaysOfWeek = excludeWeekend
};
Putting it all together
The complete example has been put together in the application displayed below. The source code for the example can be downloaded here but you will need to add a reference to the premium Visiblox dll to compile it.
Questions and comments on anything in the post are welcome!