# Styling --- ## Themes MatPlotLibNet ships with built-in themes that mirror matplotlib style sheets. ```csharp 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 Okabe-Ito 8-color palette — safe for all three common forms of color vision deficiency (deuteranopia, protanopia, tritanopia). White background, black text. ```csharp 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"`): ```csharp var map = ColorMaps.Get("okabe_ito"); ``` ### Theme.MatplotlibClassic and Theme.MatplotlibV2 Visually faithful matplotlib look-alikes — drop-in matplotlib styling in pure .NET, no Python runtime required. `Theme.MatplotlibClassic` mimics matplotlib's pre-2.0 look (white background, the iconic `bgrcmyk` 7-color cycle, DejaVu Sans 12pt). `Theme.MatplotlibV2` mimics the modern default since 2017 (white background, soft-black `#262626` text, the `tab10` 10-color cycle, DejaVu Sans 10pt). Both ship with the grid hidden by default to stay faithful to matplotlib's `plt.grid(False)` default. ```csharp 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 WCAG AAA target (white/black = 21:1 contrast ratio). Bold 13pt font, thick 1.5px dark grid, 8-color high-chroma cycle. ```csharp Plt.Create() .WithTheme(Theme.HighContrast) .Plot(x, y) .Save("high-contrast.svg"); ``` ### Custom Theme ```csharp 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"); ``` --- ## PropCycler ![PropCycler — four series cycling color and line style](images/prop_cycler.png) Cycle Color, LineStyle, MarkerStyle, and LineWidth simultaneously across series. Properties cycle at their own lengths; the series index wraps modulo the LCM. When `PropCycler` is null the original `Theme.CycleColors[]` path is used (fully backward compatible). ```csharp 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 65 built-in colormaps (130 including reversed `_r` variants) across five families: ![Colormap comparison](images/colormap_comparison.png) | 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` | | Misc | `rainbow`, `ocean`, `terrain`, `cmrmap`, `jet`, `turbo` | ```csharp 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 All 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: ```csharp 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 ```csharp 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) ```csharp 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 ```csharp Plt.Create() .AddSubPlot(1, 1, 1, ax => ax .SetYScale(AxisScale.Log) .SetYTickFormatter(new LogTickFormatter()) .Plot(x, y)) .Save("logscale.svg"); ``` ### Date Axis (manual override) ```csharp ax.SetXTickLocator(new AutoDateLocator()) .SetXTickFormatter(new AutoDateFormatter()); ``` See [[Advanced#date-axes]] for the automatic date axis workflow. --- ## Spines ```csharp // 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. ```csharp // 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 (v0.8.5) ### TextStyle `TextStyle` is a nullable partial font override. Apply it to a theme `Font` via `ApplyTo(Font)`: ```csharp var style = new TextStyle { FontSize = 18, FontWeight = FontWeight.Bold, Color = Colors.Navy }; var font = style.ApplyTo(Theme.DefaultFont); ``` ### Title and Axis Label Styling ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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 ```csharp ax.WithColorBar(cb => cb with { Orientation = ColorBarOrientation.Horizontal, Shrink = 0.8, DrawEdges = true, Label = "Intensity" }); ``` --- ## Hatch Patterns (v0.8.6) Fill a bar, area, or pie slice with a hatch pattern instead of (or on top of) a solid fill. ```csharp 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 (v0.8.6) Control how the zero baseline is positioned in a stacked area chart. ```csharp 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 (v0.8.6) ```csharp 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 (v0.8.6) ```csharp 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 (v0.8.6) Render a single colorbar shared across all subplots, positioned outside the plot area. ```csharp 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 (v0.8.6) Add a second X-axis at the top of a subplot, independent of the primary X-axis. ```csharp 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 (v0.8.6) Pass rendering options when saving a figure to disk. ```csharp using MatPlotLibNet; fig.Save("output.svg", new SaveOptions { Dpi = 150, PrettifySvg = true, SvgDecimalPrecision = 1, Title = "Annual Revenue", Author = "Finance Team" }); --- ## Annotations (v0.8.7) ### 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. ```csharp 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 ```csharp 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. ```csharp 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 ```csharp // 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 ```csharp var span = ax.AxHSpan(1.0, 2.0); span.Label = "Support zone"; span.LineStyle = LineStyle.Dashed; span.LineWidth = 1.5; span.EdgeColor = Colors.DarkGray;