Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

Adding Error Bars to Visiblox Silverlight Charts

, 1 Nov 2011
Rate this:
Please Sign up or sign in to vote.
In this blog post I will show how to implement a custom Visiblox chart series to render error bars.

Editorial Note

This article appears in the Third Party Product Reviews section. Articles in this section are for the members only and must not be used by tool vendors to promote or advertise products in any way, shape or form. Please report any spam or advertising.

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:

/// <span class="code-SummaryComment"><summary>
</span>/// Creates the geometry for the given datapoint
/// <span class="code-SummaryComment"></summary>
</span>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;
}

/// <span class="code-SummaryComment"><summary>
</span>/// Create a line figure that connects the given points
/// <span class="code-SummaryComment"></summary>
</span>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:

<object width="500" height="400" 
  data="data:application/x-silverlight," type="application/x-silverlight-2" >
<param name="source" 
  value="http://www.scottlogic.co.uk/blog/colin/wp-content/
         uploads/2011/10/VisibloxErrorBarSeries.xap" />
<a style="text-decoration: none;" 
  href="http://go.microsoft.com/fwlink/?LinkID=124807">
<img alt="Get Microsoft Silverlight" 
  src="http://go.microsoft.com/fwlink/?LinkId=108181" />
</a>
</object>

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 1 Nov 2011
Article Copyright 2011 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid