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.
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:
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.
Next, create a DataModel class to represent individual data points, then define a BenchmarkResult class to store performance metrics such as:
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; }
} 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);
}
}
} When visualizing millions of data points, choosing the right series type is critical. SfChart provides specialized Fast Series types optimized for speed:
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;
}
. . .
} 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.
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:
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!";
} 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:
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.
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:
Here’s a quick preview of the functional output of our benchmarking application:
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.
For a deeper comparison of series types and optimization strategies, refer to this helpful Syncfusion on KB article.
You can find the full source code for this sample in our GitHub demo.
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!