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>
This commit is contained in:
Scarriffle
2026-07-30 17:34:27 +02:00
parent 55db1d1363
commit 1cddb4432c
2 changed files with 31 additions and 5 deletions

View File

@@ -462,14 +462,16 @@ function KarteAblaufRing() {
); );
} }
function KarteKategorienRing() { function KarteKategorienRing({ props: karteProps }) {
const { daten, fehler } = useDaten(() => api.dashboardByCategory()); const { daten, fehler } = useDaten(() => api.dashboardByCategory());
if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>; if (fehler) return <div className="alert error"><Icon name="alert" size={16} />{fehler}</div>;
if (!daten) return <div className="empty">Lädt…</div>; if (!daten) return <div className="empty">Lädt…</div>;
// Detailansicht: jede Kategorie einzeln statt der Bündelung zu „Andere“.
const segmente = mitFarben( const segmente = mitFarben(
daten.map((k) => ({ name: k.name, value: k.article_units })), daten.map((k) => ({ name: k.name, value: k.article_units })),
istDunkel(), istDunkel(),
{ alle: !!karteProps?.detail },
); );
return <Donut segmente={segmente} gesamtLabel="Einheiten" />; return <Donut segmente={segmente} gesamtLabel="Einheiten" />;
} }
@@ -687,6 +689,7 @@ export const KARTEN = {
titel: "Kategorien-Anteile", icon: "tag", vorschau: "ring", titel: "Kategorien-Anteile", icon: "tag", vorschau: "ring",
beschreibung: "Ring: welche Kategorie wie viel ausmacht", beschreibung: "Ring: welche Kategorie wie viel ausmacht",
standard: { w: 4, h: 6 }, min: { w: 3, h: 5 }, standard: { w: 4, h: 6 }, min: { w: 3, h: 5 },
config: [{ key: "detail", type: "boolean", label: "Detailansicht (alle Kategorien einzeln, keine Gruppe Andere)" }],
komponente: KarteKategorienRing, komponente: KarteKategorienRing,
}, },
"category-bars": { "category-bars": {

View File

@@ -58,13 +58,36 @@ export function andereFarbe(dunkel) {
} }
/** /**
* Bündelt eine sortierte Liste auf SERIES_MAX Einträge plus „Andere“ und * Zusätzliche Farben für die Detailansicht (mehr Kategorien als die geprüfte
* vergibt die Farben in fester Reihenfolge. * Palette hat). Bewusst nur für den Detail-Fall: gleichmäßig verteilte Farbtöne
* Erwartet Objekte mit { name, value }. * (Goldener Winkel) statt der farbfehlsicht-geprüften Festfarben.
*/ */
export function mitFarben(eintraege, dunkel) { 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 farben = kategorieFarben(dunkel);
const sortiert = [...eintraege].sort((a, b) => b.value - a.value); 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 vorne = sortiert.slice(0, SERIES_MAX).map((e, i) => ({ ...e, color: farben[i] }));
const rest = sortiert.slice(SERIES_MAX); const rest = sortiert.slice(SERIES_MAX);
if (rest.length === 0) return vorne; if (rest.length === 0) return vorne;