Colin Eberhardt's Technology Adventures

#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.

A Simplified Grid Markup for Silverlight and WPF

December 21st, 2010

The WPF / Silverlight syntax is long and cumbersome. This blog post describe a simple attached property that allows you to specify row and column widths / heights as a simple comma separated list, e.g. RowDefinitions=”Auto,,3*,,,,2*”

The Grid is probably one of the most useful and versatile layouts that Silverlight and WPF offers. However, if you hand craft your XAML, as I do, you will probably start to find the Grid markup for defining rows and columns to be verbose and cumbersome. If we look at the following example, which uses a mixture of Auto, Star and Pixel widths / heights:

Despite this being a simple example, the required row and column definitions are highly verbose:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition/>
    <RowDefinition/>
    <RowDefinition/>
    <RowDefinition/>
    <RowDefinition/>
    <RowDefinition Height="2*"/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="100"/>
    <ColumnDefinition/>
  </Grid.ColumnDefinitions>
 
  <TextBlock Text="User Details" FontSize="20"
          HorizontalAlignment="Center"
          Grid.ColumnSpan="2"/>
 
  <TextBlock Text="Forename:"
              Grid.Row="1"/>
  <TextBox Text="Jeremy"
            Grid.Column="1" Grid.Row="1"/>
 
  <TextBlock Text="Surname:"
              Grid.Row="2"/>
  <TextBox Text="James"
            Grid.Column="1" Grid.Row="2"/>
 
  <TextBlock Text="Age:"
              Grid.Row="3"/>
  <TextBox Text="24"
            Grid.Column="1" Grid.Row="3"/>
 
  <TextBlock Text="Phone:"
              Grid.Row="4"/>
  <TextBox Text="+44 191 555467"
            Grid.Column="1" Grid.Row="4"/>
 
  <TextBlock Text="Notes:"
              Grid.Row="5"/>
  <TextBox Text="A multi-line block of text ..." 
            TextWrapping="Wrap"
            Grid.Row="6" Grid.ColumnSpan="2"/>
</Grid>

To provide a simpler alternative, I have created attached properties, one for the column definitions, and the other for the rows. These attached properties allow you to specify the columns / rows as a comma separated list of heights and widths.

Using this technique, the above example becomes:

<Grid local:GridUtils.ColumnDefinitions="100,"
      local:GridUtils.RowDefinitions="Auto,,,,,,2*">
 
  <TextBlock Text="User Details" FontSize="20"
          HorizontalAlignment="Center"
          Grid.ColumnSpan="2"/>
 
  <TextBlock Text="Forename:"
              Grid.Row="1"/>
  <TextBox Text="Jeremy"
            Grid.Column="1" Grid.Row="1"/>
 
  <TextBlock Text="Surname:"
              Grid.Row="2"/>
  <TextBox Text="James"
            Grid.Column="1" Grid.Row="2"/>
 
  <TextBlock Text="Age:"
              Grid.Row="3"/>
  <TextBox Text="24"
            Grid.Column="1" Grid.Row="3"/>
 
  <TextBlock Text="Phone:"
              Grid.Row="4"/>
  <TextBox Text="+44 191 555467"
            Grid.Column="1" Grid.Row="4"/>
 
  <TextBlock Text="Notes:"
              Grid.Row="5"/>
  <TextBox Text="A multi-line block of text ..." 
            TextWrapping="Wrap"
            Grid.Row="6" Grid.ColumnSpan="2"/>
</Grid>

The comma separated list defines the widths / heights of each column / row. The number of items in this list defined the number of rows / column that are generated. This notation supports pixel, star and auto widths / heights. Also, if you want a row or column with the default size, just leave the value blank, i.e. a row definition of “,,,” will create four rows of default height.

The code to achieve this is pretty simple, involving a couple of attached properties and string parsing in their changed event handlers. The complete code for the GridUtils class is given below, feel free to copy and paste it into your project:

using System.Windows;
using System.Windows.Controls;
 
namespace SimplifiedGrid
{
  public class GridUtils
  {
    #region RowDefinitions attached property
 
    /// <summary>
    /// Identified the RowDefinitions attached property
    /// </summary>
    public static readonly DependencyProperty RowDefinitionsProperty =
        DependencyProperty.RegisterAttached("RowDefinitions", typeof(string), typeof(GridUtils),
            new PropertyMetadata("", new PropertyChangedCallback(OnRowDefinitionsPropertyChanged)));
 
    /// <summary>
    /// Gets the value of the RowDefinitions property
    /// </summary>
    public static string GetRowDefinitions(DependencyObject d)
    {
      return (string)d.GetValue(RowDefinitionsProperty);
    }
 
    /// <summary>
    /// Sets the value of the RowDefinitions property
    /// </summary>
    public static void SetRowDefinitions(DependencyObject d, string value)
    {
      d.SetValue(RowDefinitionsProperty, value);
    }
 
    /// <summary>
    /// Handles property changed event for the RowDefinitions property, constructing
    /// the required RowDefinitions elements on the grid which this property is attached to.
    /// </summary>
    private static void OnRowDefinitionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      Grid targetGrid = d as Grid;
 
      // construct the required row definitions
      targetGrid.RowDefinitions.Clear();
      string rowDefs = e.NewValue as string;
      var rowDefArray = rowDefs.Split(',');
      foreach (string rowDefinition in rowDefArray)
      {
        if (rowDefinition.Trim() == "")
        {
          targetGrid.RowDefinitions.Add(new RowDefinition());
        }
        else
        {
          targetGrid.RowDefinitions.Add(new RowDefinition()
          {
            Height = ParseLength(rowDefinition)
          });
        }
      }
    }
 
