Skip to content

API reference — I/O & presets

behaviz.io

Unified, backend-agnostic figure output: bv.save and bv.canvas.

save dispatches to the active renderer, which validates the backend/extension combination and raises BehavizSaveError otherwise.

canvas is a context manager that creates one figure, lets plot calls inside the block omit ax= (they auto-target the block's axes), and saves and/or shows the figure once on exit.

save

save(fig, path, **kwargs)

Save fig to path using the active backend.

The format is taken from the file extension and validated against the backend (e.g. .png/.pdf/.svg on matplotlib/seaborn, .html on bokeh). Unsupported combinations raise :class:~behaviz.core.errors.BehavizSaveError.

Parameters:

Name Type Description Default
fig Any

the figure handle returned by any plot function (the first element of its (fig, ax) return).

required
path

destination path; its extension selects the format.

required
**kwargs

forwarded to the backend writer (e.g. dpi= on matplotlib).

{}

Returns:

Type Description
str

The path written, as a string.

Source code in behaviz/io.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def save(fig: Any, path, **kwargs) -> str:
    """Save ``fig`` to ``path`` using the active backend.

    The format is taken from the file extension and validated against the
    backend (e.g. ``.png``/``.pdf``/``.svg`` on matplotlib/seaborn, ``.html``
    on bokeh). Unsupported combinations raise
    :class:`~behaviz.core.errors.BehavizSaveError`.

    Args:
        fig: the figure handle returned by any plot function (the first element
            of its ``(fig, ax)`` return).
        path: destination path; its extension selects the format.
        **kwargs: forwarded to the backend writer (e.g. ``dpi=`` on matplotlib).

    Returns:
        The path written, as a string.
    """
    return get_renderer().save(fig, path, **kwargs)

canvas

canvas(spec=None, *, ax=None, save=None, show=False, **save_kwargs)

Draw several plots onto one figure, then save/show it on exit.

Inside the block, plot calls that omit ax= automatically target the block's axes (and inherit spec unless they pass their own). When the block exits cleanly the figure is saved (if save is given) and shown (if show is True). If the block raises, nothing is saved.

Parameters:

Name Type Description Default
spec Optional[PlotSpec]

the figure/axis specification for the shared axes.

None
ax Any

draw onto this existing axes instead of creating a figure — e.g. one cell of a plt.subplots grid. When given, save/show act on its parent figure.

None
save Optional[str]

optional path to write on exit (extension selects the format).

None
show bool

display the figure on exit.

False
**save_kwargs

forwarded to :func:save.

{}

Yields:

Type Description

The shared axes object (also usable explicitly, e.g. for plot_pval).

Example

with bv.canvas(save="fig.png") as ax: ... bv.plot_line(x, y) ... bv.plot_scatter(x, y2) f, axs = plt.subplots(1, 2) with bv.canvas(ax=axs[0]) as a: ... bv.plot_line(x, y)

Source code in behaviz/io.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
@contextmanager
def canvas(
    spec: Optional[PlotSpec] = None,
    *,
    ax: Any = None,
    save: Optional[str] = None,
    show: bool = False,
    **save_kwargs,
):
    """Draw several plots onto one figure, then save/show it on exit.

    Inside the block, plot calls that omit ``ax=`` automatically target the
    block's axes (and inherit ``spec`` unless they pass their own). When the
    block exits cleanly the figure is saved (if ``save`` is given) and shown
    (if ``show`` is True). If the block raises, nothing is saved.

    Args:
        spec: the figure/axis specification for the shared axes.
        ax: draw onto this existing axes instead of creating a figure — e.g. one
            cell of a ``plt.subplots`` grid. When given, ``save``/``show`` act on
            its parent figure.
        save: optional path to write on exit (extension selects the format).
        show: display the figure on exit.
        **save_kwargs: forwarded to :func:`save`.

    Yields:
        The shared axes object (also usable explicitly, e.g. for ``plot_pval``).

    Example:
        >>> with bv.canvas(save="fig.png") as ax:
        ...     bv.plot_line(x, y)
        ...     bv.plot_scatter(x, y2)
        >>> f, axs = plt.subplots(1, 2)
        >>> with bv.canvas(ax=axs[0]) as a:
        ...     bv.plot_line(x, y)
    """
    r = get_renderer()
    spec = spec or _DEFAULT_SPEC
    if ax is None:
        fig, ax = r.make_figure(spec)
    else:
        fig = r.get_figure(ax)
    token = set_active_canvas(ax, spec)
    try:
        yield ax
    finally:
        clear_active_canvas(token)

    # Reached only when the block did not raise (the finally above always runs;
    # this line is skipped when an exception propagates).
    if save is not None:
        r.save(fig, save, **save_kwargs)
    if show:
        r.show(fig)

behaviz.presets

behaviz_home

behaviz_home()

Root behaviz config directory (~/.behaviz unless BEHAVIZ_HOME set).

Source code in behaviz/presets.py
16
17
18
19
def behaviz_home() -> Path:
    """Root behaviz config directory (``~/.behaviz`` unless ``BEHAVIZ_HOME`` set)."""
    env = os.environ.get("BEHAVIZ_HOME")
    return Path(env).expanduser() if env else Path.home() / ".behaviz"

presets_dir

presets_dir()

Directory holding user preset JSON files (on the load path).

Source code in behaviz/presets.py
22
23
24
def presets_dir() -> Path:
    """Directory holding user preset JSON files (on the load path)."""
    return behaviz_home() / "presets"

examples_dir

examples_dir()

Directory holding read-only reference copies of the built-in presets.

This is not on the load path — files here are starting points to copy into presets/ and edit, so they never shadow (or go stale against) the in-code built-ins.

Source code in behaviz/presets.py
27
28
29
30
31
32
33
34
def examples_dir() -> Path:
    """Directory holding read-only reference copies of the built-in presets.

    This is **not** on the load path — files here are starting points to copy
    into ``presets/`` and edit, so they never shadow (or go stale against) the
    in-code built-ins.
    """
    return behaviz_home() / "examples"

init_home

init_home(with_examples=True)

Scaffold the behaviz home directory.

Creates ~/.behaviz/presets/ and a README.txt, and (by default) writes the built-in presets as reference copies into ~/.behaviz/examples/.

Deliberately does not seed presets/ with built-ins: doing so would shadow the in-code built-ins permanently and freeze them at install time. The examples/ copies are off the load path, so they are safe to refresh and never go stale against an upgraded behaviz.

Idempotent — safe to re-run; it refreshes README/examples in place.

Returns

dict {"home", "presets", "readme", "examples": [paths...]} describing what was written, for callers (e.g. the CLI) to report.

Source code in behaviz/presets.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def init_home(with_examples: bool = True) -> dict:
    """Scaffold the behaviz home directory.

    Creates ``~/.behaviz/presets/`` and a ``README.txt``, and (by default) writes
    the built-in presets as reference copies into ``~/.behaviz/examples/``.

    Deliberately does **not** seed ``presets/`` with built-ins: doing so would
    shadow the in-code built-ins permanently and freeze them at install time.
    The ``examples/`` copies are off the load path, so they are safe to refresh
    and never go stale against an upgraded behaviz.

    Idempotent — safe to re-run; it refreshes README/examples in place.

    Returns
    -------
    dict
        ``{"home", "presets", "readme", "examples": [paths...]}`` describing what
        was written, for callers (e.g. the CLI) to report.
    """
    home = behaviz_home()
    presets = presets_dir()
    presets.mkdir(parents=True, exist_ok=True)

    readme = home / "README.txt"
    readme.write_text(_README_TEXT)

    written: dict = {"home": home, "presets": presets, "readme": readme, "examples": []}

    if with_examples:
        ex_dir = examples_dir()
        ex_dir.mkdir(parents=True, exist_ok=True)
        for name, spec in _builtin_specs().items():
            f = ex_dir / f"{name}.json"
            f.write_text(json.dumps(_to_payload(spec), indent=2))
            written["examples"].append(f)

    return written

save_preset

save_preset(name, spec, overwrite=True)

Save a :class:PlotSpec as a user preset under ~/.behaviz/presets/.

Parameters

name : str Plain preset name (no path separators) — becomes <name>.json. spec : PlotSpec The spec to persist. A post_hook, if set, is dropped (callables cannot be serialized) with a warning. overwrite : bool, default True When False, raise FileExistsError instead of replacing an existing user preset of the same name.

Returns

pathlib.Path The path the preset was written to.

Source code in behaviz/presets.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def save_preset(name: str, spec: PlotSpec, overwrite: bool = True) -> Path:
    """Save a :class:`PlotSpec` as a user preset under ``~/.behaviz/presets/``.

    Parameters
    ----------
    name : str
        Plain preset name (no path separators) — becomes ``<name>.json``.
    spec : PlotSpec
        The spec to persist. A ``post_hook``, if set, is dropped (callables
        cannot be serialized) with a warning.
    overwrite : bool, default True
        When False, raise ``FileExistsError`` instead of replacing an existing
        user preset of the same name.

    Returns
    -------
    pathlib.Path
        The path the preset was written to.
    """
    _validate_name(name)
    if not isinstance(spec, PlotSpec):
        raise TypeError(f"save_preset expects a PlotSpec, got {type(spec).__name__}.")

    target = _preset_file(name)
    if target.exists() and not overwrite:
        raise FileExistsError(f"Preset {name!r} already exists at {target}. Pass overwrite=True to replace it.")

    presets_dir().mkdir(parents=True, exist_ok=True)
    target.write_text(json.dumps(_to_payload(spec), indent=2))
    return target

export_preset

export_preset(name, path)

Write a preset out to a standalone JSON file for sharing.

Unlike :func:save_preset (which writes into ~/.behaviz), this exports to an arbitrary location so you can email/commit/copy the file to another machine. Works for both user and built-in presets.

Parameters

name : str Name of an existing preset (user or built-in). path : str | pathlib.Path Destination. If it points at an existing directory, the file is written there as <name>.json; otherwise it is treated as the full file path.

Returns

pathlib.Path The path the preset was written to.

Source code in behaviz/presets.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def export_preset(name: str, path) -> Path:
    """Write a preset out to a standalone JSON file for sharing.

    Unlike :func:`save_preset` (which writes into ``~/.behaviz``), this exports
    to an arbitrary location so you can email/commit/copy the file to another
    machine. Works for both user and built-in presets.

    Parameters
    ----------
    name : str
        Name of an existing preset (user or built-in).
    path : str | pathlib.Path
        Destination. If it points at an existing directory, the file is written
        there as ``<name>.json``; otherwise it is treated as the full file path.

    Returns
    -------
    pathlib.Path
        The path the preset was written to.
    """
    spec = load_preset(name)  # resolves user → built-in, raises if unknown
    dest = Path(path).expanduser()
    if dest.is_dir():
        dest = dest / f"{name}.json"
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_text(json.dumps(_to_payload(spec), indent=2))
    return dest

import_preset

import_preset(path, name=None, overwrite=True)

Install a standalone preset JSON file into your ~/.behaviz library.

The reverse of :func:export_preset: after importing, the preset is loadable by name via :func:load_preset.

Parameters

path : str | pathlib.Path The preset file to import. name : str, optional Name to store it under. Defaults to the file's stem (cool.json"cool"). overwrite : bool, default True When False, raise FileExistsError if a user preset of that name already exists.

Returns

pathlib.Path The path the preset was installed to inside ~/.behaviz/presets.

Source code in behaviz/presets.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def import_preset(path, name: str | None = None, overwrite: bool = True) -> Path:
    """Install a standalone preset JSON file into your ``~/.behaviz`` library.

    The reverse of :func:`export_preset`: after importing, the preset is
    loadable by name via :func:`load_preset`.

    Parameters
    ----------
    path : str | pathlib.Path
        The preset file to import.
    name : str, optional
        Name to store it under. Defaults to the file's stem (``cool.json`` →
        ``"cool"``).
    overwrite : bool, default True
        When False, raise ``FileExistsError`` if a user preset of that name
        already exists.

    Returns
    -------
    pathlib.Path
        The path the preset was installed to inside ``~/.behaviz/presets``.
    """
    src = Path(path).expanduser()
    if not src.is_file():
        raise FileNotFoundError(f"No preset file at {src}.")

    try:
        data = json.loads(src.read_text())
    except json.JSONDecodeError as exc:
        raise ValueError(f"{src} is not valid JSON: {exc}") from exc

    if not isinstance(data, dict) or not _looks_like_preset(data):
        raise ValueError(f"{src} does not look like a behaviz preset file.")

    spec = _payload_to_spec(data, src.name)
    target_name = name if name is not None else src.stem
    return save_preset(target_name, spec, overwrite=overwrite)

load_preset

load_preset(name)

Load a preset by name and return a fresh :class:PlotSpec.

User presets take precedence over built-ins of the same name.

Source code in behaviz/presets.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
def load_preset(name: str) -> PlotSpec:
    """Load a preset by name and return a fresh :class:`PlotSpec`.

    User presets take precedence over built-ins of the same name.
    """
    _validate_name(name)

    target = _preset_file(name)
    if target.exists():
        return _load_file(target)

    builtins = _builtin_specs()
    if name in builtins:
        # Deep-copy so callers can mutate the result without touching the shared
        # built-in definition.
        return copy.deepcopy(builtins[name])

    available = ", ".join(list_presets()) or "(none)"
    raise FileNotFoundError(f"No preset named {name!r}. Available presets: {available}")

list_presets

list_presets()

Return {name: source} for every available preset, sorted by name.

source is "builtin" or "user"; a user preset shadowing a built-in is reported as "user".

Source code in behaviz/presets.py
257
258
259
260
261
262
263
264
265
266
267
def list_presets() -> dict[str, str]:
    """Return ``{name: source}`` for every available preset, sorted by name.

    ``source`` is ``"builtin"`` or ``"user"``; a user preset shadowing a
    built-in is reported as ``"user"``.
    """
    out: dict[str, str] = {name: "builtin" for name in _builtin_specs()}
    if presets_dir().exists():
        for f in sorted(presets_dir().glob("*.json")):
            out[f.stem] = "user"
    return dict(sorted(out.items()))

delete_preset

delete_preset(name)

Delete a user preset. Built-ins cannot be deleted.

Source code in behaviz/presets.py
270
271
272
273
274
275
276
277
278
279
def delete_preset(name: str) -> None:
    """Delete a user preset. Built-ins cannot be deleted."""
    _validate_name(name)
    target = _preset_file(name)
    if target.exists():
        target.unlink()
        return
    if name in _builtin_specs():
        raise ValueError(f"{name!r} is a built-in preset and cannot be deleted.")
    raise FileNotFoundError(f"No user preset named {name!r} to delete.")