In this blog post I will describe the creation of a simple range selector UserControl, which can be used alongside a Visiblox chart to create an interactive navigator for time series data.
Whether you are studying finance, politics, meteorology or sociology you are sure to encounter time series data. Time series are everywhere! And until the universe starts to collapse in on itself and the arrow of time reverses, these time series are going to keep on growing in size. When charting and exploring large time series datasets, it can help to have a navigator control - a small chart showing the entire dataset at a lower resolution, with controls that allow the user to select a range to view in detail.
This blog post describes how to create a simple navigator control which allows the user to select and drag a time range, as shown below:
The data in the above chart comes from the Time Series Data Library collected and published online by Rob Hyndman.
The Range Control
The markup for the range selector user control is a
Grid divided into three columns. The left and right hand column each contain a
Border with a blue fill which shades the sections which are outside of the selected range. These cells also each contain a
Thumb control, this is the element that the user interacts with. The central column contains a thumb that occupies the entire cell, however, its opacity is set to zero so that it is not visible:
Event handlers are added to the
DragCompleted event of each
Thumb control. The Thumb control is a bit of an odd one, you might expect that it moves itself as the user clicks and drags it, however, this is not the case. When the user clicks and drags the
Thumb, it fires
DragDelta events as the mouse moves, however, it is your responsibility to move the
Thumb to the updated location to reflect this drag operation.
This might sound odd at first, however, there are many in which a control can be moved, you can set its
Canvas location, update its
Margin, apply a
RenderTransform, to name just a few methods. In the case of the range control described in the XAML above, the
Thumb location is dictated by the width of the column that contains it. Therefore, when
DragDelta events are fired, we need to update these widths on code-behind, as shown below:
The event handler for the right hand thumb is very similar to the above. The handler for the centre thumb which allows you to drag a region of fixed time is a little more complex, updating the widths of both left and right hand columns, however, the principle is very much the same.
In order to use this control as an interactive range selector, we need to be able to specify the
DateTime range it represents and also the control needs to expose the
DateTime range for the current selection. To achieve this purpose, the control exposes a Range dependency property of type
DateTimeRange (from the Visiblox APIs), which is used to specify the overall date range. The
DragCompleted event handler above calls
UpdateExposedBounds() which updates a
Bounds dependency property to reflect the selected range:
The above code uses a simple bit of algebra to compute the Bounds as a proportion of the overall exposed Range.
Using this range control to create a 'navigator' chart and update a 'detail' is as simple as binding the range control's Range property to the
XAxis.ActualRange property of the navigator chart, and its
Bounds property to the
XAxis.Range property of the detail chart.
This can all be achieved via UI bindings as shown below:
You can see the above code in action:
With the code above, the range control
Bounds property is bound to the X axis range of the upper chart. Each time this range is changed the chart has to perform quite a bit of work, computing the new Y-axis range, re-drawing the series etc... For this reason, the implementation only updates the
Bounds property when the user finishes adjusting the range. It would be better if the chart could update whilst the user drags the navigator range. In order to do this, we need a more lightweight method of updating the upper chart.
The Visiblox axes expose a
Zoom property which can be used to supply a
Offset which rapidly updates the chart. In order to use the
Zoom property we need to convert the
Bounds exposed by the range control into a suitable zoom. This is easiest done in code-behind.
The following code handles property changed events from the range control:
The axis exposes a
GetZoom method which can be used to create a zoom which will cause the axis to display the data within the given range (in pixels). However, the range control exposes a Bounds which is in the data coordinate system (i.e. dates) rather than the screen coordinate system. Therefore, we first need to apply the
GetDataValueAsRenderPositionWithZoom coordinate system conversion method to the upper and lower bound.
With the above method, the chart now updates much more rapidly and we are able to update as the user drags the navigator range:
The Example Data
The data in the examples shows the daily maximum and minimum temperatures in Melbourne from 1981 to 1990. The data was supplied as two separate files, one with maximum temperatures, and the other with minimum temperatures.
These are parsed into a Visiblox DataSeries using the following code:
The detail chart uses this data directly, rendering the max / min values as a band series:
Because there are ~3,500 datapoints in this dataset it does not make much sense for the navigator chart to render every point. The following Linq query groups the datapoints by month, then extracts the average upper temperature for each month. This data is then supplied to the lower chart:
I will never grow tired of the power of Linq!
The range selector control has been implemented as a
UserControl, it could be made more generic by implementing it as a custom control, which would allow it to be templated. Also, it should be possible to make the
Bound properties use
double rather than
DateTime, then use a value converter in the binding to the chart. This would allow range selector to be used in context where the range being selected is not a
DateTime one. I'll leave this as an exercise for the reader!
You can download the full sourcecode here: VisibloxRangeSelector.zip
You will also need to download the free Visiblox charts to compile the code.
Regards, Colin E.