SciChart WPF 2D Charts > 2D Chart Types > Custom Series (CustomRenderableSeries)
Custom Series (CustomRenderableSeries)

Creating your Own Series

If the built-in chart types in SciChart are not enough, you can create your own RenderableSeries by using the CustomRenderableSeries API! CustomRenderableSeries inherits BaseRenderableSeries. It simply has one method which you must override - Draw().

Examples for the Custom Renderable Series can be found in the SciChart WPF Examples Suite which can be downloaded from the SciChart Website or our SciChart.WPF.Examples Github Repository.

Custom Series (CustomRenderableSeries)
Copy Code
/// <summary>
/// Defines a Custom Renderable Series - override Draw() to
/// define what is drawn to the screen at render time
/// </summary>
public class CustomRenderableSeries : BaseRenderableSeries
{
    /// <summary>
    /// Draws the series using the <see cref="IRenderContext2D"/>
    /// and the <see cref="IRenderPassData"/> passed in
    /// </summary>
    /// <param name="renderContext">The render context. This is a graphics object which has
    /// methods to draw lines, quads and polygons to the screen</param>
    /// <param name="renderPassData">The render pass data. Contains a resampled
    /// <see cref="IPointSeries"/>, the <see cref="IndexRange"/> of points on the screen
    /// and the current YAxis and XAxis <see cref="ICoordinateCalculator{T}"/>
    /// to convert data-points to screen points</param>
    protected virtual void Draw(IRenderContext2D renderContext,
                                 IRenderPassData renderPassData)
    {
        base.Draw(renderContext, renderPassData);

        // TODO: Some drawing!
    }
}

IRenderContext2D – the Immediate Mode Graphics Object    

The IRenderContext passed in to the Draw() method is the graphics context for this render pass. Use this to draw to the screen.

Note: For a full walk-through of the RenderContext API, see the section on Immediate Mode RenderContext API.

IRenderPassData – the current Data to Draw

The data to draw is contained in the IRenderPassData passed in to the Draw method. The interface can be seen below:

IRenderPassData – the current Data to Draw
Copy Code
/// <summary>
/// Defines the interface to <see cref="RenderPassData"/>, the data used in a single render pass by <see cref="BaseRenderableSeries"/> derived types
/// </summary>
public interface IRenderPassData
{
    /// <summary>
    /// Gets the integer indices of the X-Data array that are currently in range.
    /// </summary>
    /// <returns>The indices to the X-Data that are currently in range</returns>
    /// <example>If the input X-data is 0...100 in steps of 1, the VisibleRange is 10, 30 then the PointRange will be 10, 30</example>
    /// <remarks></remarks>
    IndexRange PointRange { get; }
    /// <summary>
    /// Gets the current point series.
    /// </summary>
    IPointSeries PointSeries { get; }
    /// <summary>
    /// Gets a value, indicating whether current chart is vertical
    /// </summary>
    bool IsVerticalChart { get; }
    /// <summary>
    /// Gets the current Y coordinate calculator.
    /// </summary>
    ICoordinateCalculator<double> YCoordinateCalculator { get; }
    /// <summary>
    /// Gets the current X coordinate calculator.
    /// </summary>
    ICoordinateCalculator<double> XCoordinateCalculator { get; }
    /// <summary>
    /// Gets the current pixel transformation strategy
    /// </summary>
    ITransformationStrategy TransformationStrategy { get; }
}

