Plot 100M Points WPF Performance Guide

Performance oriented c# code, memory benchmarks, and GPU architecture analysis for ProEssentials, SciChart, LightningChart, Syncfusion, and DevExpress — verified against vendor documentation and public source code

100 million points WPF
large dataset chart
GPU compute shader
zero copy data
WPF chart performance
chart benchmark
C# charting
plot millions

Plot 100M Points, WPF Chart Performance
A 5-Library C# Code Comparison

How do you display 100,000,000 data points in a WPF line chart? The answer depends entirely on which charting library you use. Some handle it in 15 lines of code with zero memory overhead. Some require specialized series types and sacrifice data fidelity. Some simply cannot do it at all.

This post provides complete, working C# code for all five major WPF charting libraries — ProEssentials, SciChart, LightningChart, Syncfusion, and DevExpress — attempting to plot the same 100 million float values into a single-series line chart. We compare every aspect: lines of code, memory consumption, render time, data fidelity, and what each library actually does with your data behind the scenes.

If you've searched for "plot millions of points WPF" or "WPF chart performance," this is the concrete code-level answer.

Try it yourself: installing ProEssentials includes a demo called GigaPrime2D (with full example project source code). This demo continuously plots 100 million points — changing all 100M values with every update. The code is deliberately minimal, making it easy for anyone to plug in a competing library and build a working side-by-side comparison at 100 million points.

The Test: 100,000,000 Float Values

Every library receives the same test: render a single-series line chart from 100 million sequential float values representing a sine wave with random noise — a pattern typical of sensor data, signal processing, and scientific acquisition. The question isn't whether the chart looks pretty — it's whether the library can handle the data at all, and what it costs in memory, time, and fidelity to do so.

ParameterValue
Data points100,000,000 (100 million)
Data typefloat (4 bytes per value) — 400 MB raw data
Chart type2-D line chart, single series, sequential X-axis
PatternSine wave with random noise (realistic signal data)
PlatformWPF (.NET 8), Windows 11, mid-range GPU
What we measureLines of code, memory overhead, render time, data fidelity

Below, we show the complete C# code for each library, annotated with what happens to your data at each step. Pay attention to two things: how the library receives your data (copy vs. pointer), and how it renders it (every point vs. resampled subset).

Library 1: ProEssentials — Zero-Copy + GPU Compute Shader

ProEssentials uses UseDataAtLocation() to point the chart directly at your existing float array. No copy, no conversion, no duplication. The chart's native DLL stores a pointer to your memory and reads it during rendering. Combined with ComputeShader = true, the GPU's compute shader constructs the chart image using potentially 2,000+ shader cores — the CPU never iterates over the 100 million values.

// ProEssentials — Plot 100 Million Points (WPF, ~15 lines)
// NuGet: Install-Package Gigasoft.ProEssentials

// 1. Allocate your data — this is the ONLY copy that will ever exist
float[] yData = new float[100_000_000];
for (int i = 0; i < yData.Length; i++)
    yData[i] = (float)Math.Sin(i * 0.0001) + (float)(rand.NextDouble() * 0.1);

// 2. Configure the chart
Pesgo1.PeData.Subsets = 1;
Pesgo1.PeData.Points = 100_000_000;
Pesgo1.PePlot.Method = SGraphPlottingMethod.Line;
Pesgo1.PeConfigure.RenderEngine = RenderEngine.Direct3D;
Pesgo1.PeData.ComputeShader = true;
Pesgo1.PeData.Filter2D3D = true;  // GPU pre-filter shader — lossless min/max

// 3. Zero-copy: point the chart at your existing array — no duplication
Pesgo1.PeData.Y.UseDataAtLocation(yData, yData.Length);

// 4. Tell the Direct3D engine to rebuild vertices and colors
Pesgo1.PeFunction.Force3dxVerticeRebuild = true;
Pesgo1.PeFunction.Force3dxNewColors = true;

// 5. Render
Pesgo1.PeFunction.ReinitializeResetImage();

The key line is UseDataAtLocation(yData, yData.Length). This is not a copy operation — it's a pointer assignment. The chart reads your array in place. With Filter2D3D = true, ProEssentials inserts a preliminary GPU compute shader into the rendering pipeline that performs lossless min/max filtering before the final rendering shader runs. Unlike the aggressive viewport-width resampling used by SciChart and DevExpress — which can reduce 100 million points down to just a few thousand — ProEssentials' filter is deliberately conservative: a series always retains at least 500,000 points after filtering, because when both the filtering shader and the rendering shader execute on the GPU in microseconds, there is no reason to throw away more data than necessary. The result is a lossless representation that preserves every peak, valley, and transient in your signal.

