New to Telerik UI for WinFormsStart a free 30-day trial

How to Plot Error Bars (x-y) on a Scatter Plot

Updated over 6 months ago

Environment

Product VersionProductAuthor
2021.1.122RadChartView for WinFormsDesislava Yordanova

Description

This tutorial demonstrates a sample approach how to achieve scatter plots with error bars.

Each point has 4 numbers , the x coordinate(x), the y coordinate(y), the uncertainty on the x coordinate (ux) and the uncertainty on the y coordinate(uy). The result is a dot on (x,y), then a horizontal line centered in the dot (x,y) with a width of 2*(ux), and a vertical line centered in (x,y) with a height of 2*(uy).

how-to-plot-error-bars-on-scatter-plot001

Solution

It will be necessary to use a custom renderer. Besides a custom renderer, it is necessary to create special scatter data points holding information about the deviations along the X and Y axes. Last, but not least, we will create a custom ScatterErrorBarsDrawPart where the actual rendering is being executed:

RadChartView's setup

C#
 public Form1()
{
    InitializeComponent();

    this.radChartView1.CreateRenderer += radChartView1_CreateRenderer;

    ScatterSeries scatterSeries = new ScatterSeries();
    scatterSeries.Name = "";
    scatterSeries.DataPoints.Add(new ScatterErrorBarsDataPoint(15, 18, 1, 2));
    scatterSeries.DataPoints.Add(new ScatterErrorBarsDataPoint(18, 10, 0.5, 3));
    scatterSeries.DataPoints.Add(new ScatterErrorBarsDataPoint(13, 15, 4, 2));
    scatterSeries.DataPoints.Add(new ScatterErrorBarsDataPoint(10, 8, 1, 1));
    scatterSeries.DataPoints.Add(new ScatterErrorBarsDataPoint(5, 12, 2, 2));

    scatterSeries.PointSize = new SizeF(8, 8);
    this.radChartView1.Series.Add(scatterSeries);
}

private void radChartView1_CreateRenderer(object sender, ChartViewCreateRendererEventArgs e)
{
    e.Renderer = new ScatterErrorBarsCartesianRenderer(e.Area as CartesianArea);
}

Custom renderer

C#
public class ScatterErrorBarsCartesianRenderer : CartesianRenderer
{

    public ScatterErrorBarsCartesianRenderer(CartesianArea area)
        : base(area)
    { }

    protected override void Initialize()
    {
        base.Initialize();

        for (int i = 0; i < this.DrawParts.Count; i++)
        {
            ScatterSeriesDrawPart scatterPart = this.DrawParts[i] as ScatterSeriesDrawPart;
            if (scatterPart != null)
            {
                this.DrawParts[i] = new ScatterErrorBarsDrawPart((ScatterSeries)scatterPart.Element, this);
            }
        }
    }
}

Custom ScatterErrorBarsDataPoint

C#
public class ScatterErrorBarsDataPoint : ScatterDataPoint
{
    private double? dx;
    private double? dy;

    public ScatterErrorBarsDataPoint(double x, double y, double dx, double dy)
        : base(x, y)
    {
        this.DeltaX = dx;
        this.DeltaY = dy;
    }

    public double? DeltaX
    {
        get
        {
            return this.dx;
        }
        set
        {
            this.dx = value;
        }
    }

    public double? DeltaY
    {
        get
        {
            return this.dy;
        }
        set
        {
            this.dy = value;
        }
    }
}

Custom ScatterErrorBarsDrawPart

C#
public class ScatterErrorBarsDrawPart : ScatterSeriesDrawPart
{
    public ScatterErrorBarsDrawPart(ScatterSeries series, IChartRenderer renderer)
        : base(series, renderer)
    {
    }