Using the IRenderPassData you can access the PointSeries (the data to draw), the PointRange (the indices of the data to draw, inclusive, the X and YCoordinateCalculator (which transforms data to pixel coordinates).

IPointSeries Derived Types

The IPointSeries interface is passed in to CustomRenderableSeries.Draw() via the IRenderPassData interface. Depending on the DataSeries type you can have a different PointSeries type and different ways to access the data.

DataSeries Type PointSeries Type
XyDataSeries Point2DSeries
XyyDataSeries XyyPointSeries
XyzDataSeries XyzPointSeries
HlcDataSeries HlcPointSeries
OhlcDataSeries OhlcPointSeries
BoxPlotDataSeries OhlcPointSeries

 

An example of how to get data out of a PointSeries is included below:

PointSeries2D

IRenderPassData – the current Data to Draw
Copy Code
public class CustomRenderableSeriesSample : CustomRenderableSeries
{
    protected override void Draw(IRenderContext2D renderContext, IRenderPassData renderPassData)
    {
        base.Draw(renderContext, renderPassData);

        // The resampled data for this render pass
        var dataPointSeries = renderPassData.PointSeries as Point2DSeries;

        if (dataPointSeries == null) return;

        var xCalc = renderPassData.XCoordinateCalculator;
        var yCalc = renderPassData.YCoordinateCalculator;

        // Iterate over the point series
        for (int i = 0; i < dataPointSeries.Count; i++)
        {
            // Get the X,Y value
            var x = dataPointSeries.XValues[i];
            var y = dataPointSeries.YValues[i];

            // If you want it, the original data-point index
            var index = dataPointSeries.Indexes[i];               

            // Transform to coordinate
            var xCoord = xCalc.GetCoordinate(x);
            var yCoord = yCalc.GetCoordinate(y);     

            // TODO: Do some drawing with renderContext
        }
    }
}

XyyPointSeries

XyyPointSeries
Copy Code
public class CustomRenderableSeriesSample : CustomRenderableSeries
{
   protected override void Draw(IRenderContext2D renderContext, IRenderPassData renderPassData)
   {
      base.Draw(renderContext, renderPassData);

      // The resampled data for this render pass
      var dataPointSeries = renderPassData.PointSeries as XyyPoint2DSeries;
      if (dataPointSeries == null) return;
      var xCalc = renderPassData.XCoordinateCalculator;
      var yCalc = renderPassData.XCoordinateCalculator;

      // Iterate over the point series
      for (int i = 0; i < dataPointSeries.Count; i++)
      {
         // Get the X,Y1,Y2 values
         var x = dataPointSeries.XValues[i];
         var y0 = dataPointSeries.YValues[i];
         var y1 = dataPointSeries.Y1Values[i];

         // Transform to coordinate
         var xCoord = xCalc.GetCoordinate(x);
         var y0Coord = yCalc.GetCoordinate(y0);
         var y1Coord = yCalc.GetCoordinate(y1);

         // TODO: Do some drawing with renderContext
      }
   }
}

XyzPointSeries

XyzPointSeries
Copy Code
public class CustomRenderableSeriesSample : CustomRenderableSeries
{
   protected override void Draw(IRenderContext2D renderContext, IRenderPassData renderPassData)
   {
      base.Draw(renderContext, renderPassData);

      // The resampled data for this render pass
      var dataPointSeries = renderPassData.PointSeries as XyzPointSeries;
      if (dataPointSeries == null) return;
      var xCalc = renderPassData.XCoordinateCalculator;
      var yCalc = renderPassData.XCoordinateCalculator;

      // Iterate over the point series
      for (int i = 0; i < dataPointSeries.Count; i++)
      {
         // Get the X,Y,Z values
         var x = dataPointSeries.XValues[i];
         var y = dataPointSeries.YValues[i];
         var z = dataPointSeries.ZPoints[i];

         // Transform to coordinate
         var xCoord = xCalc.GetCoordinate(x);
         var yCoord = yCalc.GetCoordinate(y);

         // TODO: Do some drawing with renderContext
      }
   }
}

OhlcPointSeries

OhlcPointSeries
Copy Code
public class CustomRenderableSeriesSample : CustomRenderableSeries
{
   protected override void Draw(IRenderContext2D renderContext, IRenderPassData renderPassData)
   {
      base.Draw(renderContext, renderPassData);

      // The resampled data for this render pass
      var dataPointSeries = renderPassData.PointSeries as OhlcPointSeries;
      if (dataPointSeries == null) return;
      var xCalc = renderPassData.XCoordinateCalculator;
      var yCalc = renderPassData.YCoordinateCalculator;

      // Iterate over the point series
      for (int i = 0; i < dataPointSeries.Count; i++)
      {
         // Get the Open, High, Low, Close, X values
         var x = dataPointSeries.XValues[i];
         var open = dataPointSeries.OpenValues[i];
         var high = dataPointSeries.HighValues[i];
         var low = dataPointSeries.LowValues[i];
         var close = dataPointSeries.HighValues[i];

         // Transform to coordinate
         var xCoord = xCalc.GetCoordinate(x);
         var openCoord = yCalc.GetCoordinate(open);
         var highCoord = yCalc.GetCoordinate(high);
         var lowCoord = yCalc.GetCoordinate(low);
         var closeCoord = yCalc.GetCoordinate(close);

         // TODO: Do some drawing with renderContext
      }
   }
}

Worked Example: SplineLineRenderableSeries

We have a full worked example over at https://support.scichart.com/index.php?/Knowledgebase/Article/View/17254/41/creating-a-custom-spline-line-series which shows how to create a spline series with this powerful API.

 

Worked Example: CustomLineRenderableSeries

A worked example is found below for a CustomRenderableSeries which draws a Polyline using the RenderContext API. This is no different from our Line series, but shows some of the internals of how this API works

CustomLineRenderableSeries
Copy Code
public class CustomLinesRenderableSeries : CustomRenderableSeries
{
       protected override void Draw(IRenderContext2D renderContext, IRenderPassData renderPassData)
       {
             base.Draw(renderContext, renderPassData);

             // The resampled data for this render pass
             var dataPointSeries = renderPassData.PointSeries;

             if (dataPointSeries.Count == 0) return;

             var xCalc = renderPassData.XCoordinateCalculator;
             var yCalc = renderPassData.YCoordinateCalculator;

             double xCoord0 = xCalc.GetCoordinate(dataPointSeries.XValues[0]);
             double yCoord0 = yCalc.GetCoordinate(dataPointSeries.YValues[0]);

             // Begin a batched Polyline draw operation
             using (var pen = renderContext.CreatePen(Colors.Red, true, 1.0f))
             using (var lineContext = renderContext.BeginLine(pen, xCoord0, yCoord0))
             {
                    // Iterate over the data
                    for (int i = 1; i < dataPointSeries.Count; i++)
                    {
                          // Convert data to coords
                           double xCoord = xCalc.GetCoordinate(dataPointSeries.XValues[i]);
                           double yCoord = yCalc.GetCoordinate(dataPointSeries.YValues[i]);

                           // Draw at current location
                           lineContext.MoveTo(xCoord, yCoord);
                    }
             }// When lineContext is disposed, the line draws

             // Notes: to change pen color, restart a new BeginLine() context
       }
}

Worked Example: CustomPointRenderableSeries

A worked example is found below for a CustomRenderableSeries which draws a PointMarker using the RenderContext API. This is no different from our Scatter series, but shows some of the internals of how this API works

CustomPointRenderableSeries
Copy Code
public class CustomPointRenderableSeries : CustomRenderableSeries
{
    protected override void Draw(IRenderContext2D renderContext, IRenderPassData renderPassData)
    {
        base.Draw(renderContext, renderPassData);

        // Get the CustomPointRenderableSeries.PointMarker to draw at original points
        // Assumes you have declared one in XAML or code
        //
        // e.g. CustomPointRenderableSeries.PointMarker = new EllipsePointMarker();
        //
        var pointMarker = base.GetPointMarker();
        if (pointMarker != null)
        {
            // The resampled data for this render pass
            var dataPointSeries = renderPassData.PointSeries;

            var xCalc = renderPassData.XCoordinateCalculator;
            var yCalc = renderPassData.YCoordinateCalculator;

            // Begin a batched PointMarker draw operation
            pointMarker.BeginBatch(renderContext, pointMarker.Stroke, pointMarker.Fill);

            // Iterate over the data
            for (int i = 0; i < dataPointSeries.Count; i++)
            {
                // Convert data to coords
                double xCoord = xCalc.GetCoordinate(dataPointSeries.XValues[i]);
                double yCoord = yCalc.GetCoordinate(dataPointSeries.YValues[i]);
                int dataIndex = dataPointSeries.Indexes[i];

                // Draw at current location
                pointMarker.MoveTo(renderContext, xCoord, yCoord, dataIndex);
            }

            // End the batch
            // Note: To change point color, start a new batch
            pointMarker.EndBatch(renderContext);
        }
    }
}

 

See Also