The ability to template controls in Silverlight and WPF is a
powerful feature, representing a separation of
concerns between the domain logic and the presentation of
that logic; all glued together through data binding. Support for
templating and data binding are both included in Visiblox Charts *,
and this blog post will demonstrate how to develop an application
putting these concepts into use. The application I will build is
based upon searching the Twitter public feed for a user-defined
search term. Tweets are gathered using the Twitter Search API (
as originally documented by Tim Heuer), analysed for a mood and
plotted onto a Visiblox data series.
* The TemplatedSeries and TemplatedLineSeries classes are
available in the premium
version of Visiblox Charts.
Tweet Object
Each tweet is represented by a business object of type
Tweet:
public class Tweet
{
public string AuthorName { get; set; }
public string AuthorID { get; set; }
public string Avatar { get;set; }
public string Message { get; set; }
public DateTime PublishDate { get; set; }
public string Mood { get; set; }
}
The application holds an
ObservableCollection<Tweet> of Tweet
instances, which will be updated with incoming new data. This
observable collection will be data-bound to the Visiblox Chart
instance using a BindableDataSeries.
BindableDataSeries
The BindableDataSeries class allows for data to
be bound directly to a Visiblox Chart. Internally, a
BindableDataSeries creates a BindableDataPoint for
each of the items in the collection declared as its
ItemsSource. The XValueBinding
and YValueBindingproperties identify bindings to
the properties on a business object that represent the X and Y
values for each BindableDataPoint. When setting the ItemsSource
property, the DataContext for data points in the BindableDataSeries
is your domain model; meaning these bindings can be expressed by
property name. For this application, the observable collection of
tweets will be the ItemsSource of the BindableDataSeries. The
PublishDate and Mood properties of Tweet will be the X and Y values
of each BindableDataPoint respectively:
ObservableCollection searchResults = new ObservableCollection();
BindableDataSeries bindableDataSeries = new BindableDataSeries();
bindableDataSeries.ItemsSource = searchResults;
bindableDataSeries.XValueBinding = new Binding() { Path = new PropertyPath("PublishDate") };
bindableDataSeries.YValueBinding = new Binding() { Path = new PropertyPath("Mood") }
NOTE: It is also possible to specify multiple Y values (if you
are using a CandlestickSeries, for example) using the
YValueBindingsproperty. Now the data is set up, I
will attach it to a Visiblox chart series.
TemplatedSeries and TemplatedLineSeries
Visiblox Charts has two templated classes which operate
similarly: TemplatedSeries and
TemplatedLineSeries. The TemplatedSeries class
plots only templated data points. The TemplatedLineSeries class is
an extension of LineSeries, adding line drawing in-between
templated data points. For this application I will be using the
TemplatedSeries class. Both templated classes expose the following
dependency properties:
- The PointTemplate property accepts a Control
Template (with a TargetType of TemplatedPoint). This represents the
template that is applied to each data point.
- The SelectedPointTemplate and
HighlightedPointTemplate properties allow for
templating the "selected" and "highlighted" states of a data
point.
In the XAML declaration of my chart, I added a
TemplatedSeries:
<charts:Chart x:Name="Chart" Title="TweetsAboutThisTopic">
<charts:Chart.Series>
<charts:TemplatedSeries PointTemplate="{StaticResource TimelineSeriesTemplate}" />
</charts:Chart.Series>
</charts:Chart>
The DataSeries property of a Visiblox
chart series is used to specify what data is to be plotted. So, I
assign the BindableDataSeries as the DataSeries of the
TemplatedSeries:
Chart.Series[0].DataSeries=bindableDataSeries;
To show the avatar of a Twitter user as a data point, I created
a PointTemplate which binds to the Avatarproperty
on Tweet (through a converter):
<ControlTemplate TargetType="charts:TemplatedPoint" x:Key="TimelineSeriesTemplate">
<StackPanel>
<Border VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1" Background="White">
<Image Width="40" Height="40"
Source="{Binding Path=Avatar, Converter={StaticResource ImageConverter}}" />
</Border>
</StackPanel>
</ControlTemplate>
This gives each data point the following look and feel:

