Colin Eberhardt's Technology Adventures

UKSnow Silverlight – An Rx powered Twitter, Bing Maps mashup.

November 29th, 2010

UPDATE: I have published an article on codeproject “Exploring Reactive Extensions (Rx) through Twitter and Bing Maps Mashups“, which has the full sourcecode for this mashup.

A week ago a colleague of mine posted an interesting article on parallelism in .NET 4.0 which included a few different libraries for asynchronous / parallel processing. Being a bit of a Linq fan, the Reactive Extensions jumped out at me.

With the heavy snow in the UK, I was inspired to have a go at creating a Silverlight version of Ben Marsh’s UKSnow twitter / maps mashup.

The application below gives a real-time view of the snow across the UK. To add a snow report to the map, tweet #uksnow with your postcode and the amount of snowfall out of ten (e.g. 7/10).


The application above uses Rx to coordinate requests to the Twitter and Bing maps REST APIs, with the tweets being parsed to extract the postcode, which is then geocoded via bing. Snow is added to the map with an opacity based on the amount (out of ten) in the given tweet.

If it has stopped snowing when you look at the map above, this is what it looked like at around midday today (12:00 GMT, 29th November, 2010):

I plan to write a codeproject article on Rx with a few other examples, however I just couldn’t wait to put this online because the example is much more powerful when it is actually snowing!

Regards,
Colin E.

Adding a Smoothed Line Series (Bézier curve) to a Visiblox Chart

November 22nd, 2010

In this blog post I look at how to add a new series type to the Visiblox charts by creating my own series type which renders a smoothed line using a Bézier curve.

This blog post describes how to create a new series type for the Visiblox charts, a spline series. The example below shows the new series type in action, with the various spline control points rendered (just for fun!):


Creating a New Series Type

The visual representation of a Visiblox DataSeries is the responsibility of a chart series, as defined by the IChartSeries interface. However, the abstract baseclass ChartSeriesBase is most often the best place to start when creating a new series type.

The first step towards creating my smoothed series type is to create a ChartSeries subclass. The only abstract method that we must implement is InvalidateInternal, and it is here that we will place our logic to create the series:

public class SplineSeries : ChartSeriesBase
{
 
  public SplineSeries()
  {
    DefaultStyleKey = typeof(SplineSeries);
  }
 
  protected override void InvalidateInternal()
  {
    // render logic goes here
  }
}

A template also has to be supplied for the series in the generic.xaml file. The ChartSeriesBase requires that a ZoomCanvas element is located within the template as follows:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:VisibloxSplineSeries"
    xmlns:visPrim="clr-namespace:Visiblox.Charts.Primitives;assembly=Visiblox.Charts">
 
  <Style TargetType="local:SplineSeries">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:SplineSeries">
          <visPrim:ZoomCanvas x:Name="LayoutRoot" />
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
 
</ResourceDictionary>

That’s all the boiler plate code out of the way with, let’s get down to writing our series implementation.

A Simple Line Series

We’ll start by creating a straight-line implementation. Within the UpdateInternal method the following code constructs a Path instance and adds it to the ZoomCanvas:

protected override void InvalidateInternal()
{
  RootZoomCanvas.Children.Clear();
 
  Path path = new Path();
  PathGeometry geometry = new PathGeometry();
  PathFigure figure = new PathFigure();
 
  var renderPoints = GetRenderPoints();
  figure.StartPoint = renderPoints.First();
  foreach (var renderPoint in renderPoints.Skip(1))
  {
    figure.Segments.Add(new LineSegment()
    {
      Point = renderPoint
    });
  }
 
  geometry.Figures.Add(figure);
  path.Data = geometry;
  path.StrokeThickness = 2;
  path.Stroke = new SolidColorBrush(Colors.Black);
 
  ZoomCanvas.SetIsScaledPath(path, true);
 
  RootZoomCanvas.Children.Add(path);
}
 
public List<Point> GetRenderPoints()
{
  var renderPoints = new List<Point>();
  foreach (IDataPoint point in this.DataSeries)
  {
    double xPos = XAxis.GetDataValueAsRenderPositionWithoutZoom(point.X);
    double yPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(point.Y);
    renderPoints.Add(new Point(xPos, yPos));
  }
  return renderPoints;
}

