# Chart Types > **Try it live:** Open the [Playground](https://xkqg.github.io/MatPlotLibNet/playground/) to experiment with charts in your browser. Browse the [Cookbook](https://xkqg.github.io/MatPlotLibNet/cookbook/) (25 pages) for copy-paste code examples with rendered output. MatPlotLibNet v1.11.2 includes **79 series types** across 14 categories, plus 4 streaming series, 13 map projections, and geographic polygon rendering via `MatPlotLibNet.Geo`. See the [Geographic / Maps](#geographic--maps--matplotlibnetgeo) 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](images/gallery_kde.png)](images/gallery_kde.png) **KDE** (Gaussian kernel density) | [![Violin plot](images/gallery_violin.png)](images/gallery_violin.png) **Violin** (3 groups) | [![Box plot](images/gallery_box.png)](images/gallery_box.png) **Box** (4 groups) | | [![Hexbin density](images/gallery_hexbin.png)](images/gallery_hexbin.png) **Hexbin** (2-D binning) | [![Stem plot](images/gallery_stem.png)](images/gallery_stem.png) **Stem** (sampled signal) | [![Waterfall chart](images/gallery_waterfall.png)](images/gallery_waterfall.png) **Waterfall** (finance) | | [![Sankey diagram](images/gallery_sankey.png)](images/gallery_sankey.png) **Sankey** (flow) | [![Radar chart](images/gallery_radar.png)](images/gallery_radar.png) **Radar** (5 axes) | [![Streamplot](images/gallery_streamplot.png)](images/gallery_streamplot.png) **Streamplot** (vector field) | | [![Polar scatter](images/gallery_polar_scatter.png)](images/gallery_polar_scatter.png) **Polar scatter** | [![Treemap](images/gallery_treemap.png)](images/gallery_treemap.png) **Treemap** (squarified) | [![Pie chart](images/gallery_pie.png)](images/gallery_pie.png) **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](images/bar_labels.png) ### Line — `Plot()` ```csharp 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()` ```csharp 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()` ```csharp Plt.Create().Bar(["Q1", "Q2", "Q3", "Q4"], [100, 200, 150, 250]).Save("bar.svg"); ``` ### Histogram — `Hist()` ```csharp double[] data = /* ... */; Plt.Create().Hist(data, bins: 20).Save("hist.svg"); ``` ### Pie / Donut ```csharp 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 ```csharp 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. ```csharp // 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 ```csharp 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](images/financial_dashboard.png) `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) ```csharp 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 ```csharp Plt.Create() .AddSubPlot(1, 1, 1, ax => ax .Candlestick(open, high, low, close) .Sma(period: 20) .WithLegend()) .Save("candlestick.svg"); ``` ### OHLC ```csharp Plt.Create() .AddSubPlot(1, 1, 1, ax => ax.Ohlc(open, high, low, close)) .Save("ohlc.svg"); ``` --- ## Grid / Field ![Heatmap with colormap](images/heatmap_colormap.png) ### Heatmap ```csharp 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 ```csharp Plt.Create().Contour(x, y, z).Save("contour.svg"); Plt.Create().Contourf(x, y, z).Save("contourf.svg"); ``` ### Others ```csharp 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. ```csharp // 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](images/threed_bar3d_interactive.png) 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](images/threed_bar3d_grouped.png) ```csharp 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](images/threed_planar_bars.png) 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. ```csharp 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](images/threed_planar_bars_x0_highlight.png) ```csharp 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: ```csharp 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](images/threed_line3d_helix.png) 3-D polyline through arbitrary `(x, y, z)` points. Segments are depth-sorted and projected through the `Projection3D` pipeline. ```csharp 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](images/threed_trisurf.png) 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. ```csharp 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](images/threed_contour3d.png) Computes contour lines on a grid using marching squares, then projects them into 3-D space. Each contour sits at its corresponding Z level. ```csharp 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](images/threed_quiver3d.png) 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. ```csharp 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](images/threed_voxels.png) 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`. ```csharp 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](images/threed_text3d.png) 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. ```csharp 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 ```csharp 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 ```csharp 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. ```csharp 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](../docs/cookbook/dendrograms.md) 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. ```csharp 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](../docs/cookbook/clustermap.md) 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. ```csharp 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](../docs/cookbook/pairplot.md) 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](images/treemap_drilldown.png) **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. ```csharp 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](images/nested_pie.png) A two-level `TreeNode` rendered as an inner pie + outer breakdown ring. Convenience wrapper around `Sunburst(...)` with `InnerRadius = 0`. ```csharp 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. ```csharp 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. ### Relative Rotation Graph (RRG) JdK-style 2D scatter of (RS-Ratio, RS-Momentum) per asset relative to a benchmark, with a fading tail per asset and a 100/100 quadrant grid (Leading / Weakening / Lagging / Improving). Three RS formulas ship: `DualEma` (default, trending markets), `ZScore` (mean-reversion), `LogReturn` (high-volatility). Introduced in v1.11.0; overlay support in v1.11.2. ```csharp // Basic — three alts vs BTC benchmark Plt.Create() .AddSubPlot(1, 1, 1, ax => ax .SetXLabel("RS-Ratio") .SetYLabel("RS-Momentum") .RelativeRotation( new[] { ethCloses, bnbCloses, solCloses }, btcCloses, new[] { "ETH", "BNB", "SOL" }, s => { s.TailLength = 12; s.ColorMap = ColorMaps.Plasma; })) .ToSvg(); // With absorption + ENB overlays (Layer 3 → Layer 2 feedback) Plt.Create() .AddSubPlot(1, 1, 1, ax => ax .RelativeRotation(assetCloses, bench, labels, s => { s.AbsorptionRatioPerBar = absorptionPerBar; // double[] [0..1], green→red fill s.EnbPerBar = enbPerBar; // double[], radius ∝ ENB })) .ToSvg(); ``` See [RelativeRotationSeries](RelativeRotationSeries) for the full property reference, formula details, and minimum bar requirements. ### Sankey ![Process distribution Sankey](images/sankey_process_distribution.png) 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: ```csharp 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 `` 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 ```csharp 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](images/sparkline_dashboard.png) ```csharp 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). ```csharp 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(latitude, longitude)` → `ProjectedPoint` and `Inverse(x, y)` → `GeoCoordinate?` (v1.13.0: record structs, were tuples — `readonly record struct ProjectedPoint(double X, double Y)` / `readonly record struct GeoCoordinate(double Latitude, double Longitude)`; deconstruction call sites like `var (x, y) = proj.Forward(lat, lon);` are unaffected). `Bounds` returns the existing `GeoBounds(XMin, XMax, YMin, YMax)` record struct (was a 4-tuple). ### `GeoPolygonSeries` — render any closed ring ```csharp // 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](https://www.naturalearthdata.com/) 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 ```csharp .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.