    #endregion
 
 
    #region ColumnDefinitions attached property
 
    /// <summary>
    /// Identifies the ColumnDefinitions attached property
    /// </summary>
    public static readonly DependencyProperty ColumnDefinitionsProperty =
        DependencyProperty.RegisterAttached("ColumnDefinitions", typeof(string), typeof(GridUtils),
            new PropertyMetadata("", new PropertyChangedCallback(OnColumnDefinitionsPropertyChanged)));
 
    /// <summary>
    /// Gets the value of the ColumnDefinitions property
    /// </summary>
    public static string GetColumnDefinitions(DependencyObject d)
    {
      return (string)d.GetValue(ColumnDefinitionsProperty);
    }
 
    /// <summary>
    /// Sets the value of the ColumnDefinitions property
    /// </summary>
    public static void SetColumnDefinitions(DependencyObject d, string value)
    {
      d.SetValue(ColumnDefinitionsProperty, value);
    }
 
    /// <summary>
    /// Handles property changed event for the ColumnDefinitions property, constructing
    /// the required ColumnDefinitions elements on the grid which this property is attached to.
    /// </summary>
    private static void OnColumnDefinitionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      Grid targetGrid = d as Grid;
 
      // construct the required column definitions
      targetGrid.ColumnDefinitions.Clear();
      string columnDefs = e.NewValue as string;
      var columnDefArray = columnDefs.Split(',');
      foreach (string columnDefinition in columnDefArray)
      {
        if (columnDefinition.Trim() == "")
        {
          targetGrid.ColumnDefinitions.Add(new ColumnDefinition());
        }
        else
        {
          targetGrid.ColumnDefinitions.Add(new ColumnDefinition()
          {
            Width = ParseLength(columnDefinition)
          });
        }
      }
    }
 
    #endregion
 
    /// <summary>
    /// Parses a string to create a GridLength
    /// </summary>
    private static GridLength ParseLength(string length)
    {
      length = length.Trim();
 
      if (length.ToLowerInvariant().Equals("auto"))
      {
        return new GridLength(0, GridUnitType.Auto);
      }
      else if (length.Contains("*"))
      {
        length = length.Replace("*","");
        if (string.IsNullOrEmpty(length)) length = "1";
        return new GridLength(double.Parse(length), GridUnitType.Star);
      }
 
      return new GridLength(double.Parse(length), GridUnitType.Pixel);
    }
  }
}

The code which parses the row and column strings is also executed by the Visual Studio designer, so you can see your new rows / columns immediately. It also works just fine in WPF and Silverlight.

You can download example projects in both technologies: SimplfiedGrid.zip

readers might also be interested in Mike Talbot’s AutoGrid which dynamically adds rows / columns based as children are added to the grid. A very neat idea!

(Thanks to Rob Newsome for coming up with this idea in the first place!)

Regards, Colin E.

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.

Silverlight 5 Adoption Predictions

December 6th, 2010

The announced launch of Silverlight 5 has got the developer community all excited about improved media capabilities, MVVM support, printing and 3D, but how will Silverlight adoption evolve throughout 2011. In this blog post I look at historic data and use this to predict a 76% adoption of Silverlight 5 by the end of 2011.

The Silverlight Firestarter event last week (December 2nd 2010) saw Microsoft’s Scott Guthrie use his keynote speech to announce the launch of Silverlight 5 which will emerge at some point in the first half of 2011. The new features, which my colleague Gergely details in a recent blog post, are geared towards media and business applications, which in my opinion is a good thing. This further re-enforces Silverlight’s positions as a plugin for application development, making it easier to differentiate it from HTML5.

New versions of Silverlight are being released at quite a high rate (approximately 1.5 VIPAs), however I feel that feature completeness isn’t really an obstacle that prevents people from using Silverlight, as a technology, it feels quite mature already. I think the main obstacle that prevents people from choosing Silverlight is the adoption statistics, i.e. the proportion of people who have the Silverlight plugin installed. With the announced release of Silverlight 5, I thought I would look at the current Silverlight adoption and make some predictions for the future.

There are a couple of prominent sites that collect plugin adoption staticstics, riastats and StatOwl. Currently they give adoptions statistics for Silverlight of 67% and 58% respectively, with the difference between these values most probably being due to the demographic of the sites which they collect statistics from. StatOwl provides pretty detailed historic statistics, using this data the chart below shows the adoption of various Silverlight versions over the past couple of years:

I would expect the total adoption to follow a sigmoid, as per the diffusion of innovations theory, and you can just about see that the adoption is starting to decelerate. To make future predictions I have fitted a logistic function to the total adoption. What I find quite interesting is the adoption curves for each new Silverlight version, which are the components of the total adoption. Each starts with a rapid sigmoid conversion, followed by an exponential ‘tail’. Fitting a curve to the total adoption, then normalizing reveals this shape more clearly:

If I take a guess at the Silverlight 5 launch date, based on Scott’s announcement that it will be in the first half of 2011, I can extrapolate the above chart to give the adoption curve for Silverlight 5:

The shaded area in the above chart is the extrapolated future adoption. De-normalizing the above gives the following total adoption predictions:

The above chart predicts that Silverlight 5 will have a 76% adoption by the end of 2011, and that the total Silverlight adoption will be 81%.

I will be interesting to see how good these predictions are!

Regards, Colin E.