import { useRef, useState } from "react";
import { fmt } from "../../units";
import { tokenFarbe } from "../palette";
import useGroesse from "./useGroesse";
/**
* Wasserfall- bzw. Brückendiagramm.
*
* Die Linie zeigt den Bestand, die Balken erklären jeden Sprung darin: grün nach
* oben für Einlagerungen, rot nach unten für Auslagerungen. Ein Balken beginnt
* dort, wo der vorherige Bestand stand – deshalb „Brücke“.
*
* Bewusst nur EINE Werteachse. Balken und Linie sind hier beide Artikeleinheiten,
* nur deshalb dürfen sie überhaupt zusammen in ein Bild. Wären es Vorgänge gegen
* Mengen, gehörten sie in zwei Karten.
*
* abschnitte: [{ label, opening, checked_in, checked_out }]
*/
export default function Wasserfall({ abschnitte = [], einheit = "" }) {
const plot = useRef(null);
const { breite, hoehe } = useGroesse(plot);
const [index, setIndex] = useState(null);
if (abschnitte.length < 2) {
return
Keine Daten vorhanden.
;
}
const gruen = tokenFarbe("--ok", "#2e7d55");
const rot = tokenFarbe("--danger", "#c0392b");
const linie = tokenFarbe("--accent", "#2a78d6");
const gitterFarbe = tokenFarbe("--border", "#ddd");
const textFarbe = tokenFarbe("--muted", "#888");
const flaeche = tokenFarbe("--surface", "#fff");
// Ein Abschnitt läuft von opening über die Zwischenspitze zum Endbestand.
const punkte = abschnitte.map((a) => {
const spitze = a.opening + a.checked_in;
return { ...a, spitze, schluss: spitze - a.checked_out };
});
const rand = { oben: 12, rechts: 12, unten: 24, links: 44 };
const w = Math.max(breite, 240);
const h = Math.max(hoehe, 120);
const flaecheB = Math.max(w - rand.links - rand.rechts, 10);
const flaecheH = Math.max(h - rand.oben - rand.unten, 10);
// Von null aus: Bei Mengen ist eine abgeschnittene Achse irreführend.
const maxWert = Math.max(...punkte.map((p) => Math.max(p.opening, p.spitze, p.schluss)), 1);
const skalaMax = maxWert * 1.1;
const schlitz = flaecheB / punkte.length;
const mitte = (i) => rand.links + schlitz * (i + 0.5);
const y = (wert) => rand.oben + flaecheH - (wert / skalaMax) * flaecheH;
const balkenB = Math.max(Math.min(schlitz - 2, 18), 1);
const gitter = [0, 0.5, 1].map((teil) => ({ wert: skalaMax * teil, y: y(skalaMax * teil) }));
const schritt = Math.max(1, Math.round(punkte.length / 5));
const achse = punkte.map((p, i) => ({ l: p.label, i })).filter(({ i }) => i % schritt === 0);
/** Segment eines Balkens. Unter zwei Pixeln Höhe bliebe es unsichtbar –
* dann lieber eine dünne Marke als gar nichts. */
function segment(von, bis, farbe, key) {
const oben = Math.min(y(von), y(bis));
const hoch = Math.max(Math.abs(y(von) - y(bis)), 2);
return ;
}
function bewegen(event) {
const box = event.currentTarget.getBoundingClientRect();
const rel = event.clientX - box.left - rand.links;
setIndex(Math.min(Math.max(Math.floor(rel / schlitz), 0), punkte.length - 1));
}
const aktiv = index != null ? punkte[index] : null;
return (