plot
plot(spec, *, save=None, show=True, close=True)
Render a figure specification. The spec is a plain nested dict (or a
FigureSpec); it is validated before anything is drawn and a ValueError
names the offending path, e.g. axes[1].series[0]: x and y lengths differ
(200 vs 199).
Parameters
- spec : dict or FigureSpec
- The figure (schema below).
- save : str, pathlib.Path or None, optional
- Output path; the format follows the suffix. Raster formats use the style's save dpi.
- show : bool, optional
- Call
matplotlib.pyplot.show(). DefaultTrue. - close : bool, optional
- After saving / showing, remove the figure from pyplot (
plt.close(fig)) so repeated calls do not pile up open figures. The returned figure can still be edited and saved withfig.savefig(...). DefaultTrue; passFalseto keep it open in pyplot (e.g. for inline notebook display, or when you need the canvas itself, such asfig.canvas.get_renderer()). Ignored after an interactive, non-blocking show (plt.isinteractive(), e.g. IPython with a Qt/Tk or ipympl backend), so the window stays open.
Returns
- tuple[Figure, numpy.ndarray] or None
(fig, axes)withaxesa 2-D array shaped likegrid, orNoneif the spec holds no series.
Schema
{
"grid": (2, 3), # rows, cols (default (1, 1)); axes fill row-major
"style": "paper", # name or partial dict (see Themes)
"palette": "okabe-ito", # name, colour list, or {label: colour}
"figsize": None, # default: grid x style panel size, grown for side legends
"sharex": False, "sharey": False,
"legend": "auto", # auto | axes | figure | none
"legend_title": None, # bold heading of the shared legend / the legend groups
"title": None, # figure title
"axes": [
{
"series": [...],
"xscale": "log", "yscale": "log", # "linear" (default) | "log"
"xlim": None, "ylim": None,
"xlabel": "", "ylabel": "",
"title": "", # panel name, drawn inside (upper right)
"tag": None, # extra inside label, under the title
"legend": None, # None: figure policy; "axes" (own group) | "none"
},
],
}
Series:
{
"x": array, "y": array, # 1-D, same length
"label": "",
"color": None, # None: palette (label map, then cycle)
"style": None, # linestyle ("-", (0, (6, 3))), marker ("o", "X", "D") or both ("-o")
"role": "data", # "reference": thick dark-grey line beneath the rest
"lw": None, "zorder": None,
"kind": "line", # "bars": horizontal bars, needs "x_lo" and "x_hi"
}
Non-finite points are dropped, as are non-positive values on log axes; a series left with fewer than two points (bars: none) is skipped with a warning.
Layout notes
- Tight grid: the gap between panels is
style.gapinches in both directions; margins are sized to the outer tick and axis labels. Rows and columns of the grid with no used panel take no space (8 panels in a 4×4 grid are laid out as 2×4); the returnedaxeskeep the requested shape, with those cells hidden. Hidden cells inside used rows stay (legend host). Withoutfigsizeeach panel box isstyle.panel_size; withfigsizethe figure is kept as given and the panels take what is left. - X tick labels by place in the column (hidden cells don't count): the
topmost panel puts its x tick labels (and x label) on the top spine; the
bottommost panel (also one above a hidden cell, or the only one) at the
bottom; a middle panel shows none when its x range equals the bottommost
panel's, else draws them inside above the bottom spine, with its y range
widened downward so no curve runs under them (with an explicit
ylimthey stay outside instead). Ticks stay on all four sides. - Axis labels shown by every panel are drawn once: a common y label over two
or more rows becomes
fig.supylabel, a common x label over two or more columns becomesfig.supxlabel. Otherwise a middle panel drops an x label its column's bottom panel repeats, and a y label is dropped where the panel to the left repeats it. titleis drawn inside the panel (upper right) with headroom above the data, or above the panel when wider than ~85 % of it;tagsits inside too.- Legends: one shared legend (
"auto"), or per-panel groups in the right margin ("axes"); see the overview. A legend with more entries than the style'slegend_max(default 12) is not drawn; a warning names the axes (orfigure). - With exponent ticks (
"paper"), log axes get at most ~4 labelled decades (every 1, 2, 3, 4, 5, 10, … decades) and unlabelled minor ticks on decades (every decade for steps up to 5, thinned to at most 4 between labels for larger steps, e.g. every 2 for a step of 10); an axis spanning less than 1.5 decades gets ticks at fractional exponents (1.05,1.1, ...) and no minor ticks. These are fixed positions chosen from the final limits: changing the limits of the returned axes does not re-tick them. An axis too narrow for distinct labels keeps its decade ticks. - With inside tick labels (
"paper"), the x range is widened on the left by the widest y label (plus the tick length); a panel with an explicitxlimkeeps its y tick labels outside instead. - Headroom for inside titles is applied once per shared y range (
sharey), using the largest data span of the panels sharing it. - A shared legend over
legend_maxentries that keys several panels falls back to per-panel groups ("axes"), with one warning. - Without
figsize, the figure is made tall enough for the right-margin legends.
Validation notes
- With
sharex/sharey, all axes must use the samexscale/yscale; otherwise aValueErroris raised. colormust be a string;stylemust be a string or a tuple.- Text fields (
label,title,xlabel, ...) given asNonecount as empty. - Style and palette names are checked when the spec is built; errors are
prefixed
figure.style/figure.palette.
Example
import numpy as np
from jaff.plotting import plot
nh = np.logspace(17, 22, 300)
panel = lambda name, ref, sim, rt_off: {
"title": name, "xscale": "log", "yscale": "log",
"xlabel": r"$N_{\rm H}$ (cm$^{-2}$)", "ylabel": r"$n_i/n_{\rm H}$",
"series": [
{"x": nh, "y": ref, "label": "Gong+2017", "role": "reference"},
{"x": nh, "y": sim, "label": "simulation"},
{"x": nh, "y": rt_off, "label": "RT off", "style": "o", "color": "k"},
],
}
# 2 x 2 grid, 3 panels: the shared legend fills the free bottom-right cell
plot({"grid": (2, 2), "sharex": True,
"axes": [panel("CO", *co), panel("C", *c), panel("O", *o)]})