bsplot
adapters.bsplot
bsplot figure codegen adapter.
Resolves a declarative Figure (see schema/figure.yaml) into a codegen context and renders the tvbo/templates/bsplot/ Mako tree into a self-contained, user-editable plot.py. This is the figure sibling of the simulation adapters (julia_model, pyrates, …): resolution lives here in Python, code structure lives in the Mako template.
render_code(figure, base_dir) returns the script; render(...) emits and runs it — mirroring experiment.render_code / .run.
Attributes
| Name | Description |
|---|---|
| COLOR_OPTS | Every option whose value is a colour, and so the only ones a palette role is resolved in. |
| CUSTOM_PANELS | |
| HEMISPHERES | The schema’s hemisphere vocabulary in the surface backend’s spelling. One vocabulary in the spec, whatever each backend calls it. |
| TRANSFORMS |
Functions
| Name | Description |
|---|---|
| build_context | Resolve a Figure into the template context (all IO paths + names resolved). |
| colorbar_panel | A colour scale occupying its own mosaic slot — the built-in kind: colorbar. |
| compose_caption | Compose a figure’s caption from its spec — the authored lead plus one clause per panel. |
| expand_bare_axes | Grow each of axes until it meets a neighbour’s ink, and return the ones moved. |
| fan_colors | Colours for a categorical line fan — public API, imported by the emitted plot script. |
| heatmap_orientation | A heatmap’s array as (y, x), the order pcolormesh reads (public API). |
| legend_panel | A free-standing key occupying its own mosaic slot — the built-in kind: legend. |
| load_layer | Open a custom panel’s resolved layer into a DataArray (public API). |
| network_panel | The connectome as a node-link graph, its nodes coloured by a layer — the built-in kind: network. |
| output_format | The file extension a figure’s render writes, without the dot. |
| register_panel | Register a custom-panel callable fn(fig, ax, ctx) under name (decorator). |
| register_transform | Register a presentation-only layer transform fn(da) -> da under name (decorator). |
| registered | Look a spec-declared name up in a registry, or raise an actionable error (public API). |
| render | Render and run the figure. Writes the script to script_path if given. |
| render_code | Render the Figure into a self-contained plot.py string. |
| resolve_color | A declared colour as a drawn one — public API, and the only place the translation happens. |
| resolve_colormap | A declared colormap as a resolvable name — public API, and the only place the translation happens. |
| resolve_path | Resolve a spec-relative file reference against base_dir (the study dir) — public API. |
| retired_options | Every panel option of kind that is a declared slot now, mapped to the (slot, attribute) that replaced it. |
| scale_colormap | A declared colormap name as the map a field is drawn with, pinned to center where one is asked for. |
| surface_panel | Per-vertex values painted on a mesh — the built-in kind: surface. |
| theme_rcparams | The Theme’s geometry as rcParams, applied after every style layer so a declared look always wins. |
| theme_spec | The look a figure declares, with the curated theme it names merged underneath it. |
| volume_panel | Per-region values painted into a labelled volume and projected — the built-in kind: volume. |
| write_caption | Write a figure’s composed caption to <out_dir>/<name>.caption.qmd and return the path. |
build_context
adapters.bsplot.build_context(figure, base_dir, outfile)Resolve a Figure into the template context (all IO paths + names resolved).
colorbar_panel
adapters.bsplot.colorbar_panel(fig, ax, ctx)A colour scale occupying its own mosaic slot — the built-in kind: colorbar.
Panels that share one scale cannot each own the bar: attaching it to any one of them steals that panel’s width and implies the scale is local to it. The paper puts it in an empty cell instead, and so does this.
A spec declares all of this as colorbar: — the Colorbar class names and documents every attribute. This is the one panel that states its own scale: a bar attached to another panel keys the field beside it and is refused a colormap of its own. With a layer bound and no explicit limits the limits are read from the data, so the bar cannot drift from what it describes, and center resolves exactly as the mesh resolves its own, so a bar keying centred heatmaps is the scale they were drawn on. A quantity in arbitrary units is labelled at its ends (Minimum..Maximum) rather than with numbers that mean nothing.
Returns the bar’s own axes, so a declared frame (ticks, formats, label padding) lands on the scale rather than on the blanked slot behind it.
compose_caption
adapters.bsplot.compose_caption(figure)Compose a figure’s caption from its spec — the authored lead plus one clause per panel.
Each panel contributes (letter) label — <structural descriptor> <Panel.description> in layout order, the letter taken from the same identity the panel draws (:func:_letter_identity) so caption and figure cannot disagree. Cells sharing a paper letter share its clause, each adding only what the clause does not already say, so a grid does not repeat one descriptor per cell and a sibling’s authored prose is not dropped with its letter. A sibling that authors no prose and derives no descriptor contributes nothing at all: its drawn title is a label on the figure, not a sentence in the caption. The structural descriptor is derived from the panel’s layers (:func:_panel_descriptor); the authored Figure.description (lead) and Panel.description (per-panel interpretation) are the only parts a human writes.
expand_bare_axes
adapters.bsplot.expand_bare_axes(fig, axes, pad=0.004)Grow each of axes until it meets a neighbour’s ink, and return the ones moved.
A layout engine sizes rows and columns uniformly and reserves each cell’s margins for whichever panel in that row or column needs the most of them: ticks, tick labels, an axis label. A panel that draws none of that — a schematic, a chain diagram, a captured graph — is padded to match its neighbours and prints smaller than the slot it owns, which on a print figure is millimetres of a panel that had to fit in the first place.
The rule is about ink rather than about cells: a panel expands in each direction until it reaches the drawn extent of the nearest panel that could collide with it, so it can never overlap anything and needs to know nothing about how the grid was divided. Nothing shrinks, and only space no one draws in is taken. Panels are grown one at a time against where the previous one now ends, so two panels sharing a gap cannot both claim it. The layout engine is switched off first, since it would recompute these positions away on the next draw.
Opt-in per panel (opts: {fill_cell: true}): a panel whose position carries meaning — aligned with the one above it, or sized to match a sibling — must keep the position the engine gave it.
fan_colors
adapters.bsplot.fan_colors(n)Colours for a categorical line fan — public API, imported by the emitted plot script.
A fan over a categorical dim asserts unrelated entries, and the palette’s own doctrine (see palette.ramp) gives unrelated entries hues rather than samples of an ordered ramp. Beyond the distinct hues a cycle would hand two categories one colour, so a larger fan falls back to even samples of the sequential scale, which stay pairwise distinct.
heatmap_orientation
adapters.bsplot.heatmap_orientation(da, C, x, y, nx, ny)A heatmap’s array as (y, x), the order pcolormesh reads (public API).
Decided by DIM NAME whenever the encoding names dims of the array. A SQUARE grid makes the two orientations indistinguishable by shape, so a shape test transposes half of them at random — silently swapping which axis the field varies along, which is a wrong figure rather than an ugly one. Falls back to the shape test only when the encoded channels are not dims of the array (a matrix addressed by index), where names cannot decide it.
Shared with the emitted plot.py, which imports it: the orientation is a keying decision, so it lives beside the other reference resolvers rather than being inlined per script.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| da | The layer’s DataArray, consulted for its dim ORDER only. | required | |
| C | Its values. | required | |
| x | Name encoded on the x channel. | required | |
| y | Name encoded on the y channel. | required | |
| nx | Length of the x coordinate, for the fallback. | required | |
| ny | Length of the y coordinate, for the fallback. | required |
legend_panel
adapters.bsplot.legend_panel(fig, ax, ctx)A free-standing key occupying its own mosaic slot — the built-in kind: legend.
A convention shared by several panels belongs to none of them; drawing it inside one both shrinks that panel and implies the convention is local to it. Papers put it in the grid’s spare cell, which is what this kind is.
The entries are parallel declared lists rather than one encoded string per entry, so each is a typed value the spec can validate: labels names them and colors / linestyles / markers style them, each falling back to a sensible default when shorter than labels.
A spec declares all of this as legend: — the same slot every other panel uses for its own key, because this panel IS one. The Legend class names and documents every attribute; here the entry lists are what it is for.
load_layer
adapters.bsplot.load_layer(layer)Open a custom panel’s resolved layer into a DataArray (public API).
A registered custom panel receives ctx with a layers list of resolved-layer dicts (container path, output, transform, selector — all resolved by build_context); it calls bsplot.load_layer(ctx["layers"][i]) to open the i-th one as an xarray DataArray with the declared transform and .sel already applied. A Layer.transform runs before the selection and a DataRef.transform after it, which is the order the two slots are documented in and the only thing that distinguishes them. The shared container cache means opening the same file across panels is free.
Under an animation the emitted script binds frame_dim/frame_pos onto each layer before handing the context over, so a drawer that knows nothing about animation still draws the frame the rest of the figure is at.
network_panel
adapters.bsplot.network_panel(fig, ax, ctx)The connectome as a node-link graph, its nodes coloured by a layer — the built-in kind: network.
The panel a whole-brain paper draws beside its time courses: the network the model actually ran on, with the state living on it. Registered here rather than shipped per study, so kind: network needs no code_modules; the graph itself is built by bsplot’s own create_network, which is where the edge threshold and the node/edge attributes are defined.
A spec declares all of this as network: — the Graph class names and documents every attribute, connectome being the only required one. What is only true here: symmetric defaults to FALSE, because a state variable is not a signed deviation from zero the way a brain map of differences is; projection defaults to axial, edge_percentile to 92, and labels to off, since 87 region names on one panel is a block of text rather than a figure. Edge widths scale with the weight on top of edge_width, so a strong connection reads as strong. With no layer the panel draws the bare graph in color.
output_format
adapters.bsplot.output_format(figure)The file extension a figure’s render writes, without the dot.
An animated figure’s output is its movie, so the animation’s container replaces the still’s format rather than sitting beside it — a figure written as both a png and a gif is two artefacts claiming to be one figure. Asked by figure_outputs and by the renderer, so the name they agree on is derived once.
register_panel
adapters.bsplot.register_panel(name)Register a custom-panel callable fn(fig, ax, ctx) under name (decorator).
register_transform
adapters.bsplot.register_transform(name)Register a presentation-only layer transform fn(da) -> da under name (decorator).
registered
adapters.bsplot.registered(registry, name, kind)Look a spec-declared name up in a registry, or raise an actionable error (public API).
Shared by the adapter and the emitted plot.py, which imports it, so both report a miss the same way. The registries are empty until a figure’s code_modules are imported and their register_* decorators run, so a miss almost always means code_modules is missing the module, or importing it failed.
render
adapters.bsplot.render(
figure,
base_dir='.',
outfile='figure.png',
script_path=None,
)Render and run the figure. Writes the script to script_path if given.
render_code
adapters.bsplot.render_code(figure, base_dir='.', outfile='figure.png')Render the Figure into a self-contained plot.py string.
resolve_color
adapters.bsplot.resolve_color(value)A declared colour as a drawn one — public API, and the only place the translation happens.
A palette role (highlight) or hue (palette.2) resolves against the palette in force; a hex, a backend colour name, a sequence or None passes through untouched, so a spec written before the palette existed draws exactly as it did. A custom panel colouring by role should call this rather than reading the palette itself, so one rule covers the grammar and the escape hatch alike.
resolve_colormap
adapters.bsplot.resolve_colormap(value, default='sequential')A declared colormap as a resolvable name — public API, and the only place the translation happens.
A key the project’s theme declares (diverging, or its own meg) becomes whatever that key holds; anything else is left for the backend’s own registry, so cividis and parula are untouched. None takes default, which is how a mark that names no scale lands on the project’s rather than on matplotlib’s.
resolve_path
adapters.bsplot.resolve_path(p, base_dir)Resolve a spec-relative file reference against base_dir (the study dir) — public API.
A file a figure points at (an image panel’s path, a study .mplstyle) is written relative to the spec that declares it, so the spec stays portable; the emitted plot.py runs from an arbitrary cwd and needs an absolute one. An absolute path is passed through untouched. Returns p unchanged when it is empty.
A custom panel resolving its own study-relative input (ctx["opts"] naming a tvbo Network yaml, say) should call this with ctx["base_dir"] so it follows the same rule as the rest of the spec rather than re-implementing the join.
retired_options
adapters.bsplot.retired_options(kind='')Every panel option of kind that is a declared slot now, mapped to the (slot, attribute) that replaced it.
An empty slot means the option became a slot of the panel itself. This is the single statement of the renaming half of the retirement: :func:_panel_opts refuses from it and scripts/migrate_panel_marks.py rewrites from it, so what a spec is converted into is exactly what the renderer will accept. The retirements that are a change of SHAPE rather than of name — a rule, a region, a viewpoint — are _RESHAPED_OPTS, because there is no attribute to point at.
scale_colormap
adapters.bsplot.scale_colormap(name, lo=None, hi=None, center=None)A declared colormap name as the map a field is drawn with, pinned to center where one is asked for.
An undeclared map takes the theme’s, and which of the theme’s depends on what the field is: a centred scale is signed, so it takes diverging; anything else takes sequential. That is the rule the symmetric default already encodes for the limits, applied to the map as well, so a field of signed deviations is not drawn on a one-directional ramp.
center fixes the map’s neutral colour at a value — zero for a signed change, where the sign is the reading. The limits stay the data’s own and the map is truncated to the half-range the data actually reaches, so a unit of change is the same colour distance either side of the centre and the scale carries no colour the field never takes.
The mesh and the bar that keys it both resolve here. A bar built from the untruncated map would put the neutral colour at the middle of a scale whose field crosses the centre anywhere else, and every value the reader looks up would be wrong.
surface_panel
adapters.bsplot.surface_panel(fig, ax, ctx)Per-vertex values painted on a mesh — the built-in kind: surface.
A brain map is the most-drawn panel in a network-neuroscience paper and needs no study code: the mesh is geometry the network already carries, the values are a layer like any other, and everything else is presentation. Registered here rather than shipped per study, so kind: surface works with no code_modules.
A spec declares all of this as surface: — the Surface class names and documents every attribute, and _KIND_RENAMES says where one reaches this callable under a different word. What is only true here:
symmetric defaults to TRUE, because a cortical map is usually signed deviations and reading those on an off-centre scale is misleading in a way no axis label catches. percentile defaults to 100. Geometry comes from whichever of connectome / mesh / template is given (see :func:_surface_mesh). With no layer the panel draws the bare mesh; with geometry: true the layer supplies (V, 3) vertex COORDINATES, so what it shows is the surface a reconstruction rebuilt rather than a field living on a fixed one.
theme_rcparams
adapters.bsplot.theme_rcparams(theme)The Theme’s geometry as rcParams, applied after every style layer so a declared look always wins.
Only what the theme states: a slot left unset is not a value, so the layer underneath keeps it.
theme_spec
adapters.bsplot.theme_spec(figure, base_dir)The look a figure declares, with the curated theme it names merged underneath it.
Returns the whole Theme as a plain dict — colours and geometry together — because both halves travel to the same place: the colours become the palette the emitted script puts in force, the geometry becomes the rcParams applied after every style layer. The curated theme is the base under both halves, so a figure that declares nothing, and one that declares a single tick length, get the same look everywhere it did not speak.
volume_panel
adapters.bsplot.volume_panel(fig, ax, ctx)Per-region values painted into a labelled volume and projected — the built-in kind: volume.
The volumetric counterpart of kind: surface: same layer, same colour options, geometry from an atlas instead of a mesh. Registered here rather than shipped per study, so a glass brain needs no code_modules and no per-study index table.
Values are placed BY LABEL through the atlas crosswalk, never by array position — a per-region array ordered differently from the atlas would otherwise paint every region with its neighbour’s number and still look like a brain.
A spec declares all of this as volume: — the Volume class names and documents every attribute. What is only true here: symmetric defaults to FALSE, because unlike a surface map of signed deviations a volume panel is as often a positive quantity such as a frequency; percentile defaults to 100 and intensity_projection to absmax.
write_caption
adapters.bsplot.write_caption(figure, out_dir, *, name=None)Write a figure’s composed caption to <out_dir>/<name>.caption.qmd and return the path.
A Quarto partial the manuscript pulls in with {{< include <name>.caption.qmd >}}, so the caption is generated from the figure spec and regenerates whenever a panel moves or a layer is rebound — never hand-maintained beside the figure it describes.