Colin Eberhardt's Adventures in .NET

Visiblox, Visifire, DynamicDataDisplay – Charting Performance Comparison

December 10th, 2010

A few weeks ago I published a blog post which compared the performance of the Visiblox charts and the Silverlight Toolkit charts. The results indicated that the Visblox charts are considerably faster than the Toolkit charts, however Microsoft’s David Anson did point out that the Toolkit charts were not designed with performance in mind, and that a comparison would be more fair if the ‘FastLineSeries’ series that has been on the TODO list for a while were implemented.

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 have been asked by a few people to extend the performance test to include a few other Silverlight charts to see how they compare. I have refactored the test code so that different charts can be plugged in more easily. This not only makes it easier to test the performance of other charting components, but has also allowed me to compare the different charting APIs more easily.

This blog post compares the performance of Visiblox, Visifire, Silverlight Toolkit and Dynamic Data Display (D3) charts. I also compared a few other charting components, however the terms of their trial license prohibit me from publishing the results.

A summary of the performance measurement is given below, where each chart is used to plot rapidly changing data, with the framerate being used as a measure of performance.

The test clients running with each of the different charts are shown below:

Visiblox Chart Visifire Chart
Silverlight Toolkit Chart D3 Chart

[cheetah image courtesy of flickrfavorites, snail image courtesy of Per Ola Wiberg, tortoise image courtesy of wwarby, horse image courtesy of Linnéa Gröndalen]

We’ll look at the XAML markup for each chart and the code-behind used to add data to each chart in the following sections:

Visiblox

The XAML markup for the Visiblox charts is reasonably concise, with the X & Y axes configured and styled and the three LineSeries added to the chart:

<UserControl.Resources>
  <Style TargetType="Line" x:Key="GridLineStyle">
    <Setter Property="Stroke" Value="#dddddd" />
    <Setter Property="StrokeThickness" Value="1" />
  </Style>
</UserControl.Resources>
 
<Grid x:Name="LayoutRoot" Background="White">
  <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}"/>
    </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>
</Grid>

The code-behind is also reasonably concise with the list of DataPoint objects returned by the test-harness converted into Visiblox DataSeries via a Linq Select projection.

protected override void RenderDataToChart(List<List<Histogram.DataPoint>> rgbData)
{
  _chart.Chart.Series[0].DataSeries = ListToSeries(rgbData[0]);
  _chart.Chart.Series[1].DataSeries = ListToSeries(rgbData[1]);
  _chart.Chart.Series[2].DataSeries = ListToSeries(rgbData[2]);
}
 
private DataSeries<double, double> ListToSeries(List<Histogram.DataPoint> data)
{
  return new DataSeries<double, double>(data.Select(pt => 
    new DataPoint<double, double>(pt.Location, pt.Intensity)));
}

Visifire

Visifire also has a reasonably concise XAML markup, however in order to provide maximum performance, there are a number of chart features that need to be disabled. The Visifire charts have a Line series which could be used for this comparison, however they recently released a QuickLine series type which removes certain features to provide better performance.

<Grid x:Name="LayoutRoot" Background="White">
  <vc:Chart x:Name="chart"
                AnimationEnabled="False"
                ScrollingEnabled="False">
 
    <vc:Chart.Legends>
      <vc:Legend Enabled="False"/>
    </vc:Chart.Legends>
 
    <!-- the series used to render the RGB components -->
    <vc:Chart.Series>
      <vc:DataSeries RenderAs="QuickLine"
                      Color="#A00" LineThickness="1"
                      MarkerEnabled="False" ShadowEnabled="False" />
      <vc:DataSeries RenderAs="QuickLine"
                      Color="#0A0" LineThickness="1"
                      MarkerEnabled="False" ShadowEnabled="False" />
      <vc:DataSeries RenderAs="QuickLine"
                      Color="#00A" LineThickness="1"
                      MarkerEnabled="False" ShadowEnabled="False" />
    </vc:Chart.Series>
 
    <!-- a 'hidden' X axis -->
    <vc:Chart.AxesX>
      <vc:Axis Enabled="False">
        <vc:Axis.Grids>
          <vc:ChartGrid Enabled="True"
                        LineColor="#ddd" LineThickness="1"/>
        </vc:Axis.Grids>
      </vc:Axis>
    </vc:Chart.AxesX>
 
    <vc:Chart.PlotArea>
      <vc:PlotArea ShadowEnabled="False"/>
    </vc:Chart.PlotArea>
  </vc:Chart>
