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,102 @@
|
||||
"""KIT (Karlsruhe Institute of Technology) corporate design color palette.
|
||||
|
||||
Hex values for KIT green, KIT blue, black 70%, and the corporate accent
|
||||
colors are taken verbatim from the KIT corporate design guide
|
||||
(https://kit-cd.km.kit.edu/english/341.php) — do not hand-edit them without
|
||||
checking that page.
|
||||
|
||||
The categorical *order* below is not arbitrary: it was chosen by running
|
||||
every hue through the CVD-safety/contrast checks described in the `dataviz`
|
||||
skill (fixed hue order, OKLab CVD separation under simulated color-vision
|
||||
deficiency, a normal-vision separation floor, contrast vs. a white surface)
|
||||
and keeping the ordering that clears the adjacent-pair checks. KIT yellow
|
||||
(#FCE500) is the one hue that cannot pass on its own (too light on a white
|
||||
surface, ~1.3:1 contrast) — that is a property of the hex value itself, not
|
||||
the ordering, so it is placed last and should always be paired with a
|
||||
visible direct label rather than relied on as a fill alone.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
|
||||
# Fixed-order categorical hues (KIT primary + accent colors). Order is the
|
||||
# CVD-safety mechanism: never reorder, and never cycle past the last slot
|
||||
# (fold extra series into "Other").
|
||||
CATEGORICAL = [
|
||||
"#009682", # 0 KIT green (primary)
|
||||
"#DF9B1B", # 1 orange
|
||||
"#4664AA", # 2 KIT blue (primary)
|
||||
"#A78230", # 3 brown
|
||||
"#23A1E0", # 4 cyan
|
||||
"#A3107C", # 5 purple
|
||||
"#8CB63C", # 6 pea green
|
||||
"#A22223", # 7 red
|
||||
"#FCE500", # 8 yellow — low contrast on white; always pair with a direct label
|
||||
]
|
||||
|
||||
# Single-hue (KIT blue) sequential ramp, steps 100..700. Step 700 is the exact
|
||||
# brand hex (the high/saturated end); lighter steps are tints blended toward
|
||||
# white in sRGB — there is no darker-than-brand shade.
|
||||
SEQUENTIAL_STEPS = [
|
||||
"#e3e8f2", # 100
|
||||
"#c9d2e6", # 200
|
||||
"#afbcda", # 300
|
||||
"#95a6ce", # 400
|
||||
"#7a90c2", # 500
|
||||
"#607ab6", # 600
|
||||
"#4664AA", # 700 (KIT blue)
|
||||
]
|
||||
|
||||
# Diverging KIT blue <-> KIT red, neutral gray midpoint.
|
||||
DIVERGING = {
|
||||
"low": "#4664AA",
|
||||
"mid": "#f7f7f7",
|
||||
"high": "#A22223",
|
||||
}
|
||||
|
||||
# Fixed, reserved status scale — deliberately NOT re-themed to KIT colors:
|
||||
# status is a small fixed scale with reserved meaning that must stay visually
|
||||
# distinct from the categorical slots so it never impersonates a series.
|
||||
# Never put these in the categorical cycle; always pair with an icon/label.
|
||||
STATUS = {
|
||||
"good": "#0ca30c",
|
||||
"warning": "#fab219",
|
||||
"serious": "#ec835a",
|
||||
"critical": "#d03b3b",
|
||||
}
|
||||
|
||||
# Chrome / ink roles, derived from KIT's specified "black 70%" (#404040,
|
||||
# used by KIT for headings and continuous text) on a white surface.
|
||||
INK = {
|
||||
"surface": "#ffffff",
|
||||
"primary": "#404040", # KIT black 70% — headings, titles, continuous text
|
||||
"secondary": "#6a6a6a",
|
||||
"muted": "#969696",
|
||||
"gridline": "#ececec",
|
||||
"baseline": "#c2c2c2",
|
||||
}
|
||||
|
||||
|
||||
def get_color(index: int) -> str:
|
||||
"""Return the categorical color for series `index` (0-based).
|
||||
|
||||
Raises ValueError past the validated slots instead of silently wrapping
|
||||
back to slot 0, which would collide two series on the same hue.
|
||||
"""
|
||||
if not 0 <= index < len(CATEGORICAL):
|
||||
raise ValueError(
|
||||
f"get_color({index}) out of range: only {len(CATEGORICAL)} validated categorical "
|
||||
"colors exist. Fold extra series into an 'Other' bucket or facet instead of cycling."
|
||||
)
|
||||
return CATEGORICAL[index]
|
||||
|
||||
|
||||
def sequential_cmap(name: str = "ps_sequential") -> LinearSegmentedColormap:
|
||||
"""Continuous KIT-blue sequential colormap for magnitude encoding."""
|
||||
return LinearSegmentedColormap.from_list(name, SEQUENTIAL_STEPS)
|
||||
|
||||
|
||||
def diverging_cmap(name: str = "ps_diverging") -> LinearSegmentedColormap:
|
||||
"""Continuous KIT blue-gray-red diverging colormap for polarity encoding."""
|
||||
return LinearSegmentedColormap.from_list(name, [DIVERGING["low"], DIVERGING["mid"], DIVERGING["high"]])
|
||||
Reference in New Issue
Block a user