Colin Eberhardt's Technology Adventures

WPF Charting Performance Comparisons (the Battle Continues)

April 24th, 2012

This blog post presents a thorough analysis of the performance of various WPF Charting components. The results show that a new class of charting solutions, which use raster-based graphics as opposed to retained mode vector graphics, provide a considerable performance advantage.

Introduction

Readers of my blog will know that charting and performance are two things that interest me greatly. One of the first things I did back in 2009 when I started working with WPF and Silverlight was have a go with the Microsoft ‘toolkit’ charts. Unfortunately I found these charts, and the various third party competitors, to be painfully slow when rendering just a few hundred datapoints. As a result of these findings I set about creating a high performance chart for Silverlight and WPF, which eventually became Visiblox charts.

In December 2010 I published a performance comparison that demonstrated that Visiblox was approximately 50-100 times faster than the Toolkit charts and 10 times faster than Visifire charts. At the time the only charts which came close were those developed as part of Microsoft’s now abandoned Dynamic Data Display project. Since publishing these figures, a number of the WPF and Silverlight charting vendors have started to take performance much more seriously, with Visifire more than doubling performance amid claims of being the fastest chart, amCharts have also publishing some very good performance figures as have Telerik.

Much has changed in the year and a half since my original performance blog post and I thought it would be a good idea to repeat the tests. However, this time I wanted to be more thorough, including a few different types of tests and also using WPF instead of Silverlight, due to the number of email requests I have had for a WPF charting comparison.

The Test Suite

In order to allow me to collect performance data for a range of WPF charts across a range of tests, I have written an automated performance test suite. The application uses MEF in order to allow me to easily slot in new tests and new charting providers. If you want a copy of the code, please get in touch.

The suite executes the following tests:

  1. Line Series Frame Rate – this test initially renders a ‘streaming’ line series which a fixed number of datapoints. New data is added to the chart as quickly as possible, and each time a new point is added to the front and old point is removed in order to keep a fixed number of points in view. The chart axes are auto-scaled (API permitting). The test initially starts with 50 datapoints, with each test run doubling this number.
  2. Scatter Series Frame Rate – a number of points are added to the chart. The location of these points is then animated in a manner that emulates Brownian motion. The test initially starts with 50 datapoints, with each test run doubling this number.
  3. Scatter Series Memory Usage – the above test is re-run, but this time rather than measuring render frame rate, the memory consumed as a result of adding the datapoints is recorded.

Initially I used a timer to measure framerate, followed by an attempt to measure via CompositionTarget.Rendering. However, I found that neither method gave an accurate frame rate measure, indeed the WPF team report that the CompositionTarget.Rendering can fire multiple times per frame. Instead, I opted for a more manual measurement using the WpfPerf tool, which makes use of the ETW events emitted by WPF.

Line Series Results

The results of the line series framerate tests are shown below:

NOTE: I am using a log-log scale for the above chart in order to make it easier to see the shape of the various curves.

Regarding which charting vendors I used for comparison, I tried to include all established and well known vendors in my comparison. For those which have already published performance figures or comments relating to Visiblox, I have included their names. For the others I have simply given them a token name ‘Vendor X’, in order to comply with the trial licencing terms which typically prohibit the publishing of performance figures.

One thing worth noting is that some charting vendors use sampling in order to improve framerates. I found that Vendor A (not included above) uses sampling without providing a mechanism for turning it off. Whereas most vendors that do support sampling allow you to turn it on or off and configure the sampling algorithm used. In order to provide a fair comparison, sampling was turned off on all the charts.

The chart above reveals some interesting patterns, due to the two very different classes of chart under test.

Vector based charts – WPF UIs are rendered via retained mode vector graphics. Whilst this approach is what has given rise to the very rich UI framework that WPF applications enjoy, it has been found that this approach just doesn’t perform as well as the bitmap-based approach used by Windows Forms when faced with complex UIs. This has led some people to use WinForms charts within their WPF applications! We can see that the faster vector charts from Microsoft Toolkit, Vendor B, the updated Visifire and Visiblox charts are all close to the limit of what is possible with this style of rendering.

“ … the major ‘partners’ cannot create a WPF chart that renders 2000 floating points of data in less that 5 seconds. I have tried them all and they are slow so we have had to regress to a WinForms version that renders the same data in milliseconds.” – from the channel 9 forums.

Raster based charts – Because of the limits of the WPF rendering system the two fastest charts in this test, Visiblox (via the new raster series type as part of the Ultimate Editition) and SciChart (a newcomer that exclusively uses this approach) both use a raster / bitmap graphics approach, which the WPF and Silverlight frameworks both have support for via the WriteableBitmap class. This approach yields results which are more than 100 times faster than what is possible with vector graphics.

