Skip to content

Chart Types

xkqg edited this page May 4, 2026 · 19 revisions

Chart Types

Try it live: Open the Playground to experiment with charts in your browser. Browse the Cookbook (25 pages) for copy-paste code examples with rendered output.

MatPlotLibNet v1.10 includes 78 series types across 13 categories, plus 4 streaming series, 13 map projections, and geographic polygon rendering via MatPlotLibNet.Geo. See the Geographic / Maps section near the bottom for the full geo surface.


Gallery

Each of these renders is produced by the MatPlotLibNet fidelity suite and diffed pixel-for-pixel against a matplotlib reference. Click any tile to open the full-resolution PNG.

Line chart — KDE KDE (Gaussian kernel density) Violin plot Violin (3 groups) Box plot Box (4 groups)
Hexbin density Hexbin (2-D binning) Stem plot Stem (sampled signal) Waterfall chart Waterfall (finance)
Sankey diagram Sankey (flow) Radar chart Radar (5 axes) Streamplot Streamplot (vector field)
Polar scatter Polar scatter Treemap Treemap (squarified) Pie chart Pie (with labels)

Every one of these chart types is available via a single fluent call — see the sections below for the builder signatures.


Basic

Bar and line charts

Line — Plot()

Plt.Create().Plot(x, y).Save("line.svg");
Plt.Create().Plot(x, y, s => { s.Color = Color.Blue; s.LineWidth = 2; s.Label = "Series A"; }).Save("line.svg");

Scatter — Scatter()

Plt.Create().Scatter(x, y).Save("scatter.svg");
Plt.Create().Scatter(x, y, s => { s.MarkerStyle = MarkerStyle.Circle; s.MarkerSize = 8; }).Save("scatter.svg");

Bar — Bar()

Plt.Create().Bar(["Q1", "Q2", "Q3", "Q4"], [100, 200, 150, 250]).Save("bar.svg");

Histogram — Hist()

double[] data = /* ... */;
Plt.Create().Hist(data, bins: 20).Save("hist.svg");

Pie / Donut

Plt.Create().Pie(["A", "B", "C"], [40, 35, 25]).Save("pie.svg");
Plt.Create().Donut(["A", "B", "C"], [40, 35, 25]).Save("donut.svg");

Step, Fill, Error Bars, Stem, Box, Violin

Plt.Create().Step(x, y).Save("step.svg");
Plt.Create().Fill(x, y1, y2).Save("fill.svg");       // fill between two lines
Plt.Create().ErrorBar(x, y, yErr).Save("errbar.svg");
Plt.Create().Stem(x, y).Save("stem.svg");
Plt.Create().Box(groups).Save("box.svg");             // groups: double[][]
Plt.Create().Violin(groups).Save("violin.svg");

SignalSeries / SignalXYSeries — High-Performance Large Datasets

For data ≥ 100 k points. Uses O(1) viewport slicing (uniform) or O(log n) binary search (non-uniform) + LTTB downsampling. Zero gen-2 GC allocation on the render path.

// Uniform sample rate (e.g. audio, sensor data) — O(1) IndexRangeFor
Plt.Create().Signal(samples, sampleRate: 44100).Save("audio.svg");

// Non-uniform ascending X — O(log n) binary search
Plt.Create().SignalXY(xNonUniform, y).Save("signal_xy.svg");

// One-liner shortcuts
QuickPlot.Signal(samples, sampleRate: 44100, title: "Audio").Save("audio.svg");
QuickPlot.SignalXY(x, y).Save("xy.svg");

Statistical

Plt.Create().Kde(data).Save("kde.svg");
Plt.Create().Ecdf(data).Save("ecdf.svg");
Plt.Create().Rugplot(data).Save("rug.svg");
Plt.Create().Stripplot(groups, labels).Save("strip.svg");
Plt.Create().Swarmplot(groups, labels).Save("swarm.svg");
Plt.Create().Pointplot(groups, labels).Save("point.svg");
Plt.Create().Eventplot(events).Save("event.svg");
Plt.Create().Regression(x, y).Save("regression.svg");   // with LeastSquares fit line
Plt.Create().Residual(x, y, yFit).Save("residual.svg");
Plt.Create().Count(categories).Save("count.svg");

