Neue Karten-Konfig-Option Detailansicht: mitFarben(..., { alle:true }) faerbt
jede Kategorie einzeln (Festpalette vorne, danach erzeugte Farbtoene) statt den
Rest zu Andere zu buendeln. Umschaltbar ueber das Zahnrad der Karte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|