Balkendiagramm für React
Säulen und Balken mit gerundetem Datenende, 2 px Abstand in Flächenfarbe, Werten wo sie passen, Tooltips und Pfeiltasten.
Eine von 7 Components in Charts
Kategorien auf einen Blick vergleichen
Ein Balkendiagramm vergleicht Mengen über Kategorien, und Längen liest das Auge genauer als jede andere Form. Dieses zeichnet Säulen und Balken mit runden Enden und einer kleinen Lücke zwischen gestapelten Teilen, setzt Werte dorthin, wo sie passen, und geht mit den Pfeiltasten von Balken zu Balken.
„Einfach“ zeigt einen Wert je Kategorie, der Standard für Rangfolgen und Monatssummen. „Gruppiert“ stellt zwei oder drei Reihen nebeneinander, etwa Plan A gegen Plan B je Monat. „Gestapelt“ zeigt die Anteile jeder Summe, „Waagerecht“ legt die Balken quer für lange Namen wie Produkte oder Länder.
Lass die Werteachse bei null beginnen, ein Balken ab 80 lügt über seine Länge. Sortiere nach Wert, außer die Kategorien haben eine eigene Ordnung wie Monate. Nutze den Akzent für den Balken, auf den es ankommt, und einen ruhigen Ton für den Rest, wenn du eine Aussage treffen willst.
Variants
Details
Was Balkendiagramm mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Charts
- Bereich
- Elements
- Variants
- 4
- Version
- 1.0.0 · seit 1.0.0
- Datei
- components/ui/bar-chart.tsx
- Bringt mit
- utilskitchart
- npm-Pakete
- Keine
- Sprachen
- Texte kommen über Props
- Studio
- Im Code anpassen
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle BarChartProps aus components/ui/bar-chart.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von ChartA11y an.
dataPflichtDatum[]Rows, one per category, e.g. { month: '2026-03', online: 21400, store: 9800 }
xKeyPflichtstringKey of the category. "2026-03" and "2026-03-14" are read as dates and formatted per language.
seriesPflichtChartSeries[]One bar per series and category, in slot order (accent, grey, two accent tones)
formatoptionalIntl.NumberFormatOptionsValue format, e.g. { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }
xFormatoptionalIntl.DateTimeFormatOptionstooltipXFormatoptionalIntl.DateTimeFormatOptionsstackedoptionalbooleanSeveral series side by side (default) or stacked to a total
orientationoptional'vertical' | 'horizontal'vertical columns (default) or horizontal bars with the labels on the left
valueLabelsoptionalbooleanValue at the bar end. Single series and stacked totals, only where it fits.
highlightoptionalstringSingle series: the category (x value) that keeps the accent, the others step back
heightoptionalnumberVertical: plot height in px, axis labels included
rowHeightoptionalnumberHorizontal: height of one row in px
legendoptionalbooleanLegend above the plot. Default: for two series and more.
xLabeloptionalstringHeader of the first table column
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Kommt ohne Client-State aus. Der Server schreibt sie vollständig, sie ist auch ohne JavaScript lesbar.
Bewegung
- Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 1 Stellen im Code schalten Bewegung dann ab.
- Ohne Animationen, sie steht sofort ruhig da.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 144 Zeilen
'use client';
import { round } from '@/lib/kit';
import { CartesianFrame, barPath, numberFormatter, rampColor, seriesColor, seriesOpacity, textWidth, useIntlLocale, type ChartA11y, type ChartSeries, type Datum } from '@/components/ui/chart';
export interface BarChartProps extends ChartA11y {
/** Rows, one per category, e.g. { month: '2026-03', online: 21400, store: 9800 } */
data: Datum[];
/** Key of the category. "2026-03" and "2026-03-14" are read as dates and formatted per language. */
xKey: string;
/** One bar per series and category, in slot order (accent, grey, two accent tones) */
series: ChartSeries[];
/** Value format, e.g. { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 } */
format?: Intl.NumberFormatOptions;
xFormat?: Intl.DateTimeFormatOptions;
tooltipXFormat?: Intl.DateTimeFormatOptions;
/** Several series side by side (default) or stacked to a total */
stacked?: boolean;
/** vertical columns (default) or horizontal bars with the labels on the left */
orientation?: 'vertical' | 'horizontal';
/** Value at the bar end. Single series and stacked totals, only where it fits. */
valueLabels?: boolean;
/** Single series: the category (x value) that keeps the accent, the others step back */
highlight?: string;
/** Vertical: plot height in px, axis labels included */
height?: number;
/** Horizontal: height of one row in px */
rowHeight?: number;
/** Legend above the plot. Default: for two series and more. */
legend?: boolean;
/** Header of the first table column */
xLabel?: string;
className?: string;
}
const GAP = 2;
const MAX = 24;
/**
* Columns or bars: simple, grouped, stacked and horizontal.
* <BarChart data={rows} xKey="month" series={[{ key: 'online', label: 'Online' }, { key: 'store', label: 'Store' }]} stacked />
*/
export function BarChart({ data, xKey, series, format, xFormat, tooltipXFormat, stacked = false, orientation = 'vertical', valueLabels, highlight, height = 260, rowHeight = 40, legend, xLabel, label, summary, labels, animate, className }: BarChartProps) {
const intl = useIntlLocale();
const fmt = numberFormatter(intl, format);
const horizontal = orientation === 'horizontal';
const num = (d: Datum, k: string) => (typeof d[k] === 'number' ? (d[k] as number) : 0);
const totals = data.map((d) => series.reduce((a, s) => a + Math.max(0, num(d, s.key)), 0));
const all = data.flatMap((d) => series.map((s) => num(d, s.key)));
const extent: [number, number] = stacked ? [0, Math.max(0, ...totals)] : [Math.min(0, ...all), Math.max(0, ...all)];
const single = series.length === 1;
const tipValue = (i: number) => (stacked ? totals[i] : single ? num(data[i], series[0].key) : null);
const labelW = valueLabels ? Math.max(0, ...data.map((_, i) => { const v = tipValue(i); return v === null ? 0 : textWidth(fmt(v), 12); })) + 10 : 0;
const plotHeight = horizontal ? data.length * rowHeight + 30 : height;
const dim = (i: number, si: number) => (highlight !== undefined && single && String(data[i][xKey]) !== highlight ? rampColor(1) : seriesColor(si, series[si].color));
return (
<CartesianFrame
data={data}
xKey={xKey}
series={series}
format={format}
xFormat={xFormat}
tooltipXFormat={tooltipXFormat}
height={plotHeight}
scale="band"
horizontal={horizontal}
extent={extent}
legend={legend}
legendShape="rect"
padRight={horizontal ? labelW : 0}
padTop={!horizontal && valueLabels ? 12 : 0}
total={stacked}
xLabel={xLabel}
label={label}
summary={summary}
labels={labels}
animate={animate}
className={className}
marks={({ cat, band, val, animated, n, active, focus }) => {
const zero = val(0);
const thick = stacked || single ? Math.min(MAX, band * (horizontal ? 0.56 : 0.6)) : Math.min(MAX, (band * 0.72 - GAP * (series.length - 1)) / series.length);
const groupW = stacked || single ? thick : thick * series.length + GAP * (series.length - 1);
const delay = (i: number) => (animated ? ({ '--kitch-d': `${round((i / Math.max(1, n)) * 420, 0)}ms` } as React.CSSProperties) : undefined);
return data.map((d, i) => {
const c = cat(i);
const start = round(c - groupW / 2);
const faded = active !== null && active !== i;
let marks: React.ReactNode;
if (stacked) {
let acc = 0;
const visible = series.map((s, si) => ({ s, si, v: Math.max(0, num(d, s.key)) })).filter((x) => x.v > 0);
marks = visible.map(({ s, si, v }, k) => {
const a = val(acc);
acc += v;
const b = val(acc);
const isTop = k === visible.length - 1;
// The 2px surface gap sits on top of every segment but the last
const len = Math.abs(b - a) - (isTop ? 0 : GAP);
if (len <= 0) return null;
const path = horizontal ? barPath(a, start, len, thick, isTop ? 4 : 0, 'right') : barPath(start, b + (isTop ? 0 : GAP), thick, len, isTop ? 4 : 0, 'up');
return <path key={s.key} d={path} fill={seriesColor(si, s.color)} className="kitch-series" opacity={seriesOpacity(si, focus)} />;
});
} else {
marks = series.map((s, si) => {
const v = num(d, s.key);
const p = val(v);
const off = round(start + si * (thick + GAP));
const len = Math.abs(p - zero);
if (len < 0.5) return null;
const path = horizontal ? barPath(Math.min(p, zero), off, len, thick, 4, v >= 0 ? 'right' : 'left') : barPath(off, Math.min(p, zero), thick, len, 4, v >= 0 ? 'up' : 'down');
return <path key={s.key} d={path} fill={dim(i, si)} className="kitch-series" opacity={seriesOpacity(si, focus)} />;
});
}
const tip = tipValue(i);
const text = valueLabels && tip !== null ? fmt(tip) : null;
const fits = text && (horizontal || textWidth(text, 11) <= band - 2);
const highlighted = highlight !== undefined && String(d[xKey]) === highlight;
return (
<g key={i} className="transition-opacity duration-200 motion-reduce:transition-none" opacity={faded ? 0.55 : 1}>
<g className={animated ? (horizontal ? 'kitch-grow-x' : tip !== null && tip < 0 ? 'kitch-grow-y-down' : 'kitch-grow-y') : undefined} style={delay(i)}>
{marks}
</g>
{fits && (
<text
x={horizontal ? val(tip!) + 8 : c}
y={horizontal ? c : val(Math.max(0, tip!)) - 7}
dy={horizontal ? '0.34em' : undefined}
textAnchor={horizontal ? 'start' : 'middle'}
className={`tabular-nums ${horizontal ? 'text-[12px]' : 'text-[11px]'} ${animated ? 'kitch-rise' : ''}`}
fontWeight={highlighted ? 600 : 500}
fill={highlighted || horizontal ? 'var(--foreground)' : 'var(--muted-foreground)'}
style={animated ? ({ '--kitch-d': `${round(380 + (i / Math.max(1, n)) * 420, 0)}ms` } as React.CSSProperties) : undefined}
>
{text}
</text>
)}
</g>
);
});
}}
/>
);
}Häufige Fragen
Wie installiere ich Balkendiagramm?
Mit aiw add @ai-website/bar-chart. Der Befehl legt die Datei components/ui/bar-chart.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind chart. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Balkendiagramm?
4 Varianten, Einfach, Gruppiert, Gestapelt und Waagerecht.
Kann ein KI-Assistent Balkendiagramm einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Balkendiagramm mit aiw_components und installieren sie mit aiw_add @ai-website/bar-chart.