Knowledgebase: Tips and Tricks
How to Make Line Series draw to the right-edge of the chart
Posted by Andrew BT on 02 May 2014 05:44 PM

Sometimes people ask us how they can draw Line Series to the right-edge of the chart. There's no built in way to do this yet, but there is a workaround:

*These steps apply to SciChart v3.1 or higher*

Follow these steps:

  1. Create a new class called DigitalLineSeries which inherits FastLineRenderableSeries
  2. In your DigitalLineSeries class, set IsDigitalLine = true, and add a dependency property DrawLineToEnd
  3. Override InternalDraw. Here we are going to create an IEnumerable of points to draw using the IRenderContext2D interface

 *Note: For more information on IRenderContext2D, please see The CustomRenderableSeries API article.

public class DigitalLineSeres : FastLineRenderableSeries
{
    public static readonly DependencyProperty DrawLineToEndProperty = DependencyProperty.Register(
        "DrawLineToEnd", typeof (bool), typeof (DigitalLineSeres), new PropertyMetadata(true));

    public bool DrawLineToEnd
    {
        get { return (bool) GetValue(DrawLineToEndProperty); }
        set { SetValue(DrawLineToEndProperty, value); }
    }

    public DigitalLineSeres()
    {
        IsDigitalLine = true;
    }

    protected override void InternalDraw(IRenderContext2D renderContext, IRenderPassData renderPassData)
    {
        var pointSeries = CurrentRenderPassData.PointSeries;

        // Collate data into points (x1, y1, x2, y2 ...)
        var linesEnumerable = new LinesEnumerable(pointSeries, renderPassData.XCoordinateCalculator, renderPassData.YCoordinateCalculator, IsDigitalLine) as IEnumerable;

        // New Code here
        if (DrawLineToEnd)
        {
            var dataToRender = renderPassData.PointSeries;
            double viewportWidth = this.GetParentSurface().RenderSurface.ActualWidth;
            double lastYDataValue = dataToRender[dataToRender.Count - 1].Y;
            double lastYCoordinate = renderPassData.YCoordinateCalculator.GetCoordinate(lastYDataValue);
            linesEnumerable = linesEnumerable.Concat(new[] { new Point(viewportWidth, lastYCoordinate) });
        }

        using (var pen = renderContext.CreatePen(SeriesColor, AntiAliasing, StrokeThickness, Opacity, StrokeDashArray))
        {
            renderContext.DrawLines(pen, linesEnumerable);
        }
    }
}


It will draw a line to the right edge of the viewport always. 

Updating the workaround to SciChart v3.2 or Later

In SciChart v3.2, we introduced a much faster drawing path which does not need Enumerables and we deprecated the LinesEnumerable class. So, SciChart v3.2 or later no longer has the LinesEnumerable class in it. We have published this code below so you can include LinesEnumerable in your solutions with SciChart v3.2 or later

 

LinesEnumerable Source

/// <summary>
/// A class used to convert a <see cref="IPointSeries"/> which contains resampled data-points, 
/// into a stream of <see cref="Point"/> pixel coordinates for the screen. This class re-uses logic used 
/// throughout the <see cref="BaseRenderableSeries"/> types.
/// </summary>
/// <remarks>Intended to be used only be SciChart</remarks>
public class LinesEnumerable : IEnumerable<Point>
{
    protected readonly IPointSeries _pointSeries;
    protected readonly ICoordinateCalculator<double> _xCoordinateCalculator;
    protected readonly ICoordinateCalculator<double> _yCoordinateCalculator;
    protected readonly bool _isDigitalLine;