One of the other common complaints made about WPF charts is the initial render time, with charts containing just a few thousands datapoints taking seconds to show on screen. Whilst this test focusses in framerates, it is also gives a good indication of the initial render times, with raster based charts appearing almost instantly when loaded with tens of thousands of points.

Scatter Series Results

The results of the Scatter series tests are shown below:

Again, the same pattern emerges, with the raster based approach yielding a significant increase in frame rate over the vector based.

Scatter Series Memory Usage

The final test looks at the memory consumption of the various charting providers:

The above shows that the memory consumption is directly related to the number of datapoints present in the chart, however, the scale of this relationship is very different for the charts under test. I have a feeling that the Toolkit charts have a memory leak which contributes to the poor performance in this test.

Conclusions

The overall performance of the charting solutions available to WPF (and Silverlight) developers is improving. However, the performance of the WPF rendering system appears to create an upper limit which constrains what can be achieved. The raster-based approach is not subject to these same constraints and as a result provides a considerable improvement in performance.

If you want to have access to the code I used to make these tests, please get in touch. Also, if you are a charting vendor and think you might be Vendor A, B or C in these tests, and wish to be named, let me know.

Visualising StackOverflow Tag Relationships with Silverlight

February 20th, 2012

UPDATE: I have posted the sourcecode for this control on codeproject.

Recently I have been wondering about the wealth of information that can be gleaned from the 2.5 million programming question on Stack Overflow. A few weeks back I found a tag trending tool, which can be used to measure the rise and fall in popularity of tags over time. Whilst this is a great little tool, I am sure there is much more that can be done with the freely available Stack Overflow data, for example, exploring the relationships between the many technologies people ask questions about.

On a recent trip to Copenhagen I decided to put my hours of travelling time to good use and create a Silverlight application that plots the relationships between the various tags. I created an application that downloaded the 1,000 most recent questions via the Stack Overflow API and plotted the relationships between the 20 most popular tags, as seen above.

The graph is constructed as follows:

  • The size of each segment is proportional to the number of questions relating to the tag, i.e. android and java are the most popular tags.
  • Connections between tags indicate questions that have been tagged with both technologies. The thickness of the connection indicates how many questions share these two tags, i.e. jQuery and JavaScript tags appear together quite often.
  • Each segment is coloured based on the number of connections it has, red for many connections, blue for few.

The ordering of segments can be changed using the drop-down control. Probably one of the most interesting views is the one where related tags are clustered. This is done by assigning a ‘weight’ to the current configuration of the graph by summing the length of all connections, with connections that cross the centre of the circle adding most weight. An iterative process is used to minimise the overall graph weight by moving each segment a few steps left and right, until the least ‘weighty’ configuration is found. This is the one where each tag is most closely related to its neighbours.

When clustering is applied we can see small ‘pockets’ of related technologies, with the following patterns emerging

  • The two most popular tags, Java and Android, are very closely related to each other, but have very few other relationships.
  • iOS, Objective-C and iPhone form a close-knit group. However, Objective-C questions are sometimes also tagged with C#, C and C++.
  • C#, .NET and ASP.NET are clustered, however C# has links with many other tags
  • The strongest relationship is between jQuery and JavaScript, probably due to jQuery having become the de-facto framework for JavaScript development, being used on 53% of websites.
  • There is a large cluster of connected web technologies, CSS, HTML, JavaScript, jQuery, reflecting the mix of technologies involved in creating web sites and web applications.
  • Python, whilst being a popular tag, has very few relationships, only being weakly linked to PHP.

I am planning on tidying up the code for this visualisation, making it more generic, allowing it to be used to graph other datasets. Let me know if you are interested in this!

Here is the same graph, but showing the top 30 tags, again, more interesting relationships start to emerge:

Finally, thanks to Chris P., Adrian C. and Graham O. for their ideas and input!

Regards, Colin E.

 

Adding Error Bars to Visiblox Silverlight Charts

October 21st, 2011

Having spent a number of years studying Physics at university, I have had the importance of error bars well and truly drummed into me! Within physics, or any experimental science, there are always going to be errors in the measurements you make. The more repeat measurements you make, the more confident you can be in the mean value, however you cannot remove the errors altogether. Error bars provide a way to represent the spread of experimental observations graphically, without them, it is hard to have any confidence in the conclusions drawn from the observations!

In this blog post I will show how to implement a custom Visiblox chart series to render error bars:

(The data in the above chart is from a page which details how to calculate the standard error from experimental results).