The Force3dxVerticeRebuild and Force3dxNewColors flags tell the Direct3D engine to reconstruct its vertex and color data from the source array. If you later modify values in yData and set these flags again before calling ReinitializeResetImage(), the chart reflects the changes immediately because it's reading the same memory. The entire setup is 15 lines. No special series type — the same Pesgo (Scientific Graph Object) that handles 100 points handles 100 million. ProEssentials' own example 115 plots 20 million points across 5 series (100 million total) and renders at interactive speeds without breaking a sweat — 20 million per series is simply nothing for the compute shader pipeline.

Result:

~15 lines of C#. ~0 MB memory overhead. ~15 ms render time. Lossless min/max filtering via GPU compute shader. The same Pesgo control for 10 points or 100 million.

Library 2: SciChart — Array Copy + Resampling

SciChart can display 100 million points, but the path involves two significant tradeoffs. First, dataSeries.Append() copies all 100 million values into SciChart's internal storage as double[] — doubling the memory from float (4 bytes) to double (8 bytes). Second, SciChart's rendering pipeline downsamples the data to approximately 2× the viewport width (roughly 3,840 points on a 1920-pixel display).

// SciChart — Plot 100 Million Points (WPF)
// NuGet: Install-Package SciChart, SciChart.Wpf

// 1. Allocate your data
double[] yData = new double[100_000_000];  // Note: SciChart uses double[], not float[]
double[] xData = new double[100_000_000];
for (int i = 0; i < yData.Length; i++) {
    xData[i] = i;
    yData[i] = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1;
}

// 2. Create data series — copies all 100M values into internal storage
var dataSeries = new XyDataSeries<double, double>();
dataSeries.Append(xData, yData);  // ~800 MB internal copy (float→double + overhead)

// 3. Configure resampling — without this, 100M points will not render
var lineSeries = new FastLineRenderableSeries();
lineSeries.DataSeries = dataSeries;
lineSeries.ResamplingMode = ResamplingMode.Auto;  // downsamples to ~2×viewport width

// 4. Add to chart surface
sciChartSurface.RenderableSeries.Add(lineSeries);

The ResamplingMode.Auto setting is what makes 100M points feasible in SciChart. Without it, the GPU pipeline would need to process vertex buffers for 100 million data points — which is not how SciChart's game-engine architecture works. Instead, the CPU-side resampler selects a representative subset (using min/max per pixel bin), and only that subset is sent to the GPU for rendering.

This means SciChart is rendering approximately 3,840 points out of 100 million — about 0.004% of your data. For visual overview at full zoom-out, this is often acceptable. But if you zoom in to a 1-second window of a 10-hour recording, the resampler re-scans the relevant range on the CPU to produce a new representative subset. The original 100 million values sit in SciChart's internal double[] array consuming ~800 MB, waiting to be resampled on demand.

Library 3: LightningChart — Array Copy + Specialized Series

LightningChart can handle 100 million points, but only through its SampleDataSeries — a specialized series type designed specifically for fixed-interval (uniformly-sampled) data. Standard series types (PointLineSeries, FreeformPointLineSeries) cannot handle this scale. The data is copied into an internal buffer via the AddSamples(float[]) method, which LightningChart's DirectX immediate-mode renderer then processes. A newer variant, SampleDataBlockSeries (v10.1+), offers even better performance for streaming/sweeping scenarios.

// LightningChart — Plot 100 Million Points (WPF)
// NuGet: Install-Package Arction.LightningChart.Ultimate

// 1. Allocate your data
float[] yData = new float[100_000_000];
for (int i = 0; i < yData.Length; i++)
    yData[i] = (float)Math.Sin(i * 0.0001) + (float)(rand.NextDouble() * 0.1);

// 2. Configure chart for large data — must use Non-Bindable edition for best performance
lightningChart.BeginUpdate();
lightningChart.ViewXY.XAxes[0].SetRange(0, 100_000_000);

// 3. Create SampleDataSeries (fixed-interval data) — standard PointLineSeries can't scale
var series = new SampleDataSeries(lightningChart.ViewXY, lightningChart.ViewXY.XAxes[0],
    lightningChart.ViewXY.YAxes[0]);
series.FirstSampleTimeStamp = 0;
series.SamplingFrequency = 1;  // 1 sample per X unit (sequential)