Let’s look at this code in detail. The method GetRenderPoints creates a list of points which indicate the location where each datapoint should be rendered on the chart. The series has a property DataSeries which is the data being rendered and we enumerate over each of the points in this series. The chart series also has a relationship to the X & Y axes, and it is each axis which is responsible for the converting the X & Y components of our data into a ‘screen’ coordinate. This is performed via the (succinctly named!) GetDataValueAsRenderPositionWithoutZoom method (more on this later).

The list of points returned by the GetRenderPoints method are used to define a path (a path is defined by a geometry which is defined by one or more figures which themselves are defined by one or more segments … phew!). This path is styled and added to the RootZoomCanvas, with the attached property IsScaledPath set to true.

The net result of the above code is that we now have a fully functional line series which integrates with the Visiblox behaviours such as pan & zoom:

Now back to this ZoomCanvas …

The Visiblox charts have been optimised for performance and providing user interactions. A common requirement for interactive charts is the ability to pan and zoom. Both of these can be implemented by changing the range of the X and Y axes (for example a pan operation would result in a fixed offset being applied to the axis range), however this would result in the chart having to recomputed the location of all of each of the points being rendered. As an alternative, the Visiblox axes have a Zoom property which is handled by the ZoomCanvas directly. When a Zoom is applied, the required RenderTransforms are applied to the elements in the canvas, i.e. the series, resulting in a rapid update of chart. When using the ZoomCanvas you have to indicate whether the element added is based on a geometry, which should be transformed (i.e. a zoom makes the geometry appear bigger), or whether the element location should simply by updated to reflect the current zoom, for example, when you zoom in to a chart typically you would want the datapoints to maintain a fixed size.

Gergely Orosz describes the ZoomCanvas in more detail in his recent post on Panning & Zooming the charts.

To illustrate how this works, we will add points to our series which are not scaled as we zoom into the chart. The InvalidateInternal method is updated with the additional code to render the points, note that the ellipses have a RenderTransform applied so that their centre is anchored to the correct location on the ZoomCanvas:

protected override void InvalidateInternal()
{
  // ... code for rendering the path
 
  foreach (var renderPoint in renderPoints)
  {
    Ellipse el = new Ellipse()
    {
      Stroke = new SolidColorBrush(Colors.Black),
      StrokeThickness = 2.0,
      Fill = new SolidColorBrush(Colors.White),
      Width = 9,
      Height = 9,
      RenderTransform = new TranslateTransform()
      {
        X = -4,
        Y = -4
      }
    };
    RootZoomCanvas.Children.Add(el);
    ZoomCanvas.SetElementPosition(el, renderPoint);
  }
}

You can see the series in action below:

Creating a Smoothed Curve

So far we have created a simple line series; however the aim of this article is to create a smoothed line series. The Figure within our PathGeometry is currently composed of a number of straight line segments, in order to create a smoothed line we need to use curved segments. Silverlight and WPF both support Bézier curves and these can be used to specify a curved path geometry. A Bézier curve has four points, a start point, an end point and two control points which define the curvature:

In order to create a curved path for our series we need to determine suitable control points for our Bézier segments. After a bit of googling I stumbled upon an excellent article by Kerem Kat which describes how to use Bézier curves with Bing Maps. The article sourcecode has a method that will return the data points required to render a smoothed line as a Bézier Curve, using Catmull-Rom splines.

Modifying our series to use Bézier Curves, via the GetBezierPoints method described in the above article, is pretty straightforward:

protected override void InvalidateInternal()
{
  RootZoomCanvas.Children.Clear();
 
  Path path = new Path();
  PathGeometry geometry = new PathGeometry();
  PathFigure figure = new PathFigure();
 
  // obtain the render position of each point
  var renderPoints = GetRenderPoints();
 
  // create the bezier points as per:
  // http://www.codeproject.com/KB/silverlight/MapBezier.aspx
  PointCollection bezierPoints = GetBezierPoints(renderPoints, Tension);
 
  // create the figure composed of bezier segments
  figure.StartPoint = bezierPoints[0];
  for (int i = 1; i < bezierPoints.Count; i += 3)
  {
    figure.Segments.Add(new BezierSegment()
    {
      Point1 = bezierPoints[i],       
      Point2 = bezierPoints[i + 1],   
      Point3 = bezierPoints[i + 2]    
    });
  }
 
  geometry.Figures.Add(figure);
  path.Data = geometry;
  path.StrokeThickness = 2;
  path.Stroke = new SolidColorBrush(Colors.Black);
  ZoomCanvas.SetIsScaledPath(path, true);
 
  RootZoomCanvas.Children.Add(path);
}