Financial

Financial dashboard

FigureTemplates.FinancialDashboard produces a 3-panel layout (price 60 %, volume 15 %, oscillator 25 %) with grid lines on every panel, bar-center-aligned tick labels, and optional indicator overlays. Each indicator resolves against the most recently added price series — so .BollingerBands(20) followed by .Sma(5) computes the SMA over the raw close (not over the Bollinger middle band) because both draw their input from the underlying OHLC series; chained indicators that want to operate on a prior indicator's output pass the output array explicitly.

Financial dashboard (3-panel)

FigureTemplates.FinancialDashboard(open, high, low, close, volume,
    title: "ACME Corp",
    configurePricePanel: ax => {
        ax.BollingerBands(20);   // BB(20, 2σ) with semi-transparent fill
        ax.Sma(5);
    },
    configureOscillatorPanel: ax => {
        ax.Rsi(close, 14);
        ax.AxHLine(70, rl => { rl.Color = Color.FromHex("#E24A33"); rl.LineStyle = LineStyle.Dashed; });
        ax.AxHLine(30, rl => { rl.Color = Color.FromHex("#E24A33"); rl.LineStyle = LineStyle.Dashed; });
    })
    .WithSize(1200, 700)
    .Save("financial_dashboard.svg");

Candlestick

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Candlestick(open, high, low, close)
        .Sma(period: 20)
        .WithLegend())
    .Save("candlestick.svg");

OHLC

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax.Ohlc(open, high, low, close))
    .Save("ohlc.svg");

Grid / Field

Heatmap with colormap

Heatmap

double[,] data = /* 2-D array */;
Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Heatmap(data)
        .WithColorMap("plasma")
        .WithColorBar(cb => cb with { Label = "Intensity" }))
    .Save("heatmap.svg");

Contour & Filled Contour

Plt.Create().Contour(x, y, z).Save("contour.svg");
Plt.Create().Contourf(x, y, z).Save("contourf.svg");

Others

Plt.Create().Image(bitmap).Save("image.svg");
Plt.Create().Hexbin(x, y).Save("hexbin.svg");
Plt.Create().Pcolormesh(x, y, z).Save("pcolor.svg");
Plt.Create().Tricontour(x, y, z).Save("tricontour.svg");
Plt.Create().Tripcolor(x, y, z).Save("tripcolor.svg");

3-D

Twelve 3-D series with perspective camera, per-face lighting, numeric tick marks along the bounding-box edges, and interactive SVG rotation. 3-D axes share a single cross-series depth queue — multiple 3-D series on the same axes composite correctly regardless of the order in which you add them.

// Original 6
Plt.Create().Surface3D(x, y, z).Save("surface.svg");
Plt.Create().Wireframe3D(x, y, z).Save("wireframe.svg");
Plt.Create().Scatter3D(x, y, z).Save("scatter3d.svg");
Plt.Create().Bar3D(x, y, heights).Save("bar3d.svg");
Plt.Create().PlanarBar3D(x, y, heights).Save("planar_bars.svg");
Plt.Create().Stem3D(x, y, z).Save("stem3d.svg");

// v1.3.0 — 6 new series
Plt.Create().Plot3D(x, y, z).Save("line3d.svg");            // projected polyline
Plt.Create().Trisurf(x, y, z).Save("trisurf.svg");          // Delaunay triangulated surface
Plt.Create().Contour3D(x, y, z).Save("contour3d.svg");      // marching-squares contour lines
Plt.Create().Quiver3D(x, y, z, u, v, w).Save("quiver3d.svg"); // 3D vector field
Plt.Create().Voxels(filled).Save("voxels.svg");              // face-culled cubes
Plt.Create().Text3D(x, y, z, "label").Save("text3d.svg");   // 3D annotation

