Skip to content

Formatting and Styles

cplots separates what a plot shows from how it looks:

  • Formatting belongs to one plot: axis labels, limits, scales, legend, size. You set it with chainable methods.
  • Styles are visual settings shared across plots: fonts, colours, palette. They layer from a global default, through with blocks, down to each figure and plot.

Formatting a plot

Every method returns a new plot and leaves the original unchanged, so one plot can serve as a base for several variants:

Method Sets
.axes(xlabel=, ylabel=, xlim=, ylim=, xscale=, yscale=, aspect=, secondary_x=, secondary_y=) labels, limits, scales ("linear", "log", "symlog"), aspect="equal", a secondary top x-axis / right y-axis (SecondaryAxisSpec)
.title(text) panel title
.legend(show=True, loc=, ncol=, frame=, outside=, title=) legend; .legend(False) hides it
.xticks(values=, labels=, count=, minor=, format=), .yticks(...) tick positions and labels, approximate tick count, minor ticks, label format such as ".2f" or ".0e"
.grid(on=True) background grid
.colorbar(**fields) colourbar settings of colour-mapped plots (loc, label, lim, ...)
.latex(on=True) pass labels to the backend as raw LaTeX
.size(w, h) figure size in inches
.layout(engine=) matplotlib layout engine: "constrained" (default), "tight" or None
.paper("PRD") journal size and font: "PRD", "PRD-2col", "MNRAS", "A4"
.style(style, **fields) a style for this plot

Constructor arguments use the same names (xlabel=, ylabel=, xscale=, style=, ...). They act as defaults, and the methods above override them. The older spellings still work for now but emit a DeprecationWarning:

Old New
x_label, y_label, x_scale, y_scale xlabel, ylabel, xscale, yscale
theme style
label (power spectra) labels
z_label, value_label (phase portraits) clabel
c_lim clim
cbar_loc, cbar_label_loc, colorbar_pad, colorbar_scale colorbar={"loc": ..., "label_loc": ..., "pad": ..., "scale": ...}
width_ratios, height_ratios (Figure) widths, heights
height_ratio (with_residuals) heights
labels (PhasePortrait axis names) axis_labels

Ticks

Repeated calls update only the arguments you pass, so .yticks(count=4).yticks(format=".1e") keeps both. format is a format spec shared by Python and d3 (".2f", ".1e", ".0%"). altair has no minor ticks and warns if you set minor.

plot.spec() returns the backend-agnostic specification with every override applied, which helps with testing and debugging.

Styles

A cplots.Style is a partial set of visual settings. Every field is optional, and an unset field (None) inherits from the layer below it:

Field Meaning
font_family, font_size base font
label_size, title_size, tick_size per-element font sizes (points)
background figure/axes background ("none" is transparent)
foreground axes, ticks, labels and text colour
palette colours cycled across traces
linewidth, grid, legend_frame line and frame defaults
backend_options per-backend extras, e.g. {"matplotlib": {"axes.spines.top": False}}

Built-in styles: cplots.DEFAULT (applied to every plot, see below), cplots.PUBLICATION (serif, white) and cplots.DARK (transparent background, white foreground, neon palette).

The default style

Every plot starts from cplots.DEFAULT: Computer Modern text and math (CMU Serif, falling back to DejaVu Serif when it isn't installed), the FiveThirtyEight palette (cplots.DEFAULT_PALETTE), 10 pt text, 1.5 pt lines, a white background and boxed axes with inward major and minor ticks on all four sides. Colour-mapped plots default to magma (cplots.DEFAULT_COLORMAP).

cplots.set_style(...) layers on top of DEFAULT; pass base=None to start from the backends' own defaults instead:

cplots.set_style(font_size=9)   # the cplots look, smaller text
cplots.set_style(base=None)     # plain matplotlib / plotly / altair defaults
cplots.set_style(None)          # back to DEFAULT

PUBLICATION, DARK and your own styles are layered on top of DEFAULT, so they keep whatever they don't set: .style(cplots.DARK) changes the colours and font family but keeps the inward ticks, the 1.5 pt lines and the Computer Modern math from DEFAULT.

The cascade

Styles are resolved when the plot renders, from lowest to highest priority:

  1. Global: cplots.DEFAULT, plus anything set with cplots.set_style(...)
  2. Context: with cplots.style(...): (nested blocks merge; thread- and async-safe)
  3. Figure: Figure(style=...) or figure.style(...)
  4. Plot: XYPlot(..., style=...) or plot.style(...)
cplots.set_style(cplots.PUBLICATION)          # every plot from now on

with cplots.style(cplots.DARK, font_size=14):  # just for this talk's slides
    pk.show()

pk.style(palette=("#000000", "#d62728")).show()  # this plot only

Only the fields that are set get applied, through matplotlib.rc_context for that one render, so nothing leaks into later figures and your global rcParams are never modified. Because DEFAULT is always applied, its settings win over your own matplotlib style sheet or rcParams; call cplots.set_style(base=None) to let those through instead.

Backend support

Every feature above works on matplotlib. When a backend can't render a feature, it emits a single cplots.UnsupportedFeatureWarning naming it, rather than silently ignoring it. For example, altair cannot draw aspect="equal" or shared axes, and plotly has no symlog scale. To silence these warnings:

import warnings
warnings.filterwarnings("ignore", category=cplots.UnsupportedFeatureWarning)