feat(carbon): Carbon-Konformität nachprüfbar machen statt behaupten
Recherche an der Quelle statt an der Dokumentation. Zwei Ergebnisse: 1. Carbon regelt Datenvisualisierung nur teilweise. Farben, Gitter, Achsen, Tooltip und Tickanzahl sind verbindlich festgelegt — Paddings, Margins und Dashboard-Raster dagegen NICHT. Die Carbon-Seiten zu Chart-Anatomie und Dashboards tragen ausdrücklich den Hinweis "this guidance is a work in progress" und enthalten keine Zahlenwerte. Unsere Abstandswerte stammen deshalb aus der Carbon-Spacing-Skala und der Planung mit dem Branchenpartner, nicht aus der Data-Viz-Norm. Das steht jetzt so in DESIGN-SYSTEM.md §0. 2. Eine echte Abweichung gefunden: Der Tooltip ist seit Carbon v11 hell (--cds-layer-02 #ffffff auf --cds-text-primary #161616, abgesetzt allein durch den Schatten). Unser Theme führte den dunklen v10-Stand (#393939) und berief sich dabei auf das ausgelieferte Stylesheet — die Angabe war veraltet. Korrigiert. Neu: - docs/carbon-referenz.json — kanonische Werte mit Quelle, Version und Prüfdatum, gezogen aus @carbon/charts 1.23.0 styles.min.css und configuration.ts. Bewusste Abweichungen (Balkenbreite 48 statt 16, feste Kategoriefarben statt n-Serien-Paletten) sind dort begründet. - scripts/verify-carbon.mjs + npm run verify:carbon — gleicht chartTheme.ts gegen die Referenz ab und bricht bei Drift ab. - docs/PRUEFFRAGEN.md — Prüffragen für die Gestaltungsabnahme mit nachprüfbarem Ja/Nein, plus Leseliste (FT Visual Vocabulary, IBCS, Cleveland & McGill, Few, Datawrapper Academy). Die 14er-Palette wurde gegen die CSS-Variablen --cds-charts-14-1-* geprüft und stimmt exakt; ebenso Gitter #e0e0e0, Achse #8d8d8d und Achsentext #525252. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
This commit is contained in:
1 parent
99be627dad
commit
0875ae2688
8 files changed
+412
-20
No files matched your search
@@ -47,7 +47,9 @@ features/dashboard/
|
||||
pages/ die sechs Seiten — reine Komponenten ({ vm })
|
||||
utils/ chartTheme (Carbon-Tokens), formatters, dateUtils
|
||||
ui/ Subframe-Komponentenbibliothek (unverändert übernommen)
|
||||
docs/ Design-System, KPI-Katalog, Navigation, Datenmodell, Herkunft, Zielsystem, Leitplanken
|
||||
docs/ Design-System, KPI-Katalog, Navigation, Datenmodell,
|
||||
Herkunft, Zielsystem, Leitplanken, Prüffragen
|
||||
scripts/verify-carbon.mjs gleicht chartTheme.ts gegen die Carbon-Referenz ab
|
||||
```
|
||||
|
||||
**Wichtigste Dateien für den Einbau:** `features/dashboard/data/calculations.ts`
|
||||
@@ -86,6 +88,18 @@ Attrappe ist und in welcher Reihenfolge der Einbau sinnvoll ist.
|
||||
Die Seite **/komponenten** zeigt jeden Baustein isoliert mit Dateipfad — der
|
||||
schnellste Weg, sich zu orientieren.
|
||||
|
||||
```bash
|
||||
npm run verify:carbon # Palette, neutrale Werte, Tooltip, Achsenraster gegen IBM Carbon
|
||||
```
|
||||
|
||||
Die Referenzwerte stehen mit Quelle und Version in
|
||||
[`docs/carbon-referenz.json`](docs/carbon-referenz.json), gezogen aus dem
|
||||
ausgelieferten Stylesheet von `@carbon/charts` — nicht aus der Dokumentation,
|
||||
die an mehreren Stellen davon abweicht.
|
||||
|
||||
Für die Gestaltungsabnahme: [`docs/PRUEFFRAGEN.md`](docs/PRUEFFRAGEN.md) —
|
||||
Prüffragen mit nachprüfbarem Ja/Nein, für Reviews ohne Designerauge.
|
||||
|
||||
---
|
||||
|
||||
## Zielsystem
|
||||
|
||||
+50
-6
@@ -4,10 +4,48 @@ Grundlage ist **IBM Carbon Data Visualization**, ergänzt um die Spacing- und
|
||||
Zahlenregeln aus der eigenen Planung (`UI_UX_GUIDELINES.md`, Januar 2026).
|
||||
|
||||
Die konkreten Farb- und Linienwerte sind aus dem ausgelieferten Stylesheet von
|
||||
`@carbon/charts` (`dist/styles.css`, Light Theme) übernommen — nicht aus der
|
||||
Dokumentation abgeschrieben. Alles Folgende ist in
|
||||
`@carbon/charts` (Light Theme) übernommen — nicht aus der Dokumentation
|
||||
abgeschrieben. Alles Folgende ist in
|
||||
`features/dashboard/utils/chartTheme.ts` als Code hinterlegt.
|
||||
|
||||
## 0. Was Carbon regelt — und was nicht
|
||||
|
||||
Wichtig für jeden, der hier etwas anzweifelt: **Carbon regelt Datenvisualisierung
|
||||
nur teilweise.** Die Grenze verläuft so:
|
||||
|
||||
| Frage | Regelt Carbon? | Wo steht die Antwort |
|
||||
|---|---|---|
|
||||
| Farbpalette, Reihenfolge, Statusfarben | **ja, verbindlich** | ausgeliefertes Stylesheet, CSS-Variablen `--cds-charts-14-1-*` |
|
||||
| Gitter-, Achsen- und Textfarben | **ja** | dieselben Quellen |
|
||||
| Tooltip-Aufbau | **ja** | `.cds--cc--tooltip` im Stylesheet |
|
||||
| Tickanzahl je Achse | **ja** | `configuration.ts` (y: 5, x: 15) |
|
||||
| Wann welcher Diagrammtyp | teilweise, qualitativ | Carbon „Simple/Complex charts" |
|
||||
| **Padding, Margins, Kartenabstände** | **nein** | siehe unten |
|
||||
| Dashboard-Raster, Kartenhöhen | **nein**, nur Prinzipien | siehe unten |
|
||||
|
||||
Die Carbon-Seiten zu Chart-Anatomie und Dashboards tragen ausdrücklich den
|
||||
Hinweis „this guidance is a work in progress" und enthalten **keine Zahlenwerte**
|
||||
für Abstände. Wer dort nach Paddings sucht, findet Prinzipien („create a clear
|
||||
visual hierarchy", „limit the number of metrics") — richtig, aber nicht
|
||||
nachprüfbar.
|
||||
|
||||
**Die Abstandswerte in diesem Dokument stammen deshalb nicht von Carbon**,
|
||||
sondern aus zwei anderen Quellen:
|
||||
|
||||
1. der **Carbon-Spacing-Skala und dem 2x-Grid** des allgemeinen Design-Systems
|
||||
(4/8/12/16/24/32/48…) — dieselbe Skala, aus der auch Davids
|
||||
`INTERIM-UI-DESIGN-STANDARDS.md` im Zielsystem schöpft;
|
||||
2. der eigenen Planung mit dem Branchenpartner (`UI_UX_GUIDELINES.md`).
|
||||
|
||||
Beides ist konsistent zueinander. Es ist aber ehrlicher zu sagen: Bei Farben und
|
||||
Achsen folgen wir einer Norm, bei Abständen einer begründeten Hauskonvention.
|
||||
|
||||
**Nachprüfbar gemacht:** `npm run verify:carbon` gleicht `chartTheme.ts` gegen
|
||||
`docs/carbon-referenz.json` ab — Palette, neutrale Werte, Tooltip, Achsenraster.
|
||||
Die Referenzdatei nennt Quelle, Version und Prüfdatum. Bewusste Abweichungen
|
||||
(Balkenbreite, feste Kategoriefarben) sind dort begründet hinterlegt und werden
|
||||
als Hinweis ausgegeben, nicht als Fehler.
|
||||
|
||||
---
|
||||
|
||||
## 1. Farben
|
||||
@@ -116,13 +154,19 @@ wechselt mitten im Lesen die Einheit; die Nulllinie trägt gar keine Einheit.
|
||||
|
||||
## 3. Tooltip
|
||||
|
||||
Einheitlich für alle Charts (`components/charts/ChartTooltip.tsx`):
|
||||
Einheitlich für alle Charts (`components/charts/ChartTooltip.tsx`), Werte aus
|
||||
`.cds--cc--tooltip` im ausgelieferten Stylesheet:
|
||||
|
||||
- Fläche `#393939`, Text `#ffffff`
|
||||
- Label 12 px mit 80 % Deckkraft, Wert 13 px fett in Tabellenziffern
|
||||
- kein Rahmen, kein Doppelpunkt zwischen Name und Wert
|
||||
- Fläche `#ffffff` (`--cds-layer-02`), Text `#161616` (`--cds-text-primary`)
|
||||
- abgesetzt allein durch den Schatten `0 1px 6px 0 rgba(0,0,0,.2)` — **kein Rahmen**
|
||||
- Label und Reihenname 12 px in `#525252`, Wert fett in Tabellenziffern
|
||||
- kein Doppelpunkt zwischen Name und Wert, maximal 270 px breit
|
||||
- Farbmarker je Reihe, 10 × 10 px
|
||||
|
||||
**Achtung bei älteren Quellen:** Bis Carbon v10 war der Tooltip dunkel
|
||||
(`#393939` auf weißem Text). Sehr viele Artikel und Screenshots zeigen noch
|
||||
diesen Stand. Maßgeblich ist das ausgelieferte Stylesheet, nicht die Bildersuche.
|
||||
|
||||
Ein gemeinsamer Tooltip verhindert, dass jedes Chart eigene Regeln entwickelt.
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Prüffragen für die Gestaltungsabnahme
|
||||
|
||||
Für Reviews ohne Designerauge. Die Fragen sind so gestellt, dass man sie am
|
||||
Bildschirm beantworten kann — jede hat ein nachprüfbares Ja oder Nein, keine
|
||||
Geschmacksfrage.
|
||||
|
||||
Reihenfolge nach Wirkung: Wer nur zehn Minuten hat, nimmt Abschnitt 1.
|
||||
|
||||
---
|
||||
|
||||
## 1. Die vier Fragen, die die meisten Fehler finden
|
||||
|
||||
**1. Nenne die Aussage der Seite in einem Satz.**
|
||||
Geht das nicht, fehlt die Hierarchie. Auf jeder Seite muss genau ein Block
|
||||
dominieren. Sehen alle Karten gleich aus, ist keiner wichtig.
|
||||
|
||||
**2. Gibt es ein Diagramm, dessen Balken oder Segmente alle etwa gleich groß sind?**
|
||||
Dann trägt es keine Information. Typische Ursachen: Werte in einem engen Band
|
||||
(74–96 %) auf einer Skala ab null, oder eine Rangfolge aus 5/4/3/3/3.
|
||||
Entweder die Kodierung wechseln oder die Karte streichen.
|
||||
|
||||
**3. Steht dieselbe Zahl zweimal auf der Seite?**
|
||||
Kennzahl oben und Fußzeile unter dem Diagramm sind der übliche Fall. Eine
|
||||
Wiederholung ist kein Beleg, sondern verbrauchter Platz.
|
||||
|
||||
**4. Was soll der Betrachter danach tun?**
|
||||
Findet sich darauf keine Antwort, ist es ein Bericht, kein Dashboard. Eine
|
||||
Handlungsliste braucht je Zeile einen Betrag und ein Verb.
|
||||
|
||||
---
|
||||
|
||||
## 2. Diagramme
|
||||
|
||||
| Frage | Wenn nein |
|
||||
|---|---|
|
||||
| Beginnt jede Balkenachse bei null? | Balken kodieren über Länge — abgeschnitten lügen sie. Bei engen Wertebändern Punktdiagramm statt Balken |
|
||||
| Hat jede Achse runde Werte (0/50/100, nicht 0/55/110)? | Der Leser rechnet bei jedem Blick mit |
|
||||
| Trägt eine Achse genau eine Einheit? | „0 €" neben „10 T€" ist ein Einheitenwechsel mitten in der Skala |
|
||||
| Belegt in einem Ring kein Segment mehr als etwa 80 %? | Dann ist die Bezugsgröße falsch gewählt, nicht der Diagrammtyp |
|
||||
| Hat der Ring höchstens fünf Segmente? | Alles darüber gehört zu „Sonstige" zusammengefasst |
|
||||
| Zeigt jede Legende eine Reihe, die es im Bild wirklich gibt? | Tote Legendeneinträge sind ein häufiger und peinlicher Fehler |
|
||||
| Sind Kategorie- und Ampelfarben getrennt? | Eine grüne Kategorie neben einem grünen Positiv-Status ist nicht mehr lesbar |
|
||||
| Behält eine Kategorie ihre Farbe über alle Seiten? | Sonst muss jede Legende neu gelesen werden |
|
||||
| Ist bei jeder Karte erkennbar, ob sie den Zeitfilter beachtet? | Stichtagswerte neben gefilterten Werten ohne Kennzeichnung erzeugen falsche Schlüsse |
|
||||
|
||||
---
|
||||
|
||||
## 3. Zahlen und Text
|
||||
|
||||
- Tausendertrennung überall gleich? (`1.900`, nie `1900`)
|
||||
- Dieselbe Zahl im selben Format? (`177 T€` und `177.010 €` auf einer Seite ist ein Bruch)
|
||||
- Singular und Plural korrekt? („1 Tag", nicht „1 Tage")
|
||||
- Spaltenköpfe einheitlich geschrieben?
|
||||
- Bricht irgendwo Text ab, der nicht abbrechen dürfte? (Beträge, Kartenuntertitel)
|
||||
|
||||
---
|
||||
|
||||
## 4. Raster und Zustände
|
||||
|
||||
- Erzeugt eine Karte tote Fläche, weil die Nachbarkarte höher ist?
|
||||
Gleiche Höhe ist nur bei gleicher Struktur richtig (Kennzahlenreihe), nicht bei
|
||||
Diagramm neben Tabelle.
|
||||
- Was passiert, wenn die Filter alles wegschneiden? Jede Ansicht braucht einen
|
||||
Leerzustand.
|
||||
- Ist der Tastaturfokus sichtbar? Mit Tab durch die Seite gehen.
|
||||
- Schließt jedes Menü mit Escape?
|
||||
- Verspricht ein Hover-Effekt eine Interaktion, die es nicht gibt?
|
||||
|
||||
---
|
||||
|
||||
## 5. Womit man das Auge trainiert
|
||||
|
||||
Kurz und ergiebig, in dieser Reihenfolge:
|
||||
|
||||
1. **FT Visual Vocabulary** — ein Poster, das Diagrammtypen nach Aussageabsicht
|
||||
ordnet (Vergleich, Verteilung, Anteil, Veränderung …).
|
||||
Das beste Verhältnis von Aufwand zu Ertrag überhaupt.
|
||||
`github.com/Financial-Times/chart-doctor` → `visual-vocabulary`
|
||||
2. **IBCS — International Business Communication Standards**, die SUCCESS-Regeln.
|
||||
Das ist der ausgearbeitete Standard für *Geschäfts*grafik, den Carbon nicht
|
||||
ersetzt: einheitliche Notation, Ist/Plan/Vorjahr, Informationsdichte.
|
||||
Frei unter CC BY-SA auf `ibcs.com`.
|
||||
3. **Cleveland & McGill (1984)**, „Graphical Perception". Die Studie, aus der die
|
||||
Rangfolge der Kodierungen stammt: Position wird genauer gelesen als Länge,
|
||||
Länge genauer als Winkel, Winkel genauer als Fläche. Erklärt in einer Seite,
|
||||
warum Ringe schwach und Punktdiagramme stark sind.
|
||||
4. **Stephen Few, „Information Dashboard Design"**. Das Standardwerk zur
|
||||
Dashboard-Kritik; die ersten beiden Kapitel reichen für ein geschultes Auge.
|
||||
5. **Datawrapper Academy** (`academy.datawrapper.de`) — kurze, gut belegte
|
||||
Artikel zu einzelnen Fragen („Wann Balken, wann Linien", „Warum keine zwei
|
||||
Y-Achsen").
|
||||
|
||||
Zum Zielsystem passend:
|
||||
|
||||
6. **Carbon Data Visualization** (`carbondesignsystem.com/data-visualization/`)
|
||||
für Farben, Achsen und Diagrammtypen — mit der Einschränkung aus
|
||||
[DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) Abschnitt 0: Abstände regelt Carbon nicht.
|
||||
7. **Carbon Spacing und 2x-Grid** (`carbondesignsystem.com/elements/spacing/`)
|
||||
für genau diese Abstände.
|
||||
|
||||
---
|
||||
|
||||
## 6. Was hier bewusst nicht geprüft wird
|
||||
|
||||
Layout und Anordnung sind im Zielsystem **Entscheidung des Designers**
|
||||
(siehe [LEITPLANKEN.md](LEITPLANKEN.md)). Verbindlich zu übergeben sind
|
||||
Kennzahl-Definitionen, Diagrammregeln und Verhalten. Wer diese Liste im Review
|
||||
benutzt, prüft die Substanz — nicht, ob eine Karte 40 px weiter links sitzt.
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"$kommentar": [
|
||||
"Kanonische IBM-Carbon-Werte, gegen die dieses Dashboard geprüft wird.",
|
||||
"Quelle ist NICHT die Dokumentation, sondern das ausgelieferte Stylesheet und die",
|
||||
"Default-Konfiguration von @carbon/charts — Doku und Code weichen voneinander ab.",
|
||||
"Geprüft von scripts/verify-carbon.mjs (npm run verify:carbon).",
|
||||
"Beim Aktualisieren: Werte neu aus der Quelle ziehen, nicht abtippen."
|
||||
],
|
||||
"quelle": {
|
||||
"stylesheet": "https://cdn.jsdelivr.net/npm/@carbon/charts@1.23.0/styles.min.css",
|
||||
"konfiguration": "https://github.com/carbon-design-system/carbon-charts/blob/master/packages/core/src/configuration.ts",
|
||||
"geprueftAm": "2026-08-29",
|
||||
"version": "@carbon/charts 1.23.0"
|
||||
},
|
||||
|
||||
"kategorialePalette": {
|
||||
"herkunft": "CSS-Variablen --cds-charts-14-1-1 bis --cds-charts-14-1-14",
|
||||
"regel": "Reihenfolge ist verbindlich, Farben immer von vorne vergeben",
|
||||
"werte": [
|
||||
"#6929c4",
|
||||
"#1192e8",
|
||||
"#005d5d",
|
||||
"#9f1853",
|
||||
"#fa4d56",
|
||||
"#520408",
|
||||
"#198038",
|
||||
"#002d9c",
|
||||
"#ee5396",
|
||||
"#b28600",
|
||||
"#009d9a",
|
||||
"#012749",
|
||||
"#8a3800",
|
||||
"#a56eff"
|
||||
]
|
||||
},
|
||||
|
||||
"neutraleWerte": {
|
||||
"gitterlinie": { "wert": "#e0e0e0", "token": "--cds-layer-accent-01" },
|
||||
"achsenlinie": { "wert": "#8d8d8d", "token": "--cds-border-strong-01" },
|
||||
"achsenbeschriftung": { "wert": "#525252", "token": "--cds-text-secondary" },
|
||||
"textPrimaer": { "wert": "#161616", "token": "--cds-text-primary" }
|
||||
},
|
||||
|
||||
"tooltip": {
|
||||
"herkunft": ".cds--cc--tooltip und .content-box im Stylesheet",
|
||||
"hintergrund": { "wert": "#ffffff", "token": "--cds-layer-02" },
|
||||
"text": { "wert": "#161616", "token": "--cds-text-primary" },
|
||||
"schatten": "0 1px 6px 0 rgba(0,0,0,.2)",
|
||||
"schriftgroessePx": 12,
|
||||
"maxBreitePx": 270,
|
||||
"hinweis": "Bis Carbon v10 war der Tooltip dunkel (#393939). Seit v11 ist er hell."
|
||||
},
|
||||
|
||||
"achsen": {
|
||||
"herkunft": "configuration.ts",
|
||||
"ticksYAchse": 5,
|
||||
"ticksXAchse": 15
|
||||
},
|
||||
|
||||
"bewusstAbweichend": {
|
||||
"$kommentar": "Werte, die wir kennen und absichtlich anders setzen. Der Prüfer meldet sie als Abweichung mit Begründung, nicht als Fehler.",
|
||||
"balkenMaxBreitePx": {
|
||||
"carbon": 16,
|
||||
"hier": 48,
|
||||
"grund": "Carbons 16 px sind auf dichte Mehrreihen-Charts ausgelegt. Unsere Balken zeigen vier bis sechs Kategorien in einer breiten Karte; 16 px wirken dort wie Striche. Die Regel 'Abstand ≥ halbe Balkenbreite' bleibt eingehalten."
|
||||
},
|
||||
"festeKategorieFarben": {
|
||||
"carbon": "n-Serien-Paletten (--cds-charts-2-*, -3-*, -4-*, -5-*) je nach Anzahl der Reihen",
|
||||
"hier": "feste Farbe je Gewerk und je Status über alle Charts hinweg",
|
||||
"grund": "Carbons n-Serien-Paletten optimieren den Kontrast innerhalb EINES Charts. Über mehrere Charts hinweg würde Heizung dadurch die Farbe wechseln. Wiedererkennbarkeit schlägt hier Binnenkontrast."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { CHART } from "../../utils/chartTheme";
|
||||
import { CHART, TOOLTIP_SCHATTEN } from "../../utils/chartTheme";
|
||||
|
||||
/**
|
||||
* Einheitlicher Tooltip für alle Charts.
|
||||
*
|
||||
* Carbon-Vorgabe: dunkle Fläche (#393939), heller Text, 12 px Label,
|
||||
* kein Rahmen, kein Doppelpunkt zwischen Name und Wert.
|
||||
* Ein gemeinsamer Tooltip verhindert, dass jedes Chart eigene Regeln erfindet.
|
||||
* Carbon-Vorgabe (@carbon/charts, .cds--cc--tooltip): helle Fläche
|
||||
* (--cds-layer-02), Text in --cds-text-primary, 12 px, abgesetzt allein durch
|
||||
* den Schatten — kein Rahmen, kein Doppelpunkt zwischen Name und Wert,
|
||||
* maximal 270 px breit.
|
||||
*
|
||||
* Bis Carbon v10 war der Tooltip dunkel (#393939). Viele Quellen im Netz
|
||||
* zeigen noch diesen Stand; maßgeblich ist das ausgelieferte Stylesheet.
|
||||
* Geprüft von scripts/verify-carbon.mjs.
|
||||
*
|
||||
* Die Props sind bewusst weit typisiert: Recharts reicht sein internes
|
||||
* Payload-Format durch, das je nach Chart-Typ leicht abweicht.
|
||||
@@ -49,25 +54,29 @@ export function ChartTooltip({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="pointer-events-none rounded-md px-3 py-2 shadow-lg"
|
||||
style={{ background: CHART.tooltipBg, color: CHART.tooltipFg }}
|
||||
className="pointer-events-none max-w-[270px] rounded-md px-3 py-2"
|
||||
style={{
|
||||
background: CHART.tooltipBg,
|
||||
color: CHART.tooltipFg,
|
||||
boxShadow: TOOLTIP_SCHATTEN,
|
||||
}}
|
||||
>
|
||||
{labelText ? (
|
||||
<div className="mb-1 text-[12px] opacity-80">
|
||||
<div className="mb-1 text-[12px]" style={{ color: CHART.textSekundaer }}>
|
||||
{labelPrefix ? `${labelPrefix} ` : ""}
|
||||
{labelText}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{reihen.map((reihe) => (
|
||||
<div key={reihe.name} className="flex items-center gap-2 text-[13px]">
|
||||
<div key={reihe.name} className="flex items-center gap-2 text-[12px]">
|
||||
{reihe.color ? (
|
||||
<span
|
||||
className="h-2.5 w-2.5 flex-none rounded-sm"
|
||||
style={{ background: reihe.color }}
|
||||
/>
|
||||
) : null}
|
||||
<span className="opacity-80">{reihe.name}</span>
|
||||
<span style={{ color: CHART.textSekundaer }}>{reihe.name}</span>
|
||||
<span className="ml-auto font-semibold tabular-nums">{formatWert(reihe.value)}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Achse #8d8d8d (--cds-border-strong-01)
|
||||
* Text primär #161616 (--cds-text-primary)
|
||||
* Text sek. #525252 (--cds-text-secondary)
|
||||
* Tooltip #393939 (--cds-background-inverse) auf #ffffff Text
|
||||
* Tooltip #ffffff (--cds-layer-02) auf #161616 Text, Schatten statt Rahmen
|
||||
*
|
||||
* Warum kein bunter "Marketing"-Farbsatz: Die Carbon-Palette ist auf
|
||||
* Unterscheidbarkeit und Kontrast optimiert und in dieser Reihenfolge
|
||||
@@ -148,10 +148,16 @@ export const CHART = {
|
||||
achse: "#8d8d8d",
|
||||
text: "#161616",
|
||||
textSekundaer: "#525252",
|
||||
tooltipBg: "#393939",
|
||||
tooltipFg: "#ffffff",
|
||||
// Seit Carbon v11 ist der Tooltip hell: --cds-layer-02 auf --cds-text-primary,
|
||||
// getrennt vom Untergrund allein durch den Schatten. Bis v10 war er dunkel
|
||||
// (#393939) — diese Angabe steht in vielen älteren Quellen noch so.
|
||||
tooltipBg: "#ffffff",
|
||||
tooltipFg: "#161616",
|
||||
} as const;
|
||||
|
||||
/** Tooltip-Schatten laut @carbon/charts (.cds--cc--tooltip). */
|
||||
export const TOOLTIP_SCHATTEN = "0 1px 6px 0 rgba(0,0,0,.2)";
|
||||
|
||||
/**
|
||||
* Chart-Innenabstände.
|
||||
* Carbon rechnet Achsenbeschriftungen in den Chart-Rahmen ein; die Karte selbst
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@
|
||||
"dev": "next dev -p 4300",
|
||||
"build": "next build",
|
||||
"start": "next start -p 4300",
|
||||
"lint": "eslint"
|
||||
"lint": "eslint",
|
||||
"verify:carbon": "node scripts/verify-carbon.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@subframe/core": "^1.155.0",
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Prüft chartTheme.ts gegen docs/carbon-referenz.json.
|
||||
*
|
||||
* Warum das existiert: „Wir halten uns an IBM Carbon" ist eine Behauptung,
|
||||
* solange sie niemand nachrechnet. Farbwerte driften beim Kopieren, und die
|
||||
* Carbon-Dokumentation widerspricht stellenweise dem ausgelieferten Code.
|
||||
* Dieser Prüfer macht aus der Behauptung eine Zusage.
|
||||
*
|
||||
* Aufruf: npm run verify:carbon
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const wurzel = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const referenz = JSON.parse(readFileSync(join(wurzel, "docs/carbon-referenz.json"), "utf8"));
|
||||
const theme = readFileSync(join(wurzel, "features/dashboard/utils/chartTheme.ts"), "utf8");
|
||||
|
||||
const fehler = [];
|
||||
const hinweise = [];
|
||||
|
||||
/** Liest ein exportiertes Array von Hex-Werten aus dem Theme. */
|
||||
function hexArray(name) {
|
||||
const block = theme.match(new RegExp(`export const ${name}[^=]*=\\s*\\[([\\s\\S]*?)\\]`));
|
||||
if (!block) return null;
|
||||
return [...block[1].matchAll(/#[0-9a-fA-F]{6}/g)].map((m) => m[0].toLowerCase());
|
||||
}
|
||||
|
||||
/** Liest einen einzelnen Hex-Wert aus einem Objektfeld im Theme. */
|
||||
function hexFeld(objekt, feld) {
|
||||
const block = theme.match(new RegExp(`export const ${objekt}[^=]*=\\s*\\{([\\s\\S]*?)\\}`));
|
||||
if (!block) return null;
|
||||
const treffer = block[1].match(new RegExp(`${feld}\\s*:\\s*"(#[0-9a-fA-F]{6})"`));
|
||||
return treffer ? treffer[1].toLowerCase() : null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- Kategoriale Palette */
|
||||
|
||||
const palette = hexArray("KATEGORIE_FARBEN");
|
||||
const soll = referenz.kategorialePalette.werte.map((w) => w.toLowerCase());
|
||||
|
||||
if (!palette) {
|
||||
fehler.push("KATEGORIE_FARBEN nicht gefunden");
|
||||
} else if (palette.length !== soll.length) {
|
||||
fehler.push(`Palette hat ${palette.length} Farben, Carbon hat ${soll.length}`);
|
||||
} else {
|
||||
palette.forEach((farbe, i) => {
|
||||
if (farbe !== soll[i]) {
|
||||
fehler.push(`Palette Position ${i + 1}: ${farbe} — Carbon: ${soll[i]}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------- Neutrale Werte */
|
||||
|
||||
const neutraleZuordnung = {
|
||||
grid: "gitterlinie",
|
||||
achse: "achsenlinie",
|
||||
textSekundaer: "achsenbeschriftung",
|
||||
text: "textPrimaer",
|
||||
};
|
||||
|
||||
for (const [feld, refName] of Object.entries(neutraleZuordnung)) {
|
||||
const ist = hexFeld("CHART", feld);
|
||||
const sollWert = referenz.neutraleWerte[refName].wert.toLowerCase();
|
||||
if (ist === null) fehler.push(`CHART.${feld} nicht gefunden`);
|
||||
else if (ist !== sollWert) {
|
||||
fehler.push(
|
||||
`CHART.${feld} = ${ist} — Carbon ${referenz.neutraleWerte[refName].token}: ${sollWert}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- Tooltip */
|
||||
|
||||
const tooltipBg = hexFeld("CHART", "tooltipBg");
|
||||
const tooltipFg = hexFeld("CHART", "tooltipFg");
|
||||
if (tooltipBg !== referenz.tooltip.hintergrund.wert.toLowerCase()) {
|
||||
fehler.push(
|
||||
`CHART.tooltipBg = ${tooltipBg} — Carbon ${referenz.tooltip.hintergrund.token}: ${referenz.tooltip.hintergrund.wert}`
|
||||
);
|
||||
}
|
||||
if (tooltipFg !== referenz.tooltip.text.wert.toLowerCase()) {
|
||||
fehler.push(`CHART.tooltipFg = ${tooltipFg} — Carbon: ${referenz.tooltip.text.wert}`);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------- Achsen-Tickanzahl */
|
||||
|
||||
const raster = theme.match(/nettesRaster\(\s*\n?\s*max: number,\s*\n?\s*maxIntervalle = (\d+)/);
|
||||
if (raster && Number(raster[1]) !== referenz.achsen.ticksYAchse) {
|
||||
fehler.push(
|
||||
`nettesRaster maxIntervalle = ${raster[1]} — Carbon numberOfTicks (y): ${referenz.achsen.ticksYAchse}`
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- Bewusste Abweichungen melden */
|
||||
|
||||
const balken = theme.match(/maxBreite:\s*(\d+)/);
|
||||
const abwBalken = referenz.bewusstAbweichend.balkenMaxBreitePx;
|
||||
if (balken) {
|
||||
if (Number(balken[1]) !== abwBalken.hier) {
|
||||
fehler.push(
|
||||
`BALKEN.maxBreite = ${balken[1]}, dokumentiert ist ${abwBalken.hier}. ` +
|
||||
`Entweder den Wert zurücksetzen oder docs/carbon-referenz.json nachziehen.`
|
||||
);
|
||||
} else {
|
||||
hinweises(`Balkenbreite ${abwBalken.hier} px statt Carbon ${abwBalken.carbon} px`, abwBalken.grund);
|
||||
}
|
||||
}
|
||||
function hinweises(titel, grund) {
|
||||
hinweise.push({ titel, grund });
|
||||
}
|
||||
hinweises(
|
||||
"Feste Farbe je Kategorie statt n-Serien-Palette",
|
||||
referenz.bewusstAbweichend.festeKategorieFarben.grund
|
||||
);
|
||||
|
||||
/* ------------------------------------------------------------- Ausgabe */
|
||||
|
||||
console.log(`Carbon-Abgleich gegen ${referenz.quelle.version} (${referenz.quelle.geprueftAm})\n`);
|
||||
|
||||
if (hinweise.length) {
|
||||
console.log("Bewusste Abweichungen (dokumentiert, kein Fehler):");
|
||||
for (const h of hinweise) console.log(` · ${h.titel}\n ${h.grund}\n`);
|
||||
}
|
||||
|
||||
if (fehler.length) {
|
||||
console.error(`${fehler.length} Abweichung(en) von Carbon:`);
|
||||
for (const f of fehler) console.error(` ✗ ${f}`);
|
||||
console.error("\nEntweder chartTheme.ts korrigieren oder die Abweichung in");
|
||||
console.error("docs/carbon-referenz.json unter 'bewusstAbweichend' begründen.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("✓ Palette, neutrale Werte, Tooltip und Achsenraster stimmen mit Carbon überein.");
|
||||
Reference in new issue
Block a user