New to Telerik UI for WinForms? Download free 30-day trial

How to create sine wave using RadChartView spline AreaSeries

Environment

Product Version Product Author
2022.2.511 RadChartView for WinForms Dinko Krastev

Description

To create sine wave using AreaSeries we can use a custom renderer. The CartesianRenderer is responsible for painting the area for each of your data points.

Solution

The starting point of the article is to create a CustomCartesianRenderer class that inherits CartesianRenderer and overrides the Initialize method. This method creates and arranges draw parts responsible for the rendering of each RadChartView segment. After calling the base method, the DrawParts collection contains objects that know how to draw axes, labels, series etc. The particular draw part you would like to replace is of type AreaSeriesDrawPart. Your code should be like the following:


public class CustomCartesianRenderer : CartesianRenderer
{
    public CustomCartesianRenderer(CartesianArea area)
        : base(area)
    { }

    protected override void Initialize()
    {
        base.Initialize();
        for (int i = 0; i < this.DrawParts.Count; i++)
        {
            AreaSeriesDrawPart areaPart = this.DrawParts[i] as AreaSeriesDrawPart;
            if (areaPart != null)
            {
                this.DrawParts[i] = new CustomAreaSeriesDrawPart((AreaSeries)areaPart.Element, this);
            }
        }
    }
}



Public Class CustomCartesianRenderer
    Inherits CartesianRenderer

    Public Sub New(ByVal area As CartesianArea)
        MyBase.New(area)
    End Sub

    Protected Overrides Sub Initialize()
        MyBase.Initialize()

        For i As Integer = 0 To Me.DrawParts.Count - 1
            Dim areaPart As AreaSeriesDrawPart = TryCast(Me.DrawParts(i), AreaSeriesDrawPart)

            If areaPart IsNot Nothing Then
                Me.DrawParts(i) = New CustomAreaSeriesDrawPart(CType(areaPart.Element, AreaSeries), Me)
            End If
        Next
    End Sub
End Class


Now we need to create our custom AreaSeriesDrawPart class. In the custom class we can override the DrawArea() in which we can create custom logic to calculate the area of each segment.


public class CustomAreaSeriesDrawPart : AreaSeriesDrawPart
{
    public CustomAreaSeriesDrawPart(AreaSeries series, IChartRenderer renderer)
        : base(series, renderer)
    { }