    public override void DrawSeriesParts()
    {
        PointF[] points = this.GetPointsPositionsArray();

        if (points == null)
        {
            return;
        }

        RadGdiGraphics radGraphics = new RadGdiGraphics(this.Renderer.Surface as Graphics);
        List<DataPointElement> pointElements = new List<DataPointElement>();
        DataPointElementLayoutSlotComparer comparer = new DataPointElementLayoutSlotComparer();

        if (this.Renderer is CartesianRenderer)
        {
            comparer.Vertical = ((CartesianRenderer)this.Renderer).Area.Orientation == Orientation.Vertical;
        }

        comparer.Collection = this.Element.DataPoints;

        for (int i = 0; i < this.Element.Children.Count; i++)
        {
            DataPointElement pointElement = this.Element.Children[i] as DataPointElement;
            int index = pointElements.BinarySearch(pointElement, comparer);
            pointElements.Insert(~index, pointElement);
        }

        for (int i = 0; i < pointElements.Count; i++)
        {
            DataPointElement childElement = pointElements[i];

            if (childElement == null || !childElement.IsVisible || childElement.PointSize.Width < 1f ||
                childElement.PointSize.Height < 1f)
            {
                continue;
            }

            SizeF pointSize = childElement.PointSize;
            PointF pointLocation = points[i];

            RectangleF ptRect = new RectangleF(pointLocation, pointSize);
            ptRect.Offset(pointSize.Width / -2f, pointSize.Height / -2f);

            if (childElement.BackgroundShape != null)
            {
                childElement.BackgroundShape.Paint((Graphics)radGraphics.UnderlayGraphics, ptRect);
            }


            GraphicsPath pointPath = null;

            if (childElement.Shape != null)
            {
                pointPath = childElement.Shape.CreatePath(ptRect);
            }
            else
            {
                pointPath = new GraphicsPath();
                pointPath.AddEllipse(ptRect);
            }

            if (pointPath != null)
            {
                // Paint the data point
                FillPrimitiveImpl pointFill = new FillPrimitiveImpl(childElement, null);
                pointFill.PaintFill(radGraphics, pointPath, ptRect);

                BorderPrimitiveImpl pointBorder = new BorderPrimitiveImpl(childElement, null);
                pointBorder.PaintBorder(radGraphics, null, pointPath, ptRect);

                if (childElement.Image != null)
                {
                    ImagePrimitiveImpl pointImage = new ImagePrimitiveImpl(childElement);
                    pointImage.PaintImage(radGraphics, childElement.Image, ptRect, childElement.ImageLayout, 
                        childElement.ImageAlignment, childElement.ImageOpacity, false);
                }

                pointPath.Dispose();


                NumericalAxis xAxis = (NumericalAxis)this.Element.Axes[0];
                NumericalAxis yAxis = (NumericalAxis)this.Element.Axes[1];
                ScatterErrorBarsDataPoint dataPoint = (ScatterErrorBarsDataPoint)childElement.DataPoint;

                // Paint the X deviation
                if (dataPoint.DeltaX.HasValue)
                {
                    double delta = xAxis.ActualRange.Maximum - xAxis.ActualRange.Minimum;
                    double x = this.OffsetX + xAxis.Model.LayoutSlot.X;

                    double min = dataPoint.XValue - dataPoint.DeltaX.Value;
                    double normalizedMinValue = (min - xAxis.ActualRange.Minimum) / delta;
                    double x1 = x + normalizedMinValue * xAxis.Model.LayoutSlot.Width;

                    double max = dataPoint.XValue + dataPoint.DeltaX.Value;
                    double normalizedMaxValue = (max - xAxis.ActualRange.Minimum) / delta;
                    double x2 = x + normalizedMaxValue * xAxis.Model.LayoutSlot.Width;

                    radGraphics.DrawLine(Color.Red, (float)x1, (float)dataPoint.LayoutSlot.Y + this.OffsetY, 
                        (float)x2, (float)dataPoint.LayoutSlot.Y + this.OffsetY, 1);
                }

                // Paint the Y deviation
                if (dataPoint.YValue.HasValue && dataPoint.DeltaY.HasValue)
                {
                    double delta = yAxis.ActualRange.Maximum - yAxis.ActualRange.Minimum;
                    double y = this.OffsetY + yAxis.Model.LayoutSlot.Y;

                    double min = dataPoint.YValue.Value - dataPoint.DeltaY.Value;
                    double normalizedMinValue = 1 - (min - yAxis.ActualRange.Minimum) / delta;
                    double y1 = y + normalizedMinValue * yAxis.Model.LayoutSlot.Height;

                    double max = dataPoint.YValue.Value + dataPoint.DeltaY.Value;
                    double normalizedMaxValue = 1 - (max - yAxis.ActualRange.Minimum) / delta;
                    double y2 = y + normalizedMaxValue * yAxis.Model.LayoutSlot.Height;

                    radGraphics.DrawLine(Color.Blue, (float)dataPoint.LayoutSlot.X + this.OffsetX, (float)y1, 
                        (float)dataPoint.LayoutSlot.X + this.OffsetX, (float)y2, 1);
                }
            }
        }
    }
}

See Also