Bar3D — solid cuboid bars

Bar3D single series

Matplotlib's ax.bar3d(...) equivalent — rectangular prisms rising from the XY plane with matplotlib-exact per-face shading (0.65 + 0.35·dot(n̂, l̂)). Each bar has six shaded faces; the renderer depth-sorts all faces from all bars so occlusion is correct regardless of camera angle. Add multiple Bar3D calls on one axes for a stacked grid — the shared 3-D depth queue handles compositing:

Bar3D grouped — 5 rows

record Row(double Y, Color Color);
Row[] rows = [new(0, Colors.Red), new(1, Colors.Green),
    new(2, Colors.Blue), new(3, Colors.Cyan), new(4, Colors.Gold)];

Plt.Create()
    .WithTitle("3D Bar Chart — Grouped rows")
    .WithSize(780, 620)
    .AddSubPlot(1, 1, 1, ax =>
    {
        ax.WithCamera(elevation: 25, azimuth: -60)
          .WithLighting(dx: -0.4, dy: -0.8, dz: 0.45);
        foreach (var (y, color) in rows)
        {
            double[] ys = Enumerable.Repeat(y, 20).ToArray();
            double[] zs = /* ... */;
            ax.Bar3D(xs, ys, zs, s => { s.Color = color; s.BarWidth = 0.4; });
        }
    })
    .Save("bar3d_grouped.svg");

PlanarBar3D — flat translucent 3-D bars

PlanarBar3D skyscraper

Matplotlib's ax.bar(xs, heights, zs=y, zdir='y') equivalent — also known as a "skyscraper plot" or "2D bars in different planes". Each bar is a single flat translucent rectangle in the XZ plane at a fixed Y value (no cuboid, no shading), so you can read bars through the rear planes. Great for comparing many time-series or categorical distributions stacked on planes.

Plt.Create()
    .WithTitle("Planar 3D Bars")
    .WithSize(780, 620)
    .AddSubPlot(1, 1, 1, ax =>
    {
        ax.WithCamera(elevation: 25, azimuth: -60)
          .SetXLabel("X").SetYLabel("Y").SetZLabel("Z");
        foreach (var (y, color) in rows)
        {
            double[] ys = Enumerable.Repeat(y, 20).ToArray();
            double[] zs = /* ... */;
            ax.PlanarBar3D(xs, ys, zs, s =>
            {
                s.Color    = color;
                s.BarWidth = 0.8;
                s.Alpha    = 0.8;   // translucency
            });
        }
    })
    .Save("planar_bars.svg");

Per-bar colour override via Colors[]

PlanarBar3DSeries.Colors is a parallel Color[]? array (same convention as ScatterSeries.Colors / PieSeries.Colors). When set, it overrides the per-series Color on a per-bar basis — enabling three colour lookup modes from one API:

  • Per Y / per plane — set s.Color = planeColor on each series
  • Per X — set s.Colors = xs.Select(x => lookup(x)).ToArray()
  • Combined — set both: Colors[i] wins where defined, Color is the fallback

The sample below highlights every bar at x = 0 in dark cyan, regardless of which plane it sits on:

PlanarBar3D x=0 highlight

var darkCyan = Color.FromHex("#008B8B");
ax.PlanarBar3D(xs, ys, zs, s =>
{
    s.Color    = planeColor;
    s.BarWidth = 0.8;
    s.Alpha    = 0.8;
    // Per-X override: every bar at x == 0 is dark cyan.
    s.Colors = xs.Select(x => x == 0 ? darkCyan : planeColor).ToArray();
});

No callbacks, no new API surface — one Color[]? array handles all three modes. See Advanced for the full multi-series depth-compositing story.

Camera, lighting, and rotation

Camera, lighting, Z-axis label/range, and interactive rotation — applies to every 3-D series:

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Surface3D(x, y, z)
        .SetXLabel("x").SetYLabel("y").SetZLabel("sin(r)/r")
        .SetZLim(-0.3, 1.0)
        .WithCamera(elevation: 35, azimuth: -55, distance: 8)
        .WithLighting(ambient: 0.4, diffuse: 0.6, dirX: 1.0)
        .With3DRotation())
    .Save("surface3d.svg");