    protected override void DrawArea()
    {
        CartesianRenderer renderer = (CartesianRenderer)this.Renderer;
        AreaSeries area = this.Element as AreaSeries;
        Graphics graphics = renderer.Graphics;
        RadGdiGraphics radGraphics = new RadGdiGraphics(graphics);

        RectangleF rect = ChartRenderer.ToRectangleF(this.Element.Model.LayoutSlot);
        RectangleF clipRect = (RectangleF)renderer.Area
            .GetType()
            .GetMethod("GetCartesianClipRect", BindingFlags.Instance | BindingFlags.NonPublic)
            .Invoke(renderer.Area, new object[]{});

        PointF topLeft = new PointF(clipRect.X, clipRect.Y);
        PointF topRight = new PointF(clipRect.Right - 1, clipRect.Y);
        PointF lowerRight = new PointF(clipRect.Right - 1, clipRect.Bottom - 1);
        PointF lowerLeft = new PointF(clipRect.X, clipRect.Bottom - 1);

        List<PointF[]> allPoints = GetPointsPositionsArrays();

        foreach (PointF[] points in allPoints)
        {
            if (points.Length < 2)
            {
                continue;
            }

            GraphicsPath fillPath = this.GetLinePaths(points);

            if (fillPath == null)
            {
                continue;
            }

            if (this.Element.View.GetArea<CartesianArea>().Orientation == System.Windows.Forms.Orientation.Vertical)
            {
                if (area.VerticalAxis.IsInverse)
                {
                    fillPath.AddLine(points[points.Length - 1], new PointF(points[points.Length - 1].X, topRight.Y));
                    fillPath.AddLine(topRight, topLeft);
                    fillPath.AddLine(new PointF(points[0].X, topLeft.Y), points[0]);
                }
                else
                {
                    fillPath.AddLine(points[points.Length - 1], new PointF(points[points.Length - 1].X, points[0].Y));
                }
            }
            else
            {
                if (area.HorizontalAxis.IsInverse)
                {
                    fillPath.AddLine(points[points.Length - 1], topRight);
                    fillPath.AddLine(topRight, lowerRight);
                    fillPath.AddLine(lowerRight, points[0]);
                }
                else
                {
                    fillPath.AddLine(points[points.Length - 1], topLeft);
                    fillPath.AddLine(topLeft, lowerLeft);
                    fillPath.AddLine(lowerLeft, points[0]);
                }
            }

            FillPrimitiveImpl fill = new FillPrimitiveImpl(this.Element, null);
            fill.PaintFill(radGraphics, fillPath, clipRect);

            GraphicsPath borderPath = new GraphicsPath();
            AreaSeries series = (AreaSeries)this.Element;

            borderPath = new GraphicsPath();

            if (series.StrokeMode == AreaSeriesStrokeMode.All ||
                series.StrokeMode == AreaSeriesStrokeMode.AllButPlotLine ||
                series.StrokeMode == AreaSeriesStrokeMode.LeftAndPoints ||
                series.StrokeMode == AreaSeriesStrokeMode.LeftLine)
            {
                if (this.Element.View.GetArea<CartesianArea>().Orientation == System.Windows.Forms.Orientation.Vertical)
                {
                    if (area.VerticalAxis.IsInverse)
                    {
                        borderPath.AddLine(topLeft, points[0]);
                    }
                    else
                    {
                        borderPath.AddLine(lowerLeft, points[0]);
                    }
                }
                else
                {
                    if (area.HorizontalAxis.IsInverse)
                    {
                        borderPath.AddLine(lowerRight, points[0]);
                    }
                    else
                    {
                        borderPath.AddLine(lowerLeft, points[0]);
                    }
                }
            }

            if (series.StrokeMode == AreaSeriesStrokeMode.All ||
                series.StrokeMode == AreaSeriesStrokeMode.AllButPlotLine ||
                series.StrokeMode == AreaSeriesStrokeMode.LeftAndPoints ||
                series.StrokeMode == AreaSeriesStrokeMode.Points ||
                series.StrokeMode == AreaSeriesStrokeMode.RightAndPoints)
            {
                GraphicsPath path = GetLinePaths(points);

                if (path != null)
                {
                    borderPath.AddPath(path, true);
                }
            }

            if (series.StrokeMode == AreaSeriesStrokeMode.All ||
                series.StrokeMode == AreaSeriesStrokeMode.AllButPlotLine ||
                series.StrokeMode == AreaSeriesStrokeMode.RightAndPoints ||
                series.StrokeMode == AreaSeriesStrokeMode.RightLine)
            {
                if (this.Element.View.GetArea<CartesianArea>().Orientation == System.Windows.Forms.Orientation.Vertical)
                {
                    if (area.VerticalAxis.IsInverse)
                    {
                        borderPath.AddLine(points[points.Length - 1], topRight);
                    }
                    else
                    {
                        borderPath.AddLine(points[points.Length - 1], lowerRight);
                    }
                }
                else
                {
                    if (area.HorizontalAxis.IsInverse)
                    {
                        borderPath.AddLine(points[points.Length - 1], topRight);
                    }
                    else
                    {
                        borderPath.AddLine(points[points.Length - 1], topLeft);
                    }
                }
            }

            if (series.StrokeMode == AreaSeriesStrokeMode.All ||
                series.StrokeMode == AreaSeriesStrokeMode.PlotLine)
            {
                if (this.Element.View.GetArea<CartesianArea>().Orientation == System.Windows.Forms.Orientation.Vertical)
                {
                    if (area.VerticalAxis.IsInverse)
                    {
                        borderPath.AddLine(topRight, topLeft);
                    }
                    else
                    {
                        borderPath.AddLine(lowerRight, lowerLeft);
                    }
                }
                else
                {
                    if (area.HorizontalAxis.IsInverse)
                    {
                        borderPath.AddLine(topRight, lowerRight);
                    }
                    else
                    {
                        borderPath.AddLine(lowerLeft, topLeft);
                    }
                }
            }

            BorderPrimitiveImpl border = new BorderPrimitiveImpl(this.Element, null);
            border.PaintBorder(radGraphics, null, borderPath, rect);

            if (series.Image != null)
            {
                graphics.SetClip(fillPath);
                ImagePrimitiveImpl image = new ImagePrimitiveImpl(series);
                image.PaintImage(radGraphics, series.Image, clipRect, series.ImageLayout, series.ImageAlignment, series.ImageOpacity, false);
                graphics.ResetClip();
            }
        }
    }
}