Creating a Custom Series

As described in my previous blog post on creating a spline series, to create a new series type, you sub-class one of the Visiblox base-classes, in this case MultiValueSeriesBase is a suitable starting point:

public class ErrorBarSeries : MultiValueSeriesBase
{ 
  protected override FrameworkElement CreatePoint(IDataPoint dataPoint)
  {
    throw new NotImplementedException();
  }
 
  protected override void RenderDataLabels()
  {
    throw new NotImplementedException();
  }
}

I don’t want data labels, so the only method I need to implement is CreatePoint, which takes the (multi-valued) point to be rendered as its only argument. The lifecycle of point creating and destruction is taken care of by the base-class.

The IDataPoint has a string indexer which is used to retrieve multiple Y values for multi-valued series. It is a good idea to define these in a single place, here we define the three y-values required for an error-bar series:

public static readonly string ErrorUp = "ErrorUp";
 
public static readonly string ErrorDown = "ErrorDown";
 
public static readonly string Value = "Value";

The CreatePoint implementation for this series creates a Path as follows:

protected override FrameworkElement CreatePoint(IDataPoint dataPoint)
{
  var lineGeometry = BuildGeometry(dataPoint);
 
  Path line = new Path();
  line.Stroke = new SolidColorBrush(Colors.Black);
  line.Fill = new SolidColorBrush(Colors.Gray);
  line.StrokeThickness = 1.0;
  line.StrokeLineJoin = PenLineJoin.Bevel;
  line.Data = lineGeometry;
  line.SetValue(ZoomCanvas.IsScaledPathProperty, true);
 
  return line;
}

The BuildGeometry method does most of the work, extracting the values from the IDataPoint, transforming them (via the axis) to the required coordinate system, then creating a suitable geometry:

/// <summary>
/// Creates the geometry for the given datapoint
/// </summary>
private PathGeometry BuildGeometry(IDataPoint dataPoint)
{
  var halfWidth = SuggestedPointWidth * WidthFactor;
 
  // obtain the data values
  var topDataValue = dataPoint[ErrorUp] as IComparable;
  var middleDataValue = dataPoint[Value] as IComparable;
  var bottomDataValue = dataPoint[ErrorDown] as IComparable;
 
  // convert to a the required render coordinates
  double topRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(topDataValue);
  double middleRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(middleDataValue);
  double bottomRenderPos = YAxis.GetDataValueAsRenderPositionWithoutZoom(bottomDataValue);
 
  double xMiddleRenderPos = XAxis.GetDataValueAsRenderPositionWithoutZoom(dataPoint.X);
  double xRightRenderPos = xMiddleRenderPos - halfWidth;
  double xLeftRenderPos = xMiddleRenderPos + halfWidth;
 
  // build a suitable gemoetry
  PathGeometry lineGeometry = new PathGeometry();
 
  PathFigure upperVerticalLine = CreateLineFigure(
    new Point(xMiddleRenderPos, middleRenderPos - halfWidth),
    new Point(xMiddleRenderPos, topRenderPos));
  lineGeometry.Figures.Add(upperVerticalLine);
 
  PathFigure lowerVerticalLine = CreateLineFigure(
    new Point(xMiddleRenderPos, bottomRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos + halfWidth));
  lineGeometry.Figures.Add(lowerVerticalLine);
 
  PathFigure upperBar = CreateLineFigure(
    new Point(xLeftRenderPos, topRenderPos),
    new Point(xRightRenderPos, topRenderPos));
  lineGeometry.Figures.Add(upperBar);
 
  PathFigure lowerBar = CreateLineFigure(
    new Point(xLeftRenderPos, bottomRenderPos),
    new Point(xRightRenderPos, bottomRenderPos));
  lineGeometry.Figures.Add(lowerBar);
 
  PathFigure center = CreateLineFigure(
    new Point(xMiddleRenderPos - halfWidth, middleRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos + halfWidth),
    new Point(xMiddleRenderPos + halfWidth, middleRenderPos),
    new Point(xMiddleRenderPos, middleRenderPos - halfWidth)
  );
  lineGeometry.Figures.Add(center);
 
  return lineGeometry;
}
 
/// <summary>
/// Create a line figure that connects the given points
/// </summary>
private PathFigure CreateLineFigure(params Point[] points)
{
  // add all the points (except the first)
  var pointCollection = new PointCollection();
  foreach (var point in points.Skip(1))
  {
    pointCollection.Add(point);
  }
 
  // create a figure, using the first point as the StartPoint.
  return new PathFigure()
  {
    IsClosed = true,
    StartPoint = points.First(),
    Segments = new PathSegmentCollection()
    {
      new PolyLineSegment
      {
        Points = pointCollection
      }
    }
  };
}

