DEV Community

Cover image for WPF Chart Performance Benchmark: Rendering 1 Million Data Points
Zahra Sandra Nasaka for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

WPF Chart Performance Benchmark: Rendering 1 Million Data Points

TL;DR: Ever tried rendering 1 million data points in a WPF chart without crashing your app? This guide benchmarks two high-performance series types, FastLineSeries and FastLineBitmapSeries, to evaluate rendering speed, memory usage, and UI responsiveness. Learn how to choose the right charting strategy for scalable, real-time data visualization in WPF applications.

Welcome to the Chart of the Week blog series!

Handling large datasets in WPF Charts can be challenging. Slow rendering and laggy interactions can ruin the user experience. In this guide, we’ll benchmark WPF chart performance using two high-speed series types, FastLineSeries and FastLineBitmapSeries, by rendering 1 million data points. If you’re building dashboards, health trackers, or financial apps, this walkthrough will help you optimize chart responsiveness and memory usage.

Benchmark performance in WPF Charts


Benchmark performance in WPF Charts

Why chart performance matters

Modern applications often deal with massive datasets. For WPF desktop apps, efficient rendering and smooth interactions are critical. The SfChart is designed for high-performance charting, enabling the fast visualization of large datasets without compromising speed or responsiveness.

This benchmark demonstrates how specialized series types can deliver fast load times, responsive panning and zooming, and stable memory usage, even with millions of points.

What you’ll learn:

  • Build high-performance WPF Charts: Configure charts to handle up to 1 million data points.
  • Choose the right series type: Compare FastLineSeries and FastLineBitmapSeries for speed and responsiveness.
  • Implement a benchmarking workflow: Measure load time, pan/scroll latency, zoom performance, and memory usage.
  • Scale confidently: Ensure your charts remain smooth as data grows.

Building the benchmarking app

Step 1: Configure the WPF Chart

Begin by installing the Syncfusion.SfChart.WPF package via NuGet and referring to the official documentation to set up the chart control in your WPF application.

Step 2: Define the Models

Next, create a DataModel class to represent individual data points, then define a BenchmarkResult class to store performance metrics such as:

  • InitialLoadMs: Time to render after data binding.
  • PanScrollMs: Time for panning or scrolling.
  • ZoomMs: Time for zoom operations.
  • MemoryMB: Memory consumed during rendering.
  • AvgUIFrameMs: Average frame time for UI responsiveness.

Refer to the code example.

public class DataModel
{
    public double XValue { get; set; }
    public double YValue { get; set; }

    public DataModel(double x, double y)
    {
        XValue = x;
        YValue = y;
    }
}

