Theme and Primitives#

Theme, Palettes, and Figure Sizing#

coco_pipe.viz.theme is the single source of truth for visual styling across both backends. Matplotlib rcParams and the Plotly coco template are derived from the same palettes and font choices, so changing the theme in one place updates static and interactive plots consistently.

1. Theme Modes#

Three rcParam presets target the most common output contexts:

Mode

Use case

paper

Print figures, tight font sizes (11 pt body, 12 pt titles).

notebook

Jupyter / on-screen review at default zoom (13/14 pt).

poster

Conference posters and slide decks (16/18 pt).

All three share the same colorblind-safe palette and remove top/right spines.

from coco_pipe.viz.theme import coco_theme, set_coco_theme

# Scoped: safe in tests and notebooks (always restores prior rcParams).
with coco_theme("paper", colorblind=True):
    fig, ax = plt.subplots()
    ax.plot(x, y)

# Global: applies for the rest of the process.
set_coco_theme("notebook")

Prefer the context manager in notebooks and tests; reach for set_coco_theme only at script entry points.

2. Palettes#

Four palette constants are exported from coco_pipe.viz.theme and reused by every domain plot:

Constant

Use

SEQUENTIAL ("viridis")

Non-negative continuous values (importance, counts, density).

DIVERGING ("RdBu_r")

Signed values centered on zero (signed importance, contrasts, residuals).

QUALITATIVE ("tab10")

Up to ~10 categorical labels.

QUALITATIVE_COLORBLIND

Okabe–Ito-style 8-color cycle, the default when

("colorblind")

colorblind=True is passed to the theme.

The Plotly colorway and color scales are seeded from the same constants in coco_pipe.viz.interactive._utils, so interactive figures stay aligned with the matplotlib output.

3. Figure Sizing for Papers#

figure_size returns inches sized for one-column or two-column manuscripts. Width defaults match common journal templates (3.5 in single, 7.0 in double); pass width_pt to override using a journal-specific value.

from coco_pipe.viz.theme import coco_theme, figure_size

with coco_theme("paper"):
    fig, ax = plt.subplots(figsize=figure_size(columns=1, aspect_ratio=0.7))
    fig2, ax2 = plt.subplots(figsize=figure_size(columns=2, aspect_ratio=0.5))

    # Override with a journal-specific column width (in points)
    fig3, ax3 = plt.subplots(figsize=figure_size(columns=1, width_pt=246))

4. Saving Figures#

save_figure standardizes DPI, background color, and bounding-box mode so exports look the same regardless of which plot was used.

from coco_pipe.viz.theme import save_figure

save_figure(fig, "fig1.pdf")                # 300 dpi, white background, tight bbox
save_figure(fig, "fig1.png", dpi=600)       # higher resolution for raster output

For interactive Plotly figures, use fig.write_html(...) / fig.write_image(...) directly — the coco template is already applied.

5. End-to-End Paper Figure Recipe#

import matplotlib.pyplot as plt
from coco_pipe.viz import plot_decoding_scores, plot_confusion_matrix
from coco_pipe.viz.theme import coco_theme, figure_size, save_figure

with coco_theme("paper", colorblind=True):
    fig, axes = plt.subplots(
        1, 2, figsize=figure_size(columns=2, aspect_ratio=0.45),
        constrained_layout=True,
    )
    plot_decoding_scores(result, metric="accuracy", ax=axes[0])
    plot_confusion_matrix(result, model="logistic_regression", ax=axes[1])
    save_figure(fig, "figures/fig1.pdf")

6. Compatibility Notes#

  • coco_theme only mutates Matplotlib rcParams; it does not touch the Plotly template (which is registered eagerly at import time).

  • Setting colorblind=True swaps the matplotlib axes.prop_cycle to the Okabe–Ito colors but leaves continuous colormaps alone — those remain perceptually uniform (viridis/RdBu_r).

  • The coco Plotly template is named COCO_TEMPLATE and can be applied manually with fig.update_layout(template="coco") if you build a Plotly figure outside the wrappers.

Plotting Primitives (Base)#

The coco_pipe.viz.base module exposes 12 generic plotting primitives. Every domain plot in viz.decoding and viz.dim_reduction ultimately calls one of them. They are also available directly for custom figures that don’t fit one of the higher-level templates.

All primitives share a consistent contract:

  • Accept ax (existing axes) or figsize (new axes).

  • Return (matplotlib.figure.Figure, matplotlib.axes.Axes).

  • Apply titles/labels/legends through coco_pipe.viz._utils.finalize_axes().

  • Use the theme palettes for default colors.

1. Catalog#

Primitive

Use

plot_bar

Ranked bars with optional error bars, sorting, top_n, horizontal/vertical layout, colormap-by-value.

plot_line

2D line with optional error band or bars.

plot_error_points

Scatter of point estimates with x/y error bars and optional reference lines.

plot_distribution_groups

Grouped box or violin with overlaid jittered points.

plot_scatter2d

2D scatter with categorical or continuous color encoding, optional error bars and reference lines.

plot_scatter3d

3D scatter with categorical or continuous color encoding.

plot_heatmap