Note the Tension property which is passed to the GetBezierPoints method, this is a property of Catmull-Rom splines which describes how ‘smoothed’ the line should be. The SplineSeries exposes this as a dependency property, as shown in the example below:

<vis:Chart x:Name="chart" LegendVisibility="Collapsed">
  <vis:Chart.Series>
    <local:SplineSeries Tension="{Binding Path=Value, ElementName=tensionSlider}"/>
  </vis:Chart.Series>
</vis:Chart>
 
<StackPanel Orientation="Horizontal" Grid.Row="1">
  <TextBlock Text="Tension:"/>
  <Slider x:Name="tensionSlider"
        Maximum="5.0" Minimum="1.0" Value="2.0" 
        Width="100"/>
  <TextBlock Text="{Binding Path=Value, ElementName=tensionSlider}"
              Width="18"/>
</StackPanel>

You can see the effect of changing the tension on the spline:

For a Bit Of Fun …

I thought it would be fun to visualise the Catmull-Rom spline construction by also rendering the control points for each Bézier curve. You can see the results below:

You can download the full sourcecode for the article here: VisibloxSplineSeries.zip

To run the examples you will also need to download the Visiblox charts from www.visiblox.com.

Regards, Colin E.

Using a Grid as the Panel for an ItemsControl

November 15th, 2010

In this blog post I look at how to use a Grid as the ItemsPanel for an ItemsControl, solving a few of the issues that crop up along the way.

The Grid is probably the most useful of Silverlight and WPF’s panels (panels are elements which provide a mechanism for laying out their children). The ItemsControl provides a mechanism for generating multiple instances of some template based on the data that is bound to it. The ItemsControl ‘concept’ is highlight versatile, and is used as the foundation for many of the framework controls, I find myself using it all the time.

Isn’t it a shame that Grid and ItemsControl don’t play together nicely?

So what do I mean by this? I am sure that if you have ever tried to generate Grid a layout using an ItemsControl you will know what I am talking about, but if not, here’s a very brief summary of the problem. An ItemsControl manages a number of child elements, you can configure the type of panel the ItemsControl adds children to via its ItemsPanel property. If I have a list of strings which I want to render using a Grid, I might expect the following to work … in XAML:

<ItemsControl ItemsSource="{Binding}">
  <!-- host the items generated by this ItemsControl in a grid -->
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Grid/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <!-- render each bound item using a TextBlock-->
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

And in code-behind:

this.DataContext = new string[]
  { "one", "two", "three", "four", "five"  };

So what is wrong with the above? There are a couple of problems:

  1. When using a Grid you must add the required number of rows / columns via the RowDefinitions ColumnDefinitions properties. In the above example what we really want is for the grid rows to be added dynamically based on the number of items in the bound data. Unfortunately this is not supported either by the Grid or the ItemsControl.
  2. The second problem is that items within a grid must declare the row / column that they occupy via the Grid.Row and Grid.Column attached properties. You might think that you could add a Grid.Row property to the TextBlock in the above example, binding it to some other property of the bound collection. However, this will not work because the TextBlock elements are not added into the grid directly; each one is added as the content of a ContentPresenter which is generated for us.

The above two problems mean that it is not possible to use a Grid as the panel within an ItemsControl. In this blog post I will show a solution to these problems …

Dynamically adding RowDefinitions

The first problem to overcome is how to dynamically add the required number of RowDefinitions to our Grid. To support this I added an attached property ItemsPerRow, which upon attachment handles the LayoutUpdated event. This event is fired when items are added to the grid, this enables us to compute the number of RowDefinitions that are required, and also to assign a row index to each of the Grid’s children. The following code is the property changed handler for the attached ItemsPerRow property:

/// <summary>
/// Handles property changed event for the ItemsPerRow property, constructing
/// the required ItemsPerRow elements on the grid which this property is attached to.
/// </summary>
private static void OnItemsPerRowPropertyChanged(DependencyObject d,
                    DependencyPropertyChangedEventArgs e)
{
  Grid grid = d as Grid;
  int itemsPerRow = (int)e.NewValue;
 
  // construct the required row definitions
  grid.LayoutUpdated += (s, e2) =>
    {
      var childCount = grid.Children.Count;
 
      // add the required number of row definitions
      int rowsToAdd = (childCount - grid.RowDefinitions.Count) / itemsPerRow;
      for (int row = 0; row < rowsToAdd; row++)
      {
        grid.RowDefinitions.Add(new RowDefinition());
      }
 
      // set the row property for each chid
      for (int i = 0; i < childCount; i++)
      {
        var child = grid.Children[i] as FrameworkElement;
        Grid.SetRow(child, i / itemsPerRow);
      }
    };
}