// 4. Add all 100M samples — copies float[] into internal buffer
series.AddSamples(yData, false);

// 5. Add series to chart
lightningChart.ViewXY.SampleDataSeries.Add(series);
lightningChart.EndUpdate();

// Note: SampleDataBlockSeries (v10.1+) is the newer, faster alternative
//       for streaming/sweeping data. Both require Non-Bindable edition
//       for best performance — MVVM data binding is limited.

The critical limitation: for best performance, LightningChart requires the Non-Bindable edition. LightningChart's WPF product ships in Bindable and Non-Bindable editions — the Non-Bindable edition is faster and supports multithreading, but sacrifices WPF's MVVM data binding infrastructure. If your application uses WPF's data binding pattern (as most modern WPF apps do), you face a tradeoff between performance and architectural compatibility.

LightningChart renders all 100 million points (lossless) through its DirectX pipeline. The tradeoff is the array copy (~400 MB additional memory), the mandatory specialized series type, the Non-Bindable edition recommendation, and LightningChart's continuous rendering loop that keeps the GPU active even when the chart is idle.

Library 4: Syncfusion — Object-Per-Point Architecture

Syncfusion's WPF charting uses an object-per-point data model. Each data value must be wrapped in a class instance (typically with X and Y properties) and added to an IEnumerable collection. At 100 million points, this means creating 100 million .NET objects — each with a 24-byte object header (on 64-bit), two double properties (16 bytes), and padding. Total: approximately 2.4 GB of managed heap allocations on top of the original 400 MB of source data.

// Syncfusion — Plot 100 Million Points (WPF)
// NuGet: Install-Package Syncfusion.SfChart.WPF

// 1. Create data model class — Syncfusion requires object-per-point
public class DataPoint {
    public double X { get; set; }
    public double Y { get; set; }
}

// 2. Allocate 100 million objects (~2.4 GB+ with object headers)
var data = new ObservableCollection<DataPoint>();
for (int i = 0; i < 100_000_000; i++)
    data.Add(new DataPoint {
        X = i,
        Y = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1
    });
// ⚠ This loop alone takes minutes and will likely trigger OutOfMemoryException
//    Syncfusion's practical ceiling is ~1 million points

// 3. Use FastLineBitmapSeries for best performance
var series = new FastLineBitmapSeries();
series.ItemsSource = data;
series.XBindingPath = "X";
series.YBindingPath = "Y";
chart.Series.Add(series);

In practice, this allocation loop takes minutes and will typically trigger an OutOfMemoryException before completion, especially on 32-bit processes or systems with limited memory. Syncfusion's practical ceiling for responsive charting is approximately 1 million points using FastLineBitmapSeries. The library is well-suited for business dashboards with moderate data volumes, but not designed for the 100-million-point scientific/engineering scenario.

Library 5: DevExpress — Object-Per-Point + Resampling

DevExpress WPF uses a LineSeries2D with AllowResample = true to handle large data. Note: DevExpress's SwiftPlotSeriesView, often referenced in performance discussions, is WinForms-only and does not exist in the WPF chart control. In WPF, AllowResample (introduced in v20.1) enables viewport-aware resampling that limits rendering to the visible range. DevExpress's own blog post tested this approach up to 50 million points. However, the data model is still object-per-point — at 100 million, you must allocate ~2.4 GB of DataPoint objects before rendering can begin.

// DevExpress — Plot 100 Million Points (WPF)
// NuGet: Install-Package DevExpress.Wpf.Charts

// 1. Create data model — DevExpress WPF requires object-per-point
public class DataPoint {
    public double Argument { get; set; }
    public double Value { get; set; }
}

// 2. Allocate 100 million objects (~2.4 GB+ with object headers)
var data = new List<DataPoint>(100_000_000);
for (int i = 0; i < 100_000_000; i++)
    data.Add(new DataPoint {
        Argument = i,
        Value = Math.Sin(i * 0.0001) + rand.NextDouble() * 0.1
    });
// ⚠ Minutes of allocation, ~2.4 GB managed heap
//    DevExpress tested AllowResample up to 50M (v20.1 blog post)

// 3. Use LineSeries2D with AllowResample (resamples to viewport)
var series = new LineSeries2D();
series.DataSource = data;
series.ArgumentDataMember = "Argument";
series.ValueDataMember = "Value";
series.AllowResample = true;   // critical — resamples to viewport width
series.MarkerVisible = false;  // required for large-data performance
series.LineStyle.Thickness = 1;