</Grid>

The code-behind required to add data to the chart is straightforward. However, you cannot simply create a new DataPointCollection and replace the current Series.DataPoints, instead you have to clear the existing collection, then rebuild it.

protected override void RenderDataToChart(List<List<Histogram.DataPoint>> rgbData)
{
  AddDataToSeries(_chart.Chart.Series[0], rgbData[0]);
  AddDataToSeries(_chart.Chart.Series[1], rgbData[1]);
  AddDataToSeries(_chart.Chart.Series[2], rgbData[2]);
}
 
private void AddDataToSeries(DataSeries series, List<Histogram.DataPoint> list)
{
  series.DataPoints.Clear();
  foreach (var pt in list)
  {
    series.DataPoints.Add(new Visifire.Charts.DataPoint()
    {
      XValue = pt.Location,
      YValue = pt.Intensity
    });
  }
}

Silverlight Toolkit

The Silverlight Toolkit XAML is quite verbose, mostly due to the performance enhancements described in my previous blog post.

<UserControl.Resources>
  <Style TargetType="dataVis:Legend" x:Key="CollapsedLegendStyle">
    <Setter Property="Visibility" Value="Collapsed"/>
    <Setter Property="Width" Value="0"/>
  </Style>
 
  <Style TargetType="Control" x:Key="CollapsedStyle">
    <Setter Property="Visibility" Value="Collapsed"/>
  </Style>
 
  <ControlTemplate x:Key="SimplifiedDataPoint" TargetType="tk:LineDataPoint">
  </ControlTemplate>
</UserControl.Resources>
 
<Grid x:Name="LayoutRoot" Background="White">
  <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>
</Grid>

However, the code-behind is the most concise of all the charts I tested because the chart databinds directly to the properties of the DataPoint business objects.

protected override void RenderDataToChart(List<List<DataPoint>> rgbData)
{
  ((LineSeries)_chart.Chart.Series[0]).ItemsSource = rgbData[0];
  ((LineSeries)_chart.Chart.Series[1]).ItemsSource = rgbData[1];
  ((LineSeries)_chart.Chart.Series[2]).ItemsSource = rgbData[2];
}

Dynamic Data Display

The D3 charts are quite different to the others which I have tested. These charts were developed by a Microsoft team based in Russia and are released on codeplex. They claim outstanding performance with large volumes of data, and are often recommended on sites such as stackoverflow when questions relating to charting performance arise.

The XAML markup for the D3 charts is pretty minimal!

<Grid x:Name="LayoutRoot" Background="White">
  <d3:ChartPlotter Name="plotter">
  </d3:ChartPlotter>
</Grid>

However, this is because the D3 charts do not support configuration via markup. It took me quite a while to work out how to configure the charts in code-behind, the D3 APIs are not very intuitive. I am not the only one to have noticed this; Lee Campbell, in his “WPF Charting Comparisons” blog post stated ” … it took me hours of reading forums, looking at samples and coding to just get my Model showing on the screen”. Oh dear!

I had to resort to navigating the visual tree (using Linq to VisualTree) to locate the pan and zoom controls which are added to the chart by default, and remove them.

However, in fairness, the Silverlight version of D3 charts is a partial port of the WPF version, but both share a similar programmatic style of usage.

The following code is used to configure the chart in code-behind:

public DDDChart()
{
  InitializeComponent();
 
  this.Loaded += new RoutedEventHandler(DDDChart_Loaded);
}
 