We can now create an instance of this series in XAML:

<vis:Chart x:Name="chart">
  <vis:Chart.Series>
    <local:ErrorBarSeries/>
  </vis:Chart.Series>
</vis:Chart>

Supplying data to the chart via MultiValuedDataPoint as follows:

public MainPage()
{
  InitializeComponent();
 
  var data = new DataSeries<double, double>();
  data.Add(CreatePoint(-195, 1.4, 0.2));
  data.Add(CreatePoint(0, 62.2, 9.3));
  data.Add(CreatePoint(20, 70.4, 6.5));
  data.Add(CreatePoint(100, 77.4, 1.9));
 
  chart.Series[0].DataSeries = data;
}
 
private MultiValuedDataPoint<double, double> CreatePoint(double x, double y, double error)
{
  var point = new MultiValuedDataPoint<double, double>(x,
    new Dictionary<object, double>()
    {
      { ErrorBarSeries.ErrorUp, y + error },
      { ErrorBarSeries.ErrorDown, y - error },
      { ErrorBarSeries.Value, y }
    });
  return point;
}

This results in the following chart:

Binding to a Multi-valued Series

In the previous example we created instances of MultiValuedDataPoint, a Visiblox type for representing multi-valued points. As an alternative, we can create model objects to represent each point, rendering them in the chart via databinding.

We first modify the code to create a collection of Measurement instances (a simple model object that implements INotifyPropertyChanged):

public MainPage()
{
  InitializeComponent();
 
  var data = new ObservableCollection<Measurement>();
  data.Add(CreateMeasurement(-195, 1.4, 0.2));
  data.Add(CreateMeasurement(0, 62.2, 9.3));
  data.Add(CreateMeasurement(20, 70.4, 6.5));
  data.Add(CreateMeasurement(100, 77.4, 1.9));
  this.DataContext = data;    
}
 
private Measurement CreateMeasurement(double x, double y, double error)
{
  return new Measurement()
    {
      XValue = x,
      YValue = y,
      YValueErrorUp = y + error,
      YValueErrorDown = y - error
    };
}

The markup for the chart is modified to use a BindableDataSeries, with bindings specified for the various component of the error bar series. Also, the ItemsSource of the BindableDataSeries is bound to the inherited DataContext:

<local:ErrorBarSeries>
  <local:ErrorBarSeries.DataSeries>
    <vis:BindableDataSeries ItemsSource="{Binding}"
                            XValueBinding="{Binding XValue}">
      <vis:BindableDataSeries.YValueBindings>
        <vis:YValueBinding YValueKey="Value" Binding="{Binding YValue}"/>
        <vis:YValueBinding YValueKey="ErrorUp" Binding="{Binding YValueErrorUp}"/>
        <vis:YValueBinding YValueKey="ErrorDown"  Binding="{Binding YValueErrorDown}"/>
      </vis:BindableDataSeries.YValueBindings>              
    </vis:BindableDataSeries>
  </local:ErrorBarSeries.DataSeries>
</local:ErrorBarSeries>

We can also display our data in a DataGrid, allowing us to manipulate the values (not that I condone manipulation of scientific data!), with the changes being reflected in the chart:

<sdk:DataGrid Grid.Row="1"
              x:Name="grid"
              ItemsSource="{Binding}"/>

This gives us the following application:

You can edit the values, with the changes reflected immediately in the chart above.

You can download the sourcecode for the above example here: VisibloxErrorBarSeries.zip

Regards, Colin E.

#uksnow #silverlight The Movie! – Happy Christmas.

December 24th, 2010

It’s Christmas Eve and time for some fun! A few weeks back I published an article on Reactive Extensions where I created a Bing Maps / Twitter mashup that plotted the geolocation of #uksnow twitter tags. This twitter hashtag was popularised by Ben Marsh and is used by us weather obsessed brits to give a realtime update of snowfall. To use this hashtag tweet #uksnow with your postcode and the amount of snowfall out of ten (e.g. 7/10).

The snow has continued to fall in record quantities across the uk, so I thought it might be fun to create “#uksnow The Movie”, where you can replay the snowfall across the UK. You can see the finished app below. Hit the ‘play’ button, or drag the time indicator on the chart to replay the snowfall and re-live the fun! You can also mouse-over the snowflakes to see the original #uksnow tweet.

To create this application I modified my original app to extract as many #uksnow tweets from twitter as possible. Unfortunately the public search API only server tweets from the last ~ 7 days. If anyone knows of a free historic service, please let me know!

