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.

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; }
}
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);
}
}
}
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;
}
. . .
}
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!";
}
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.

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:

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
- Build a WPF Health Tracker Dashboard: Visualize Water Intake & Sleep Patterns with Interactive Charts
- Build a WPF Health Tracker Dashboard: Visualize Calories and Steps with Interactive Charts
- Visualize Your Music: Real-Time WPF Charts that Sync with Sound
- How to Build a Real-Time Cloud Monitoring Dashboard Using WPF Charts
This article was originally published at Syncfusion.com.
Top comments (0)