// 4. Add to XYDiagram2D
var diagram = (XYDiagram2D)chartControl.Diagram;
diagram.Series.Add(series);

// Note: SwiftPlotSeriesView is WinForms-only — NOT available in WPF.
//       WPF uses LineSeries2D + AllowResample for large data.

At 100 million points, creating 100 million .NET objects is borderline feasible on a 64-bit system with sufficient RAM (~2.8 GB total including source data). If the allocation succeeds, AllowResample will render only the viewport subset — similar to SciChart's resampling approach. But unlike SciChart (which stores raw doubles in a flat array), DevExpress stores wrapped objects with 24-byte headers, making the memory cost per point roughly 6× higher. DevExpress's tested ceiling is 50 million; 100 million is untested territory per their published benchmarks.

Side-by-Side Results

Here's what happens when each library attempts to plot 100,000,000 float values:

FactorProEssentialsSciChartLightningChartSyncfusionDevExpress
Plots 100M?✅ Yes — natively⚠️ With resampling⚠️ Specific series type❌ OOM at ~16M⚠️ ~50M tested (resample)
Lines of C#~15~18~22N/A (OOM)~22
Data loadingZero-copy pointerFull array copyArray copy (AddSamples)Object-per-pointObject-per-point
Internal data typefloat (your array)double (internal copy)float (internal buffer)object wrapperobject wrapper
Memory: your data400 MB400 MB400 MB400 MB400 MB
Memory: library overhead~0 MB~800 MB~400 MB~2,400 MB+~2,400 MB+
Total memory~400 MB~1,200 MB~800 MBOOM crash~2,800 MB (borderline)
Load timeInstant (pointer)Seconds (copy)Sub-second (memcpy)Minutes / crashMinutes (100M objects)
Render time~15 ms (GPU compute)Fast (resampled subset)Fast (SampleDataSeries)Fast (resampled subset)
Data fidelity100% — lossless GPU filter~0.004% — resampled100%Resampled (viewport only)
Special series type?No — same PesgoResamplingMode requiredSampleDataSeriesFastLineBitmapSeriesLineSeries2D + AllowResample
MVVM compatible?YesYesNo (non-bindable)YesYes
GPU renderingCompute shadersGame-engine pipelineDirectX immediateCPU onlyDirectX (optional)

Why Zero-Copy Changes Everything

The memory comparison above reveals the fundamental architectural difference. When ProEssentials calls UseDataAtLocation(yData, yData.Length), it stores a pointer — 8 bytes on x64. The chart's native DLL reads your float[] directly during rendering. No conversion from float to double. No intermediate DataSeries object. No managed collection wrapping each value.

This has cascading benefits beyond memory savings. Loading time is instant (a pointer assignment vs. iterating 100 million values). GC pressure is zero (no managed allocations means no garbage collection pauses during rendering). And if you have two charts showing different views of the same data — like a 3-D surface and a 2-D contour — both can call UseDataAtLocation on the same array. Two charts, one array, zero duplication.

SciChart's Append() model copies your float[] into an internal double[] — tripling the per-value memory (4 bytes source → 8 bytes internal + overhead). LightningChart's AddSamples() copies the array into an internal buffer. Syncfusion and DevExpress wrap each value in an object with a 24-byte header. These are architectural decisions baked into each library's data pipeline, not configuration options you can change.

Data ModelOverhead (100M pts)Used By
Zero-copy pointer~0 MBProEssentials
Array copy (float)~400 MBLightningChart
Array copy (double)~800 MBSciChart
Object-per-point~2,400 MB+Syncfusion, DevExpress

The math:

100M floats × 4 bytes = 400 MB (your data). ProEssentials adds ~0 MB. SciChart adds ~800 MB. LightningChart adds ~400 MB. Syncfusion/DevExpress add ~2,400 MB — if they don't crash first.

What Resampling Costs You

SciChart and DevExpress both use resampling as a practical engineering tradeoff: display a representative subset of the data rather than rendering all of it. On a 1920-pixel-wide chart, there are at most 1,920 horizontal pixel columns. Rendering 100 million points into 1,920 columns means ~52,083 points compete for each pixel. SciChart's resampler picks the min and max value per bin, producing approximately 3,840 representative points — enough to preserve the visual envelope of the data. DevExpress's AllowResample uses a similar viewport-limited approach.

