Skip to content

Add area fill support to DataStreamer#5023

Merged
swharden merged 3 commits intoScottPlot:mainfrom
manaruto:fill-area-datastreamer
Aug 8, 2025
Merged

Add area fill support to DataStreamer#5023
swharden merged 3 commits intoScottPlot:mainfrom
manaruto:fill-area-datastreamer

Conversation

@manaruto
Copy link
Contributor

@manaruto manaruto commented Aug 6, 2025

This PR adds area fill functionality to the DataStreamer class, bringing it to feature parity with the Scatter plot type. Users can now visualize streaming data with filled areas above and below a baseline value.

Fix Filled curve with DataStreamer? #4948

1-DataStreamer Class

Added fill properties matching the Scatter class API:

    public bool FillY { get; set; } = false;
    public double FillYValue { get; set; } = 0;
    public Color FillYAboveColor { get; set; } = Colors.Blue.WithAlpha(.2);
    public Color FillYBelowColor { get; set; } = Colors.Blue.WithAlpha(.2);
    public Color FillYColor { get => FillYAboveColor; set { FillYAboveColor = value; FillYBelowColor = value; } }

2-Modified Render() method to:

Query the renderer for data segments via new GetSegments() method
Draw area fills for each segment before the renderer draws lines
Handle separate above/below coloring relative to the baseline
Properly handle partial buffers during initial data streaming

    public virtual void Render(RenderPack rp)
    {
        if (Renderer is not IDataStreamerView viewWithSegs)
        {
            Renderer.Render(rp);
            return;
        }

        var segs = viewWithSegs.GetSegments(rp);
        if (segs.Count == 0)
            return;

        if (FillY)
        {
            float yBasePx = Axes.YAxis.GetPixel(FillYValue + Data.OffsetY, rp.DataRect);

            using SKPaint paint = new();
            foreach (Pixel[] seg in segs)
            {
                using SKPath path = new();
                path.MoveTo(seg[0].X, seg[0].Y);
                for (int i = 1; i < seg.Length; i++)
                    path.LineTo(seg[i].X, seg[i].Y);

                PixelRect rect = new(seg);
                using SKPath fill = new(path);
                fill.LineTo(rect.Right, yBasePx);
                fill.LineTo(rect.Left, yBasePx);

                // above
                if (yBasePx > rect.Top)
                {
                    PixelRect clip = new(rp.DataRect.Left, rp.DataRect.Right, rp.DataRect.Top, yBasePx);
                    FillStyle fs = new() { IsVisible = true, Color = FillYAboveColor };
                    rp.CanvasState.Save();
                    rp.CanvasState.Clip(clip);
                    Drawing.FillPath(rp.Canvas, paint, fill, fs, clip);
                    rp.CanvasState.Restore();
                }

                // below
                if (yBasePx < rect.Bottom)
                {
                    PixelRect clip = new(rp.DataRect.Left, rp.DataRect.Right, yBasePx, rp.DataRect.Bottom);
                    FillStyle fs = new() { IsVisible = true, Color = FillYBelowColor };
                    rp.CanvasState.Save();
                    rp.CanvasState.Clip(clip);
                    Drawing.FillPath(rp.Canvas, paint, fill, fs, clip);
                    rp.CanvasState.Restore();
                }
            }
        }

        Renderer.Render(rp);

        Data.CountTotalOnLastRender = Data.CountTotal;
    }

3-IDataStreamerView Interface

Added GetSegments() method that returns IReadOnlyList<Pixel[]>
This allows renderers to expose their segment structure for proper fill rendering

IReadOnlyList<Pixel[]> GetSegments(RenderPack rp);

4-Wipe and Scroll Renderers

Implemented GetSegments() in both renderers to return pixel arrays representing the visual segments
Refactored Render()

BREAKING CHANGE: IDataStreamerView interface now requires GetSegments() method implementation

            Streamer1.FillY = true;
            //Streamer1.FillYColor = Streamer1.Color.WithAlpha(.25);   // or set Above/Below separately
            Streamer1.FillYAboveColor = Colors.BlueViolet;
            Streamer1.FillYBelowColor = Colors.DarkOrange;
            Streamer1.FillYValue = -15;                               // baseline
            Streamer1.ViewWipeRight(0.1);
fill-stream.mp4

- Add FillY properties matching Scatter class API (FillY, FillYValue, FillYAboveColor, FillYBelowColor)
- Implement two-tone fill rendering for areas above/below baseline
- Add GetSegments() method to IDataStreamerView interface
- Update Wipe and Scroll renderers to expose segment structure
- Handle partial buffers correctly during initial streaming

BREAKING CHANGE: IDataStreamerView interface now requires GetSegments() method implementation
@swharden
Copy link
Member

swharden commented Aug 8, 2025

Hi @manaruto, this is really complicated - thanks so much for this PR! The video is helpful too. Merging now 🚀

@swharden swharden enabled auto-merge (squash) August 8, 2025 00:37
@swharden swharden merged commit a1667d3 into ScottPlot:main Aug 8, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Filled curve with DataStreamer?

2 participants