private LineGraph InitGraph(Color color)
{
  var animatedX = new List<double>();
  var animatedY = new List<double>();
 
  // add some points, otherwise we get some layout-related exception
  animatedX.Add(0);
  animatedY.Add(0);
 
  // create the X & Y sources
  _xSource = new EnumerableDataSource<double>(animatedX);
  _xSource.SetXMapping(x => x);
  _ySource = new EnumerableDataSource<double>(animatedY);
  _ySource.SetYMapping(y => y);
 
  // Adding graph to plotter
  var graph = new LineGraph(new CompositeDataSource(_xSource, _ySource), "");
  graph.LineColor = color;
  graph.LineThickness = 1;
  plotter.Children.Add(graph);
 
  return graph;
}
 
private void DDDChart_Loaded(object sender, RoutedEventArgs e)
{
  // remove the mouse pan and zoom controls
  var navigationControl = plotter.Children.OfType<MouseNavigation>().Single();
  plotter.Children.Remove(navigationControl);
 
  var zoomControl = plotter.Descendants().OfType<buttonsNavigation>().Single();
  ((Panel)zoomControl.Ancestors().First()).Children.Remove(zoomControl);
 
  _graphOne = InitGraph(Color.FromArgb(255,200,0,0));
  _graphTwo = InitGraph(Color.FromArgb(255, 0, 200, 0));
  _graphThree = InitGraph(Color.FromArgb(255, 0, 0, 200));
 
  // Force everything plotted to be visible
  plotter.FitToView();
 
  plotter.Legend.Visibility = Visibility.Collapsed;
 
}

The following code is used to change the data for each series. Again, the D3 APIs are a little complex. Also, the chart does not have a mode where the X & Y axes compute their range based on the data. Instead, this is performed programatically via FitToView.

protected override void RenderDataToChart(List<List<DataPoint>> rgbData)
{
  RenderDataToGraph(_chart.LineGraphR, rgbData[0]);
  RenderDataToGraph(_chart.LineGraphG, rgbData[1]);
  RenderDataToGraph(_chart.LineGraphB, rgbData[2]);
 
  // re-scale the chart based on the rendered data
  _chart.Plotter.FitToView();
}
 
private void RenderDataToGraph(LineGraph graph, List<DataPoint> histogram)
{
  var source = graph.DataSource as CompositeDataSource;
 
  // obtain the two sources which the composite is composed of
  var xSource = source.DataParts.ElementAt(0) as EnumerableDataSource<double>;
  var ySource = source.DataParts.ElementAt(1) as EnumerableDataSource<double>;
 
  var dataX = new List<double>();
  var dataY = new List<double>();
  foreach(var point in histogram)
  {
    dataX.Add(point.Location);
    dataY.Add(point.Intensity);
  }
  xSource.Data = dataX;
  ySource.Data = dataY;
}

Conclusions

In summary, the Visiblox and Visifire charts seem to have the cleanest API, with concise XAML markup to define the charts and little code-behind required to update the series. The Silverlight Toolkit charts XAML markup is a bit verbose, mostly due to performance optimisation. Finally, the D3 charts have a complex and difficult to follow API. In terms of performance, the Visiblox and D3 charts give similar results, the Visifire charts are a little behind with, with a frame rate that is approximately 1/3 of the leading charts, and the Silverlight Toolkit charts come last.

You can download the full sourcecode of this comparison here: ChartPerformance.zip

NOTE: To build the examples you need to download the charting components from their respective web pages:

Finally, a couple of my colleagues have created similar charting tests using other technologies, you might be interested in Graham’s Flex implementation, or Chris’s cute HTML5 implementation.

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.

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.

Ineffective Data Visualisation … and how to fix it

April 30th, 2010

This blog post looks at a recently published set of charts in a UK newspaper and how they fail to help in the comprehension of the data which they visualise. I will also look at much more effective ways of displaying this same data.

At Scott Logic we tend to spend quite a bit of our time thinking about the effective visualisation of data. In the financial sector data abounds, with stock prices changing every second, traders and analysts have a lot of data at their disposal. Without methods to analyse and visualise this data it is easy to gets lost in the sheer quantity. For this reason, the works of Edward Tufte and Stephen Few are often passed round the office!

With the UK General election looming, statistics and trends are a common feature in our news. Unfortunately these seems to lead to a whole slew of charts and graphics which succeed in their artistry but fail miserably in helping the reader understand the data which the graphics represent.