Note the use of a lambda expression for the event handler in order to ‘capture’ a reference to the Grid (as described in my previous blog post on binding the ScrollViewers offset properties).

Changing our markup to use the above attached property:

<ItemsControl ItemsSource="{Binding}">  
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <!-- use the ItemsPerRow attached property to dynamically add rows -->
      <Grid local:GridUtils.ItemsPerRow="1"
          ShowGridLines="True"/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <TextBlock Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

The array of strings bound in code-behind results in the following grid layout being rendered:

Great, all fixed. Time to grab a coffee.

Unfortunately it isn’t that simple. The above example has a single item per grid row, typically you would want to have multiple items in each row, with each item positioned in a different column. This is where it gets tricky. DataTemplate only supports a single child element, so we will have to wrap the elements which we want to add into our grid in a panel of some sort. However, if we do this, the column index that we apply to the element of each row will be ignored because the Grid only honours the Row and Column properties of its direct descendants.

Creating a Row Template

In order to circumnavigate this issue, I have created a ‘phantom’ panel, which hosts the elements that are added to each grid row. When a phantom panel is added to the grid, its children are removed and added to the grid and the phantom is destroyed.

public class PhantomPanel : Panel
{
 
}

The property changed handler shown above is extended to remove this phantom:

private static void OnItemsPerRowPropertyChanged(DependencyObject d,
                    DependencyPropertyChangedEventArgs e)
{
  Grid grid = d as Grid;
  int itemsPerRow = (int)e.NewValue;
 
  // construct the required row definitions
  grid.LayoutUpdated += (s, e2) =>
    {
      // iterate over any new content presenters (i.e. instances of our DataTemplate)
      // that have been added to the grid
      var presenters = grid.Children.OfType<ContentPresenter>().ToList();
      foreach (var presenter in presenters)
      {
        // the child of each DataTemplate should be our 'phantom' panel
        var phantom = VisualTreeHelper.GetChild(presenter, 0) as PhantomPanel;
        if (phantom != null)
        {
 
          // remove each of the children of the phantom and add to the grid
          foreach (FrameworkElement child in phantom.Children.ToList())
          {
            phantom.Children.Remove(child);
            grid.Children.Add(child);
            // ensure the child maintains its original datacontext
            child.DataContext = phantom.DataContext;
          }
 
          // remove the presenter (and phantom)
          grid.Children.Remove(presenter);
        }
      }
 
      var childCount = grid.Children.Count;
      int rowDifference = (childCount / itemsPerRow) - grid.RowDefinitions.Count;
 
      // if new items have been added, create the required grid rows
      // and assign the row index to each child
      if (rowDifference != 0)
      {
        grid.RowDefinitions.Clear();
        for (int row = 0; row < (childCount / itemsPerRow); row++)
        {
          grid.RowDefinitions.Add(new RowDefinition());
        }
 
        // set the row property for each chid
        for (int i = 0; i < childCount; i++)
        {
          var child = grid.Children[i] as FrameworkElement;
          Grid.SetRow(child, i / itemsPerRow);
        }
      }
    };
}

The above code is pretty straightforward, however there is one subtlety, we must ensure that the DataContext of each child element is set to the DataContext that the ItemsControl assigned to each PhantomPanel, i.e. the individual items that are bound to the Grid.

With the above code it is now possible to create a more complex grid layout:

<StackPanel Orientation="Vertical">
 
  <sdk:DataGrid ItemsSource="{Binding}" Margin="10"/>
 
  <Border x:Name="LayoutRoot" Background="White"
        BorderBrush="Black" BorderThickness="1" Margin="10">
 
    <ItemsControl ItemsSource="{Binding}">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <Grid local:GridUtils.ItemsPerRow="2">
            <Grid.ColumnDefinitions>
              <ColumnDefinition/>
              <ColumnDefinition/>
            </Grid.ColumnDefinitions>
          </Grid>
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <!-- the row ‘template’ within a phantom panel -->
          <local:PhantomPanel>
            <TextBlock Text="{Binding Path=Item}"/>
            <TextBlock Text="{Binding Path=Quantity}" Grid.Column="1"/>
          </local:PhantomPanel>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
 
  </Border>
 
