/** * Farben für die Diagramme. * * Zwei getrennte Aufgaben, zwei getrennte Paletten – das ist keine Kosmetik: * * 1. ZUSTAND (ok / bald ablaufend / abgelaufen) ist eine *Statusangabe*. Dafür * gelten die Tokens der Anwendung (--ok, --warn, --danger). Statusfarben * werden nie als Serienfarben zweckentfremdet und stehen nie allein: In der * Legende steht immer auch der Text. * * 2. KATEGORIEN sind *Identität*. Dafür eine feste Reihenfolge geprüfter Farben. * Die Reihenfolge ist Teil der Sicherheit gegen Farbfehlsichtigkeit – sie * wird nicht umsortiert und nicht im Kreis wiederholt. Über sechs Kategorien * hinaus wird zu „Andere“ gebündelt, statt weitere Farben zu erfinden. * * Dunkel ist kein Umdrehen der Hellwerte, sondern eigene Stufen für den dunklen * Untergrund. */ // Feste Reihenfolge. Höchstens SERIES_MAX Einträge werden eingefärbt. export const CATEGORY_COLORS_LIGHT = [ "#2a78d6", // blau "#eb6834", // orange "#1baf7a", // aqua "#eda100", // gelb "#e87ba4", // magenta "#008300", // grün ]; export const CATEGORY_COLORS_DARK = [ "#3987e5", "#d95926", "#199e70", "#c98500", "#d55181", "#008300", ]; /** Ab hier wird gebündelt – „Andere“ bekommt einen neutralen Grauton. */ export const SERIES_MAX = 6; export const OTHER_COLOR_LIGHT = "#8a8f98"; export const OTHER_COLOR_DARK = "#6f7783"; /** Erkennt das aktive Farbschema (Systemeinstellung + eigener Umschalter). */ export function istDunkel() { if (typeof document === "undefined") return false; const gesetzt = document.documentElement.getAttribute("data-theme"); if (gesetzt === "dark") return true; if (gesetzt === "light") return false; return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false; } export function kategorieFarben(dunkel) { return dunkel ? CATEGORY_COLORS_DARK : CATEGORY_COLORS_LIGHT; } export function andereFarbe(dunkel) { return dunkel ? OTHER_COLOR_DARK : OTHER_COLOR_LIGHT; } /** * Zusätzliche Farben für die Detailansicht (mehr Kategorien als die geprüfte * Palette hat). Bewusst nur für den Detail-Fall: gleichmäßig verteilte Farbtöne * (Goldener Winkel) statt der farbfehlsicht-geprüften Festfarben. */ function extraFarben(anzahl, dunkel) { const s = dunkel ? 62 : 66; const l = dunkel ? 58 : 46; return Array.from({ length: anzahl }, (_, j) => { const ton = Math.round((28 + j * 137.508) % 360); return `hsl(${ton}, ${s}%, ${l}%)`; }); } /** * Vergibt Farben für eine sortierte Anteilsliste (Objekte mit { name, value }). * * Standard: höchstens SERIES_MAX Einträge in fester Reihenfolge, der Rest zu * „Andere“ gebündelt. Mit `alle` (Detailansicht) bekommt JEDE Kategorie eine * eigene Farbe (Festpalette vorne, danach erzeugte Töne) – ohne „Andere“. */ export function mitFarben(eintraege, dunkel, { alle = false } = {}) { const farben = kategorieFarben(dunkel); const sortiert = [...eintraege].sort((a, b) => b.value - a.value); if (alle) { const extra = extraFarben(Math.max(0, sortiert.length - farben.length), dunkel); return sortiert.map((e, i) => ({ ...e, color: i < farben.length ? farben[i] : extra[i - farben.length], })); } const vorne = sortiert.slice(0, SERIES_MAX).map((e, i) => ({ ...e, color: farben[i] })); const rest = sortiert.slice(SERIES_MAX); if (rest.length === 0) return vorne; return [ ...vorne, { name: `Andere (${rest.length})`, value: rest.reduce((s, e) => s + e.value, 0), color: andereFarbe(dunkel), }, ]; } /** Zustandsfarben kommen aus den CSS-Tokens, damit Hell/Dunkel automatisch passt. */ export const STATUS_SERIES = [ { key: "ok", name: "In Ordnung", cssVar: "--ok" }, { key: "soon", name: "Bald ablaufend", cssVar: "--warn" }, { key: "expired", name: "Abgelaufen", cssVar: "--danger" }, { key: "no_date", name: "Ohne MHD", cssVar: "--muted" }, ]; /** Liest einen CSS-Tokenwert aus – die Diagramme zeichnen in echtem SVG. */ export function tokenFarbe(name, fallback = "#888") { if (typeof document === "undefined") return fallback; const wert = getComputedStyle(document.documentElement).getPropertyValue(name); return wert?.trim() || fallback; }