467 Stellen. Nicht durch einen anderen Strich ersetzt, sondern durch das
Satzzeichen, das an der jeweiligen Stelle richtig ist: Doppelpunkt, wo eine
Erlaeuterung folgt; Komma, wo der Satz weiterlaeuft; Punkt, wo zwei
Hauptsaetze aneinanderstiessen. Ein Halbgeviertstrich waere nur derselbe
Griff mit kuerzerem Strich gewesen.
Regelbasiert ersetzt und danach nachgeprueft, weil eine Regel allein nicht
reicht. Gefunden und einzeln korrigiert:
- Eingeschobene Saetze mit Strichpaar. Nur der oeffnende Strich passte ins
Muster, der schliessende blieb als "—," stehen. Drei Stellen, jetzt mit
Klammern oder als eigener Satz.
- Doppelte Doppelpunkte, wo beide Striche eines Paars zu Doppelpunkten wurden
("Globale Regeln: Schrift, Groessen, Abstaende: kommen aus HiveGrid").
Sieben Stellen in Doku und Oberflaeche, jetzt in Klammern.
- Pfade, denen ein Komma statt eines Doppelpunkts folgte.
- Ausgerichtete Kommentartabellen, in denen der Strich eine Spalte war.
Nicht angefasst: drei Geviertstriche, die keine Prosa sind, sondern
Tabellenplatzhalter fuer "kein Wert" (Trichter, Auftraege, Kunden). Dort ist
der Strich die Angabe selbst; ihn zu entfernen liesse leere Zellen zurueck.
tsc, lint, build, verify:carbon und verify:layout laufen sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
137 lines
4.1 KiB
TypeScript
137 lines
4.1 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import {
|
|
Area,
|
|
CartesianGrid,
|
|
ComposedChart,
|
|
Line,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
XAxis,
|
|
YAxis,
|
|
} from "recharts";
|
|
import { ChartTooltip } from "./ChartTooltip";
|
|
import { ChartLeer } from "./ChartLeer";
|
|
import {
|
|
CHART,
|
|
MARGIN,
|
|
MIN_TICK_ABSTAND,
|
|
PRIMAER_FARBE,
|
|
VERGLEICH_FARBE,
|
|
achseX,
|
|
achseY,
|
|
achsenIntervall,
|
|
nettesRaster,
|
|
} from "../../utils/chartTheme";
|
|
import type { ZeitreihePunkt } from "../../data/types";
|
|
|
|
/**
|
|
* Zeitverlauf als Fläche mit optionaler Vergleichslinie (Vorjahr).
|
|
*
|
|
* Bewusst `ComposedChart`, nicht `AreaChart`: Recharts sammelt in einem
|
|
* AreaChart ausschließlich `Area`-Kinder ein (generateCategoricalChart mit
|
|
* GraphicalChild = Area). Ein `<Line>` darin wird stillschweigend verworfen,
|
|
* die Vergleichsreihe stand monatelang in der Legende, ohne je gezeichnet zu
|
|
* werden. ComposedChart nimmt beide Typen.
|
|
*
|
|
* Carbon-Regeln, die hier umgesetzt sind:
|
|
* - nur horizontale Gridlines (#e0e0e0), vertikale lenken ab
|
|
* - keine Tick-Marks, Achsenbeschriftung 12 px in #525252
|
|
* - lineare Interpolation: eine Monotone-Kurve erfindet Zwischenwerte
|
|
* zwischen zwei Messpunkten, die es nicht gibt
|
|
* - Vergleichsreihe gestrichelt und neutral, damit sie die Hauptreihe
|
|
* nicht um Aufmerksamkeit bringt
|
|
*/
|
|
export function AreaVerlauf({
|
|
daten,
|
|
formatWert,
|
|
/** Kürzere Schreibweise für die Y-Achse; ohne Angabe wie formatWert. */
|
|
formatAchse,
|
|
hoehe = 260,
|
|
zeigeVergleich = false,
|
|
farbe = PRIMAER_FARBE,
|
|
labelPrefix,
|
|
}: {
|
|
daten: ZeitreihePunkt[];
|
|
formatWert: (wert: number) => string;
|
|
formatAchse?: (wert: number) => string;
|
|
hoehe?: number;
|
|
zeigeVergleich?: boolean;
|
|
farbe?: string;
|
|
labelPrefix?: string;
|
|
}) {
|
|
// Zwei Verläufe auf einer Seite dürfen sich keinen Gradienten teilen.
|
|
const gradientId = `verlauf-${React.useId().replace(/:/g, "")}`;
|
|
|
|
const hatWerte = daten.some((d) => d.value > 0 || (d.vergleich ?? 0) > 0);
|
|
if (!daten.length || !hatWerte) return <ChartLeer hoehe={hoehe} />;
|
|
|
|
// Ausdünnen der Zeitachse: siehe achsenIntervall() in chartTheme.ts.
|
|
const intervall = achsenIntervall(daten.length);
|
|
|
|
const maximum = Math.max(
|
|
...daten.map((d) => Math.max(d.value, zeigeVergleich ? (d.vergleich ?? 0) : 0))
|
|
);
|
|
const raster = nettesRaster(maximum);
|
|
|
|
return (
|
|
<div style={{ height: hoehe }}>
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={daten} margin={MARGIN.standard}>
|
|
<defs>
|
|
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor={farbe} stopOpacity={0.24} />
|
|
<stop offset="100%" stopColor={farbe} stopOpacity={0.02} />
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
<CartesianGrid stroke={CHART.grid} vertical={false} />
|
|
<XAxis
|
|
dataKey="label"
|
|
interval={intervall}
|
|
minTickGap={MIN_TICK_ABSTAND}
|
|
{...achseX}
|
|
/>
|
|
<YAxis
|
|
domain={raster.domain}
|
|
ticks={raster.ticks}
|
|
tickFormatter={formatAchse ?? formatWert}
|
|
{...achseY}
|
|
/>
|
|
<Tooltip
|
|
cursor={{ stroke: CHART.achse, strokeWidth: 1, strokeDasharray: "3 3" }}
|
|
content={(props) => (
|
|
<ChartTooltip {...props} formatWert={formatWert} labelPrefix={labelPrefix} />
|
|
)}
|
|
/>
|
|
|
|
<Area
|
|
type="linear"
|
|
dataKey="value"
|
|
name="Aktueller Zeitraum"
|
|
stroke={farbe}
|
|
strokeWidth={2}
|
|
fill={`url(#${gradientId})`}
|
|
dot={false}
|
|
activeDot={{ r: 4, fill: farbe, stroke: "#fff", strokeWidth: 2 }}
|
|
/>
|
|
|
|
{zeigeVergleich ? (
|
|
<Line
|
|
type="linear"
|
|
dataKey="vergleich"
|
|
name="Vorjahr"
|
|
stroke={VERGLEICH_FARBE}
|
|
strokeWidth={1.5}
|
|
strokeDasharray="4 4"
|
|
dot={false}
|
|
activeDot={{ r: 4, fill: VERGLEICH_FARBE, stroke: "#fff", strokeWidth: 2 }}
|
|
/>
|
|
) : null}
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
}
|