Plot style#

Figure style (decision D27b).

Two things live here, and they act at two different moments.

PATH and context() are the style sheet – colours, fonts, line widths, figure size. Those are matplotlib rcParams, so they have to be active before a figure is created; they cannot be applied to a line that is already drawn. The sheet ships with the package and is never applied at import time – importing a library must not mutate the user’s matplotlib state:

import matplotlib.pyplot as plt
import comnumpy.style

plt.style.use(comnumpy.style.PATH)      # global, explicit
with comnumpy.style.context():          # or scoped
    ...

apply() is the per-axis convention: what an error-rate figure of this library looks like, what an IQ plane looks like. It runs after the drawing, on an axis the caller filled themselves:

fig, ax = plt.subplots()
line, = ax.semilogy(snr_dB, ser, "o", fillstyle="none", label="16-QAM")
ax.semilogy(snr_dB, ser_theory, "-", color=line.get_color())
style.apply(ax, "error_rate")

That is the whole point of the split: the reader sees the two matplotlib calls that draw the curves, and the library is left with the part that is a house convention rather than a plotting decision.

Colors come from the Okabe-Ito colorblind-safe palette, and no information is carried by color alone (decision D27c): semantic tables such as comnumpy.ofdm.allocation.CARRIER_STYLE pair each color with a glyph and a hatch.

context()[source]#

Context manager applying the comnumpy style locally.

Returns:

contextlib.AbstractContextManager – The matplotlib style context for with comnumpy.style.context():.

use() → None[source]#

Activate the style sheet for every figure created from now on.

One line at the top of a script, after the imports and before the first figure – the colours and the figure size are rcParams, so a figure already created keeps the ones that were active when it was:

import comnumpy.style as style
style.use()

Explicit on purpose: importing this package must not change the user’s matplotlib state behind their back (decision D27b). Use context() instead to scope the change to a block.

apply(ax: Axes, kind: str, *, legend: bool | None = None) → Axes[source]#

Give an axis the house look for one kind of figure.

What it does#

Three things, and deliberately no more:

  • fills the labels that are still empty. A label the caller has already set is never overwritten – ax.set_ylabel("BER") followed by apply(ax, "error_rate") keeps "BER". The defaults are in KINDS;

  • turns on the grid, on both decades when the corresponding axis is logarithmic and on the major ticks otherwise;

  • adds a legend when the axis carries labelled artists and does not have one yet.

kind="iq" additionally sets an equal aspect ratio, because a constellation read on unequal axes is a different constellation.

What it does not do#

It never touches the data and never changes a scale. Whether a curve is drawn with plot or semilogy, with markers or without, in which colour, is the caller’s statement about their measurement, and it stays visible in the caller’s code. This function decorates what is already there.

The colours, fonts, line widths and figure size are not here either: those are rcParams, they have to be active before the figure exists, and they come from the style sheet – see PATH and context().

param ax:

The axis to decorate, already drawn on.

type ax:

matplotlib.axes.Axes

param kind:

One of the keys of KINDS: "error_rate", "iq", "time" or "spectrum".

type kind:

str

param legend:

Force the legend on or off. The default, None, adds one when the axis has labelled artists and none already.

type legend:

bool, optional, keyword-only

returns:

matplotlib.axes.Axes – The same axis, so the call can end a chain of statements.

raises ValueError:

If kind is not one of KINDS.

Examples

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> from comnumpy import style
>>> snr = np.arange(0, 12, 2)
>>> _, ax = plt.subplots()
>>> _ = ax.semilogy(snr, 10.0 ** (-snr / 8), "o", label="QPSK")
>>> _ = style.apply(ax, "error_rate")
>>> ax.get_xlabel(), ax.get_ylabel()
('SNR [dB]', 'error rate')

A label the caller set is left alone:

>>> _, ax = plt.subplots()
>>> _ = ax.semilogy(snr, 10.0 ** (-snr / 8))
>>> ax.set_ylabel("BER")
Text(...)
>>> _ = style.apply(ax, "error_rate")
>>> ax.get_ylabel()
'BER'