distance controls perspective (orthographic when omitted). SetZLabel / SetZLim configure the Z-axis just like SetXLabel / SetYLabel do on 2-D charts — they write to the Axes.ZAxis (Axis3D) model property. With3DRotation() embeds JS for mouse-drag and keyboard rotation (arrow keys + Home reset). See Advanced for more.

Note on builder placement (v1.1.4+) — call WithCamera / WithLighting inside the AddSubPlot(..., ax => ax...) lambda so they apply to the actual 3-D subplot. Calling them at the Plt.Create().WithCamera(...) level only configures the (unused) figure-level default axes on multi-subplot figures.

Line3D — projected polyline (v1.3.0)

Line3D helix

3-D polyline through arbitrary (x, y, z) points. Segments are depth-sorted and projected through the Projection3D pipeline.

double[] t = Enumerable.Range(0, 200).Select(i => i * 0.1).ToArray();
double[] x = t.Select(v => Math.Cos(v)).ToArray();
double[] y = t.Select(v => Math.Sin(v)).ToArray();
double[] z = t;

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Plot3D(x, y, z, s => { s.Color = Colors.Blue; s.Label = "Helix"; })
        .WithCamera(elevation: 25, azimuth: -60)
        .With3DRotation())
    .Save("line3d.svg");

Trisurf3D — Delaunay triangulated surface (v1.3.0)

Trisurf3D

Takes unstructured (x, y, z) point clouds and generates a triangulated surface mesh via Delaunay triangulation. Each triangle face is depth-sorted and shaded via Vec3.FaceNormal + Color.Shade() extensions.

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Trisurf(x, y, z, s => { s.Color = Colors.Teal; s.Alpha = 0.8; })
        .WithCamera(elevation: 30, azimuth: -45)
        .With3DRotation())
    .Save("trisurf.svg");

Contour3D — 3-D contour lines (v1.3.0)

Contour3D

Computes contour lines on a grid using marching squares, then projects them into 3-D space. Each contour sits at its corresponding Z level.

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Contour3D(x, y, z, s => { s.Levels = 10; })
        .WithCamera(elevation: 35, azimuth: -55)
        .With3DRotation())
    .Save("contour3d.svg");

Quiver3D — 3-D vector field (v1.3.0)

Quiver3D

Draws arrows in 3-D space. Each arrow has a shaft (line segment) and a cone head. Useful for visualising electromagnetic fields, fluid flow, or gradient vectors.

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Quiver3D(x, y, z, u, v, w, s => { s.Color = Colors.Red; s.ArrowLength = 0.3; })
        .WithCamera(elevation: 25, azimuth: -60)
        .With3DRotation())
    .Save("quiver3d.svg");

Voxels — volumetric cubes (v1.3.0)

Voxels

Renders a bool[,,] voxel grid as face-culled cubes. Adjacent filled voxels suppress shared faces, reducing draw count. All visible faces are depth-sorted through DepthQueue3D.

var filled = new bool[5, 5, 5];
// fill some voxels...
filled[2, 2, 2] = true;
filled[2, 2, 3] = true;

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Voxels(filled, s => { s.Color = Colors.Orange; s.Alpha = 0.8; })
        .WithCamera(elevation: 30, azimuth: -50)
        .With3DRotation())
    .Save("voxels.svg");

Text3D — 3-D annotations (v1.3.0)

Text3D

Places text labels at arbitrary (x, y, z) positions. The 3-D coordinate is projected to 2-D pixel space and the text is drawn at the projected position.

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .Surface3D(x, y, z)
        .Text3D(0, 0, 1.0, "Peak", s => { s.Color = Colors.Red; })
        .WithCamera(elevation: 35, azimuth: -55)
        .With3DRotation())
    .Save("text3d.svg");

Polar

