Files
Vorrania/web/src/dashboard/palette.js
Scarriffle 1cddb4432c Web: Kategorien-Anteile-Karte umschaltbar auf Detailansicht (keine Gruppe Andere)
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>
2026-07-30 17:34:27 +02:00

118 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}