2D matrix heatmap with diverging-around-center support, optional cell annotations, colorbar.

plot_hexbin

2D hexagonal density binning.

plot_streamfield

Stream plot from a gridded (U, V) velocity field.

plot_topomap

Sensor-level topographic map (MNE-backed).

Each primitive accepts a permissive input type (Series, mapping, sequence, DataFrame, ndarray as appropriate) and coerces internally so callers don’t have to pre-normalize.

2. Bars: plot_bar#

Ranked horizontal or vertical bars with optional error magnitudes and a colormap applied across values.

from coco_pipe.viz import plot_bar

fig, ax = plot_bar(
    {"accuracy": 0.71, "balanced_accuracy": 0.66, "roc_auc": 0.78},
    errors={"accuracy": 0.02, "balanced_accuracy": 0.03, "roc_auc": 0.015},
    sort=True,
    top_n=10,
    orientation="horizontal",
    cmap="viridis",
    ylabel="Score",
    title="Cross-validated scores",
)

Used internally by plot_decoding_scores(kind="bar"), plot_feature_importance, plot_eigenvalues, and more.

3. Lines: plot_line#

A single 2D line with an optional uncertainty band (error_style="band") or error bars (error_style="bar").

from coco_pipe.viz import plot_line

fig, ax = plot_line(
    times, scores,
    yerr=score_std,
    error_style="band",
    label="LogisticRegression",
    legend=True,
    xlabel="Time (s)", ylabel="Accuracy",
)

Backbone of every temporal-curve plot.

4. Grouped Distributions: plot_distribution_groups#

Box or violin plots across groups with optional jittered individual points overlaid. Non-finite values are dropped automatically.

from coco_pipe.viz import plot_distribution_groups

fig, ax = plot_distribution_groups(
    [fold_scores_lr, fold_scores_rf, fold_scores_svm],
    labels=["LR", "RF", "SVM"],
    kind="violin",
    showmeans=True,
    ylabel="Accuracy",
)

5. Scatters: plot_scatter2d / plot_scatter3d#

Categorical (labels) or continuous (c) color encoding — passing labels disables the continuous branch. Both 2D and 3D variants accept the same color/legend options. 2D additionally supports x/y error bars and dashed reference lines.

from coco_pipe.viz import plot_scatter2d, plot_scatter3d

# Categorical
fig, ax = plot_scatter2d(
    emb[:, 0], emb[:, 1],
    labels=class_ids, alpha=0.7,
    legend_title="Class",
)

# Continuous
fig, ax = plot_scatter2d(
    emb[:, 0], emb[:, 1],
    c=time_index, cmap="viridis", colorbar=True,
    reference_x=0.0, reference_y=0.0,
)

# 3D
fig, ax = plot_scatter3d(
    emb[:, 0], emb[:, 1], emb[:, 2],
    labels=class_ids,
)

6. Heatmaps: plot_heatmap#

Numeric 2D matrices with diverging-around-center support (set center to a numeric reference and the colormap is centered with TwoSlopeNorm). Use annotate=True for cell annotations.

from coco_pipe.viz import plot_heatmap

fig, ax = plot_heatmap(
    confusion_df,
    cmap="viridis",
    annotate=True,
    annotation_format=".2f",
    colorbar_label="Count",
    title="Confusion matrix",
)

Backbone of confusion matrices, generalization matrices, and the co-ranking matrix.

7. Density: plot_hexbin#

Hexagonal density binning for 2D scatter clouds too dense for individual points.

8. Vector Fields: plot_streamfield#

Stream-line rendering of a gridded (U, V) vector field. Underlying scatter points (e.g., raw embedding samples) can be overlaid via points. Colors encode local flow speed.

9. Topographic Maps: plot_topomap#

Sensor-level topographic visualization backed by mne.viz.plot_topomap.

Sensor positions can come from any of three sources:

  • info: an MNE mne.Info object (preferred when available).

  • coords: an (N, 2+) array, {name: (x, y)} mapping, or DataFrame with x/y columns and a FeatureName or Sensor column.

  • The function infers names from values (Series/mapping) or index.

from coco_pipe.viz import plot_topomap

fig, ax = plot_topomap(
    importance_series,            # {sensor_name: value}
    info=raw.info,                # MNE Info
    symmetric=True,
    cbar_label="Importance",
)

When symmetric=True (the default) the color scale is anchored at zero with the diverging palette; otherwise the sequential palette is used.

10. Composing Primitives#

Domain plots are deliberately thin. To build a custom figure that mixes plots, share an axes between primitives:

import matplotlib.pyplot as plt
from coco_pipe.viz import plot_line, plot_distribution_groups
from coco_pipe.viz.theme import coco_theme, figure_size

with coco_theme("paper"):
    fig, axes = plt.subplots(
        1, 2, figsize=figure_size(columns=2, aspect_ratio=0.4),
        constrained_layout=True,
    )
    plot_line(times, mean_score, yerr=score_std, ax=axes[0],
              xlabel="Time (s)", ylabel="Accuracy")
    plot_distribution_groups(
        [fold_lr, fold_rf], labels=["LR", "RF"], ax=axes[1], ylabel="Accuracy"
    )