double[] r = [1, 2, 3, 4, 5];
double[] theta = Enumerable.Range(0, 5).Select(i => i * Math.PI / 2.5).ToArray();

Plt.Create().PolarPlot(r, theta).Save("polar_line.svg");
Plt.Create().PolarScatter(r, theta).Save("polar_scatter.svg");

// Wind rose / radar bar
double[] speeds = [5, 10, 8, 3, 7, 12, 6, 9];
double[] dirs   = Enumerable.Range(0, 8).Select(i => i * Math.PI / 4).ToArray();
Plt.Create().PolarBar(speeds, dirs, b => b.BarWidth = 0.7).Save("windrose.svg");

Hierarchical / Flow

Treemap & Sunburst

var tree = new TreeNode
{
    Label = "Revenue",
    Children =
    [
        new TreeNode { Label = "Products",  Value = 400 },
        new TreeNode { Label = "Services",  Value = 300 },
        new TreeNode { Label = "Licensing", Value = 200 }
    ]
};

Plt.Create().Treemap(tree).Save("treemap.svg");
Plt.Create().Sunburst(tree).Save("sunburst.svg");

Dendrogram

Hierarchical-clustering visualisation rendered as the canonical "U"-shape segments — every internal node is a merge whose vertical position equals the merge distance (carried in TreeNode.Value). Matches SciPy's scipy.cluster.hierarchy.dendrogram rendering convention.

Plt.Create()
    .Dendrogram(tree, s =>
    {
        s.Orientation = DendrogramOrientation.Top;   // Top, Bottom, Left, Right
        s.CutHeight = 1.5;                           // dashed reference line + cluster colours
        s.CutLineColor = Colors.Red;
        s.ColorByCluster = true;                     // qualitative IColorMap (default Tab10)
    })
    .Save("dendrogram.svg");

CutHeight uses strict less-than (node.Value < cut), matching SciPy's dendrogram(color_threshold=…) visual convention. Single-leaf trees render the lone label at the plot centre; all-zero merge distances collapse the U-shapes to the leaf baseline (a unit maxMerge fallback avoids division by zero). See the cookbook entry for a full walkthrough.

Clustermap

Composites a heatmap with optional row and column dendrograms into a single subplot — the seaborn sns.clustermap idiom. When trees are provided, rows and/or columns are automatically reordered to match the dendrogram leaf traversal order so cells align visually with the tree structure.

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax.Clustermap(data, s =>
    {
        s.RowTree = rowTree;           // leaf Value = original row index
        s.ColumnTree = colTree;        // leaf Value = original column index
        s.RowDendrogramWidth = 0.15;   // fraction of width, default 0.15, clamped [0, 0.9]
        s.ColumnDendrogramHeight = 0.15;
        s.ColorMap = ColorMaps.RdBu;
        s.ShowLabels = true;
        s.LabelFormat = "F2";
    }))
    .ToSvg();

Leaf TreeNode.Value must be the zero-based original index of the row or column. Internal nodes carry the merge distance. Malformed trees (out-of-range indices, duplicates, wrong count) fall back to identity order silently. See the cookbook entry for a full walkthrough.

Pair Grid

N×N matrix of subplots from N variables — the seaborn pairplot idiom. Diagonal cells show univariate distributions (histogram or KDE); off-diagonal cells show bivariate scatters of (i, j). Optional hue groups colour the off-diagonal scatters by category — the killer feature for cluster validation and category-aware EDA.

double[][] vars = [petalLength, petalWidth, sepalLength, sepalWidth];
int[]      hue  = species.Select(s => (int)s).ToArray();
string[]   hLab = ["Setosa", "Versicolor", "Virginica"];

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax.PairGrid(vars, s =>
    {
        s.Labels       = ["Petal L", "Petal W", "Sepal L", "Sepal W"];
        s.HueGroups    = hue;
        s.HueLabels    = hLab;
        s.DiagonalKind = PairGridDiagonalKind.Kde;     // Histogram (default), Kde, None
        s.Triangular   = PairGridTriangle.LowerOnly;   // Both (default), LowerOnly, UpperOnly
    }))
    .ToSvg();

