Repository navigation
Styling
MatPlotLibNet ships with built-in themes that mirror matplotlib style sheets.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
Plt.Create().WithTheme(Theme.Default).Plot(x, y).Save("default.svg");
Plt.Create().WithTheme(Theme.Dark).Plot(x, y).Save("dark.svg");
Plt.Create().WithTheme(Theme.Solarize).Plot(x, y).Save("solarized.svg");This theme uses the Okabe-Ito 8-color palette, which is colorblind-safe for deuteranopia, protanopia, and tritanopia. The background is white and the text is black.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [12, 15, 14, 18, 21];
Plt.Create()
.WithTheme(Theme.ColorBlindSafe)
.Plot(x, y, s => s.Label = "Revenue")
.Save("accessible.svg");The same colors are also available as a colormap ("okabe_ito" / "okabe_ito_r"):
using MatPlotLibNet.Styling.ColorMaps;
var map = ColorMaps.Get("okabe_ito");These two themes reproduce the matplotlib look in pure .NET; no Python runtime is required. Theme.MatplotlibClassic mimics matplotlib's pre-2.0 look (white background, bgrcmyk 7-color cycle, DejaVu Sans 12pt). Theme.MatplotlibV2 mimics the modern default (white background, soft-black #262626 text, tab10 10-color cycle, DejaVu Sans 10pt). Both ship with the grid hidden by default, matching matplotlib's plt.grid(False).
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
double[] y2 = [1, 3, 2, 4, 3];
Plt.Create()
.WithTheme(Theme.MatplotlibV2)
.AddSubPlot(1, 1, 1, ax => ax
.Plot(x, y, s => s.Label = "Series 1")
.Plot(x, y2, s => s.Label = "Series 2")
.WithLegend())
.Save("matplotlib_v2.svg");See MatplotlibThemes for the full color cycles, comparison table, and implementation notes.
This theme targets WCAG AAA: white and black give a 21:1 contrast ratio. It uses a bold 13pt font, a thick 1.5px dark grid, and an 8-color high-chroma cycle.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
Plt.Create()
.WithTheme(Theme.HighContrast)
.Plot(x, y)
.Save("high-contrast.svg");using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
var theme = Theme.CreateFrom(Theme.Dark)
.WithBackground(Color.FromHex("#1a1a2e"))
.WithFont(f => f with { Family = "Consolas", Size = 14 })
.Build();
Plt.Create().WithTheme(theme).Plot(x, y).Save("custom.svg");A figure's theme travels with it: ToJson writes it as data, property by property, and FromJson, the MCP server and
every transport read it back, so a theme made with ThemeBuilder comes back whole too. A figure on the default theme
writes no theme at all.
Four themes have backgrounds designed for a wall display rather than a report: Theme.OpsNight, OpsPanel, OpsWarm, OpsContrast.
Every theme also defines its own alarm colours, so a state mark never takes a colour from the series cycle:
using MatPlotLibNet.Styling;
var theme = Theme.OpsNight;
Color resting = theme.Alarm.Resting; // nothing wrong — the neutral shade
Color warning = theme.Alarm.Warning; // out of band
Color critical = theme.Alarm.Critical; // acted on now
Color unknown = theme.Alarm.Unknown; // no information (a hatch, never a colour)
var custom = Theme.CreateFrom(Theme.Dark)
.WithAlarmPalette(new AlarmPalette(Colors.Gray, Colors.Orange, Colors.Crimson, Colors.DarkGray))
.Build();In these themes, colour is reserved for states that need attention. See Chart Types#control-room for the tiles that use the alarm palette.