public class BenchmarkResult
{
    public string SeriesType { get; set; } = "";
    public long InitialLoadMs { get; set; }
    public long PanScrollMs { get; set; }
    public long ZoomMs { get; set; }
    public double MemoryMB { get; set; }
    public double AvgUIFrameMs { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Set up the ViewModel

Create a BenchmarkViewModel class that implements INotifyPropertyChanged to enable real-time UI updates as data changes. Inside this class, generate a dataset consisting of 10 series, each containing 100,000 data points, using the GenerateDataset() method, resulting in a total of 1 million points.

Define two commands, StartBenchmarkCommand, which initiates the benchmarking process, and ClearCommand, which resets the chart and results. This structure promotes a clean separation of concerns, making the application easier to maintain, extend, and test.

Refer to the code example.

public class BenchmarkViewModel : INotifyPropertyChanged
{
    . . .

    public ICommand StartBenchmarkCommand { get; }
    public ICommand ClearCommand { get; }

    private readonly int seriesCount = 10;
    private readonly int pointsPerSeries = 100_000;
    private List<ObservableCollection<DataModel>> dataset = new();

    public BenchmarkViewModel()
    {
        StartBenchmarkCommand = new RelayCommand(async _ => await RunBenchmarkAsync(), _ => Chart != null);
        ClearCommand = new RelayCommand(_ => Clear());
        GenerateDataset();
    }

    private void GenerateDataset()
    {
        dataset.Clear();
        for (int s = 0; s < seriesCount; s++)
        {
            var col = new ObservableCollection<DataModel>();
            double y = rng.NextDouble() * 10.0; // common initial band

            for (int i = 0; i < pointsPerSeries; i++)
            {
                col.Add(new DataModel(i, y));
                if (randomNumber.NextDouble() > .5)
                {
                    y += randomNumber.NextDouble();
                }
                else
                {
                    y -= randomNumber.NextDouble();
                }
            }
            dataset.Add(col);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Select high-performance series types

When visualizing millions of data points, choosing the right series type is critical. SfChart provides specialized Fast Series types optimized for speed:

  • FastLineSeries: Renders data using polyline segments for smooth visualization of continuous trends across large datasets.
  • FastLineBitmapSeries: Uses WritableBitmap for extreme performance, ideal for data-intensive scenarios where minor visual compromises are acceptable. This series type supports anti-aliasing. Enable anti-aliasing only when visual clarity is a priority, as it can slightly impact performance.

Refer to the code example.

public class BenchmarkViewModel : INotifyPropertyChanged
{
  . . .
private ChartSeriesCollection CreateSeries(bool isBitmap, bool antiAliasing)
{
    var list = new ChartSeriesCollection();
    for (int s = 0; s < seriesCount; s++)
    {
        if (isBitmap)
        {
            list.Add(new FastLineBitmapSeries
            {
                ItemsSource = dataset[s],
                XBindingPath = nameof(DataModel.XValue),
                YBindingPath = nameof(DataModel.YValue),
                EnableAntiAliasing = antiAliasing, 
                StrokeThickness = 1,
                Stroke = new SolidColorBrush(ColorFromIndex(s))
            });
        }
        else
        {
            list.Add(new FastLineSeries
            {
                ItemsSource = dataset[s],
                XBindingPath = nameof(DataModel.XValue),
                YBindingPath = nameof(DataModel.YValue),
                StrokeThickness = 1,
                Stroke = new SolidColorBrush(ColorFromIndex(s))
            });
        }
    }
    return list;
} 
 . . .
}
Enter fullscreen mode Exit fullscreen mode

The benchmarking app uses these fast series types to demonstrate SfChart’s ability to efficiently handle large datasets, making it well-suited for performance-critical applications.

Step 5: Execute benchmarking workflow

The RunBenchmarkAsync method coordinates the entire benchmarking process from start to finish. It performs a series of timed operations to evaluate performance under different conditions.

Specifically, it measures:

  • Initial load time.
  • Memory usage before and after rendering.
  • Pan and zoom responsiveness.
  • Average UI frame time

Refer to the code example.

public async Task RunBenchmarkAsync()
{
    if (Chart == null) return;

    Status = "Benchmark Started";
    CommandManager.InvalidateRequerySuggested();

    if (Chart.PrimaryAxis is ChartAxisBase2D x)
    {
        x.ZoomFactor = 1;
        x.ZoomPosition = 0;
    }

    // GC before measure for accurate memory tracking
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    bool isBitmap = SelectedSeriesType != "FastLineSeries";
    bool enableAntiAliasing = isBitmap && EnableAA;

    ChartSeriesCollection seriesCollection = CreateSeries(isBitmap, enableAntiAliasing);

    long memBefore = GC.GetTotalMemory(true);
    Chart.Series.Clear();

    // Scenario 1: Measure load time
    var stopWatch = Stopwatch.StartNew();

    Chart.Series = seriesCollection; 
    await WaitForChartRenderAsync(); 
    stopWatch.Stop();
    long memAfter = GC.GetTotalMemory(true);
    long exactLoadMs = stopWatch.ElapsedMilliseconds;

    double memMB = (memAfter - memBefore) / (1024.0 * 1024.0);

    await WaitForRenderAsync();
    await Task.Delay(DelayAfterLoadMs);

    // Scenario 2: Pan/Scroll
    frameTimes.Clear();
    int panSteps = 30;
    stopWatch.Restart();
    for (int i = 0; i < panSteps; i++)
    {
        Chart.PrimaryAxis.ZoomFactor = 0.2;
        Chart.PrimaryAxis.ZoomPosition = i / (double)panSteps * (1.0 - Chart.PrimaryAxis.ZoomFactor);
        await WaitForChartRenderAsync();
    }
    stopWatch.Stop();
    long panMs = stopWatch.ElapsedMilliseconds;

    // Scenario 3: Zoom cycles
    frameTimes.Clear();
    stopWatch.Restart();
    for (int i = 0; i < 5; i++)
    {
        await ZoomToAsync(0.1, i * 0.15);
        await ZoomToAsync(0.02, i * 0.15);
        await ZoomToAsync(1.0, 0.0);
    }
    stopWatch.Stop();
    long zoomMs = stopWatch.ElapsedMilliseconds;

    double avgUiMs = frameTimes.Count > 0 ? frameTimes.Average() : 0;

    Status = "Benchmark Completed!";
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Display benchmark results in the UI

To present benchmarking results effectively, the application displays key metrics such as LoadMetricsText, PanMetricsText, ZoomMetricsText, MemoryMetricsText, and UiRespMetricsText using TextBlock controls. These metrics are formatted via the FormatThreeCases helper method in the BenchmarkViewModel, offering a comparative view across:

  • FastLineSeries.
  • FastLineBitmapSeries.
  • FastLineBitmapSeries with anti-aliasing enabled.

Additionally, the UI integrates SfLineSparkline controls to visualize historical performance trends for load time and UI responsiveness over the last 10 benchmark runs, providing users with a quick and intuitive overview of performance consistency.

Displaying benchmark results in the UI using WPF Charts


Displaying benchmark results in the UI using WPF Charts

Evaluate benchmark results

Once the benchmarking process is complete and the results are displayed in the UI, it’s important to interpret the metrics to understand how well the chart performs under load. Here’s a breakdown of what each value indicates:

  • Initial load: ~57 ms for 1 million points.
  • Pan/scroll: ~252 ms.
  • Zoom: ~133 ms.
  • UI frame time: ~4.47 ms/frame (well below 16.67 ms for 60 FPS).

Here’s a quick preview of the functional output of our benchmarking application:

Evaluating benchmark performance results using Syncfusion WPF Charts


Evaluating benchmark performance results using Syncfusion WPF Charts

Performance best practices

This benchmark highlights how Syncfusion WPF Charts handle one million data points efficiently, showing the importance of using fast series types for responsive, high-performance applications.

  • Disable ListenPropertyChange for large datasets unless real-time updates are needed.
  • Suspend and resume notifications during batch updates using SuspendSeriesNotification() and ResumeSeriesNotification().
  • Use fast series types for large datasets.
  • Profile performance using tools like Visual Studio Diagnostics or dotTrace.
  • Monitor UI frame time for smooth interactions.

For a deeper comparison of series types and optimization strategies, refer to this helpful Syncfusion on KB article.

GitHub reference

You can find the full source code for this sample in our GitHub demo.

Conclusion

Thank you for taking the time to go through this blog. This benchmarking exercise demonstrates how Syncfusion WPF Charts efficiently handle large datasets, up to one million points, with optimized load times, smooth interactions, and minimal memory usage. FastLineSeries and FastLineBitmapSeries are key to achieving high performance in data-intensive applications. By applying these best practices, developers can build scalable, responsive WPF charts that deliver a superior user experience.

If you’re a Syncfusion user, you can download the setup from the license and downloads page. Otherwise, you can download a free 30-day trial.

You can also contact us through our support forum, support portal, or feedback Portal for queries. We are always happy to assist you!

Related Blogs

This article was originally published at Syncfusion.com.

Top comments (0)