Table of Contents

matplotlib in C#

Install: dotnet add package MatPlotLibNet — add MatPlotLibNet.Skia as well for PNG, PDF and GIF.

If you know matplotlib and you are now writing C#, you do not need to learn a new charting library from scratch. This library was built around matplotlib's own model — a figure holds axes, an axes holds series, a series carries its own style — so most of what you know transfers name for name. This page is the lookup table: the pyplot call on the left, the line that draws the same chart on the right.

Two differences are worth reading before the tables, because they explain almost every row.

There is no global current figure. In pyplot, plt.plot(...) draws on whichever figure is current, and plt.gca() hands you the axes behind it. Here you always hold the object you are drawing on, so there is nothing to get and nothing to close. That removes plt.gca, plt.gcf, plt.cla, plt.clf, plt.close and plt.hold from the table: none of them has an equivalent, because none of them has anything to do.

Calls chain. Every verb returns the thing you called it on, so a chart is one expression instead of a paragraph of statements. That is why the C# column reads ax.Plot(...).WithTitle(...) where Python reads two lines.

The same chart, twice

# Python
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(x, y, color='steelblue', linewidth=2, label='Signal')
ax.set_xlabel('Time (s)')
ax.set_ylabel('Amplitude')
ax.set_title('Measurement')
ax.legend()
ax.grid(True)
fig.savefig('chart.png', dpi=150)
// C#
using MatPlotLibNet;
using MatPlotLibNet.Styling;

Plt.Create()
    .WithSize(800, 600)
    .WithDpi(150)
    .AddSubPlot(1, 1, 1, ax => ax
        .Plot(x, y, s => { s.Color = Colors.SteelBlue; s.LineWidth = 2; s.Label = "Signal"; })
        .SetXLabel("Time (s)")
        .SetYLabel("Amplitude")
        .WithTitle("Measurement")
        .WithLegend()
        .WithGrid(g => g with { Visible = true }))
    .Save("chart.png");

Figures and axes

matplotlib MatPlotLibNet Notes
plt.figure() Plt.Create() Starts a figure. .Build() hands you the Figure; .Save(…) finishes it in one go.
plt.subplots() figure.AddSubPlot(1, 1, 1, ax => …) The axes is handed to the lambda instead of returned.
plt.subplots(2, 3) figure.AddSubPlot(2, 3, index, ax => …) Called once per panel; index runs 1…6, row by row, as in matplotlib.
plt.figure(figsize=(8, 6)) figure.WithSize(800, 600) Pixels, not inches — there is no implicit DPI multiply.
plt.figure(dpi=150) figure.WithDpi(150) Scales the raster export; the SVG stays resolution-free.
fig.suptitle('…') figure.WithTitle("…") The figure's own title, above the panels.
plt.tight_layout() figure.TightLayout()
plt.subplots(constrained_layout=True) figure.ConstrainedLayout()
gridspec.GridSpec(2, 2, height_ratios=…) figure.WithGridSpec(2, 2, heightRatios) Ratios are arrays, same meaning.
plt.subplots_adjust(hspace=…) figure.WithSubPlotSpacing(sp => sp with { VerticalGap = … })
plt.subplot_mosaic('AB;CC') Plt.Mosaic("AB;CC") Same string layout language.
fig.add_axes([…]) ax.AddInset(bounds, inset => …) An inset belongs to the axes it sits in.
plt.savefig('c.png') figure.Save("c.png") The extension picks the format: .svg, .png, .pdf, .gif.
fig.canvas.tostring_rgb() figure.ToSvg() The markup as a string, for a web response.
plt.show() Nothing renders to a screen by itself; see the Playground or the Blazor, WPF, MAUI, Avalonia and Uno controls.
plt.gca(), plt.gcf() There is no current figure to get.
plt.close(), plt.clf() Nothing is held open.

The plots

matplotlib MatPlotLibNet Notes
ax.plot(x, y) ax.Plot(x, y)
ax.scatter(x, y) ax.Scatter(x, y)
ax.scatter(x, y, c=density) ax.DensityScatter(x, y) Counts the points per cell and colours by it; no density array to compute.
ax.bar(labels, heights) ax.Bar(labels, heights)
ax.bar with an offset per group ax.GroupedBar(categories, groups) The offset arithmetic every grouped bar chart repeats is done for you.
ax.barh(labels, widths) ax.Bar(labels, values, s => s.Orientation = BarOrientation.Horizontal) One series flag rather than a second verb.
ax.step(x, y) ax.Step(x, y)
ax.stem(x, y) ax.Stem(x, y)
ax.stackplot(x, ys) ax.StackPlot(x, ySets)
ax.fill_between(x, y1, y2) ax.FillBetween(x, y, y2)
ax.errorbar(x, y, yerr=e) ax.ErrorBar(x, y, errorLow, errorHigh)
ax.pie(sizes, labels=…) ax.Pie(sizes, labels)
donut via wedgeprops ax.Donut(sizes, labels)
ax.eventplot(positions) ax.Eventplot(positions)
ax.table(cellText=…) ax.Table(cells)
ax.Sankey(nodes, links)
squarify.plot ax.Treemap(root) matplotlib has no treemap; this one is built in.
nested ax.pie ax.Sunburst(root)

Labels, limits, scales and the frame