    /// <summary>
    /// Initializes a new instance of the <see cref="LinesEnumerable" /> class.
    /// </summary>
    /// <param name="pointSeries">The point series.</param>
    /// <param name="xCoordinateCalculator">The x coordinate calculator.</param>
    /// <param name="yCoordinateCalculator">The y coordinate calculator.</param>
    /// <param name="isDigitalLine">if set to <c>true</c> return a digital line .</param>
    public LinesEnumerable(IPointSeries pointSeries, ICoordinateCalculator<double> xCoordinateCalculator, ICoordinateCalculator<double> yCoordinateCalculator, bool isDigitalLine)
    {
        _pointSeries = pointSeries;
        _xCoordinateCalculator = xCoordinateCalculator;
        _yCoordinateCalculator = yCoordinateCalculator;
        _isDigitalLine = isDigitalLine;
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
    /// </returns>
    public virtual IEnumerator<Point> GetEnumerator()
    {
        return _isDigitalLine ?
            (IEnumerator<Point>)new DigitalLinesIterator(_pointSeries, _xCoordinateCalculator, _yCoordinateCalculator) :
            new LinesIterator(_pointSeries, _xCoordinateCalculator, _yCoordinateCalculator);
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
    /// </returns>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

 

LinesIterator Source

/// <summary>
/// A custom <see cref="IEnumerator{Point}"/> implementation to provide line coordinates from <see cref="IPointSeries"/> input
/// </summary>
public class LinesIterator : PointSeriesEnumerator, IEnumerator<Point>
{
    private readonly ICoordinateCalculator<double> _xCoordinateCalculator;
    private readonly ICoordinateCalculator<double> _yCoordinateCalculator;

    /// <summary>
    /// Initializes a new instance of the <see cref="LinesIterator" /> class.
    /// </summary>
    /// <param name="pointSeries">The point series.</param>
    /// <param name="xCoordinateCalculator">The x coordinate calculator.</param>
    /// <param name="yCoordinateCalculator">The y coordinate calculator.</param>
    public LinesIterator(IPointSeries pointSeries, ICoordinateCalculator<double> xCoordinateCalculator, ICoordinateCalculator<double> yCoordinateCalculator)
        : base(pointSeries)
    {
        _xCoordinateCalculator = xCoordinateCalculator;
        _yCoordinateCalculator = yCoordinateCalculator;
    }

    /// <summary>
    /// Advances the enumerator to the next element of the collection.
    /// </summary>
    /// <returns>
    /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
    /// </returns>
    public override bool MoveNext()
    {
        if (!base.MoveNext())
            return false;

        var pt = base.CurrentPoint2D;
        if (double.IsNaN(pt.Y))
        {
            Current = new Point(float.NaN, float.NaN);
            return true;
        }

        var x = (float)_xCoordinateCalculator.GetCoordinate(pt.X);
        var y = (float)_yCoordinateCalculator.GetCoordinate(pt.Y);

        var isVerticalChart = !_xCoordinateCalculator.IsHorizontalAxisCalculator;
        Current = TransformPoint(new Point(x, y), isVerticalChart);

        return true;
    }

    /// <summary>
    /// Sets the enumerator to its initial position, which is before the first element in the collection.
    /// </summary>
    public override void Reset()
    {
        base.Reset();

        Current = new Point(float.NaN, float.NaN);
    }

    /// <summary>
    /// Gets the <see cref="IPoint" /> in the collection at the current position of the enumerator.
    /// </summary>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    public new Point Current { get; private set; }

    /// <summary>
    /// Gets the <see cref="IPoint" /> in the collection at the current position of the enumerator.
    /// </summary>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    object IEnumerator.Current
    {
        get { return Current; }
    }

    public static Point TransformPoint(Point point, bool isVerticalChart)
    {
        //swap coords if vertical chart
        if (isVerticalChart)
        {
            var x = point.X;

            point.X = point.Y;
            point.Y = x;
        }

        return point;
    }
}

 

DigitalLinesIterator Source

/// <summary>
/// A custom <see cref="IEnumerator{T}"/> implementation to provide digital line pixel coordinates from <see cref="IPointSeries"/> input
/// </summary>
public class DigitalLinesIterator : PointSeriesEnumerator, IEnumerator<Point>
{
    private readonly ICoordinateCalculator<double> _xCoordinateCalculator;
    private readonly ICoordinateCalculator<double> _yCoordinateCalculator;

    private Point _next;
    private bool _toggle;

    /// <summary>
    /// Initializes a new instance of the <see cref="DigitalLinesIterator" /> class.
    /// </summary>
    /// <param name="pointSeries">The point series.</param>
    /// <param name="xCoordinateCalculator">The x coordinate calculator.</param>
    /// <param name="yCoordinateCalculator">The y coordinate calculator.</param>
    public DigitalLinesIterator(IPointSeries pointSeries, ICoordinateCalculator<double> xCoordinateCalculator,
                                ICoordinateCalculator<double> yCoordinateCalculator)
        : base(pointSeries)
    {
        _xCoordinateCalculator = xCoordinateCalculator;
        _yCoordinateCalculator = yCoordinateCalculator;

        _toggle = false;
    }


    /// <summary>
    /// Advances the enumerator to the next element of the collection.
    /// </summary>
    /// <returns>
    /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
    /// </returns>
    public override bool MoveNext()
    {
        var isVerticalChart = !_xCoordinateCalculator.IsHorizontalAxisCalculator;

        if (_toggle)
        {
            Current = _next;
            Current = LinesIterator.TransformPoint(Current, isVerticalChart);

            _toggle = false;

            return true;
        }

        bool isFirstPoint = IsReset;

        if (!base.MoveNext())
            return false;

        var pt = base.CurrentPoint2D;
        if (double.IsNaN(pt.Y))
        {
            Current = new Point(float.NaN, float.NaN);
            return true;
        }

        var x = (float)_xCoordinateCalculator.GetCoordinate(pt.X);
        var y = (float)_yCoordinateCalculator.GetCoordinate(pt.Y);

        _next = new Point(x, y);

        Current = isFirstPoint ? _next : new Point(_next.X, isVerticalChart ? Current.X : Current.Y);
        Current = LinesIterator.TransformPoint(Current, isVerticalChart);

        _toggle = !isFirstPoint;

        return true;
    }

    /// <summary>
    /// Sets the enumerator to its initial position, which is before the first element in the collection.
    /// </summary>
    public override void Reset()
    {
        base.Reset();

        _toggle = false;
        Current = new Point(float.NaN, float.NaN);
    }

    /// <summary>
    /// Gets the <see cref="IPoint" /> in the collection at the current position of the enumerator.
    /// </summary>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    public new Point Current { get; private set; }

    /// <summary>
    /// Gets the <see cref="IPoint" /> in the collection at the current position of the enumerator.
    /// </summary>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    object IEnumerator.Current
    {
        get { return Current; }
    }

}

 

(1 vote(s))
Helpful
Not helpful

CONTACT US

Not sure where to start? Contact us, we are happy to help!


CONTACT US

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL. Email: Legal Company Number: 07430048, VAT Number: 101957725