Public Class CustomAreaSeriesDrawPart
    Inherits AreaSeriesDrawPart

    Public Sub New(ByVal series As AreaSeries, ByVal renderer As IChartRenderer)
        MyBase.New(series, renderer)
    End Sub

    Protected Overrides Sub DrawArea()
        Dim renderer As CartesianRenderer = CType(Me.Renderer, CartesianRenderer)
        Dim area As AreaSeries = TryCast(Me.Element, AreaSeries)
        Dim graphics As Graphics = renderer.Graphics
        Dim radGraphics As RadGdiGraphics = New RadGdiGraphics(graphics)
        Dim rect As RectangleF = ChartRenderer.ToRectangleF(Me.Element.Model.LayoutSlot)
        Dim clipRect As RectangleF = CType(renderer.Area.[GetType]().GetMethod("GetCartesianClipRect", BindingFlags.Instance Or BindingFlags.NonPublic).Invoke(renderer.Area, New Object() {}), RectangleF)
        Dim topLeft As PointF = New PointF(clipRect.X, clipRect.Y)
        Dim topRight As PointF = New PointF(clipRect.Right - 1, clipRect.Y)
        Dim lowerRight As PointF = New PointF(clipRect.Right - 1, clipRect.Bottom - 1)
        Dim lowerLeft As PointF = New PointF(clipRect.X, clipRect.Bottom - 1)
        Dim allPoints As List(Of PointF()) = GetPointsPositionsArrays()

        For Each points As PointF() In allPoints

            If points.Length < 2 Then
                Continue For
            End If

            Dim fillPath As GraphicsPath = Me.GetLinePaths(points)

            If fillPath Is Nothing Then
                Continue For
            End If

            If Me.Element.View.GetArea(Of CartesianArea)().Orientation = System.Windows.Forms.Orientation.Vertical Then

                If area.VerticalAxis.IsInverse Then
                    fillPath.AddLine(points(points.Length - 1), New PointF(points(points.Length - 1).X, topRight.Y))
                    fillPath.AddLine(topRight, topLeft)
                    fillPath.AddLine(New PointF(points(0).X, topLeft.Y), points(0))
                Else
                    fillPath.AddLine(points(points.Length - 1), New PointF(points(points.Length - 1).X, points(0).Y))
                End If
            Else

                If area.HorizontalAxis.IsInverse Then
                    fillPath.AddLine(points(points.Length - 1), topRight)
                    fillPath.AddLine(topRight, lowerRight)
                    fillPath.AddLine(lowerRight, points(0))
                Else
                    fillPath.AddLine(points(points.Length - 1), topLeft)
                    fillPath.AddLine(topLeft, lowerLeft)
                    fillPath.AddLine(lowerLeft, points(0))
                End If
            End If

            Dim fill As FillPrimitiveImpl = New FillPrimitiveImpl(Me.Element, Nothing)
            fill.PaintFill(radGraphics, fillPath, clipRect)
            Dim borderPath As GraphicsPath = New GraphicsPath()
            Dim series As AreaSeries = CType(Me.Element, AreaSeries)
            borderPath = New GraphicsPath()

            If series.StrokeMode = AreaSeriesStrokeMode.All OrElse series.StrokeMode = AreaSeriesStrokeMode.AllButPlotLine OrElse series.StrokeMode = AreaSeriesStrokeMode.LeftAndPoints OrElse series.StrokeMode = AreaSeriesStrokeMode.LeftLine Then

                If Me.Element.View.GetArea(Of CartesianArea)().Orientation = System.Windows.Forms.Orientation.Vertical Then

                    If area.VerticalAxis.IsInverse Then
                        borderPath.AddLine(topLeft, points(0))
                    Else
                        borderPath.AddLine(lowerLeft, points(0))
                    End If
                Else

                    If area.HorizontalAxis.IsInverse Then
                        borderPath.AddLine(lowerRight, points(0))
                    Else
                        borderPath.AddLine(lowerLeft, points(0))
                    End If
                End If
            End If

            If series.StrokeMode = AreaSeriesStrokeMode.All OrElse series.StrokeMode = AreaSeriesStrokeMode.AllButPlotLine OrElse series.StrokeMode = AreaSeriesStrokeMode.LeftAndPoints OrElse series.StrokeMode = AreaSeriesStrokeMode.Points OrElse series.StrokeMode = AreaSeriesStrokeMode.RightAndPoints Then
                Dim path As GraphicsPath = GetLinePaths(points)

                If path IsNot Nothing Then
                    borderPath.AddPath(path, True)
                End If
            End If

            If series.StrokeMode = AreaSeriesStrokeMode.All OrElse series.StrokeMode = AreaSeriesStrokeMode.AllButPlotLine OrElse series.StrokeMode = AreaSeriesStrokeMode.RightAndPoints OrElse series.StrokeMode = AreaSeriesStrokeMode.RightLine Then

                If Me.Element.View.GetArea(Of CartesianArea)().Orientation = System.Windows.Forms.Orientation.Vertical Then

                    If area.VerticalAxis.IsInverse Then
                        borderPath.AddLine(points(points.Length - 1), topRight)
                    Else
                        borderPath.AddLine(points(points.Length - 1), lowerRight)
                    End If
                Else

                    If area.HorizontalAxis.IsInverse Then
                        borderPath.AddLine(points(points.Length - 1), topRight)
                    Else
                        borderPath.AddLine(points(points.Length - 1), topLeft)
                    End If
                End If
            End If

            If series.StrokeMode = AreaSeriesStrokeMode.All OrElse series.StrokeMode = AreaSeriesStrokeMode.PlotLine Then

                If Me.Element.View.GetArea(Of CartesianArea)().Orientation = System.Windows.Forms.Orientation.Vertical Then

                    If area.VerticalAxis.IsInverse Then
                        borderPath.AddLine(topRight, topLeft)
                    Else
                        borderPath.AddLine(lowerRight, lowerLeft)
                    End If
                Else

                    If area.HorizontalAxis.IsInverse Then
                        borderPath.AddLine(topRight, lowerRight)
                    Else
                        borderPath.AddLine(lowerLeft, topLeft)
                    End If
                End If
            End If

            Dim border As BorderPrimitiveImpl = New BorderPrimitiveImpl(Me.Element, Nothing)
            border.PaintBorder(radGraphics, Nothing, borderPath, rect)

            If series.Image IsNot Nothing Then
                graphics.SetClip(fillPath)
                Dim image As ImagePrimitiveImpl = New ImagePrimitiveImpl(series)
                image.PaintImage(radGraphics, series.Image, clipRect, series.ImageLayout, series.ImageAlignment, series.ImageOpacity, False)
                graphics.ResetClip()
            End If
        Next
    End Sub
