Ring beantwortet das Ueberfahren in seiner Mitte statt im Tooltip
Der Tooltip legte sich ueber die Ringmitte: "Klima & Lueftung 268,0 T€" und "GESAMT 2,27 Mio. €" uebereinander, beides unlesbar. Ein Ring von 168 px hat keinen Platz fuer ein schwebendes Feld daneben, egal wohin man es schiebt. Der Tooltip faellt deshalb ganz weg. Beim Ueberfahren zeigt die Mitte das beruehrte Segment mit Name, Wert und Anteil, im Ruhezustand weiter den Gesamtwert. Die Flaeche ist ohnehin belegt und die einzige, die gross genug ist; ausserdem wiederholte der Tooltip nur, was die Legende daneben schon zeigt. Das ist auch die Carbon-Loesung. Damit das Ueberfahren des Rings selbst wirkt (bisher setzte nur die Legende den Zustand), liegen die Handler jetzt auch am Pie. Zur Breite der Beschriftung: erst am Container bemessen, dann an der Hoehe, beides falsch. Der Ring ist so gross wie die **kleinere** der beiden Seiten. Jetzt begrenzt min() beide. Der Wert ist nachgemessen statt geschaetzt: Bei 200 px Ringhoehe ist der Innenkreis 124 px breit und "KLIMA & LÜFTUNG" in einer Zeile 109 px. Das passt rechnerisch und sieht trotzdem gedraengt aus, weil der Innenkreis ein Kreis ist und die Beschriftung ueber der Mittellinie steht, wo er schmaler wird. Bei 48 % bricht der Name auf zwei Zeilen um und bekommt Luft. Das Abblenden der uebrigen Segmente bleibt. Im Trichter war dasselbe Mittel falsch, hier ist es richtig, und der Unterschied steht als Kommentar dabei: Dort kodiert die Balkenlaenge eine Menge, und Deckkraft liest sich als "weniger". Hier kodiert die Farbe nur eine Kategorie. 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
1896d2772b
commit
f5a3ccd634
1 file changed
+48
-9
@@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
|
||||
import { ChartTooltip } from "./ChartTooltip";
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts";
|
||||
import { ChartLeer } from "./ChartLeer";
|
||||
import { DONUT, farbeFuer, gruppiereRest } from "../../utils/chartTheme";
|
||||
|
||||
@@ -19,6 +18,15 @@ import { DONUT, farbeFuer, gruppiereRest } from "../../utils/chartTheme";
|
||||
* eine Container-Query, weil dieselbe Karte mal ein Drittel und mal die halbe
|
||||
* Seitenbreite einnimmt, der Viewport weiß davon nichts.
|
||||
* - Klick auf einen Legendeneintrag hebt das Segment hervor
|
||||
*
|
||||
* **Kein Tooltip.** Der Ring beantwortet das Überfahren in seiner eigenen
|
||||
* Mitte: Statt Gesamtwert steht dort das berührte Segment mit Wert und Anteil.
|
||||
*
|
||||
* Ein schwebender Tooltip war hier zweimal falsch. Er legte sich über die
|
||||
* Mitte und machte beide Angaben unlesbar, weil ein Ring von 168 px keinen
|
||||
* Platz daneben hat. Und er wiederholte nur, was die Legende daneben schon
|
||||
* zeigt. Die Mitte ist im Ruhezustand ohnehin belegt und die einzige Fläche,
|
||||
* die groß genug ist.
|
||||
*/
|
||||
export function Donut({
|
||||
daten,
|
||||
@@ -47,6 +55,8 @@ export function Donut({
|
||||
if (!segmente.length) return <ChartLeer hoehe={hoehe} />;
|
||||
|
||||
const gesamt = segmente.reduce((summe, s) => summe + s.value, 0);
|
||||
const anteilVon = (wert: number) => (gesamt > 0 ? (wert / gesamt) * 100 : 0).toFixed(0);
|
||||
const hervorgehoben = segmente.find((s) => s.name === aktiv) ?? null;
|
||||
|
||||
return (
|
||||
<div className="@container">
|
||||
@@ -66,32 +76,61 @@ export function Donut({
|
||||
stroke="#ffffff"
|
||||
strokeWidth={2}
|
||||
isAnimationActive={false}
|
||||
onMouseEnter={(eintrag: { name?: string }) => setAktiv(eintrag?.name ?? null)}
|
||||
onMouseLeave={() => setAktiv(null)}
|
||||
>
|
||||
{segmente.map((segment, index) => (
|
||||
<Cell
|
||||
key={segment.name}
|
||||
fill={farbeFuer(segment.name, index)}
|
||||
/* Abblenden der übrigen Segmente ist hier richtig, im
|
||||
Trichter war es falsch. Der Unterschied: Dort kodiert die
|
||||
Balkenlänge eine Menge, und Deckkraft liest sich in der
|
||||
Sprache dieses Diagramms als „weniger". Hier kodiert die
|
||||
Farbe nur eine Kategorie, sie trägt keine Größe. Und der
|
||||
Ring hat keine Zeile, die man stattdessen hinterlegen
|
||||
könnte: Das Segment ist das Element. */
|
||||
opacity={aktiv && aktiv !== segment.name ? 0.35 : 1}
|
||||
/>
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip content={(props) => <ChartTooltip {...props} formatWert={formatWert} />} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
|
||||
<span className="text-[11px] uppercase tracking-[0.06em] text-text-neutral-tertiary">
|
||||
{mitteLabel}
|
||||
{/* Die Textbreite muss am Ringdurchmesser hängen, nicht am Container.
|
||||
Der Ring ist so groß wie die **kleinere** der beiden Seiten, und die
|
||||
Karte kann sehr viel breiter als hoch sein. Deshalb begrenzt
|
||||
`min()` beides: 56 % der Breite und 56 % der bekannten Höhe.
|
||||
|
||||
Warum 48 % und nicht 62 % wie der Innenradius: Nachgemessen ist der
|
||||
Innenkreis bei 200 px Ringhöhe 124 px breit, „KLIMA & LÜFTUNG" in
|
||||
einer Zeile 109 px. Das passt rechnerisch und sieht trotzdem
|
||||
gedrängt aus, weil der Innenkreis ein Kreis ist und die
|
||||
Beschriftung über der Mittellinie steht, wo er schmaler wird. Bei
|
||||
48 % bricht der Name auf zwei Zeilen um und bekommt Luft. */}
|
||||
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center">
|
||||
<span
|
||||
className="break-words text-[11px] uppercase leading-tight tracking-[0.06em] text-text-neutral-tertiary"
|
||||
style={{ maxWidth: `min(48%, ${Math.round(hoehe * 0.48)}px)` }}
|
||||
>
|
||||
{hervorgehoben ? hervorgehoben.name : mitteLabel}
|
||||
</span>
|
||||
<span className="text-[15px] font-semibold tabular-nums text-text-neutral-primary">
|
||||
{(formatMitte ?? formatWert)(gesamt)}
|
||||
{hervorgehoben
|
||||
? formatWert(hervorgehoben.value)
|
||||
: (formatMitte ?? formatWert)(gesamt)}
|
||||
</span>
|
||||
{hervorgehoben ? (
|
||||
<span className="text-[11px] tabular-nums text-text-neutral-tertiary">
|
||||
{anteilVon(hervorgehoben.value)} %
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="flex w-full flex-col gap-1.5">
|
||||
{segmente.map((segment, index) => {
|
||||
const anteil = gesamt > 0 ? (segment.value / gesamt) * 100 : 0;
|
||||
const anteil = anteilVon(segment.value);
|
||||
return (
|
||||
<li key={segment.name}>
|
||||
<button
|
||||
@@ -110,7 +149,7 @@ export function Donut({
|
||||
<span className="flex-none whitespace-nowrap text-[12px] tabular-nums text-text-neutral-primary">
|
||||
<span className="font-semibold">{formatWert(segment.value)}</span>
|
||||
<span className="ml-1.5 text-text-neutral-tertiary">
|
||||
{anteil.toFixed(0)} %
|
||||
{anteil} %
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
Reference in new issue
Block a user