A PropCycler cycles Color, LineStyle, MarkerStyle, and LineWidth at the same time across series. Each property cycles at its own length, and the series index wraps around at the least common multiple (LCM) of those lengths. When PropCycler is null, the theme's CycleColors[] is used instead.
All four are drawn. A series that leaves its line style, width or marker at what a new series holds (solid, 1.5 points, no marker; a scatter holds a circle) takes the cycler's entry for its place; a series that sets another value keeps its own, as a series with a colour of its own does. A value equal to the default cannot be told from one left alone, so under a cycler of dashes a series cannot be forced back to solid by setting solid. The entries reach lines, steps, scatters, the outline of an area, error-bar widths, density curves, ECDFs, regression, signal, polar and 3-D lines, both secondary axes and every legend swatch. A cycler that lists only colours changes nothing else, and no built-in theme has a cycler.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [0, 1, 2, 3, 4];
double[][] y =
[
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8],
];
var cycler = new PropCyclerBuilder()
.WithColors(Colors.Blue, Colors.Orange, Colors.Green, Colors.Red)
.WithLineStyles(LineStyle.Solid, LineStyle.Dashed)
.WithMarkerStyles(MarkerStyle.Circle, MarkerStyle.Square)
.WithLineWidths(2.0, 1.5)
.Build();
// A cycler travels in a theme: embed it once and reuse the theme across figures
var theme = Theme.CreateFrom(Theme.Dark)
.WithPropCycler(cycler)
.Build();
Plt.Create()
.WithTheme(theme)
.AddSubPlot(1, 1, 1, ax =>
{
for (int i = 0; i < 4; i++)
ax.Plot(x, y[i], s => s.Label = $"Series {i + 1}");
ax.WithLegend();
})
.Save("cycler.svg");MatPlotLibNet includes 74 base maps. Each one is registered automatically with a reversed _r variant, which makes 148 colormaps in six families:

