FEAT-7: Statistik page - Wuerfe/Jahr + avg Wurfstaerke bars, Farbschlag h-bars (FEAT-4 chip colors), Bestandsentwicklung line, Verluste/Jahr + /statistik route & nav entry (Mehr overflow follows via Kevin)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,9 @@ const navItems = [
|
||||
{ to: '/becken', label: de.nav.enclosures, icon: '🛁', end: false },
|
||||
{ to: '/kontakte', label: de.nav.contacts, icon: '📇', end: false },
|
||||
{ to: '/genetik', label: de.nav.genetics, icon: '🧬', end: false },
|
||||
// FEAT-7: vorerst 7. Tab; „Mehr“-Overflow für das Smartphone ist mit Kevin
|
||||
// abgestimmt (FEAT-7-Konversation) und ersetzt diesen Eintrag dann.
|
||||
{ to: '/statistik', label: de.nav.statistics, icon: '📊', end: false },
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
28
gerbil-manager-web/src/components/BarChart.css
Normal file
28
gerbil-manager-web/src/components/BarChart.css
Normal file
@@ -0,0 +1,28 @@
|
||||
/* FEAT-7: Stile des SVG-Balkendiagramms (BarChart.tsx). */
|
||||
|
||||
.bar-chart {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bar-chart__axis {
|
||||
stroke: var(--color-border);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.bar-chart__bar {
|
||||
fill: var(--color-accent);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.bar-chart__value {
|
||||
font-size: 12px;
|
||||
fill: var(--color-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bar-chart__label {
|
||||
font-size: 12px;
|
||||
fill: var(--color-text-muted);
|
||||
}
|
||||
79
gerbil-manager-web/src/components/BarChart.tsx
Normal file
79
gerbil-manager-web/src/components/BarChart.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* FEAT-7: Schlichtes, responsives SVG-Balkendiagramm (eine Serie) —
|
||||
* Geschwister von LineChart (FEAT-6), gleiche Konventionen: kein
|
||||
* Chart-Paket, viewBox-skaliert, __axis/__grid/__label-Klassen.
|
||||
* Eigene CSS-Datei (Standing-Rule-2-Muster: seitennahe Stile kapseln).
|
||||
*/
|
||||
import './BarChart.css'
|
||||
|
||||
export interface Bar {
|
||||
label: string
|
||||
value: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
bars: Bar[]
|
||||
/** Zugänglicher Titel des Diagramms. */
|
||||
title: string
|
||||
/** Nachkommastellen der Wertbeschriftung (Standard 0). */
|
||||
decimals?: number
|
||||
}
|
||||
|
||||
const W = 600
|
||||
const H = 240
|
||||
const PAD = { top: 24, right: 12, bottom: 28, left: 12 }
|
||||
|
||||
export default function BarChart({ bars, title, decimals = 0 }: Props) {
|
||||
const innerW = W - PAD.left - PAD.right
|
||||
const innerH = H - PAD.top - PAD.bottom
|
||||
const max = Math.max(...bars.map((b) => b.value), 1)
|
||||
const slot = innerW / bars.length
|
||||
const barW = Math.min(56, slot * 0.6)
|
||||
|
||||
const format = (v: number) =>
|
||||
v.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: decimals })
|
||||
|
||||
return (
|
||||
<svg
|
||||
className="bar-chart"
|
||||
viewBox={`0 0 ${W} ${H}`}
|
||||
role="img"
|
||||
aria-label={title}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<line
|
||||
x1={PAD.left}
|
||||
y1={H - PAD.bottom}
|
||||
x2={W - PAD.right}
|
||||
y2={H - PAD.bottom}
|
||||
className="bar-chart__axis"
|
||||
/>
|
||||
{bars.map((bar, i) => {
|
||||
const h = (bar.value / max) * innerH
|
||||
const x = PAD.left + i * slot + (slot - barW) / 2
|
||||
const y = H - PAD.bottom - h
|
||||
return (
|
||||
<g key={bar.label}>
|
||||
<rect x={x} y={y} width={barW} height={h} rx={3} className="bar-chart__bar" />
|
||||
<text
|
||||
x={x + barW / 2}
|
||||
y={y - 6}
|
||||
textAnchor="middle"
|
||||
className="bar-chart__value"
|
||||
>
|
||||
{format(bar.value)}
|
||||
</text>
|
||||
<text
|
||||
x={x + barW / 2}
|
||||
y={H - PAD.bottom + 18}
|
||||
textAnchor="middle"
|
||||
className="bar-chart__label"
|
||||
>
|
||||
{bar.label}
|
||||
</text>
|
||||
</g>
|
||||
)
|
||||
})}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user