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 theaxes.prop_cycleor explicitly select colors by index from the current file's scheme (obtained viacolours.get_current_file_scheme()). Thecolours.gradient()function defaults to using the active file's scheme. - Because
savefig.dpiis inrc, the higher‑resolution export in helper_save()only needs to passdpi=args.dpi. - Plot titles and axis labels are consistently applied using the
_set_axes_labelshelper function to ensure uniform font weight and style across all plots, including dual-axis plots likefirst_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¶
- Canvas: 8 × 6 in @ 300 dpi; inner axes square.
- Ticks: 6 pt majors, ≈ 2 minors; inward on all sides.
- Dual axis: coloured spines + tick marks; black labels.
- Typography: title semibold; axis labels normal.
- Other: no gridlines; legend frameless; colour cycle =
file-specific shuffled scheme derived from colours.get_palette(). pytest→ no style‑related warnings.- Run CLI on a known
.xlsxand visually inspect every output; no unexpected gridlines, font mismatches or clipped labels. - Verify SVG opens correctly in Inkscape / Illustrator.
- 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.