ScottPlot.NET
GitHub Repo stars

Render Video with ScottPlot

Animated plots can be generated by rendering images frame-by-frame and combining them into a video file. In this example, we will use FFMpeg to generate a video file of a ScottPlot as the axis limits are panned from left to right.

  1. Create a new .NET Console project

  2. Modify the csproj to target 8.0-windows

  3. Add the FFMpegCore NuGet package

  4. Add the ScottPlot NuGet package

  5. Add the following into Program.cs

using FFMpegCore;
using FFMpegCore.Pipes;
using ScottPlot;
using SkiaSharp;

// prepare a plot with sample data
double[] values = Generate.Sin(10_000, oscillations: 100);
Plot plot = new();
plot.Add.Signal(values);

// setup the frame maker
double videoDuration = 5;
double frameRate = 30;
int frameCount = (int)(videoDuration * frameRate);
IEnumerable<IVideoFrame> frames = FrameMaker(600, 300, frameCount, frameRate, plot);

// generate the video
RawVideoPipeSource videoFramesSource = new(frames) { FrameRate = frameRate };
bool success = FFMpegArguments
    .FromPipeInput(videoFramesSource)
    .OutputToFile("output.webm", overwrite: true, options => options.WithVideoCodec("libvpx-vp9"))
    .ProcessSynchronously();
static IEnumerable<IVideoFrame> FrameMaker(int width, int height, int frameCount, double frameRate, Plot plot)
{
    for (int i = 0; i < frameCount; i++)
    {
        Console.WriteLine($"\rRendering frame {i + 1} of {frameCount}");
        plot.Axes.SetLimitsX(i, i + 100); // pan from left to right

        using SKBitmap bmp = new(width, height);
        using SKCanvas canvas = new(bmp);
        using SKBitmapFrame frame = new(bmp);
        plot.Render(canvas, width, height);
        yield return frame;
    }
}
class SKBitmapFrame(SKBitmap bmp) : IVideoFrame, IDisposable
{
    public int Width => Source.Width;
    public int Height => Source.Height;
    public string Format => "bgra";
    private readonly SKBitmap Source = bmp;
    public void Serialize(Stream pipe) => 
        pipe.Write(Source.Bytes, 0, Source.Bytes.Length);
    public Task SerializeAsync(Stream pipe, CancellationToken token) => 
        pipe.WriteAsync(Source.Bytes, 0, Source.Bytes.Length, token);
    public void Dispose() => Source.Dispose();
}