Diagonal histograms are stacked-overlapping per hue group with Alpha = 0.6; KDE renders one curve per group. Off-diagonal scatter dot radius is controlled by MarkerSize. OffDiagonalKind = None produces a diagonal-only "marginals" view; OffDiagonalKind = Hexbin renders flat-top hexagonal density grids for high-cardinality EDA where scatter overplots (activated in v1.10; hue is ignored when Hexbin is active). Cell gutters (CellSpacing, default 0.02) are clamped to [0, 0.2]. See the cookbook entry for a full walkthrough.

Treemap drilldown (v1.1.4, rewritten in v1.7.2 Phase P, default flipped in v1.7.2 Phase W)

Treemap drilldown

Steady-pictures UX (v1.7.2 Phase W). The initial interactive view is pixel-identical to the static SVG — every node at every depth (incl. depth-3 and beyond) is visible on first paint. Z-order means children paint OVER parents, so the deepest visible label is what the user reads in any overlapping region. Clicking a parent rectangle now collapses its entire subtree (transitively, via an ancestry-walk visibility model — descendants' own collapse state is preserved); click again to restore. Multiple subtrees can be collapsed independently. Leaves are not clickable.

Why default-expanded? Earlier (Phase P) the script started with only depth-1 visible and required clicks to expand — but that meant entering interactive mode visually shifted the chart (the root header strip went blank), and the user had to click to discover content. Phase W flipped this: the user sees everything by default ("when no browserInteraction he sees all" applies in both modes), and clicks become an opt-in focus gesture instead of a discovery requirement.

Keyboard-accessible via tabindex="0" and ARIA roles. The interactive script is embedded in the SVG output when WithTreemapDrilldown() is called on the FigureBuilder, and every rect is tagged with data-treemap-node / -depth / -parent so the script can navigate the hierarchy without re-parsing the structure. For static SVG output with deep trees, call .WithAutoSize(root) so the canvas is big enough to fit every label cleanly without overflow.

Plt.Create()
    .WithTreemapDrilldown()
    .AddSubPlot(1, 1, 1, ax => ax.Treemap(catalogue, s => s.ShowLabels = true))
    .Save("treemap_drilldown.svg");

Nested pie (v1.1.4)

Nested pie

A two-level TreeNode rendered as an inner pie + outer breakdown ring. Convenience wrapper around Sunburst(...) with InnerRadius = 0.

var departments = new TreeNode
{
    Label = "Revenue",
    Children = [
        new() { Label = "Electronics", Children = [
            new() { Label = "Phones",  Value = 40 },
            new() { Label = "Laptops", Value = 35 },
            new() { Label = "Audio",   Value = 25 }] },
        /* more departments... */
    ]
};
Plt.Create()
    .WithTitle("Revenue by department and product")
    .AddSubPlot(1, 1, 1, ax => ax.NestedPie(departments))
    .Save("nested_pie.svg");

Network Graph

Nodes-and-edges-in-2D for correlation networks (Pearson edge weights), lead-lag flow (TransferEntropy directed edges), Louvain community visualisation (node colour = community ID), and minimum spanning trees. Three deterministic layouts ship in v1.10 PR 1 (Manual / Circular / Hierarchical); ForceDirected (Fruchterman–Reingold) follows in PR 2.

GraphNode[] nodes =
[
    new("AAPL", ColorScalar: 0.2),
    new("MSFT", ColorScalar: 0.2),
    new("GOOG", ColorScalar: 0.7),
];
GraphEdge[] edges =
[
    new("AAPL", "MSFT", Weight: 0.85),
    new("AAPL", "GOOG", Weight: 0.42, IsDirected: true),
    new("MSFT", "GOOG", Weight: 0.55, IsDirected: true),
];

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax.NetworkGraph(nodes, edges, s =>
    {
        s.Layout = GraphLayout.Circular;     // Manual / Circular / Hierarchical
        s.ColorMap = ColorMaps.Viridis;
        s.ShowNodeLabels = true;
        s.NodeRadiusScale = 8.0;
    }))
    .ToSvg();