| Family | Examples |
|---|---|
| Sequential |
viridis, plasma, inferno, magma, cividis, blues, greens, gray, hot, spring, summer, autumn, winter, cool, afmhot
|
| Diverging |
rdbu, rdylgn, bwr, seismic, coolwarm, prgn, rdgy
|
| Qualitative |
tab10, tab20, set1, set2, set3, paired, dark2, accent
|
| Cyclic |
hsv, twilight, twilight_shifted
|
| Accessible |
okabe_ito, petroff6, petroff8, petroff10
|
| Misc |
rainbow, ocean, terrain, cmrmap, jet, turbo
|
The accessible family is for a chart that has to be read by everyone. okabe_ito is the eight-colour palette
from Okabe and Ito; the three Petroff sequences come from Accessible Color Sequences for Data Visualization
and pick colours so that every pair inside a sequence stays apart under the common forms of colour blindness,
and in grayscale as well. Pick the one whose number matches how many things you are drawing.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
using MatPlotLibNet.Styling.ColorMaps;
double[,] data = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
// Apply to a heatmap
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Heatmap(data)
.WithColorMap("plasma")
.WithColorBar(cb => cb with { Label = "Intensity", Extend = ColorBarExtend.Both }))
.Save("heatmap.svg");
// Registry lookup — case-insensitive, append _r for reversed variant
var map = ColorMapRegistry.Get("rdylgn");
var reversed = ColorMapRegistry.Get("viridis_r");
// Custom gradient with positioned stops
var custom = LinearColorMap.FromPositions("my_map",
[
new ColorStop(0.0, Color.FromHex("#000080")),
new ColorStop(0.3, Color.FromHex("#FFFFFF")),
new ColorStop(1.0, Color.FromHex("#800000")),
]);
// Discrete categorical colormap (no interpolation)
var discrete = new ListedColorMap("my_cat", [Colors.Blue, Colors.Red, Colors.Green]);A map that ships goes into JSON by its name. A map built in code, like the two above, goes in as what it is made of:
its colours, their positions, and its under, over and bad colours, so it comes back from FromJson, the MCP server and
every transport as itself. A map of a type you write yourself is code; it travels only by a name registered with
ColorMapRegistry.Register, which the reader has to register too.
These are all the series that support IColorMap (via IColormappable): HeatmapSeries, ImageSeries, Histogram2DSeries, ContourSeries, ContourfSeries, SurfaceSeries, ScatterSeries, PcolormeshSeries, HexbinSeries, SpectrogramSeries, TripcolorSeries, TricontourSeries, HierarchicalSeries.
ScatterSeries supports per-point colormap coloring via the C scalar array:
using MatPlotLibNet;
using MatPlotLibNet.Styling.ColorMaps;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
double[] values = [5, 30, 55, 80, 100];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax.Scatter(x, y, s =>
{
s.C = values; // scalar per point
s.ColorMap = ColorMaps.Viridis;
s.VMin = 0;
s.VMax = 100;
s.Normalizer = new PowerNormNormalizer(gamma: 0.5);
}))
.Save("scatter_colormap.svg");using MatPlotLibNet.Styling.ColorMaps;
INormalizer[] normalizers =
[
LinearNormalizer.Instance, // (v - min) / (max - min) — default
new LogNormalizer(), // log scale mapping
new TwoSlopeNormalizer(center: 0.0), // diverging, center at value
new BoundaryNormalizer([0.0, 10.0, 50.0, 100.0]), // discrete boundaries
new SymLogNormalizer(linthresh: 1.0), // symmetric log — linear near zero, log beyond
new PowerNormNormalizer(gamma: 0.5), // power-law: gamma < 1 expands low values
new CenteredNormNormalizer(vcenter: 0), // maps chosen center to 0.5
NoNormNormalizer.Instance, // pass-through — data already in [0, 1]
];using MatPlotLibNet;
using MatPlotLibNet.Styling;
using MatPlotLibNet.Styling.ColorMaps;
double[,] data = { { -5, 2, 3 }, { 4, double.NaN, 6 }, { 7, 8, 15 } };
Color[] stops = [Color.FromHex("#000080"), Color.FromHex("#FFFFFF"), Color.FromHex("#800000")];
var cm = new LinearColorMap("custom", stops)
{
UnderColor = Color.FromHex("#222222"), // values below VMin
OverColor = Color.FromHex("#FFFFFF"), // values above VMax
BadColor = Color.FromHex("#FF00FF"), // NaN / masked values
};
// ColorBar extension slots show under/over colors
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Heatmap(data)
.WithColorMap(cm)
.WithColorBar(cb => cb with { Extend = ColorBarExtend.Both }))
.Save("extremes.svg");The missing cell (NaN) is drawn in the bad colour, as in matplotlib; a map without a BadColor leaves it transparent.
using MatPlotLibNet;
using MatPlotLibNet.Models;
using MatPlotLibNet.Rendering.TickFormatters;
double[] x = [1, 2, 3, 4, 5];
double[] y = [1, 10, 100, 1_000, 10_000];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.SetYScale(AxisScale.Log)
.SetYTickFormatter(new LogTickFormatter())
.Plot(x, y))
.Save("logscale.svg");A log axis has no place for 0, so a mark that stands on 0 (a bar, a histogram bin, a filled area, a stacked area, a
box or a violin) runs from the bottom edge of the plot, as matplotlib's log scale clips it (nonpositive='clip'), and
the axis starts a margin below the lowest value such marks reach, so the shortest one shows. A point at 0 or below is
left out, and a line breaks there.
using MatPlotLibNet;
using MatPlotLibNet.Rendering.TickFormatters;
using MatPlotLibNet.Rendering.TickLocators;
DateTime[] dates = [.. Enumerable.Range(0, 30).Select(day => DateTime.Today.AddDays(day))];
double[] y = [.. Enumerable.Range(0, 30).Select(day => (double)(day % 7))];
var locator = new AutoDateLocator();
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Plot(dates, y)
.SetXTickLocator(locator)
.SetXTickFormatter(new AutoDateFormatter(locator)))
.Save("date_axis.svg");See Advanced#date-axes for the automatic date axis workflow.
using MatPlotLibNet;
using MatPlotLibNet.Models;
double[] x = [-2, -1, 0, 1, 2];
double[] y = [-4, -1, 0, 1, 4];
Plt.Create()
.AddSubPlot(1, 2, 1, ax => ax
.Plot(x, y)
// Hide top and right (publication style)
.HideTopSpine().HideRightSpine())
.AddSubPlot(1, 2, 2, ax => ax
.Plot(x, y)
// Move x-axis to y = 0
.WithSpines(s => s with
{
Bottom = s.Bottom with { Position = SpinePosition.Data, PositionValue = 0 }
}))
.Save("spines.svg");Grid lines are on by default with all built-in themes, matching matplotlib's default: solid #B0B0B0 lines at width 0.8.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
Plt.Create()
// Default — major grid on both axes
.AddSubPlot(2, 3, 1, ax => ax.Plot(x, y))
// Customize color and style
.AddSubPlot(2, 3, 2, ax => ax.Plot(x, y).WithGrid(g => g with
{
Color = Color.FromHex("#444444"),
LineStyle = LineStyle.Dashed,
LineWidth = 0.5
}))
// Only X-axis grid lines
.AddSubPlot(2, 3, 3, ax => ax.Plot(x, y).WithGrid(g => g with { Axis = GridAxis.X }))
// Add minor grid lines
.AddSubPlot(2, 3, 4, ax => ax.Plot(x, y).WithGrid(g => g with { Which = GridWhich.Both }))
// Turn off entirely
.AddSubPlot(2, 3, 5, ax => ax.Plot(x, y).ShowGrid(false))
.Save("grids.svg");GridWhich controls tick level: Major (default), Minor, Both. GridAxis controls which axis: X, Y, Both (default).
TextStyle is a nullable partial font override. Apply it to a theme Font via ApplyTo(Font):
using MatPlotLibNet.Styling;
var style = new TextStyle { FontSize = 18, FontWeight = FontWeight.Bold, Color = Colors.Navy };
var font = style.ApplyTo(Theme.Default.DefaultFont);using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4];
double[] y = [12, 15, 14, 18];
var figure = Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Plot(x, y)
.WithTitle("Revenue 2026", ts => ts with { FontSize = 22, Color = Css4Colors.DarkBlue })
.SetXLabel("Quarter", ts => ts with { FontSize = 14 })
.SetYLabel("USD (M)", ts => ts with { FontSize = 14, FontSlant = FontSlant.Italic }))
.Build();
// Title alignment, set on the built axes
figure.SubPlots[0].TitleLoc = TitleLocation.Left; // Left / Center (default) / Right
figure.Save("titles.svg");using MatPlotLibNet;
using MatPlotLibNet.Models;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
double[] y2 = [1, 3, 2, 4, 3];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Plot(x, y, s => s.Label = "North")
.Plot(x, y2, s => s.Label = "South")
.WithLegend(l => l with
{
NCols = 2,
Title = "Series",
FontSize = 11,
FrameOn = true,
FrameAlpha = 0.9,
FancyBox = true,
Shadow = true,
EdgeColor = Colors.Gray,
MarkerScale = 1.2,
Position = LegendPosition.LowerRight
}))
.Save("legend.svg");using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
var figure = Plt.Create().Plot(x, y).Build();
var ax = figure.SubPlots[0];
ax.XAxis.MajorTicks = ax.XAxis.MajorTicks with
{
Direction = TickDirection.In, // In / Out (default) / InOut
Length = 8,
Width = 1.2,
Color = Colors.DarkGray,
LabelSize = 11,
LabelColor = Css4Colors.DimGray,
Pad = 5
};
figure.Save("ticks.svg");A right-hand or top axis takes the same settings through figure.SubPlots[0].SecondaryYAxis!.MajorTicks and
SecondaryXAxis!.MajorTicks; the examples page shows one with its ticks turned inward.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4, 5];
double[] y = [2, 4, 3, 5, 4];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Plot(x, y)
.WithSpines(s => s with
{
Top = s.Top with { Visible = false },
Right = s.Right with { Visible = false },
Left = s.Left with { Color = Colors.Navy, LineStyle = LineStyle.Solid },
Bottom = s.Bottom with { LineWidth = 1.5 }
}))
.Save("spine_styling.svg");SetYAxisColor and SetXAxisColor colour an axis' spine, tick marks, tick labels and title in one call, so an axis
reads as belonging to the line drawn in that colour. They exist on the axes builder (left and bottom axis) and on
WithSecondaryYAxis / WithSecondaryXAxis (right and top axis); everything else on the axis is kept, and the colour
survives ToJson / FromJson. Keep an axis title that names the series and its unit, so the colour is never the only
thing that tells a reader which axis a line belongs to.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] hour = [0, 6, 12, 18, 24];
double[] temperature = [12, 11, 21, 17, 13];
double[] humidity = [88, 90, 52, 63, 81];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Plot(hour, temperature, s => s.Color = Colors.Crimson)
.SetYLabel("Temperature (°C)")
.SetYAxisColor(Colors.Crimson)
.WithSecondaryYAxis(right => right
.Plot(hour, humidity, s => s.Color = Colors.SteelBlue)
.SetYLabel("Relative humidity (%)")
.SetYAxisColor(Colors.SteelBlue)))
.Save("axis_color.svg");The result, with both lines named at their ends, is pictured on the examples page.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[,] data = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Heatmap(data)
.WithColorBar(cb => cb with
{
Orientation = ColorBarOrientation.Horizontal,
Shrink = 0.8,
DrawEdges = true,
Label = "Intensity"
}))
.Save("colorbar_horizontal.svg");Fill a bar, area, or pie slice with a hatch pattern instead of (or on top of) a solid fill.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
string[] categories = ["A", "B", "C", "D"];
double[] values = [4, 7, 3, 5];
double[] x = [0, 1, 2, 3, 4];
double[] y1 = [1, 3, 2, 4, 3];
double[] y2 = [0, 1, 0.5, 1.5, 1];
Plt.Create()
// Bar series with cross hatch
.AddSubPlot(1, 3, 1, ax => ax.Bar(categories, values, s =>
{
s.Color = Colors.SteelBlue;
s.Hatch = HatchPattern.Cross;
s.HatchColor = Colors.White;
}))
// Area series with forward-diagonal lines
.AddSubPlot(1, 3, 2, ax => ax.FillBetween(x, y1, y2, s =>
{
s.Hatch = HatchPattern.ForwardDiagonal;
s.HatchColor = Colors.DarkGray;
}))
// Pie with per-slice hatches
.AddSubPlot(1, 3, 3, ax => ax.Pie(values, categories, s =>
{
s.Hatches = [HatchPattern.Horizontal, HatchPattern.Vertical,
HatchPattern.DiagonalCross, HatchPattern.Dots];
}))
.Save("hatches.svg");Available patterns: None, ForwardDiagonal (/), BackDiagonal (\), Horizontal (-), Vertical (|), Cross (+), DiagonalCross (x), Dots (.), Stars (*).
Series with hatch support: BarSeries, HistogramSeries, AreaSeries, StackedAreaSeries, PieSeries, ContourfSeries.
Control how the zero baseline is positioned in a stacked area chart.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [0, 1, 2, 3, 4];
double[][] ySets =
[
[1, 2, 3, 2, 1],
[2, 1, 2, 3, 2],
[1, 3, 1, 2, 3],
];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax.StackPlot(x, ySets, s =>
{
s.Baseline = StackedBaseline.Wiggle; // matplotlib's baseline='wiggle'
}))
.Save("streamgraph.svg");| Value | Description |
|---|---|
Zero (default) |
Stack grows upward from y=0 |
Symmetric |
Stack centered at y=0 (ThemeRiver) |
Wiggle |
matplotlib's wiggle: the middles of the layers stay as near to zero as the stack allows, the lower layers weighing more |
WeightedWiggle |
matplotlib's weighted_wiggle, the streamgraph layout: also follows each layer's slope, weighted by its size |
Both wiggles are matplotlib's own formulas. A missing value (NaN) leaves a gap at its own point only.
StreamGraph(x, layers, baseline) on the axes builder draws each layer as an area of its own, with its own legend
entry; a StackPlot layer's name does not reach the legend.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [0, 1, 2, 3, 4];
double[] y1 = [1, 3, 2, 4, 3];
double[] y2 = [0, 1, 0.5, 1.5, 1];
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax.FillBetween(x, y1, y2, s =>
{
s.EdgeColor = Css4Colors.DarkBlue; // separate stroke for boundary lines
s.StepMode = DrawStyle.StepsPre; // step interpolation (Pre/Mid/Post)
}))
.Save("area_steps.svg");StepMode uses the same DrawStyle enum as LineSeries: Default (smooth), StepsPre, StepsMid, StepsPost.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[] x = [.. Enumerable.Range(0, 40).Select(i => i * 0.25)];
double[] y = [.. Enumerable.Range(0, 40).Select(i => i * 0.25)];
var z = new double[y.Length, x.Length];
for (int row = 0; row < y.Length; row++)
for (int col = 0; col < x.Length; col++)
z[row, col] = Math.Sin(x[col]) * Math.Cos(y[row]);
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax.Surface(x, y, z, s =>
{
s.EdgeColor = Colors.Black.WithAlpha(40); // wireframe stroke (null = default)
s.RowStride = 2; // render every 2nd row — faster for large grids
s.ColStride = 2; // render every 2nd column
}))
.Save("surface.svg");Render a single colorbar shared across all subplots, positioned outside the plot area.
using MatPlotLibNet;
using MatPlotLibNet.Styling;
double[,] dataA = { { 1, 2, 3 }, { 4, 5, 6 } };
double[,] dataB = { { 6, 5, 4 }, { 3, 2, 1 } };
Plt.Create()
.WithColorBar(cb => cb with
{
Label = "Signal Intensity",
Orientation = ColorBarOrientation.Vertical, // or Horizontal
Shrink = 0.9,
Padding = 10
})
.AddSubPlot(1, 2, 1, ax => ax.Heatmap(dataA))
.AddSubPlot(1, 2, 2, ax => ax.Heatmap(dataB))
.Save("shared_colorbar.svg");The colorbar picks up its colormap and range from the first IColorBarDataProvider series found across all subplots.
Add a second X-axis at the top of a subplot, independent of the primary X-axis.
using MatPlotLibNet;
using MatPlotLibNet.Models;
using MatPlotLibNet.Styling;
double[] x = [1, 2, 3, 4];
double[] y = [10, 20, 15, 25];
double[] x2 = [100, 200, 300, 400];
double[] celsius = [-10, 5, 20, 35, 50];
double[] power = [1.8, 1.4, 1.0, 1.3, 1.9];
// On the Axes model
var figure = new Figure();
var axes = figure.AddSubPlot();
axes.Plot(x, y);
axes.TwinY();
var secondary = axes.PlotXSecondary(x2, y);
secondary.Color = Colors.Red;
secondary.Label = "Secondary";
figure.Save("twin_y.svg");
// Or via AxesBuilder
Plt.Create()
.AddSubPlot(1, 1, 1, ax => ax
.Plot(x, y)
.WithSecondaryXAxis(b =>
{
b.SetXLabel("Temperature (°C)");
b.SetXLim(-10, 50);
b.PlotXSecondary(celsius, power, s => s.Color = Css4Colors.OrangeRed);
}))
.Save("secondary_x.svg");Top-edge ticks and label are rendered automatically. The secondary X range is computed from XSecondarySeries data.
Save takes a SaveOptions record, and each format uses what it can:
| Setting | What it does |
|---|---|
Dpi |
sizes a PNG: a figure is laid out in pixels at 96 dpi, and the PNG is drawn with dpi / 96 pixels for each, so 500 × 350 at 150 dpi is 781 × 547. Left at 96 it leaves the figure's own WithDpi in force; any other value overrides it for that save. An SVG, a PDF page and JSON have no pixels and ignore it. |
PrettifySvg |
writes the SVG indented, one element to a line |
SvgDecimalPrecision |
rounds positions and sizes in the SVG to 0 to 15 decimals, half away from zero; stroke widths, opacities and text keep their digits |
Title, Author
|
the title and creator in the SVG's metadata, the PDF's document information and the PNG's text chunks |
A GIF follows the dpi of each frame's figure. A format that cannot use a setting ignores it, so the same options can be
handed to any save. WithDpi on the figure builder sets the resolution once for every PNG of the figure.
using MatPlotLibNet;
double[] x = [1, 2, 3, 4];
double[] y = [12, 15, 14, 18];
var fig = Plt.Create().Plot(x, y).Build();
fig.Save("output.svg", new SaveOptions
{
PrettifySvg = true,
SvgDecimalPrecision = 1,
Title = "Annual Revenue",
Author = "Finance Team"
});
fig.Save("output.png", new SaveOptions { Dpi = 150, Title = "Annual Revenue" }); // needs MatPlotLibNet.SkiaLabels on AxHLine / AxVLine are now rendered automatically. A line beyond the data widens the automatic range, as
matplotlib's axhline does, so it is drawn inside the plot; beyond a limit you set (or a zoom), neither the line nor its
label is drawn.
using MatPlotLibNet;
using MatPlotLibNet.Models;
using MatPlotLibNet.Styling;
double[] x = [0, 10, 20, 30, 40, 50];
double[] y = [20, 60, 110, 150, 90, 70];
var figure = new Figure();
var ax = figure.AddSubPlot();
ax.Plot(x, y);
var line = ax.AxHLine(50.0);
line.Label = "Mean";
line.Color = Colors.Red;
// Horizontal: label appears right-aligned at the right edge, above the line.
// Vertical: label appears near the top of the line.Control how the arrow connects the annotation text to its target.
var ann = ax.Annotate("Peak", 42.0, 150.0);
ann.ArrowTargetX = 45.0;
ann.ArrowTargetY = 90.0;
ann.ConnectionStyle = ConnectionStyle.Arc3; // or Straight / Angle / Angle3
ann.ConnectionRad = 0.4; // curvature for Arc3/Angle3
ann.ArrowStyle = ArrowStyle.FancyArrow;
ann.ArrowHeadSize = 10;ConnectionStyle |
Shape |
|---|---|
Straight |
Direct line (default) |
Arc3 |
Bezier arc, offset by ConnectionRad × distance
|
Angle |
Horizontal then vertical (elbow) |
Angle3 |
Smoothed elbow with bezier rounding |
ann.ArrowStyle = ArrowStyle.Wedge; // wider filled arrowhead
ann.ArrowStyle = ArrowStyle.CurveB; // open curved head at target
ann.ArrowStyle = ArrowStyle.BracketAB; // perpendicular brackets at both ends
ann.ArrowStyle = ArrowStyle.None; // no arrowhead (line only)Wrap annotation text in a styled box.
var note = ax.Annotate("Note", 1.5, 1.5);
note.BoxStyle = BoxStyle.Round;
note.BoxPadding = 6;
note.BoxCornerRadius = 8;
note.BoxFaceColor = Colors.White;
note.BoxEdgeColor = Colors.Gray;
note.BoxLineWidth = 1.5;BoxStyle |
Shape |
|---|---|
None |
No box (default) |
Square |
Plain rectangle |
Round |
Rounded corners (bezier arcs) |
RoundTooth |
Rounded rect with zigzag bottom edge |
Sawtooth |
Sawtooth edges on all sides |
using MatPlotLibNet;
using MatPlotLibNet.Models;
double[] x = [0, 10, 20, 30, 40, 50];
double[] y = [20, 60, 110, 150, 90, 70];
// Single call sets text, position, and arrow target:
Plt.Create()
.Plot(x, y)
.Annotate("High", 42.0, 150.0, arrowX: 45.0, arrowY: 90.0, ann =>
{
ann.ConnectionStyle = ConnectionStyle.Angle;
ann.ArrowStyle = ArrowStyle.FancyArrow;
})
.ToSvg();var span = ax.AxHSpan(1.0, 2.0);
span.Label = "Support zone";
span.LineStyle = LineStyle.Dashed;
span.LineWidth = 1.5;
span.EdgeColor = Colors.DarkGray;
figure.Save("annotations.svg");MatPlotLibNet 1.21.0
- Home
- Getting Started
- Fluent Cheatsheet
- Examples — each release in code
-
Package Map
- MatPlotLibNet — the core package
- Chart Types
- Styling
- Advanced
- Streaming
- Bidirectional SignalR
- Interactive Controls
- MCP Server
- Verso
- Notebooks
- Accessibility
- International Text
- Benchmarks
- Roadmap
- Contributing