Skip to content

Styling

H.P. Gansevoort edited this page Sep 12, 2026 · 16 revisions

Styling


Themes

MatPlotLibNet ships with built-in themes that mirror matplotlib style sheets.

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.Solarized).Plot(x, y).Save("solarized.svg");

Theme.ColorBlindSafe

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.

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"):

var map = ColorMaps.Get("okabe_ito");

Theme.MatplotlibClassic and Theme.MatplotlibV2

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).

Plt.Create()
    .WithTheme(Theme.MatplotlibV2)
    .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.

Theme.HighContrast

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.

Plt.Create()
    .WithTheme(Theme.HighContrast)
    .Plot(x, y)
    .Save("high-contrast.svg");

Custom Theme

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");

Operator themes and the alarm palette

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:

var theme = Theme.OpsNight;
theme.Alarm.Resting;    // nothing wrong — the neutral shade
theme.Alarm.Warning;    // out of band
theme.Alarm.Critical;   // acted on now
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.


PropCycler

PropCycler — four series cycling color and line style

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.

using MatPlotLibNet.Styling;

var cycler = new PropCyclerBuilder()
    .WithColors(Color.Blue, Color.Orange, Color.Green, Color.Red)
    .WithLineStyles(LineStyle.Solid, LineStyle.Dashed)
    .WithMarkerStyles(MarkerStyle.Circle, MarkerStyle.Square)
    .WithLineWidths(2.0, 1.5)
    .Build();

// Apply to a figure
Plt.Create()
    .WithPropCycler(cycler)
    .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");

// Embed in a theme for reuse across figures
var theme = Theme.CreateFrom(Theme.Dark)
    .WithPropCycler(cycler)
    .Build();

Color Maps

MatPlotLibNet includes 74 base maps. Each one is registered automatically with a reversed _r variant, which makes 148 colormaps in six families:

Colormap comparison

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.Styling.ColorMaps;

// 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",
[
    (0.0, Color.FromHex("#000080")),
    (0.3, Color.FromHex("#FFFFFF")),
    (1.0, Color.FromHex("#800000")),
]);

// Discrete categorical colormap (no interpolation)
var discrete = new ListedColorMap("my_cat", [Color.Blue, Color.Red, Color.Green]);

Colormappable Series

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:

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);
});

Normalizers

LinearNormalizer.Instance               // (v - min) / (max - min) — default
new LogNormalizer()                     // log scale mapping
new TwoSlopeNormalizer(center: 0.0)    // diverging, center at value
new BoundaryNormalizer(double[] bounds) // 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]

Extreme Values (Under / Over / Bad)

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
.WithColorBar(cb => cb with { Extend = ColorBarExtend.Both })

Axis Formatting

Log Scale

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .SetYScale(AxisScale.Log)
        .SetYTickFormatter(new LogTickFormatter())
        .Plot(x, y))
    .Save("logscale.svg");

Date Axis (manual override)

ax.SetXTickLocator(new AutoDateLocator())
  .SetXTickFormatter(new AutoDateFormatter());

See Advanced#date-axes for the automatic date axis workflow.


Spines

// Hide top and right (publication style)
ax.HideTopSpine().HideRightSpine();

// Move x-axis to y = 0
ax.WithSpines(s => s with
{
    Bottom = s.Bottom with { Position = SpinePosition.Data(0) }
});

Grid Lines

Grid lines are on by default with all built-in themes, matching matplotlib's default: solid #B0B0B0 lines at width 0.8.

// Default — major grid on both axes
ax.Plot(x, y);

// Customize color and style
ax.WithGrid(g => g with
{
    Color     = Color.FromHex("#444444"),
    LineStyle = LineStyle.Dashed,
    LineWidth = 0.5
});

// Only X-axis grid lines
ax.WithGrid(g => g with { Axis = GridAxis.X });

// Add minor grid lines
ax.WithGrid(g => g with { Which = GridWhich.Both });

// Turn off entirely
ax.ShowGrid(false);

GridWhich controls tick level: Major (default), Minor, Both. GridAxis controls which axis: X, Y, Both (default).


Chrome Configuration

TextStyle

TextStyle is a nullable partial font override. Apply it to a theme Font via ApplyTo(Font):

var style = new TextStyle { FontSize = 18, FontWeight = FontWeight.Bold, Color = Colors.Navy };
var font = style.ApplyTo(Theme.DefaultFont);

Title and Axis Label Styling

ax.WithTitle("Revenue 2026", ts => ts with { FontSize = 22, Color = Colors.DarkBlue })
  .SetXLabel("Quarter", ts => ts with { FontSize = 14 })
  .SetYLabel("USD (M)", ts => ts with { FontSize = 14, FontSlant = FontSlant.Italic });