IsDirected edges get an arrowhead at the target end (reuses ArrowHeadBuilder.FancyArrow). EdgeThicknessScale multiplies per-edge Weight to stroke width; NodeRadiusScale multiplies per-node SizeScalar to circle radius. DataFrame extension: df.NetworkGraph("source", "target", weightCol: "weight", directedCol: "directed") derives nodes from the union of source/target columns.

Sankey

Process distribution Sankey

v1.1.4 rewrote the Sankey renderer around an 8-step pipeline: explicit column assignment, node alignment, iterative vertical relaxation, gradient link colour modes, sub-labels, inside-labels, hover emphasis, and a full vertical orientation. Five new samples ship in images/sankey_*.png:

  • sankey_process_distribution.svg — 5-column process-industry cascade with tonnage sub-labels, gradient links, hover emphasis enabled (hover a node to isolate its reachable flow chain)
  • sankey_income_statement.svg — J&J Q1 FY25 income statement with semantic green/red node colouring and "+2% Y/Y" sub-label deltas
  • sankey_customer_journey.svg — 4-timestep alluvial where Home legitimately reappears in multiple columns (uses explicit SankeyNode.Column override to pin each node to its semantic timestep regardless of link topology)
  • sankey_un_expenses.svg — 2-column baseline, outside labels, clean HideAllAxes() canvas
  • sankey_severity_cascade.svg — 4-column patient severity state transitions with 24 relaxation iterations minimising crossings in a dense many-to-many topology
  • sankey_vertical.svg — vertical orientation (top-to-bottom flow) conversion funnel

Minimal example:

SankeyNode[] nodes = [
    new("Coal",        Color.FromHex("#6B8E23")),
    new("Gas",         Color.FromHex("#4682B4")),
    new("Electricity", Color.FromHex("#FFD700"), SubLabel: "80 TWh"),
    new("Heat",        Color.FromHex("#D2691E"), SubLabel: "40 TWh")];
SankeyLink[] links = [new(0, 2, 50), new(1, 2, 30), new(1, 3, 20)];

Plt.Create()
    .WithSankeyHover()                          // v1.1.4 hover emphasis script
    .AddSubPlot(1, 1, 1, ax => ax
        .HideAllAxes()
        .Sankey(nodes, links, s =>
        {
            s.NodeWidth = 24;
            s.Iterations = 20;                  // vertical relaxation passes
            s.LinkColorMode = SankeyLinkColorMode.Gradient;
            s.Orient = SankeyOrientation.Horizontal;  // or Vertical
        }))
    .Save("sankey.svg");

Key properties (v1.1.4):

Property Meaning
SankeyNode.SubLabel / SubLabelColor Secondary label drawn one line below at 80 % font size; useful for "$13.9B +2%" metric/delta pairs
SankeyNode.Column Explicit column override; pins node to a semantic column regardless of BFS topology (alluvial / time-step Sankeys)
SankeySeries.NodeAlignment Justify (default) / Left / Right / Center — matches D3 sankey* modes
SankeySeries.Iterations Vertical relaxation passes (default 6); higher for denser topologies
SankeySeries.LinkColorMode Source / Target / Gradient (default). Gradient emits an SVG <linearGradient> per link.
SankeySeries.Orient Horizontal (default) / Vertical — top-to-bottom flow
SankeySeries.InsideLabels Draw labels centred inside the node rect (when NodeWidth is wide enough)

Vector / Flow

Plt.Create().Quiver(x, y, u, v).Save("quiver.svg");
Plt.Create().Barbs(x, y, u, v).Save("barbs.svg");
Plt.Create().Streamplot(x, y, u, v).Save("stream.svg");

Specialty

Sparkline dashboard

