Skip to content

StepLineSeries #4

@JuanSilili

Description

@JuanSilili

I want to implement StepLineSeries' step-drawing inversion. For example, for two points of the same data source, now the X-axis line is drawn first and then the Y-axis line, can you add an attribute to control the Y-axis line first and then the X-axis line when drawing.

I add an attribute 'IsReverse ',then I modified part of the code in the function Invalidate to reverse the order of the coordinate sequence,This will achieve the effect I want.
` private bool _isReverse = false;
///


/// Reverse step line
///

public bool IsReverse { get => _isReverse; set => SetProperty(ref _isReverse, value); }

/// <inheritdoc cref="ChartElement{TDrawingContext}.Invalidate(Chart{TDrawingContext})"/>
public override void Invalidate(Chart<TDrawingContext> chart)
{
    var cartesianChart = (CartesianChart<TDrawingContext>)chart;    
    var primaryAxis = cartesianChart.YAxes[ScalesYAt];
    var secondaryAxis = cartesianChart.XAxes[ScalesXAt];

    var drawLocation = cartesianChart.DrawMarginLocation;
    var drawMarginSize = cartesianChart.DrawMarginSize;
    var secondaryScale = secondaryAxis.GetNextScaler(cartesianChart);
    var primaryScale = primaryAxis.GetNextScaler(cartesianChart);
    var actualSecondaryScale = secondaryAxis.GetActualScaler(cartesianChart);
    var actualPrimaryScale = primaryAxis.GetActualScaler(cartesianChart);

    var gs = _geometrySize;
    var hgs = gs / 2f;
    var sw = Stroke?.StrokeThickness ?? 0;
    var p = primaryScale.ToPixels(pivot);

    // see note #240222
    var segments = _enableNullSplitting
        ? Fetch(cartesianChart).SplitByNullGaps(point => DeleteNullPoint(point, secondaryScale, primaryScale))
        : [Fetch(cartesianChart)];

    var stacker = (SeriesProperties & SeriesProperties.Stacked) == SeriesProperties.Stacked
        ? cartesianChart.SeriesContext.GetStackPosition(this, GetStackGroup())
        : null;

    var actualZIndex = ZIndex == 0 ? ((ISeries)this).SeriesId : ZIndex;
    var clipping = GetClipRectangle(cartesianChart);

    if (stacker is not null)
    {
        // see note #010621
        actualZIndex = 1000 - stacker.Position;
        if (Fill is not null) Fill.ZIndex = actualZIndex;
        if (Stroke is not null) Stroke.ZIndex = actualZIndex;
    }

    var dls = (float)DataLabelsSize;

    var segmentI = 0;
    var pointsCleanup = ChartPointCleanupContext.For(everFetched);

    if (!_strokePathHelperDictionary.TryGetValue(chart.Canvas.Sync, out var strokePathHelperContainer))
    {
        strokePathHelperContainer = [];
        _strokePathHelperDictionary[chart.Canvas.Sync] = strokePathHelperContainer;
    }

    if (!_fillPathHelperDictionary.TryGetValue(chart.Canvas.Sync, out var fillPathHelperContainer))
    {
        fillPathHelperContainer = [];
        _fillPathHelperDictionary[chart.Canvas.Sync] = fillPathHelperContainer;
    }

    var uwx = secondaryScale.MeasureInPixels(secondaryAxis.UnitWidth);
    uwx = uwx < gs ? gs : uwx;
    var hasSvg = this.HasVariableSvgGeometry();

    var isFirstDraw = !chart._drawnSeries.Contains(((ISeries)this).SeriesId);

    foreach (var segment in segments)
    {
        var hasPaths = false;
        var isSegmentEmpty = true;
        VectorManager<StepLineSegment, TDrawingContext>? strokeVector = null, fillVector = null;

        double previousPrimary = 0, previousSecondary = 0;

        **//Reverse cooordinate for step line
        List<ChartPoint> Reversepoint = new();
        foreach (var point in segment)
        {
            Reversepoint.Add(point);
        }
        if (_isReverse)
        {
            Reversepoint.Reverse();
        }

        //    foreach (var point in segment) //old code
        foreach (var point in Reversepoint)**
        {
            if (!hasPaths)
            {
                hasPaths = true;

                var fillLookup = GetSegmentVisual(segmentI, fillPathHelperContainer, VectorClosingMethod.CloseToPivot);
                var strokeLookup = GetSegmentVisual(segmentI, strokePathHelperContainer, VectorClosingMethod.NotClosed);

                if (fillLookup.Path.Commands.Count == 1)
                {
                    Fill?.RemoveGeometryFromPainTask(cartesianChart.Canvas, fillLookup.Path);
                    fillLookup.Path.Commands.Clear();
                    fillPathHelperContainer.RemoveAt(segmentI);

                    fillLookup = GetSegmentVisual(segmentI, fillPathHelperContainer, VectorClosingMethod.CloseToPivot);
                }

                if (strokeLookup.Path.Commands.Count == 1)
                {
                    Stroke?.RemoveGeometryFromPainTask(cartesianChart.Canvas, strokeLookup.Path);
                    strokeLookup.Path.Commands.Clear();
                    strokePathHelperContainer.RemoveAt(segmentI);

                    strokeLookup = GetSegmentVisual(segmentI, strokePathHelperContainer, VectorClosingMethod.NotClosed);
                }

                var isNew = fillLookup.IsNew || strokeLookup.IsNew;
                var fillPath = fillLookup.Path;
                var strokePath = strokeLookup.Path;

                strokeVector = new VectorManager<StepLineSegment, TDrawingContext>(strokePath);
                fillVector = new VectorManager<StepLineSegment, TDrawingContext>(fillPath);

                if (Fill is not null)
                {
                    Fill.AddGeometryToPaintTask(cartesianChart.Canvas, fillPath);
                    cartesianChart.Canvas.AddDrawableTask(Fill);
                    Fill.ZIndex = actualZIndex + 0.1;
                    Fill.SetClipRectangle(cartesianChart.Canvas, clipping);
                    fillPath.Pivot = p;
                    if (isNew)
                    {
                        fillPath.Animate(EasingFunction ?? cartesianChart.EasingFunction, AnimationsSpeed ?? cartesianChart.AnimationsSpeed);
                    }
                }
                if (Stroke is not null)
                {
                    Stroke.AddGeometryToPaintTask(cartesianChart.Canvas, strokePath);
                    cartesianChart.Canvas.AddDrawableTask(Stroke);
                    Stroke.ZIndex = actualZIndex + 0.2;
                    Stroke.SetClipRectangle(cartesianChart.Canvas, clipping);
                    strokePath.Pivot = p;
                    if (isNew)
                    {
                        strokePath.Animate(EasingFunction ?? cartesianChart.EasingFunction, AnimationsSpeed ?? cartesianChart.AnimationsSpeed);
                    }
                }

                strokePath.Opacity = IsVisible ? 1 : 0;
                fillPath.Opacity = IsVisible ? 1 : 0;
            }

            var coordinate = point.Coordinate;

            isSegmentEmpty = false;
            var s = 0d;
            if (stacker is not null)
                s = coordinate.PrimaryValue > 0
                    ? stacker.GetStack(point).Start
                    : stacker.GetStack(point).NegativeStart;

            var visual = (StepLineVisualPoint<TDrawingContext, TVisual>?)point.Context.AdditionalVisuals;
            var dp = coordinate.PrimaryValue + s - previousPrimary;
            var ds = coordinate.SecondaryValue - previousSecondary;

            if (!IsVisible)
            {
                if (visual is not null)
                {
                    visual.Geometry.X = secondaryScale.ToPixels(coordinate.SecondaryValue);
                    visual.Geometry.Y = p;
                    visual.Geometry.Opacity = 0;
                    visual.Geometry.RemoveOnCompleted = true;

                    visual.StepSegment.Xi = secondaryScale.ToPixels(coordinate.SecondaryValue - ds);
                    visual.StepSegment.Xj = secondaryScale.ToPixels(coordinate.SecondaryValue);
                    visual.StepSegment.Yi = p;
                    visual.StepSegment.Yj = p;



                    point.Context.Visual = null;
                }

                pointsCleanup.Clean(point);

                continue;
            }

            if (visual is null)
            {
                var v = new StepLineVisualPoint<TDrawingContext, TVisual>();
                visual = v;

                if (isFirstDraw)
                {
                    v.Geometry.X = secondaryScale.ToPixels(coordinate.SecondaryValue);
                    v.Geometry.Y = p;
                    v.Geometry.Width = 0;
                    v.Geometry.Height = 0;

                    //v.StepSegment.Xi = secondaryScale.ToPixels(coordinate.SecondaryValue - ds);
                    //v.StepSegment.Xj = secondaryScale.ToPixels(coordinate.SecondaryValue);
                    //v.StepSegment.Yi = p;
                    //v.StepSegment.Yj = p;

                    v.StepSegment.Yi = primaryScale.ToPixels(coordinate.PrimaryValue + s - dp);
                    v.StepSegment.Yj = primaryScale.ToPixels(coordinate.PrimaryValue + s);
                    v.StepSegment.Xi = p;
                    v.StepSegment.Xj = p;
                }

                point.Context.Visual = v.Geometry;
                point.Context.AdditionalVisuals = v;
                OnPointCreated(point);
            }

            visual.Geometry.Opacity = 1;

            if (hasSvg)
            {
                var svgVisual = (IVariableSvgPath<TDrawingContext>)visual.Geometry;
                if (_geometrySvgChanged || svgVisual.SVGPath is null)
                    svgVisual.SVGPath = GeometrySvg ?? throw new Exception("svg path is not defined");
            }

            _ = everFetched.Add(point);

            GeometryFill?.AddGeometryToPaintTask(cartesianChart.Canvas, visual.Geometry);
            GeometryStroke?.AddGeometryToPaintTask(cartesianChart.Canvas, visual.Geometry);

            visual.StepSegment.Id = point.Context.Entity.MetaData!.EntityIndex;

            if (Fill is not null) fillVector!.AddConsecutiveSegment(visual.StepSegment, !isFirstDraw);
            if (Stroke is not null) strokeVector!.AddConsecutiveSegment(visual.StepSegment, !isFirstDraw);

            visual.StepSegment.Xi = secondaryScale.ToPixels(coordinate.SecondaryValue - ds);
            visual.StepSegment.Xj = secondaryScale.ToPixels(coordinate.SecondaryValue);
            visual.StepSegment.Yi = primaryScale.ToPixels(coordinate.PrimaryValue + s - dp);
            visual.StepSegment.Yj = primaryScale.ToPixels(coordinate.PrimaryValue + s);

            var x = secondaryScale.ToPixels(coordinate.SecondaryValue);
            var y = primaryScale.ToPixels(coordinate.PrimaryValue + s);

            visual.Geometry.MotionProperties[nameof(visual.Geometry.X)]
                .CopyFrom(visual.StepSegment.MotionProperties[nameof(visual.StepSegment.Xj)]);
            visual.Geometry.MotionProperties[nameof(visual.Geometry.Y)]
                .CopyFrom(visual.StepSegment.MotionProperties[nameof(visual.StepSegment.Yj)]);
            visual.Geometry.TranslateTransform = new LvcPoint(-hgs, -hgs);

            visual.Geometry.Width = gs;
            visual.Geometry.Height = gs;
            visual.Geometry.RemoveOnCompleted = false;

            visual.FillPath = fillVector!.AreaGeometry;
            visual.StrokePath = strokeVector!.AreaGeometry;

            var hags = gs < 8 ? 8 : gs;

            if (point.Context.HoverArea is not RectangleHoverArea ha)
                point.Context.HoverArea = ha = new RectangleHoverArea();

            _ = ha
                .SetDimensions(x - uwx * 0.5f, y - hgs, uwx, gs)
                .CenterXToolTip();

            _ = coordinate.PrimaryValue >= pivot
                ? ha.StartYToolTip()
                : ha.EndYToolTip().IsLessThanPivot();

            pointsCleanup.Clean(point);

            if (DataLabelsPaint is not null)
            {
                var label = (TLabel?)point.Context.Label;

                if (label is null)
                {
                    var l = new TLabel { X = x - hgs, Y = p - hgs, RotateTransform = (float)DataLabelsRotation, MaxWidth = (float)DataLabelsMaxWidth };
                    l.Animate(EasingFunction ?? cartesianChart.EasingFunction, AnimationsSpeed ?? cartesianChart.AnimationsSpeed);
                    label = l;
                    point.Context.Label = l;
                }

                DataLabelsPaint.AddGeometryToPaintTask(cartesianChart.Canvas, label);
                label.Text = DataLabelsFormatter(new ChartPoint<TModel, TVisual, TLabel>(point));
                label.TextSize = dls;
                label.Padding = DataLabelsPadding;

                if (isFirstDraw)
                    label.CompleteTransition(
                        nameof(label.TextSize), nameof(label.X), nameof(label.Y), nameof(label.RotateTransform));

                var m = label.Measure(DataLabelsPaint);
                var labelPosition = GetLabelPosition(
                    x - hgs, y - hgs, gs, gs, m, DataLabelsPosition,
                    SeriesProperties, coordinate.PrimaryValue > Pivot, drawLocation, drawMarginSize);
                if (DataLabelsTranslate is not null) label.TranslateTransform =
                    new LvcPoint(m.Width * DataLabelsTranslate.Value.X, m.Height * DataLabelsTranslate.Value.Y);

                label.X = labelPosition.X;
                label.Y = labelPosition.Y;
            }

            OnPointMeasured(point);

            previousPrimary = coordinate.PrimaryValue + s;
            previousSecondary = coordinate.SecondaryValue;
        }

        strokeVector?.End();
        fillVector?.End();

        if (GeometryFill is not null)
        {
            cartesianChart.Canvas.AddDrawableTask(GeometryFill);
            GeometryFill.SetClipRectangle(cartesianChart.Canvas, clipping);
            GeometryFill.ZIndex = actualZIndex + 0.3;
        }
        if (GeometryStroke is not null)
        {
            cartesianChart.Canvas.AddDrawableTask(GeometryStroke);
            GeometryStroke.SetClipRectangle(cartesianChart.Canvas, clipping);
            GeometryStroke.ZIndex = actualZIndex + 0.4;
        }

        if (!isSegmentEmpty) segmentI++;
    }

    var maxSegment = fillPathHelperContainer.Count > strokePathHelperContainer.Count
        ? fillPathHelperContainer.Count
        : strokePathHelperContainer.Count;

    for (var i = maxSegment - 1; i >= segmentI; i--)
    {
        if (i < fillPathHelperContainer.Count)
        {
            var segmentFill = fillPathHelperContainer[i];
            Fill?.RemoveGeometryFromPainTask(cartesianChart.Canvas, segmentFill);
            segmentFill.Commands.Clear();
            fillPathHelperContainer.RemoveAt(i);
        }

        if (i < strokePathHelperContainer.Count)
        {
            var segmentStroke = strokePathHelperContainer[i];
            Stroke?.RemoveGeometryFromPainTask(cartesianChart.Canvas, segmentStroke);
            segmentStroke.Commands.Clear();
            strokePathHelperContainer.RemoveAt(i);
        }
    }

    if (DataLabelsPaint is not null)
    {
        cartesianChart.Canvas.AddDrawableTask(DataLabelsPaint);
        DataLabelsPaint.SetClipRectangle(cartesianChart.Canvas, clipping);
        DataLabelsPaint.ZIndex = actualZIndex + 0.5;
    }

    pointsCleanup.CollectPoints(
        everFetched, cartesianChart.View, primaryScale, secondaryScale, SoftDeleteOrDisposePoint);

    _geometrySvgChanged = false;
}

`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions