Add plotstyle: reusable KIT-branded matplotlib styling toolkit

Introduces a new plotstyle package providing consistent, presentation/thesis-ready
matplotlib figures: a KIT corporate-design color palette (categorical, sequential,
diverging, status), a theme applied via use() (KIT-black bottom spine only, left/bottom
ticks, horizontal gridlines, left-aligned titles, LaTeX text in Latin Modern Sans),
new_figure() with size presets and figure-level title/params subtitles, a
same-size colorbar() helper, style_legend() and panel_label() building blocks, and
savefig(). Ships as an optional "plotting" extra so core gallery installs stay
lightweight, with a full test suite and a runnable example notebook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 14:19:30 +02:00
parent 8cae6533d5
commit 36ae18e880
11 changed files with 3164 additions and 5 deletions
+97
View File
@@ -0,0 +1,97 @@
"""Legend and panel-label helpers for consistent multi-panel figures."""
from __future__ import annotations
import warnings
from matplotlib.axes import Axes
from matplotlib.colors import to_rgba
from .colors import INK
_PANEL_LOCS = {
"upper left": (0.02, 0.98, "left", "top"),
"upper right": (0.98, 0.98, "right", "top"),
"lower left": (0.02, 0.08, "left", "bottom"),
"lower right": (0.98, 0.08, "right", "bottom"),
}
def style_legend(
ax: Axes,
loc: str = "outside right upper",
frameon: bool = False,
title: str = None,
**kwargs,
):
"""Add a figure-level legend, placed outside the axes by default.
Builds on `ax`'s handles/labels (or explicit `handles=`/`labels=` kwargs)
but attaches the legend to `ax`'s figure via `fig.legend(...)`, so it sits
outside the plot area rather than overlapping the data.
A `title` is strongly encouraged: an untitled legend floating outside the
axes loses its visual link to what it's describing, e.g.
`style_legend(ax, title="Series")`. Without one, this emits a warning
rather than failing — the legend still renders.
"""
if title is None:
warnings.warn(
"style_legend() called without a title — an outside legend reads better with one, "
"e.g. style_legend(ax, title='Series').",
stacklevel=2,
)
fig = ax.get_figure()
handles = kwargs.pop("handles", None)
labels = kwargs.pop("labels", None)
if handles is None or labels is None:
handles, labels = ax.get_legend_handles_labels()
legend = fig.legend(handles, labels, loc=loc, frameon=frameon, title=title, **kwargs)
if legend.get_title() is not None:
legend.get_title().set_fontweight("bold")
return legend
def panel_label(
ax: Axes,
label: str,
loc: str = "lower right",
fontweight: str = "bold",
box: bool = True,
**kwargs,
):
"""Add a panel label like "(a)" for multi-panel thesis/paper figures.
Defaults to the bottom-right corner, colored to match the axes' xlabel,
on a light-grey semi-transparent rounded box with a slim solid border.
Pass `box=False` for bare text with no box.
"""
try:
x, y, ha, va = _PANEL_LOCS[loc]
except KeyError as exc:
raise ValueError(f"Unknown panel_label loc {loc!r}. Choose from {sorted(_PANEL_LOCS)}.") from exc
kwargs.setdefault("color", ax.xaxis.label.get_color())
if box:
kwargs.setdefault(
"bbox",
dict(
boxstyle="round,pad=0.3",
facecolor=to_rgba(INK["gridline"], alpha=0.8),
edgecolor=INK["baseline"],
linewidth=0.8,
),
)
return ax.text(
x,
y,
f"({label})",
transform=ax.transAxes,
ha=ha,
va=va,
fontweight=fontweight,
**kwargs,
)