The application above contains an XML file with all the historic tweets. When it starts up, it parses the XML file.The chart displays the tweets ‘binned’ in 30 minute intervals, which is achieved via a simple GroupBy query:

// parse the XML file
_tweets = doc.Descendants("tweet").Select(el => new GeoCodedUKSnowTweet()
                                  {
                                    Author = el.Attribute("author").Value,
                                    Title = el.Attribute("title").Value,
                                    ProfileImageUrl = el.Attribute("profile").Value,
                                    Location = StringToPoint(el.Attribute("loc").Value),
                                    SnowFactor = int.Parse(el.Attribute("snowfactor").Value),
                                    Timestamp = DateTime.Parse(el.Attribute("timestamp").Value)
                                  })
                                  .OrderBy(t => t.Timestamp)                                        
                                  .ToList();
 
// total the tweets per 30 minute interval
var groupedByHour = _tweets.GroupBy(t => RoundDateToMinutes(t.Timestamp, 30))
                          .Select(group => 
                            new TweetsPerHour() {
                              Hour = group.Key,
                              Tweets = group.Count()
                            }).ToList();
 
// associate the above with the chart
chart.Series[0].YAxis = chart.SecondaryYAxis;
((BindableDataSeries)chart.Series[0].DataSeries).ItemsSource = groupedByHour;

I used the free Visiblox chart to display the tweet frequency. A LineSeries with a BindableDataSeries is used to render the results of the query above using databinding.

<vis:LineSeries.DataSeries>
  <vis:BindableDataSeries
         XValueBinding="{Binding Path=Hour}"
         YValueBinding="{Binding Path=Tweets}"/>
</vis:LineSeries.DataSeries>

The Visiblox chart has a pluggable behaviour concept which allows you to add interactivity to the chart. The vertical line that indicates the current time uses this behaviour framework. When the user interacts with the chart via mouse movements, these interactions are directed to any active behaviours, which can then update their state, possibly interacting with the chart or its axes. For example, when the code which updates the date as the user drags the line is shown below:

public override void MouseLeftButtonDown(Point position)
{
  base.MouseLeftButtonDown(position);
  _mouseDown = true;
}
 
public override void MouseLeftButtonUp(Point position)
{
  base.MouseLeftButtonUp(position);
  _mouseDown = false;
}
 
public override void MouseMove(Point position)
{
  if (_mouseDown)
  {
    CurrentDate = (DateTime)Chart.XAxis.GetRenderPositionAsDataValueWithZoom(position.X);
  }
}

For the sake of simplicity, the calendar and clock controls are implemented as UserControls, for example the calendar has the following markup:

<Grid x:Name="LayoutRoot" Background="Transparent">
  <Border CornerRadius="8"
            Background="White"
            Margin="5">
    <Border.Effect>
      <DropShadowEffect Color="LightGray"
                        Opacity="0.8"
                        ShadowDepth="2"/>
    </Border.Effect>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="15"/>
        <RowDefinition/>
      </Grid.RowDefinitions>
      <Border CornerRadius="8,8,0,0"
              BorderThickness="0">
        <Border.Background>
          <LinearGradientBrush StartPoint="0,0"
                                EndPoint="0,1">
            <GradientStop Color="White" Offset="0"/>
            <GradientStop Color="Red" Offset="0.9"/>
            <GradientStop Color="DarkRed" Offset="1"/>
          </LinearGradientBrush>
        </Border.Background>
      </Border>
      <TextBlock Text="Friday"
                  x:Name="DayOfWeek"
                  FontWeight="Bold"
                  FontSize="8"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"/>
      <TextBlock Text="28"
                  x:Name="Day"
                  Grid.Row="1"
                  FontWeight="Bold"
                  FontSize="20"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"/>
    </Grid>    
  </Border>
</Grid>

And the following minimal code-behind:

public partial class CalendarControl : UserControl
{
  public CalendarControl()
  {
    InitializeComponent();
  }
 
  public void SetDate(DateTime date)
  {
    DayOfWeek.Text = date.ToString("ddd");
    Day.Text = date.ToString("dd");
  }
}

The clock control is very similar, using a few concepts that I wrote abut in my gauge control blog post a few weeks back. It is good to see that Silverlight allows quick and direct implementations of controls, you do not always have to use MVVM, lookless controls and dependency properties. If it is Christmas Eve, and you are itching to get home and grab a drink, anything goes ;-)

You can download the full sourcecode here: UKSnowTheMovie.zip

Right … I’m off home.

Regards, Colin E.