There's a moment in almost every .NET reporting project where someone says "and we need a chart in the PDF," and the room goes quiet.
Because that's the point where a perfectly clean C# codebase acquires a headless Chromium. Or a native rendering dependency that works on the dev laptop and mysteriously doesn't in the Alpine container. Or a third-party licence with a seat count attached.
TerraFluent.Chart.Reporting 2.0 is my answer to that moment. It's a fluent, MIT-licensed .NET library that renders 26 chart types as self-contained SVG, entirely in managed C#. No JavaScript runtime. No native binaries. No browser. It's the fourth package in the TerraFluent suite, and it's out now.
dotnet add package TerraFluent.Chart.Reporting
Sixty seconds to a chart
using TerraFluent.Chart.Reporting.Builder;
string svg = ChartBuilder.Create()
.Title("Monthly Website Visitors")
.Subtitle("Jan – Jun 2026")
.Size(700, 400)
.XAxis("Month", "Jan", "Feb", "Mar", "Apr", "May", "Jun")
.YAxis("Visitors", min: 0)
.Series(s => s
.AddLine("Visitors", new double[] { 12400, 14800, 16200, 18500, 21000, 19800 }))
.RenderToSvg();
That svg is a complete, standalone <svg> document. Write it to a file, inline it in a Razor page, embed it in a PDF, drop it in an email. It renders the same on every machine because nothing outside your process participated in drawing it.
That last point is the whole design. A chart that is text rather than pixels is a chart you can diff, cache, inline, and reason about — and one that stays sharp at any zoom or print resolution.
The 26 types
The catalogue is deliberately weighted toward reporting rather than scientific plotting:
| Family | Types |
|---|---|
| Standard | Line, Spline, Area, Column, Bar, Pie/Donut, Scatter, Bubble |
| Financial | Waterfall, Candlestick, OHLC, ColumnRange, AreaRange, ErrorBar |
| Flow & hierarchy | Sankey, Treemap, Funnel, Gantt, Stream, Parliament |
| Statistical | BoxPlot, Radar, Heatmap, Dumbbell, DataRing, Gauge |
Each has a matching builder method, so the API surface is predictable:
.Series(s => s.AddColumn("Units Sold", new double[] { 8400, 5200, 3100 }))
.Series(s => s.AddWaterfall(name: "P&L", data: values, totals: totalFlags))
.Series(s => s.Add("Market Share", new double[] { 38, 27, 21, 14 }).AsPie())
There are also computed overlays that derive a series from data you already handed over — AddLinearRegression, AddMovingAverage, AddExponentialSmoothing — which saves the usual detour through a stats helper before you can draw a trend line.
Sankey, Gantt, Treemap, Stream and Parliament are worth calling out: those are the ones that normally push teams toward a JavaScript library, because most server-side .NET plotting libraries simply don't have them.
Three render modes, one API
This is the feature I'd point at first, because it solves a problem most charting libraries don't acknowledge: the same chart has to survive very different destinations.
ChartBuilder.Create().AsStatic() // pure SVG — no JS, no CSS hover, no SMIL
ChartBuilder.Create().AsAnimated() // SVG + CSS hover + SMIL, still zero JavaScript
ChartBuilder.Create().AsInteractive() // adds embedded JS: tooltips, legend toggling, export
- Static is what goes in a PDF or an email client. Outlook will not run your JavaScript, and a PDF rasteriser will not honour your hover rules.
- Animated uses SMIL and CSS only, which means a chart that moves in a Blazor app or an embedded HTML report without shipping a script bundle.
- Interactive is the browser-only mode, for when the chart lives on a real page.
Duration is a separate knob — .Animate(800) in milliseconds, with easing options .EaseOut(), .EaseInOut(), .Bounce(), .Elastic() and .Linear(). Switching modes doesn't change the rest of the chart definition: you build it once and pick the output at the end.
The parts that matter in real reports
Announcement posts love to list chart types. Here's what actually saves you time on the third sprint:
Axes and formatting — label templates like .YAxisFormat("${value}k") or "{value}%", .LabelRotation = -45 for crowded category labels, explicit .Min/.Max/.TickInterval for numeric axes, and a secondary axis via .YAxis2() when a report genuinely calls for one.
Annotations — plot bands to shade a target range, and reference lines with labels and dash styles for thresholds and SLAs. Most "why is this number bad?" questions get answered by a line at 95%, not by another series.
Stacking — .StackNormal() for cumulative totals, .StackPercent() for composition normalised to 100%.
Missing data is a first-class case. Series accept double?[], and null renders as a genuine gap rather than a zero or an interpolated lie. If you have ever shipped a dashboard where a failed ETL run showed up as a dramatic cliff to zero, you know why this matters.
Output where you need it — RenderToSvg(), RenderToFile(), RenderToBytes(), RenderToDataUri() (handy for inlining straight into an <img> or an email), and RenderToHtml()/RenderToHtmlFile(), which wrap the SVG in a <figure> with an optional caption and CSS class. RenderToStream() is there for the synchronous path, with async variants and CancellationToken support on .NET 6+.
Dependency injection and .Fork() — register IChartBuilder and call .Fork() to take an isolated deep copy of the builder state. That's how you keep a shared base configuration (corporate theme, fonts, size) without one request mutating another's chart. It also makes "render this same chart static for the PDF and interactive for the web" a two-line operation.
ISvgRenderer — the rendering engine is replaceable. Useful for snapshot-testing chart output, or bending the markup to an in-house design system.
It's part of a suite, and that's the point
A chart on its own is a picture. A chart inside a document is a deliverable. Because the output is a self-contained SVG string, it drops straight into the rest of the suite:
- TerraFluent.Pdf.Reporting — embed as vector graphics, so the chart stays sharp at print resolution instead of being a blurry PNG
- TerraFluent.Html.Reporting — inline it in a paginated, print-ready HTML report
- TerraFluent.Docx.Reporting — embed in a real Word document
The packages share no runtime dependency, so taking the chart library doesn't drag in the other three.
How it compares
Here's where I'll be straight with you, because a comparison table that says "everything else is bad" is worth nothing.
.NET already has genuinely excellent charting libraries. ScottPlot and LiveCharts2 in particular are superb at things this library deliberately doesn't attempt. The honest question isn't "which is best" — it's "which problem are you solving."
Facts below verified against NuGet in September 2026:
| TerraFluent.Chart | ScottPlot | OxyPlot.Core | LiveCharts2 | |
|---|---|---|---|---|
| Licence | MIT | MIT | MIT | MIT |
| Latest | 2.0.0 | 5.1.59 | 2.2.0 (Sep 2024) | 2.0.5 |
| Runtime dependencies | None | SkiaSharp, SkiaSharp.HarfBuzz, HarfBuzzSharp + Linux native assets | None | LiveChartsCore, SkiaSharp, SkiaSharp.HarfBuzz |
| Native binaries | No | Yes (libSkiaSharp) | No | Yes (libSkiaSharp) |
| Primary output | SVG (vector, text) | Raster bitmap; SVG via SaveSvg |
SVG, PDF | Raster via Skia |
| Targets | netstandard2.0/2.1, net6/8/10 | netstandard2.0, net462, net8/9/10 | netstandard2.0, net462, net6/8 | netstandard2.0, net462, net8 |
| Built for | Server-side documents | Interactive desktop + large datasets | Cross-platform app plotting | MVVM desktop/mobile UI |
ScottPlot
ScottPlot is a fantastic library and the right answer for a large class of problems — interactive plotting, scientific work, and rendering datasets with millions of points, where its raster pipeline genuinely outperforms vector approaches at scale.
The trade-off is the dependency chain. ScottPlot 5 pulls SkiaSharp, SkiaSharp.HarfBuzz and HarfBuzzSharp, which means a native libSkiaSharp in your deployment. On Linux that historically means reaching for SkiaSharp.NativeAssets.Linux.NoDependencies, and installing libfreetype6, libfontconfig1 and fontconfig if you'd rather your text didn't render as tofu boxes. It does have SaveSvg() and GetSvgXml(), but SVG is an export path over a Skia canvas rather than the native output format, and the tracker carries the occasional rough edge there.
Pick ScottPlot when: you're building a desktop app, exploring data interactively, or plotting very large datasets.
OxyPlot
OxyPlot is the closest genuine alternative, and I want to be fair about that: OxyPlot.Core has no dependencies on netstandard2.0/net6/net8, and its SvgExporter produces SVG without any native code. If dependency-free server-side SVG is your only requirement, OxyPlot already does that and has done for years.
Two things to weigh. First, text measurement: without supplying an ITextMeasurer, SVG export falls back to the PDF render context's measurer, which handles simple Type-1 fonts (Helvetica/Arial, Roman, Courier) limited to WinAnsi encoding — a real constraint if your labels aren't Latin. Second, cadence: OxyPlot.Core 2.2.0 shipped in September 2024, and the chart catalogue is oriented toward classic plotting rather than the reporting forms (Sankey, Gantt, Treemap, Funnel, Parliament) that business documents keep asking for.
Pick OxyPlot when: you want a mature, dependency-free plotting library with a long track record and your chart needs are the classic set.
LiveCharts2
LiveCharts2 has the nicest data-binding story of the four. MVVM-friendly, observable collections that animate when the underlying data changes, full MAUI support. For a desktop or mobile app where charts are live UI, it's a joy.
It's also built on SkiaSharp, and it's optimised for a rendering loop attached to a view — not for a stateless request that has to emit a chart into a PDF and forget about it.
Pick LiveCharts2 when: your charts live in a WPF/Avalonia/MAUI app and need to react to changing data.
The commercial suites
Syncfusion, DevExpress and Telerik all ship comprehensive, mature charting with professional support behind it. If you're already paying for one of those suites, use it — you've bought it, and it's good.
The consideration is commercial rather than technical: per-developer licensing, renewal cycles, and redistribution terms that need reading before you ship a product that embeds them. TerraFluent.Chart is MIT, which means no seat count, no revenue threshold, and no "contact sales" step between you and production.
The headless browser approach
Chart.js or Highcharts driven through Puppeteer/Playwright gets you the enormous JavaScript charting ecosystem, which is a genuine advantage in breadth.
It also gets you a Chromium in your container image, a Node runtime, a process lifecycle to babysit, a memory profile that surprises people, cold starts that make serverless painful, and a browser to patch every time a CVE lands. For one chart in a monthly PDF, that is an enormous amount of operational surface. (Highcharts also carries commercial licensing for most business use.)
Where this library is the wrong choice
Worth saying plainly:
- Millions of data points. Vector SVG means one DOM node per mark. For dense scientific plotting, a raster pipeline like ScottPlot's is the correct architecture, not a compromise.
- Charts as live app UI. If the chart is bound to an observable collection and animates on change, LiveCharts2 is built for that and this isn't.
- 3D or GPU-accelerated visualisation. Not attempted.
- You already own a commercial suite. Use what you've paid for.
What's left — and it's a large, underserved slice — is server-side charts that end up inside documents: invoices, monthly statements, compliance reports, emailed dashboards, anything that has to render identically on a build server, in a container, and in a Lambda, with nothing installed.
That's the job this library is built for.
Getting started
dotnet add package TerraFluent.Chart.Reporting
It runs on netstandard2.0 and up, which means a .NET Framework 4.6.1 app that's been in production for a decade can use it just as happily as a brand-new .NET 10 minimal API.
- 📊 Docs: terrafluent.dev/chart
- 🖼️ Showcase: all 26 types, rendered
- 📦 NuGet:
TerraFluent.Chart.Reporting - 📜 Release notes: terrafluent.dev/releases
If you try it, I'd genuinely like to know where it falls short. The chart types in this release exist because people told me which ones they were being forced into JavaScript for — that feedback loop is the whole reason the flow and hierarchy family got built.
Happy charting. 📈