The bulk of the chart architecture is now in place! In summary,
The ObservableCollection fires out CollectionChanged notifications
when new tweets are received. The BindableDataSeries updates its
collection of BindableDataPoints accordingly, and displays them on
the graph using the PointTemplate. Next, I looked at handling
incoming data, and assigning a 'mood' to each tweet.
Incoming Tweets and Evaluating Mood
A Timer controls when to fire each search query, which I have
set to thirty seconds. From the resultant data, the last tweet ID
is stored to ensure that repeated data is not returned from future
search queries. Therefore, it is possible to guarantee the data
contains no duplicates, and is in the correct order. The 'mood' of
a tweet is evaluated by searching for occurrences of emotional
words and emoticons. This is
a fairly basic analysis technique, but for the purpose of this
example it will suffice! I created a helper class,
MoodEvaluator, with a GetMood method which accepts
a string and returns another string. Internally it holds a
dictionary of positive and negative words, with scores assigned
relating to the emotion of the word - so "love" is classed as more
positive than "like". The overall score is converted to one of the
following categories:
- Very Positive: A score of 5 or more.
- Positive: A score between 2 and 4.
- Neutral: A score between 1 and -1,
- Negative: A score between -2 and -4.
- Very Negative: A score of -5 or less.
Visiblox Charts support category axes, although for this example
the categories have an explicit order and have a fixed range. The
Categories property of
CategoryRange accepts a list of IComparables, with
each entry representing a fixed category. The categories are
plotted according to the order specified, which can be reversed
using the SortOrderproperty, should this default
behaviour not suit your needs.
Chart.YAxis.Range = new CategoryRange() { Categories = {"Very Negative", "Negative", "Neutral", "Positive", "Very Positive"} };
Having many templated controls in any application can incur a
performance cost. This is reduced by enforcing a limit on the size
of the ObservableCollection of search results. When the limit is
reached, data is removed from the first element. As a further
optimisation, the Y axis range is limited, as the bounds are a
known constant in this application. Both of these design decisions
will optimise the
performance of Visiblox Charts.
BusyIndicator Control
I also created an additional Busy Indicator control, which is
simply a ContentControl
with an additional IsBusy dependency property. For simplicity, I
used a
T4 template to automatically generate the boilerplate code for
the dependency property of this control. The Visual State
Manager hides the content declared within the
Content property when IsBusy is false. This
control is applied overlapping the chart area, and displays in the
upper-right corner to inform the user when new tweets are
fetched:

Tool Tips
Finally, I added a ToolTipTemplate to the data, so that each
tweet and its owner can be shown: Given that this is applied to
each point, and the DataContext of each point is a Tweet, it is
possible to bind directly to properties on Tweet in the same manner
as with PointTemplate! This produces the following outcome on a
mouse over:

The chart XAML declaration, therefore, looks like the following
(with some styling removed here):
<charts:Chart x:Name="Chart" Title="TweetsAboutThisTopic">
<charts:Chart.XAxis>
<charts:DateTimeAxis Title="Time"/>
</charts:Chart.XAxis>
<charts:Chart.YAxis>
<charts:CategoryAxis Title="Mood"/>
</charts:Chart.YAxis>
<charts:Chart.Series>
<charts:TemplatedSeries PointTemplate="{StaticResource TimelineSeriesTemplate}"
ToolTipEnabled="True"
ToolTipPosition="Bottom"
ToolTipTemplate="{StaticResource ToolTipTemplate}"
SelectionMode="None" />
</charts:Chart.Series>
</charts:Chart>
Outcome
Here's the application!
Due to Silverlight's cross-domain policy, when the application
is ran in a web browser the avatars are not loaded. For this
reason, the application must be ran in out-of-browser mode. This
can be uninstalled from your Control Panel, or from the application
itself by right-clicking and selecting "Remove this
application".
The source code of this application (without the Visiblox dll)
can be downloaded
here. Enjoy!