matplotlib MatPlotLibNet Notes
ax.set_xlabel('…') ax.SetXLabel("…")
ax.set_ylabel('…') ax.SetYLabel("…")
ax.set_title('…') ax.WithTitle("…")
ax.set_xlim(0, 10) ax.SetXLim(0, 10)
ax.set_ylim(0, 10) ax.SetYLim(0, 10)
ax.set_xscale('log') ax.SetXScale(AxisScale.Log) Linear, Log, SymLog and Logit, as in matplotlib.
ax.set_yscale('log') ax.SetYScale(AxisScale.Log)
ax.legend() ax.WithLegend()
ax.legend(loc='upper left') ax.WithLegend(LegendPosition.UpperLeft)
ax.grid(True) ax.WithGrid(g => g with { Visible = true }) The grid is a value you change, so one call sets colour, style and which axis at once.
ax.minorticks_on() ax.WithMinorTicks()
ax.margins(x=0.1) ax.SetXMargin(0.1)
ax.margins(0) ax.WithTightMargins()
ax.spines['top'].set_visible(False) ax.HideTopSpine() HideRightSpine() for the other one.
ax.axhline(y=0) ax.AxHLine(0)
ax.axvline(x=0) ax.AxVLine(0)
ax.axhspan(1, 2) ax.AxHSpan(1, 2)
ax.axvspan(1, 2) ax.AxVSpan(1, 2)
ax.annotate('…', xy=…) ax.Annotate("…", x, y) Arrows, callout boxes and axes-fraction placement are all on the same call.
ax.twinx() ax.WithSecondaryYAxis(right => …)
ax.sharex(other) ax.ShareX("time")
plt.xticks(rotation=45) ax.WithXTickLabelRotation(45)
ax.xaxis.set_major_formatter(f) ax.SetXTickFormatter(formatter)
ax.xaxis.set_major_locator(l) ax.SetXTickLocator(locator)
mdates.DateFormatter('%H:%M') ax.SetXDateFormat("HH:mm") .NET format strings, not strftime.

Colour and style

matplotlib MatPlotLibNet Notes
color='steelblue' Colors.SteelBlue All 148 CSS4 names are there.
color='#1f77b4' Color.FromHex("#1f77b4")
color='red' Color.FromName("red")
linestyle='--' LineStyle.Dashed
marker='o' MarkerStyle.Circle
cmap='viridis' ColorMaps.Viridis 148 colormaps, matplotlib's own included.
cmap='coolwarm' ColorMaps.Coolwarm
plt.colormaps() ColorMaps.All
plt.style.use('dark_background') figure.WithTheme(Theme.Dark) 30 themes; Theme.MatplotlibV2 is matplotlib's own default palette.
plt.style.use('ggplot') figure.WithTheme(Theme.Ggplot)
plt.style.use('seaborn') figure.WithTheme(Theme.Seaborn)
mpl.rcParams[…] = … Theme.CreateFrom(Theme.Default) Build a theme from an existing one instead of mutating a global dictionary.
fig.colorbar(im) ax.WithColorBar()

Distributions and statistics

matplotlib / seaborn MatPlotLibNet Notes
ax.hist(data, bins=20) ax.Hist(data, 20)
ax.hist2d(x, y) ax.Histogram2D(x, y)
ax.boxplot(data) ax.BoxPlot(data)
ax.violinplot(data) ax.Violin(data)
ax.hexbin(x, y) ax.Hexbin(x, y)
sns.kdeplot(data) ax.Kde(data)
sns.rugplot(data) ax.Rugplot(data)
sns.stripplot(…) ax.Stripplot(datasets)
sns.swarmplot(…) ax.Swarmplot(datasets)
sns.countplot(…) ax.Countplot(values)
sns.pointplot(…) ax.Pointplot(datasets)
ax.ecdf(data) ax.Ecdf(data)
sns.heatmap(matrix) ax.Heatmap(matrix)
sns.clustermap(matrix) ax.Clustermap(matrix)
sns.pairplot(frame) ax.PairGrid(variables)
scipy.cluster.hierarchy.dendrogram ax.Dendrogram(root)

Images, grids and vector fields

matplotlib MatPlotLibNet Notes
ax.imshow(matrix) ax.Image(matrix)
ax.pcolormesh(x, y, z) ax.Pcolormesh(xEdges, yEdges, values)
ax.contour(x, y, z) ax.Contour(x, y, z)
ax.contourf(x, y, z) ax.Contourf(x, y, z)
ax.quiver(x, y, u, v) ax.Quiver(x, y, u, v)
ax.quiverkey(q, …) ax.QuiverKey(x, y, u, "5 m/s")
ax.streamplot(x, y, u, v) ax.Streamplot(x, y, u, v)
ax.barbs(x, y, u, v) ax.Barbs(x, y, u, v)
ax.tricontour(…) ax.Tricontour(x, y, z)
ax.tripcolor(…) ax.Tripcolor(x, y, z)
ax.specgram(signal) ax.Spectrogram(signal)

Three dimensions

matplotlib MatPlotLibNet Notes
fig.add_subplot(projection='3d') ax.WithProjection(elevation, azimuth) The projection is a property of the axes, set where you draw.
ax.plot_surface(x, y, z) ax.Surface(x, y, z)
ax.plot_wireframe(x, y, z) ax.Wireframe(x, y, z)
ax.plot_trisurf(x, y, z) ax.Trisurf(x, y, z)
ax.scatter(x, y, z) ax.Scatter3D(x, y, z)
ax.plot(x, y, z) ax.Plot3D(x, y, z)
ax.contour3D(x, y, z) ax.Contour3D(x, y, z)
ax.bar3d(…) ax.Bar3D(x, y, z)
ax.voxels(filled) ax.Voxels(filled)
ax.text(x, y, z, '…') ax.Text3D(x, y, z, "…")
ax.view_init(elev, azim) ax.WithCamera(30, -60)

Polar

matplotlib MatPlotLibNet Notes
plt.subplot(projection='polar') + plot ax.PolarPlot(theta, r) The polar frame comes with the series.
polar scatter ax.PolarScatter(theta, r)
polar bar ax.PolarBar(theta, r)
radar chart, by hand ax.Radar(labels, values) matplotlib has no radar verb; this one is built in.

Where to go next