"""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"]])