Just this morning I was reading an article in the Metro newspaper about the changes in party support over the past week’s opinion polls and the voting habits of different age groups. The article was supported by the following graphic:

One of the key ideas behind the charting and visualising of data is to allow the reader to rapidly digest the data, spot trends, understand relationships, etc… Unfortunately, the graphics above fail miserably in this respect. Here are some of the faults I spotted:

(1) Chart title – the main chart title relates to the chart on the right, but not to the chart on the left.

(2) Choice of colours - if you look at the datapoints on the right-hand chart it is not easy to determine which party they relate to due to a poor choice of colour, peach and salmon?!

(3) Trends are hidden - the main purpose of the right hand chart is to illustrate the trends in party support with relation to age. To do this you have to hunt for the same coloured point from one age band to the next.

(4) Gridlines – the right-hand chart has labels every 5 percent point, but gridlines every 2 points. This means that there is not a gridline for each label, this makes it very hard to determine the actual value of each datapoint.

(5) Doughnut – the doughnut (i.e. the stylised pie-chart with a yummy hole) has a couple of problems, which week does it represent the split in party support for? this week? last week? Also, which is the bigger pie piece, Lib. Dem. or Conservative? It is impossible to tell without reaching for your protractor (I seem to have left mine at home today).

(6) Arbitrary graphics – I cannot see any reason, other than artistic licence, for the vertical highlights on the right-hand chart. This is misleading, it draws the eye to these areas of the chart with the expectation that they are highlighted for some reason.

(7) Change not visualised – the change in support from last week to this week is not visualised in any way, it is presented in tabular form. This means that the reader might miss important information, for example, a 10 percent point raise from 10 to 20 is clearly more significant than a rise from 70 to 80, this is made quite obvious if we visualise the change.

(8) Units – the indication of units is quite distant from the data.

I am sure there are more problems … if you spot any others, leave a comment.

So, let’s see if we can rectify some of these issues. Starting with the chart on the right, its main purpose is to illustrate the relationship between age group and party support. In this case it is vital that the reader of this chart can easily navigate from the datapoint which indicated Conservative party support (for example) in one age range the next. With this in a mind, a line chart is much more appropriate and the trends become immediately visible:

Note also the colours, these are no longer arbitrarily assigned. Each political party has a party colour which, if used, allows most people to instantly determine the party each line relates to. The gridlines are also more sensibly placed and we have lost the ‘artistic’ highlights. Finally, the Y axis starts at zero, this allow the reader to instantly see the scale of the differences between the popularity figures without having to read the axis range.

Now, let’s turn our attention to the doughnut and the accompanying table. The reader should be able to determine two key pieces of information from these, (1) The relative popularity of each party and (2) The change in popularity since last week. It would be ideal if the two could be combined so that the reader can also compare the scale of this change with the overall difference in popularity. In order to allow this, it is much better to display the information in a single chart:

With the above chart we can see at-a-glance the relative popularity of each party again displayed in party colours. I must admit it took me a little while to work out how to indicate which columns represented this week’s figures and which were last week’s. I tried using variations in the column intensity, but this is a hard concept to indicate via a key, I also added small labels, but this just complicates and clutters. Finally I realised that by adding a pattern I could maintain the party colour, yet clearly relate the columns for the previous week (This makes use of the Gestalt Principle of Similarity). Unfortunately Excel 2007, which I used to create these charts, does not support patterns, however I found this excellent add-in from Andy Pope, and I thoroughly recommend it.

I think the two charts I have presented are much clearer than those in the original graphics from the newspaper article. However, a direct comparison between the two would not be entirely fair. The graphics used in the media often have further constraints imposed on them, (1) They are often restricted in size, having to fit within a fixed page size layout, (2) They should be eye-catching and visually appealing, drawing a potential reader towards the article.

With this in mind, I have re-worked the graphs above into the same layout and size as the originals. I have even added drop-shadows for visual appeal …

I think the above is a good compromise between providing an artistically pleasing graphic whilst still allowing the reader to understand the data (and from there spot trends etc…).

Regards, Colin E.

Update: Thanks to Graham Odds for a few extra ideas about tidying up the final charts.