End Class


What's left is to apply the custom renderer to the RadChartView and add spline AreaSeries with our data.


public Form1()
{
    InitializeComponent();

    this.radChartView1.CreateRenderer += radChartView1_CreateRenderer;

    AreaSeries areaSeries = new AreaSeries();
    areaSeries.Spline = true;

    for (int x = 0; x < 60; x++) 
    {  
        areaSeries.DataPoints.Add(1 + 1 * Math.Sin(x / Math.PI), x); 
    }

    this.radChartView1.Series.Add(areaSeries);  
    CategoricalAxis horizontalAxis = areaSeries.HorizontalAxis as CategoricalAxis;
    horizontalAxis.StartPositionAxis = areaSeries.VerticalAxis;  
    horizontalAxis.StartPositionValue = 1;
}

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



Class SurroundingClass
    Public Sub New()
        InitializeComponent()
        AddHandler Me.radChartView1.CreateRenderer, AddressOf radChartView1_CreateRenderer
        Dim areaSeries As AreaSeries = New AreaSeries()
        areaSeries.Spline = True

        For x As Integer = 0 To 60 - 1
            areaSeries.DataPoints.Add(1 + 1 * Math.Sin(x / Math.PI), x)
        Next

        Me.radChartView1.Series.Add(areaSeries)
        Dim horizontalAxis As CategoricalAxis = TryCast(areaSeries.HorizontalAxis, CategoricalAxis)
        horizontalAxis.StartPositionAxis = areaSeries.VerticalAxis
        horizontalAxis.StartPositionValue = 1
    End Sub

    Private Sub radChartView1_CreateRenderer(ByVal sender As Object, ByVal e As ChartViewCreateRendererEventArgs)
        e.Renderer = New CustomCartesianRenderer(TryCast(e.Area, CartesianArea))
    End Sub
End Class

In this article