</StackPanel>

The above XAML renders a list of simple model objects that implement INotifyPropertyChanged in a DataGrid and an ItemsControl (using our Grid layout). Notice that if you edit the properties of the bound objects via the DataGrid, these changes are reflected in the Grid below, i.e. it is all working!

Handling CollectionChanged Events

The above example looks pretty complete, however there is one remaining issue, handling changes to the bound collection. The problem with the above solution is that it effectively subverts the ItemsControls usage of the ItemsPanel. The ItemsControl will expect that items that it adds to the Panel to appear at certain indices, by removing our phantom panels and adding their contente directly this is no longer the case.

A simple hack (and yes, it really is a hack!) is to confuse the ItemsControl into thinking that any change to our bound collection is a reset, i.e. the collection has changed completely, so the UI needs to be rebuilt from scratch. To do this, I have created an attached ItemsSource property that adapts the bound collection, ensuring that any collection changes result in the ItemsControl rebuilding its UI. The changed handler for this attached property is given below:

/// <summary>
/// Handles property changed event for the ItemsSource property.
/// </summary>
private static void OnItemsSourcePropertyChanged(DependencyObject d,
  DependencyPropertyChangedEventArgs e)
{
  ItemsControl control = d as ItemsControl;
 
  // set the ItemsSource of the ItemsControl that this property is attached to
  control.ItemsSource = e.NewValue as IEnumerable;
 
  INotifyCollectionChanged notifyCollection = e.NewValue as INotifyCollectionChanged;
  if (notifyCollection != null)
  {
    // if a collection changed event occurs, reset the ItemsControl's
    // ItemsSource, rebuilding the UI 
    notifyCollection.CollectionChanged += (s, e2) =>
      {
        control.ItemsSource = null;
        control.ItemsSource = e.NewValue as IEnumerable;
      };
  }
}

The above attached property is used in exactly the same way as the ItemsControl.ItemsSource property which it adapts. The XAML below shows how the above attached property can be used in place of the ItemsControl property:

<ItemsControl local:GridUtils.ItemsSource="{Binding}">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Grid local:GridUtils.ItemsPerRow="3">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="2*"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
      </Grid>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <local:PhantomPanel>
        <TextBlock Text="{Binding Path=Item}"/>
        <TextBlock Text="{Binding Path=Quantity}" Grid.Column="1"/>
        <Line Stroke="LightGray" StrokeThickness="1"
              VerticalAlignment="Bottom"
              X1="0" X2="1" Y1="0" Y2="0"
              Stretch="Fill"
              Grid.ColumnSpan="2"/>
      </local:PhantomPanel>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

And here it is with a DataGrid bound to the same data:

You can add, remove and update the bound objects and the Grid rendered via the ItemsControl remains synchronised with the bound data.

Conclusions

Finally, the ItemsControl and Grid can be used together, a cause for much rejoicing! I must admit, I am pretty pleased with my solution; however, it is far from perfect. There are a couple of areas of this implementation that you should be wary of.

Firstly, the use of the LayoutUpdated event which we use to determine when items are added to our Grid. This event gets fired when anything changes in our visual tree’s layout. It has the potential to be called a lot, which is why I am always careful to check conditions and exit this method as quickly as possible if the UI is not in the state I am interested in.

Secondly, adapting the ItemsSource to force a Reset whenever an items is added or removed is pretty costly!

If you do not use the technique for rendering large quantities of data it will probably be just fine. It may be fine for large volumes of data also, however, it is always better to be aware of potential issue.

If anyone has any ideas on how to improve on this technique, please leave a comment below:

You can download the full project sourcecode: ItemsControlGrid.zip

Regards, Colin E.

Visiblox Charts vs. Silverlight Toolkit Charts – a test of Performance

November 8th, 2010

This blog post compares the performance of the Visiblox and Silverlight Toolkit charts using a simple image processing tool to test and illustrate their differences in performance. The results show that the Visiblox charts are approximately 50 – 100 times faster that the Silverlight Toolkit charts.

UPDATE: I have published a more up-to-date and extensive test of WPF charting components in a more recent blog post. This new test looks at how raster-graphics can be used to significantly enhance performance.

