Skip to content

Battery Data Processor – Plotting Style Guide (v0.1 – 2025‑11‑08)

Goal: give every figure the same instantly recognisable visual identity – a crisp black frame, airy layout, consistent fonts and a colour palette that ties back to batteryproc.colours.


1  Design principles

Element Decision Rationale
Frame & axes 1.1 mm black spines on all four sides. No gridlines. Matches literature style (see Li₂FeS₂ / MoO₄ examples) and keeps focus on data.
Ticks Major + minor ticks, pointing inwards, on all sides. “Lab notebook” look; easy to judge intersections inside the frame.
Font Primary: Arial (falls back to Liberation Sans → DejaVu Sans). Legible on screens & print; available on all OSs.
Titles & labels Sentence case; weight = ‘normal’; titles 12 pt, labels 10 pt. Readability and hierarchy.
Colours For each data file, a unique, consistent colour scheme is automatically generated by shuffling the canonical palette returned by colours.get_palette(). This scheme is based on the processed file's name via colours.init_seed and is exposed through colours.get_current_file_scheme(). All plots generated for that specific file share this deterministic order. The gradient() helper defaults to using the first two colours from this file-specific scheme. Typically, the first colour is assigned to 'Charge' and the second to 'Discharge'. Brand consistency.
Lines / markers 1.4 pt linewidth, 5 pt markers with 0.5 pt edge. Clear yet not heavy.
Legend Frame‑less, fontsize ‘small’, auto‑locate. Minimal visual noise.
Figure size Default single‑panel: 4 × 3 in (101.6 × 76.2 mm) @ 300 dpi. Fits half‑page in reports; scales cleanly to slides.
Export .png + .svg, bbox_inches="tight". Raster for web, vector for LaTeX / Illustrator.

2  Implementation – one function does it all

Add/replace the current _set_style() in batteryproc/plots/_style.py exactly as shown. (_set_style and its sibling helpers — _initialize_plot, _set_axes_labels, _set_legend, _save_plot — all live in batteryproc/plots/_style.py; the plot builders that call them live in batteryproc/plots/builders.py.)

# batteryproc/plots/_style.py
from cycler import cycler
import matplotlib.pyplot as plt
import seaborn as sns            # we still lean on seaborn

def _set_style(file_color_scheme: list[str]) -> None:
    """
    Register and activate the BatteryProc plotting style.

    Called once per data file by the command-line interface after the file-specific color scheme has been initialized.
    """
    rc = {
        # ---- Figure ----------------------------------------------------
        "figure.figsize":       (8, 6),        # inch
        "figure.dpi":           300,           # live view; savefig dpi set elsewhere
        "figure.facecolor":     "white",

        # ---- Fonts -----------------------------------------------------
        "font.family":          "sans-serif",
        "font.sans-serif":      ["Arial", "Liberation Sans", "DejaVu Sans"],
        "font.size":            10,
        "axes.titlesize":       12,
        "axes.labelsize":       10,

        # ---- Axes & spines ---------------------------------------------
        "axes.edgecolor":       "black",
        "axes.linewidth":       1.1,
        "axes.facecolor":       "white",
        "axes.grid":            False,
        "axes.prop_cycle":      cycler("color", file_color_scheme),

        # ---- Ticks -----------------------------------------------------
        "xtick.direction":      "in",
        "ytick.direction":      "in",
        "xtick.top":            True,
        "ytick.right":          True,
        "xtick.minor.visible":  True,
        "ytick.minor.visible":  True,
        "xtick.major.size":     6,
        "ytick.major.size":     6,
        "xtick.minor.size":     3,
        "ytick.minor.size":     3,
        "xtick.major.width":    1.0,
        "ytick.major.width":    1.0,
        "xtick.minor.width":    0.8,
        "ytick.minor.width":    0.8,

        # ---- Lines & markers -------------------------------------------
        "lines.linewidth":      1.4,
        "lines.markersize":     5,
        "lines.markeredgewidth":0.5,

        # ---- Legend ----------------------------------------------------
        "legend.frameon":       False,
        "legend.fontsize":      "small",
        "legend.loc":           "best",

        # ---- Saving ----------------------------------------------------
        "savefig.dpi":          300,
        "savefig.bbox":        "tight",
    }

    # 1) Apply seaborn “ticks” template, which already kills the grid.
    sns.set_theme(style="ticks", rc=rc)

    # 2) A final rcParams.update() ensures we overwrite any seaborn defaults.
    plt.rcParams.update(rc)

Key points

  • sns.set_theme(..., rc=rc) lets us inherit Seaborn’s sane defaults (tick styling etc.) but we immediately overwrite anything we don’t like.
  • The colour cycle for each file is set to its unique, shuffled scheme. Direct calls to colours.pick() within plot functions have been minimized; plots now primarily use the axes.prop_cycle or explicitly select colors by index from the current file's scheme (obtained via colours.get_current_file_scheme()). The colours.gradient() function defaults to using the active file's scheme.
  • Because savefig.dpi is in rc, the higher‑resolution export in helper _save() only needs to pass dpi=args.dpi.
  • Plot titles and axis labels are consistently applied using the _set_axes_labels helper function to ensure uniform font weight and style across all plots, including dual-axis plots like first_cycle_overview.

3  Per‑plot conventions

Plot type Extra rules
Capacity / Energy vs Cycle Markers always marker="o" for charge, "x" for discharge.
Voltage profiles Use gradient(n) to create a monotone ramp; final cycle over‑plotted in solid black ("#000000", lw=1.2).
Dual‑axis plots Colour each y‑axis spine & tick labels to match its line (see first‑cycle overview). Main title set via _set_axes_labels.
dQ/dV Use same colour for charge & discharge of a given cycle, but different line style ("-" vs "--").

4  Inset axes template

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

ax_main = fig.add_subplot()
# …plot main data…

ax_in  = inset_axes(ax_main, width="35%", height="35%", loc="upper left",
                    borderpad=1.2)
# …plot inset…
for spine in ax_in.spines.values():
    spine.set_linewidth(1.1)

Never show tick labels inside the inset; keep spines & ticks for scale context.


5  Testing checklist

  1. Canvas: 8 × 6 in @ 300 dpi; inner axes square.
  2. Ticks: 6 pt majors, ≈ 2 minors; inward on all sides.
  3. Dual axis: coloured spines + tick marks; black labels.
  4. Typography: title semibold; axis labels normal.
  5. Other: no gridlines; legend frameless; colour cycle = file-specific shuffled scheme derived from colours.get_palette().
  6. pytest → no style‑related warnings.
  7. Run CLI on a known .xlsx and visually inspect every output; no unexpected gridlines, font mismatches or clipped labels.
  8. Verify SVG opens correctly in Inkscape / Illustrator.
  9. Convert one figure to grayscale – all lines remain distinguishable (our palette passes contrast check).

6  Future‑proofing

  • The style is centralised. Changing one rc entry updates every plot.
  • If we ever need a dark background variant: copy the dict, flip figure.facecolor, tweak text colours, and register as "batteryproc-dark".
  • Keep this guide version‑tagged to sync with releases.

Quick reference card (paste near top of plots/_style.py)

# >>> Style cheat‑sheet <<<
# * 8 × 6 in @ 300 dpi
# * Arial, 10 pt labels, 12 pt titles
# * Black spines (1.1 pt), ticks in/on all sides
# * No grid, no legend frame
# * Colour cycle = batteryproc.colours.get_current_file_scheme()
# * Lines 1.4 pt, markers 5 pt

Maintain this guide in docs/guides/style_guide.md and link to it from the README so contributors know where to look.