Plt.Create().Radar(categories, values).Save("radar.svg");
Plt.Create().Waterfall(labels, deltas).Save("waterfall.svg");
Plt.Create().Funnel(labels, values).Save("funnel.svg");
Plt.Create().Gauge(value: 0.72, min: 0, max: 1).Save("gauge.svg");
Plt.Create().ProgressBar(value: 0.65).Save("progress.svg");
Plt.Create().Sparkline(timeSeries).Save("sparkline.svg");
Plt.Create().Table(headers, rows).Save("table.svg");
Plt.Create().Spectrogram(signal, sampleRate: 44100).Save("spec.svg");
Plt.Create().BrokenBar(yranges, xranges).Save("brokenbar.svg");

Geographic / Maps — MatPlotLibNet.Geo

The MatPlotLibNet.Geo package adds 13 map projections, the GeoPolygonSeries (renders any closed ring as a styled polygon — choropleth, ocean fill, land fill, custom shapes), and embedded Natural Earth 110m data (coastlines + countries, no external download required).

using MatPlotLibNet.Geo.Projections;

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .WithProjection(new Mercator())
        .Ocean(new Mercator(), new Color(180, 220, 255))
        .Land(new Mercator(), new Color(240, 230, 200))
        .Coastlines(new Mercator(), Colors.Black, lineWidth: 0.5)
        .Borders(new Mercator(), Colors.Gray, lineWidth: 0.3))
    .Save("worldmap.svg");

Projections (13)

Family Projection Notes
Cylindrical PlateCarree Equirectangular — the simplest 1:1 lat/lon mapping
Cylindrical Mercator Conformal — preserves angles (web maps)
Cylindrical TransverseMercator Mercator rotated 90° — UTM zones
Pseudo-cylindrical Mollweide Equal-area; world maps
Pseudo-cylindrical Robinson Compromise; common in atlases
Pseudo-cylindrical Sinusoidal Equal-area; meridian-true
Pseudo-cylindrical EqualEarth Modern equal-area (Šavrič et al. 2018)
Pseudo-cylindrical NaturalEarthProjection Compromise; aesthetic for global view
Conic AlbersEqualArea Equal-area conic — ideal for mid-latitude regions (US, Europe)
Conic LambertConformal Conformal conic — aviation charts
Azimuthal Orthographic "View from space" — globe rendering
Azimuthal Stereographic Conformal azimuthal — polar regions
Azimuthal AzimuthalEquidistant Distances from centre preserved

All projections implement IGeoProjection and round-trip through Forward(lon, lat)(x, y) and Inverse(x, y)(lon, lat)?.

GeoPolygonSeries — render any closed ring

// Choropleth: colour countries by a metric.
var ax = Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax.WithProjection(new Robinson()));

foreach (var (country, value) in countryData)
{
    var poly = NaturalEarth110m.Countries.First(c => c.Properties["NAME"] == country);
    ax.AddSeries(new GeoPolygonSeries(poly, new Robinson())
    {
        FillColor = ColorMaps.Viridis.GetColor(value / maxValue),
        EdgeColor = Colors.White,
        EdgeWidth = 0.3,
    });
}

NaturalEarth110m — embedded reference data

Bundled GeoJSON for the Natural Earth 110m cultural and physical datasets. No HTTP fetch, no download step, no external dependencies.

Property Contents
NaturalEarth110m.Coastlines World coastline rings (134 features)
NaturalEarth110m.Countries World countries with Properties["NAME"], ["ISO_A2"], etc. (177 features)

Convenience extensions

.WithProjection(IGeoProjection)            // Sets axes coordinate transform
.Coastlines(projection, color, lineWidth)  // Adds Natural Earth 110m coastlines
.Borders(projection, color, lineWidth)     // Adds Natural Earth 110m country borders
.Ocean(projection, color)                  // Fills entire map background
.Land(projection, color)                   // Fills land masses

Each extension method is built on GeoPolygonSeries — the lowest-level primitive — so you can mix the convenience helpers with hand-rolled GeoPolygonSeries for choropleth or custom-shape rendering in the same axes.

Clone this wiki locally