For overview displays ("show me the full dataset"), this is often visually indistinguishable from lossless rendering. The problem appears when precision matters: anomaly detection in scientific data, exact peak identification in signal processing, or regulatory compliance in medical/pharmaceutical recording where every sample must be verifiable. If a 1-sample transient occurred at point 47,291,033, a resampler that reduces 52,083 points to 2 representatives per pixel column may or may not select that specific point.

ProEssentials takes a fundamentally different approach with its Filter2D3D GPU compute shader. Rather than aggressively reducing 100 million points to a handful of screen-width representatives, the Filter2D3D shader performs a conservative, lossless min/max pre-filter pass on the GPU — and the result always retains at least 500,000 points. The logic is simple: when both the filtering shader and the final rendering shader execute on the GPU in microseconds, there is no reason to be aggressive about discarding data. The filter preserves every min and max within each filtering window, so transients, spikes, and anomalies are never discarded. It is fundamentally lossless — the rendered image is identical to one produced from the full dataset. LightningChart also renders losslessly through its DirectX pipeline, but requires an array copy (~400 MB overhead) and a continuous rendering loop. ProEssentials achieves the same lossless result with zero memory overhead and on-demand rendering.

What It Costs to Plot 100 Million Points

Four libraries can attempt to render 100 million points, though only three have been tested at that scale. Here's what each costs — including the licensing model, because a library that can handle 100M points but costs $288,750 over 5 years is a different proposition than one that costs $11,999 once:

FactorProEssentialsSciChartLightningChartDevExpress
License (1 dev)$4,799 perpetual~$1,749 / year~$5,775 / year~$1,799 / year
10 devs × 5 years$11,999 total~$87,450~$288,750~$89,950
Account to evaluate?NoYesYes (name + phone)Yes
NuGet install1 public package3–5 packages1 packagePrivate feed
Deploy size5–8 MB15–25 MB80–150 MB20–40 MB
SupportFree, unlimited, lifetime10 tickets / year2–10 tickets / yearSubscription-tier support
Tested at 100M?YesYes (via resampling)Yes50M max (their blog)

ProEssentials is the only library that combines zero-copy data loading, lossless GPU compute shader filtering, on-demand frame model, perpetual licensing, and free unlimited support — at a fraction of the subscription cost of the other libraries that can handle the dataset.

The Bottom Line

Four libraries can attempt 100 million points in WPF. Only ProEssentials does it with zero memory overhead, lossless GPU filtering, and on-demand rendering — in 15 lines of code. SciChart handles the scale through resampling (fast, but lossy). LightningChart handles it through a specialized series type (lossless, but requires array copy, Non-Bindable edition, continuous GPU). DevExpress can attempt it with AllowResample (resampling, but with 2.4 GB of object overhead — untested past 50M in their own benchmarks).

Syncfusion is not designed for this scenario. Its object-per-point architecture hits memory limits well below 100 million, with out-of-memory crashes documented in their own forums at 16 million points. DevExpress faces the same object-per-point bottleneck but partially mitigates it with viewport resampling — if the initial allocation succeeds. Both libraries excel at business dashboards with datasets under 1 million points, where their broad UI control suites provide value that dedicated charting libraries don't.

If your application needs to display 100 million points — sensor data, signal processing, scientific acquisition, industrial monitoring — the code above shows exactly what each library requires. Copy it, run it, measure it. The numbers speak for themselves.

GPU Architecture Deep Dive

How compute shaders, on-demand rendering, and zero-copy data loading work under the hood.

Read more
AI Code Assistance

pe_query.py validates every property path against the compiled DLL binary — no hallucinated API calls.

Read more
Pricing & Support Comparison

5-year TCO for 10 developers across all five libraries. Perpetual vs. subscription licensing.

Read more
Sources & References

All competitor code examples in this post were constructed from official documentation, public GitHub repositories, and vendor forum posts. API names, method signatures, and architectural claims were verified against the sources listed below. Links current as of February 2026.

SciChart

LightningChart

Syncfusion

DevExpress

Try It Yourself — No Account Required

ProEssentials is available on NuGet with no registration, no account creation, and no sales call. Install the package, paste the code above, and run it. If you have questions, support is free, unlimited, and provided directly by the developers who built the engine.

Contact the ProEssentials Team →

Our Mission

Your success is our #1 goal by providing the easiest and most professional benefit to your organization and end-users.

We are Engineers

ProEssentials was born from professional Electrical Engineers needing their own charting components. Join our large list of top engineering companies using ProEssentials.

Thank You

Thank you for being a ProEssentials customer, and thank you for researching the ProEssentials charting engine.