Skip to content

MatplotlibThemes

xkqg edited this page Apr 18, 2026 · 3 revisions

Matplotlib Themes

Drop-in matplotlib look in pure .NET — no Python runtime required.

Two themes that mimic Python matplotlib's iconic visual style: the legendary pre-2.0 classic look and the modern v2.0+ default. Use them when you want your .NET charts to feel at home next to plots from a Jupyter notebook, a research paper, or pyplot.plot(...).


Why these themes exist

Matplotlib is the dominant scientific plotting library in the Python world, and its aesthetic is instantly recognizable in academic papers, ML blog posts, textbooks, and notebooks. People who move between Python and .NET know what a matplotlib chart should look like — and they expect that style to be available without dragging in a Python runtime.

These themes give you exactly that: the same colors, the same fonts, the same defaults — rendered entirely by MatPlotLibNet.


Theme.MatplotlibClassic — the pre-2.0 look

The look that appeared in scientific papers from matplotlib's release until 2017. White background, the iconic bgrcmyk 7-color cycle, DejaVu Sans 12pt, and grid hidden by default (matplotlib has always shipped with plt.grid(False); users opt in).

using MatPlotLibNet;
using MatPlotLibNet.Styling;

double[] x = [0, 1, 2, 3, 4, 5];
double[] y1 = [0, 1, 4, 9, 16, 25];
double[] y2 = [0, 2, 4, 6,  8, 10];

Plt.Create()
    .WithTheme(Theme.MatplotlibClassic)
    .WithTitle("Classic matplotlib look")
    .Plot(x, y1, s => s.Label = "Quadratic")
    .Plot(x, y2, s => s.Label = "Linear")
    .WithLegend()
    .Save("classic.svg");

Color cycle — the legendary bgrcmyk

# Hex Name
0 #0000FF blue
1 #008000 green
2 #FF0000 red
3 #00BFBF cyan
4 #BF00BF magenta
5 #BFBF00 yellow
6 #000000 black

At a glance

Property Value
Background #FFFFFF (white)
Axes background #FFFFFF (white)
Foreground text #000000 (pure black)
Font family DejaVu Sans, Bitstream Vera Sans, sans-serif
Font size 12.0pt
Grid hidden by default (Visible = false)
Cycle length 7 colors

Theme.MatplotlibV2 — the modern default (since 2017)

The look every Jupyter notebook ships with today. White background, soft-black #262626 text (matplotlib v2 deliberately softened pure black), the modern tab10 10-color cycle, DejaVu Sans 10pt, and grid hidden by default.

Plt.Create()
    .WithTheme(Theme.MatplotlibV2)
    .WithTitle("Modern matplotlib look")
    .Plot(x, y1, s => s.Label = "Quadratic")
    .Plot(x, y2, s => s.Label = "Linear")
    .WithLegend()
    .Save("v2.svg");

Color cycle — tab10

# Hex Name
0 #1f77b4 blue
1 #ff7f0e orange
2 #2ca02c green
3 #d62728 red
4 #9467bd purple
5 #8c564b brown
6 #e377c2 pink
7 #7f7f7f gray
8 #bcbd22 olive
9 #17becf cyan

At a glance

Property Value
Background #FFFFFF (white)
Axes background #FFFFFF (white)
Foreground text #262626 (soft black)
Font family DejaVu Sans, sans-serif
Font size 10.0pt
Grid hidden by default (Visible = false)
Cycle length 10 colors

Opting into a grid

Both matplotlib themes ship with the grid off to stay faithful to matplotlib's plt.grid(False) default. To turn it on for a single plot, use the standard grid API:

Plt.Create()
    .WithTheme(Theme.MatplotlibV2)
    .AddSubPlot(1, 1, 1, ax => ax
        .Plot(x, y)
        .ShowGrid(true))                           // simple toggle
    .Save("v2_with_grid.svg");

Or override the theme grid via ThemeBuilder:

var v2WithGrid = Theme.CreateFrom(Theme.MatplotlibV2)
    .WithGrid(g => g with { Visible = true, Color = Color.FromHex("#B0B0B0") })
    .Build();

Comparison

Aspect MatplotlibClassic MatplotlibV2 Theme.Default
Era mimicked matplotlib < 2.0 matplotlib 2.0+ none specifically
Cycle bgrcmyk (7) tab10 (10) tab10 (10)
Foreground text pure black soft black #262626 pure black
Font size 12pt 10pt 13pt (default)
Grid default off off on
Use case retro / classic modern Jupyter look MatPlotLibNet's own default

Theme.Default already happens to use the tab10 colors, but MatplotlibV2 is the deliberate choice when you want to be the matplotlib look — different font stack, soft-black text, and the matplotlib-style hidden-by-default grid.


Community Themes (Phase P — visually distinct since v1.7.2)

Phase P made the six community themes first-class. Before Phase P they rendered near-identically to Theme.Default; each now carries its own palette, font, size, and grid defaults, so dropping one in visibly changes the chart without any other API calls.

Theme Palette Font Size Grid
Theme.Grayscale grayscale cycle (no hues) default default default
Theme.Paper default serif 11pt off
Theme.Presentation default bold 16pt default
Theme.Poster default bold 20pt on, 1.5 linewidth
Theme.GitHub GitHub brand palette default default #E1E4E8
Theme.Minimal default default 11pt off

Use them the same way as the matplotlib themes — Plt.Create().WithTheme(Theme.Paper)…. The Phase P rework is covered by byte-distinct-output contract tests so a future regression (a theme collapsing back to Default) turns CI red.


Pixel-verified against matplotlib (v1.1.3)

Since v1.1.3 every chart that has a matplotlib equivalent is verified against a pinned matplotlib (3.10.8) reference PNG under both themes. The fidelity test suite runs 146 tests = 73 fixtures × 2 themes:

# regenerate reference PNGs (developers only — not run in CI)
pip install -r tools/mpl_reference/requirements.txt
python tools/mpl_reference/generate.py --all --style both

# run the dual-theme fidelity tests
dotnet run --project Tst/MatPlotLibNet.Fidelity/MatPlotLibNet.Fidelity.Tests.csproj

Each test renders one MatPlotLibNet figure, exports to PNG via the SkiaSharp backend, and compares against the matplotlib reference using three independent metrics: RMS pixel error, block SSIM (structural similarity), and MaxColorDeltaE (CIE ΔE*76 across the 5 dominant colours). Failures emit a 3-panel side-by-side diff PNG (reference | actual | abs-difference heatmap) under bin/Debug/net10.0/fidelity-failures/{theme}_{name}.diff.png.

Coverage: 73 fixtures = 12 core (line, scatter, bar, hist, pie, box, violin, heatmap, contour, polar, candlestick, errorbar) + 45 Phase 5 (XY, grid, field, polar, categorical, distribution, 3D, financial, special) + 15 Phase 6 (pandas_ta technical indicators) + 1 composition (multi-subplot suptitle + mathtext labels + mathtext legend).

The v2 fixtures are generated via plt.style.context('default') — that is the modern matplotlib default style (tab10 cycle, DejaVu Sans 10pt, soft-black #262626 foreground), the exact look Theme.MatplotlibV2 reproduces.


Implementation notes

Both themes are built by an internal MatplotlibThemeFactory that centralizes the shared font stack and grid defaults via a single Build(...) helper, so the two themes only differ in the values they actually disagree on (color cycle, font size, foreground text). The font stack is captured as a record struct (MatplotlibFontStack) — named fields with value-equality semantics, not a positional tuple.


See also

Clone this wiki locally