I was recently asked by my friends at Visiblox to test out the latest release of their charts for WPF and Silverlight. One of the key features of the Visiblox charts is their performance; they have been designed to both render and update rapidly in response to changes in the underlying data. I decided to put their latest release to the test in a head-to-head against the Silverlight Toolkit charts.

Microsoft announced the release of the Silverlight Toolkit charts in late 2008 and have since released a number of updates, some of which have focussed on performance. I would anticipate that the relatively mature Silverlight Toolkit charts are probably most developer’s first choice when they need to display data graphically within their application.

Before we get into the details of my investigation, here are the two applications working side-by-side, one using the Visiblox chart and the other using the Toolkit charts. Click and drag a line across the image to plot the RGB pixel intensities along the line in the chart above. This is a reasonably tough challenge for the chart, with three series rendered of up to 400 points each. From the examples below you can see that the Visiblox charts are clearly faster.

Visiblox Chart Silverlight Toolkit Chart

[Hare image used under CC licence from flickr, tortoise used royalty free from stock.xchng]

I have had quite a bit of experience with the Silverlight Toolkit in the past and have written a few blog posts which detail how to add interactivity to the charts. The early releases were pretty slow, due to the large number of ‘Control’ instances that the chart constructs, (there are un-official guidelines that you should limit the number of control instances to a few hundred for WPF & Silverlight). However, subsequent releases were quite a bit faster. David Anson also published a number of very useful hints and tips on maximising chart performance on his blog. I have followed all of David’s guidelines to provide a fair comparison.

The following sections will provide a little more detail regarding the implementation of this application and how it differs between the Visiblox and Toolkit versions.

Visiblox Charts

The markup for the above application is quite simple:

<StackPanel Orientation="Vertical" x:Name="LayoutRoot">
  <Grid Height="180">
 
    <chart:Chart x:Name="chart"
                LegendVisibility="Collapsed"
                Margin="5,0,5,5">
      <chart:Chart.XAxis>
        <!-- a 'hidden' X axis -->
        <chart:LinearAxis ShowAxis="False"
                          GridlineStyle="{StaticResource GridLineStyle}"
                          IsMarginEnabled="False"/>
      </chart:Chart.XAxis>
      <chart:Chart.YAxis>
        <!-- A Y axis with labels within the chart area -->
        <chart:LinearAxis ShowLabels="True"
                          LabelsPosition="Inside"
                          GridlineStyle="{StaticResource GridLineStyle}"
                          ShowMajorTicks="False"
                          ShowMinorTicks="False"/>
      </chart:Chart.YAxis>
      <chart:Chart.Series>
        <!-- the series used to render the RGB components -->
        <chart:LineSeries LineStroke="#A00"/>
        <chart:LineSeries LineStroke="#0A0"/>
        <chart:LineSeries LineStroke="#00A"/>
      </chart:Chart.Series>
    </chart:Chart>
    <TextBlock x:Name="instructions"
                Text="Use the mouse to 'drag' a line across the image below ..."
                FontSize="10" Foreground="#333333" Margin="50" TextWrapping="Wrap"/>
  </Grid>
 
  <Grid x:Name="grid"
        MouseLeftButtonUp="Grid_MouseLeftButtonUp"
        MouseLeftButtonDown="Grid_MouseLeftButtonDown">
    <Image Width="300" Height="200" x:Name="image"               
                    Source="/hare.jpg"/>
    <Line Stroke="Black" StrokeThickness="3"
                  x:Name="line"/>
  </Grid>
 
</StackPanel>

The above markup constructs a Chart, Image and a Line within a StackPanel. The code-behind loads the image into a WriteableBitmap in order to access its pixel values, and the mouse event handlers attached to the grid are used to detect when the user drags the line. The technique I published previously on throttling mouse events is used to handle the mouse move event in order to construct the chart data:

