36ae18e880
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>
103 lines
3.9 KiB
Python
103 lines
3.9 KiB
Python
"""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"]])
|