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:
@@ -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,
|
||||
)
|
||||
Reference in New Issue
Block a user