private void ThrottledEvent_ThrottledMouseMove(object sender, MouseEventArgs e)
{
  if (!lButtonDown)
    return;
 
  line.X2 = e.GetPosition(grid).X;
  line.Y2 = e.GetPosition(grid).Y;
 
  // compute distance between the points
  double distance = Math.Sqrt((line.X1 - line.X2) * (line.X1 - line.X2) +
      (line.Y1 - line.Y2) * (line.Y1 - line.Y2));
 
  // create the Visiblox dataseries for the R, G & B components
  var dataR = new DataSeries<double, double>("R");
  var dataG = new DataSeries<double, double>("G");
  var dataB = new DataSeries<double, double>("B");
 
  // build the charts
  for (double pt = 0; pt < distance; pt++)
  {
    double xPos = line.X1 + (line.X2 - line.X1) * pt / distance;
    double yPos = line.Y1 + (line.Y2 - line.Y1) * pt / distance;
 
    int xIndex = (int)xPos;
    int yIndex = (int)yPos;
 
    int pixel = pixelData[xIndex + yIndex * 300];
 
    // the RGB values are 'packed' into an int, here we unpack them
    byte B = (byte)(pixel & 0xFF);
    pixel >>= 8;
    byte G = (byte)(pixel & 0xFF);
    pixel >>= 8;
    byte R = (byte)(pixel & 0xFF);
    pixel >>= 8;
 
    // add each datapoint
    dataR.Add(new DataPoint<double, double>(pt, R));
    dataG.Add(new DataPoint<double, double>(pt, G));
    dataB.Add(new DataPoint<double, double>(pt, B));
  }
 
  // set the data for each series
  chart.Series[0].DataSeries = dataR;
  chart.Series[1].DataSeries = dataG;
  chart.Series[2].DataSeries = dataB;
}

The Visiblox series (which implement IChartSeries) require date to be presented as an IDataSeries, which is a collection of IDataPoint instances. In the above code the chart data is copied directly into a DataSeries. Databinding is also possible via BindableDataSeries, however, for maximum performance it is always better to use DataSeries and copy the data across directly.

Silverlight Toolkit Charts

The Toolkit version of the application is much the same as the Visiblox one. The only difference in the application markup is for the chart itself, as shown below:

<ControlTemplate x:Key="SimplifiedDataPoint" TargetType="tk:LineDataPoint">
</ControlTemplate>
...
<tk:Chart x:Name="chart"
          LegendStyle="{StaticResource CollapsedLegendStyle}"
          TitleStyle="{StaticResource CollapsedStyle}">
 
  <!-- define the line series -->
  <tk:LineSeries ItemsSource="{Binding}"   
                  TransitionDuration="0"
                  DependentValueBinding="{Binding Intensity}" 
                  IndependentValueBinding="{Binding Location}">
    <tk:LineSeries.DataPointStyle>
      <Style TargetType="tk:LineDataPoint">
        <Setter Property="Visibility" Value="Collapsed"/>
        <Setter Property="Template" Value="{StaticResource SimplifiedDataPoint}"/>
        <Setter Property="Background" Value="#A00"/>
      </Style>
    </tk:LineSeries.DataPointStyle>
  </tk:LineSeries>
 
  <tk:LineSeries ItemsSource="{Binding}"   
                  TransitionDuration="0"
                  DependentValueBinding="{Binding Intensity}" 
                  IndependentValueBinding="{Binding Location}">
    <tk:LineSeries.DataPointStyle>
      <Style TargetType="tk:LineDataPoint">
        <Setter Property="Visibility" Value="Collapsed"/>
        <Setter Property="Template" Value="{StaticResource SimplifiedDataPoint}"/>
        <Setter Property="Background" Value="#0A0"/>
      </Style>
    </tk:LineSeries.DataPointStyle>
  </tk:LineSeries>
 
  <tk:LineSeries ItemsSource="{Binding}"   
                  TransitionDuration="0"
                  DependentValueBinding="{Binding Intensity}" 
                  IndependentValueBinding="{Binding Location}">
    <tk:LineSeries.DataPointStyle>
      <Style TargetType="tk:LineDataPoint">
        <Setter Property="Visibility" Value="Collapsed"/>
        <Setter Property="Template" Value="{StaticResource SimplifiedDataPoint}"/>
        <Setter Property="Background" Value="#00A"/>
      </Style>
    </tk:LineSeries.DataPointStyle>
  </tk:LineSeries>
 
  <!-- configure the axes -->
  <tk:Chart.Axes>
    <tk:LinearAxis Orientation="X" Height="0">
    </tk:LinearAxis>
  </tk:Chart.Axes>
</tk:Chart>