// Title alignment
ax.TitleLoc = TitleLocation.Left;   // Left / Center (default) / Right

Legend Configuration

ax.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
});

Tick Customization

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 = Colors.DimGray,
    Pad        = 5
};

Spine Styling

ax.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 }
});

ColorBar Orientation and Shrink

ax.WithColorBar(cb => cb with
{
    Orientation = ColorBarOrientation.Horizontal,
    Shrink      = 0.8,
    DrawEdges   = true,
    Label       = "Intensity"
});

Hatch Patterns

Fill a bar, area, or pie slice with a hatch pattern instead of (or on top of) a solid fill.

using MatPlotLibNet.Styling;

// Bar series with cross hatch
ax.Bar(categories, values, s =>
{
    s.Color      = Colors.SteelBlue;
    s.Hatch      = HatchPattern.Cross;
    s.HatchColor = Colors.White;
});

// Area series with forward-diagonal lines
ax.Area(x, y1, y2, s =>
{
    s.Hatch      = HatchPattern.ForwardDiagonal;
    s.HatchColor = Colors.DarkGray;
});

// Pie with per-slice hatches
ax.Pie(values, s =>
{
    s.Hatches = [HatchPattern.Horizontal, HatchPattern.Vertical,
                 HatchPattern.DiagonalCross, HatchPattern.Dots];
});

Available patterns: None, ForwardDiagonal (/), BackDiagonal (\), Horizontal (-), Vertical (|), Cross (+), DiagonalCross (x), Dots (.), Stars (*).

Series with hatch support: BarSeries, HistogramSeries, AreaSeries, StackedAreaSeries, PieSeries, ContourfSeries.


StackedBaseline

Control how the zero baseline is positioned in a stacked area chart.

using MatPlotLibNet.Styling;

ax.StackedArea(x, ySets, s =>
{
    s.Baseline = StackedBaseline.Wiggle;   // Byron-Wattenberg stream graph
});
Value Description
Zero (default) Stack grows upward from y=0
Symmetric Stack centered at y=0 — useful for symmetric data
Wiggle Byron-Wattenberg baseline minimizes perceived slope (stream graph)
WeightedWiggle Like Wiggle but weighted by layer magnitude

AreaSeries Enhancements

ax.Area(x, y1, y2, s =>
{
    s.EdgeColor = Colors.DarkBlue;          // separate stroke for boundary lines
    s.StepMode  = DrawStyle.StepsPre;       // step interpolation (Pre/Mid/Post)
});

StepMode uses the same DrawStyle enum as LineSeries: Default (smooth), StepsPre, StepsMid, StepsPost.


SurfaceSeries Enhancements

ax.Surface(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
});

Figure-Level ColorBar

Render a single colorbar shared across all subplots, positioned outside the plot area.

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.


TwinY — Secondary X-Axis

Add a second X-axis at the top of a subplot, independent of the primary X-axis.

ax.TwinY();
ax.PlotXSecondary(x2, y, s => { s.Color = Colors.Red; s.Label = "Secondary"; });

// Or via AxesBuilder
ax.WithSecondaryXAxis(b =>
{
    b.SetXLabel("Temperature (°C)");
    b.SetXLim(-10, 50);
    b.PlotXSecondary(celsius, power, s => s.Color = Colors.OrangeRed);
});

Top-edge ticks and label are rendered automatically. The secondary X range is computed from XSecondarySeries data.


SaveOptions

Pass rendering options when saving a figure to disk.

using MatPlotLibNet;

fig.Save("output.svg", new SaveOptions
{
    Dpi                 = 150,
    PrettifySvg         = true,
    SvgDecimalPrecision = 1,
    Title               = "Annual Revenue",
    Author              = "Finance Team"
});

---

## Annotations
### Reference Line Labels

Labels on `AxHLine` / `AxVLine` are now rendered automatically.

```csharp
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.

Connection Styles

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

Arrow Head Styles

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)

Callout Boxes

Wrap annotation text in a styled box.

var ann = ax.Annotate("Note", 1.5, 1.5);
ann.BoxStyle        = BoxStyle.Round;
ann.BoxPadding      = 6;
ann.BoxCornerRadius = 8;
ann.BoxFaceColor    = Colors.White;
ann.BoxEdgeColor    = Colors.Gray;
ann.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

Fluent API — Annotation with Arrow

// 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();

Span Region Border and Label

var span = ax.AxHSpan(1.0, 2.0);
span.Label     = "Support zone";
span.LineStyle = LineStyle.Dashed;
span.LineWidth = 1.5;
span.EdgeColor = Colors.DarkGray;

Clone this wiki locally