The markup here is a little more complex, in part because I am following the advice given by David Anson in his charting performance blog post in order to maximise the chart performance. With reference to David’s post, the following tips have been implemented:

  1. Turn off the fade in/out VSM animations – the template used by the DataPoint in my example has no visual states, hence will not be animated
  2. Change to a simpler DataPoint Template – the template in the example is as simple as possible, it is empty!
  3. Add the points more efficiently – the ItemsSource for each series are being updated via an ‘atomic’ re-assignment.
  4. Disable the data change animations – the TransitionDuration property has been set to zero as per David’s recommendation.

The other three tips in the blog post were not relevant to this test scenario.

The code-behind for the Toolkit chart version of this application is again much the same as for the Visiblox charts, the main difference is due to the Toolkit chart binding to business objects rather than having its own series type. Here is the implementation of the (throttled) mouse move event handler, and the business object which is bound to the chart.

/// <summary>
/// Handles mouse move to draw the line and intensity histograms
/// </summary>
private void ThrottledEvent_ThrottledMouseMove(object sender, MouseEventArgs e)
{
  if (!lButtonDown)
    return;
 
  line.X2 = e.GetPosition(grid).X;
  line.Y2 = e.GetPosition(grid).Y;
 
  // compute distance between the points
  double distance = Math.Sqrt((line.X1 - line.X2) * (line.X1 - line.X2) +
      (line.Y1 - line.Y2) * (line.Y1 - line.Y2));
 
  // create the series for the R, G &amp; B components
  var dataR = new List<DataPoint>();
  var dataG = new List<DataPoint>();
  var dataB = new List<DataPoint>();
 
  // build the charts
  for (double pt = 0; pt < distance; pt++)
  {
    double xPos = line.X1 + (line.X2 - line.X1) * pt / distance;
    double yPos = line.Y1 + (line.Y2 - line.Y1) * pt / distance;
 
    int xIndex = (int)xPos;
    int yIndex = (int)yPos;
 
    int pixel = pixelData[xIndex + yIndex * 300];
 
    // the RGB values are 'packed' into an int, here we unpack them
    byte B = (byte)(pixel & 0xFF);
    pixel >>= 8;
    byte G = (byte)(pixel & 0xFF);
    pixel >>= 8;
    byte R = (byte)(pixel & 0xFF);
    pixel >>= 8;
 
    // add each datapoint to the series
    dataR.Add(new DataPoint(pt, R));
    dataG.Add(new DataPoint(pt, G));
    dataB.Add(new DataPoint(pt, B));
  }
 
  // add each series to the chart
  ((LineSeries)chart.Series[0]).ItemsSource = dataR;
  ((LineSeries)chart.Series[1]).ItemsSource = dataG;
  ((LineSeries)chart.Series[2]).ItemsSource = dataB;
}
 
...
 
/// <summary>
/// A value object used to present the data to the chart
/// </summary>
public class DataPoint
{
  public double Location { get; private set; }
  public double Intensity { get; private set; }
 
  public DataPoint(double location, double intensity)
  {
    Location = location;
    Intensity = intensity;
  }
}

Conclusions

From the above example application we can see that the Visiblox charts performs approximately 50 – 100 times faster than the Silverlight Toolkit charts. This is of course a limited test that focuses on a specific mode of operation, however, the Visiblox charts have been designed with a focus on performance, so I would expect a similar result in other performance test cases.

The difference in speed between the two charting components can be broadly attributed to three key differences in design:

  1. Lightweight visuals – as mentioned in the introduction, the Toolkit charts are ‘control’ heavy, pushing the limits of the framework guidelines, in order to provide flexible styling and templating. The Visiblox charts use a much more lightweight approach, however, if templating is required a TemplatedLineSeries can be used.
  2. Databinding as an option – with Visiblox it is possible to present the data directly to the chart using the DataSeries class, whilst with the Toolkit charts you have to use databinding which brings with it a performance overhead. Again, with Visiblox you can use databinding to business objects if you wish. With the above Visiblox example application the performance is approximately halved by using databinding, this is still 25-50 times faster than the toolkit charts! (The sourcecode download includes the databinding Visiblox vesion).
  3. Optimised handling of changes – the Visiblox charts are optimised in such a way as to minimise the impact of changes in the data or other properties of the chart. You can update datapoints, entire series, or change the various properties of an axis and be guaranteed that the chart will only re-computed its layout once.

You can download the full sourcecode for the example applications given in this blog post here: ChartPerformance.zip

For the Visiblox examples you will need to visit www.visiblox.com to download their chart component, and for the Toolkit examples you will need to download and install the Silverlight Toolkit.

Regards, Colin E.