Kerzendiagramm
Kurse als Eröffnung, Hoch, Tief und Schluss. Als Kerzen, Kerzen mit Umsatz darunter, mit Zeitraumschalter von einem Monat bis zu einem Jahr oder als OHLC-Balken mit gleitendem Schnitt. Steigende Tage hohl, fallende gefüllt.
Eine von 6 Components in KPI-, Live- und Karten-Charts
Variants
Details
Was Kerzendiagramm mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- KPI-, Live- und Karten-Charts
- Bereich
- Elements
- Variants
- 4
- Version
- 1.0.0 · seit 1.1.0
- Datei
- components/ui/candlestick-chart.tsx
- Bringt mit
- utilskitchartchart-figure
- npm-Pakete
- Keine
- Sprachen
- Englisch und Deutsch, 13 eigene Texte
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle CandlestickChartProps aus components/ui/candlestick-chart.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von Omit<ChartA11y, 'label'>, ChartFigureProps an.
labeloptionalstringAccessible name of the chart. Default: the title.
variantoptionalCandlestickVariantcandleswith-volumerangesohlc
dataPflichtCandle[]Oldest first
formatoptionalIntl.NumberFormatOptionsPrice format, e.g. { style: 'currency', currency: 'EUR' }
rangesoptional{ label: string; days: number }[]ranges: the switch, days per option, 0 for everything as weeks. Default 1M, 3M, 1Y.
averageoptionalnumberohlc: days of the moving average. Default 20.
heightoptionalnumberHeight of the price plot in px. Default 300.
Eingebaute Texte
Diese Texte schreibt Kerzendiagramm selbst. Die Sprache folgt dem KitProvider, über die Prop labels ersetzt du einzelne.
Alle 13 Texte anzeigen
| Schlüssel | Englisch | Deutsch |
|---|---|---|
| open | Englisch Open | Deutsch Eröffnung |
| high | Englisch High | Deutsch Hoch |
| low | Englisch Low | Deutsch Tief |
| close | Englisch Close | Deutsch Schluss |
| change | Englisch Change | Deutsch Veränderung |
| volume | Englisch Volume | Deutsch Umsatz |
| rise | Englisch Closed higher | Deutsch Höher geschlossen |
| fall | Englisch Closed lower | Deutsch Tiefer geschlossen |
| average | Englisch {n} day average | Deutsch Schnitt {n} Tage |
| range | Englisch Period | Deutsch Zeitraum |
| date | Englisch Date | Deutsch Datum |
| week | Englisch Week of {d} | Deutsch Woche ab {d} |
| summary | Englisch From {a} to {b}, low {lo}, high {hi}. | Deutsch Von {a} bis {b}, Tief {lo}, Hoch {hi}. |
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Kerzendiagramm reagiert selbst auf die Tasten Escape, Pfeil hoch, Pfeil runter, Pfeil links, Pfeil rechts, Pos1, Ende, Bild hoch und Bild runter.
- Setzt aria-checked und aria-label, damit Screenreader Zweck und Zustand ansagen.
- Nutzt die Rollen radio und radiogroup.
- Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.
Bewegung
- Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
- Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig. 1 Stellen im Code schalten Bewegung dann ab.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 356 Zeilen
'use client';
import { useState } from 'react';
import { cn, round, useTexts } from '@/lib/kit';
import { ChartRoot, ChartSurface, ChartTable, ChartTooltip, Swatch, niceTicks, numberFormatter, textWidth, tickFormatter, useEntrance, useIntlLocale, useShown, useWidth, type ChartA11y, type TooltipRow } from '@/components/ui/chart';
import { ChartFigure, PlotAxes, linear, type ChartFigureProps } from '@/components/ui/chart-figure';
/*
* Prices over time as open, high, low and close.
* candles a body from open to close and a wick from low to high
* with-volume the candles and the traded volume in a second panel below,
* its own scale, the same time axis (never a second y axis)
* ranges a switch for one month, three months and a year, the year
* folded into weeks so every candle stays readable
* ohlc open and close as ticks on a bar, with a moving average
*
* Rising days are hollow, falling days filled: the shape tells the
* direction, the success and destructive tones only repeat it. A crosshair
* finds the day, the price of the day sits as a tag on the axis.
*/
export type CandlestickVariant = 'candles' | 'with-volume' | 'ranges' | 'ohlc';
export interface Candle {
/** "2026-03-14" */
date: string;
open: number;
high: number;
low: number;
close: number;
volume?: number;
}
export interface CandlestickChartProps extends Omit<ChartA11y, 'label'>, ChartFigureProps {
/** Accessible name of the chart. Default: the title. */
label?: string;
variant?: CandlestickVariant;
/** Oldest first */
data: Candle[];
/** Price format, e.g. { style: 'currency', currency: 'EUR' } */
format?: Intl.NumberFormatOptions;
/** ranges: the switch, days per option, 0 for everything as weeks. Default 1M, 3M, 1Y. */
ranges?: { label: string; days: number }[];
/** ohlc: days of the moving average. Default 20. */
average?: number;
/** Height of the price plot in px. Default 300. */
height?: number;
className?: string;
}
const TEXTS = {
en: { open: 'Open', high: 'High', low: 'Low', close: 'Close', change: 'Change', volume: 'Volume', rise: 'Closed higher', fall: 'Closed lower', average: '{n} day average', range: 'Period', date: 'Date', week: 'Week of {d}', summary: 'From {a} to {b}, low {lo}, high {hi}.' },
de: { open: 'Eröffnung', high: 'Hoch', low: 'Tief', close: 'Schluss', change: 'Veränderung', volume: 'Umsatz', rise: 'Höher geschlossen', fall: 'Tiefer geschlossen', average: 'Schnitt {n} Tage', range: 'Zeitraum', date: 'Datum', week: 'Woche ab {d}', summary: 'Von {a} bis {b}, Tief {lo}, Hoch {hi}.' },
};
const UP = 'var(--kitfg-up)';
const DOWN = 'var(--kitfg-down)';
/** Folds days into weeks (Monday based) */
function weekly(days: Candle[]): Candle[] {
const out: Candle[] = [];
for (const d of days) {
const dt = new Date(`${d.date}T00:00:00Z`);
const monday = new Date(dt);
monday.setUTCDate(dt.getUTCDate() - ((dt.getUTCDay() + 6) % 7));
const key = monday.toISOString().slice(0, 10);
const last = out[out.length - 1];
if (last && last.date === key) {
last.high = Math.max(last.high, d.high);
last.low = Math.min(last.low, d.low);
last.close = d.close;
last.volume = (last.volume ?? 0) + (d.volume ?? 0);
} else out.push({ ...d, date: key });
}
return out;
}
/**
* Open, high, low and close per day: candles, with volume, with a range switch, or as OHLC bars.
* <CandlestickChart variant="with-volume" data={[{ date: '2026-03-02', open: 41.2, high: 42.8, low: 40.9, close: 42.5, volume: 182000 }]} />
*/
export function CandlestickChart(props: CandlestickChartProps) {
const { variant = 'candles', data, format, average = 20, height: heightProp, label, summary, labels, animate = true, title, value, delta, meta, source, framed, className } = props;
const t = useTexts(TEXTS, undefined);
const intl = useIntlLocale();
const fmt = numberFormatter(intl, format ?? { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const tick = tickFormatter(intl, format);
const vol = numberFormatter(intl, { notation: 'compact', maximumFractionDigits: 1 });
const pct = numberFormatter(intl, { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1, signDisplay: 'exceptZero' });
const [wrapRef, width] = useWidth<HTMLDivElement>(720);
const [rootRef, anim] = useEntrance<HTMLDivElement>(animate);
const [active, setActive] = useState<number | null>(null);
const { shown, open, jump } = useShown(active);
const ranges = props.ranges ?? (intl === 'de-DE' ? [{ label: '1 M', days: 22 }, { label: '3 M', days: 65 }, { label: '1 J', days: 0 }] : [{ label: '1M', days: 22 }, { label: '3M', days: 65 }, { label: '1Y', days: 0 }]);
const [range, setRange] = useState(1);
const phone = width < 520;
const name = label ?? title ?? '';
// Which candles: ranges slice or fold, a phone shows fewer days of the plain variants
const series: Candle[] = (() => {
if (variant === 'ranges') {
const r = ranges[range] ?? ranges[0];
return r.days ? data.slice(-r.days) : weekly(data);
}
const max = phone ? 44 : 90;
return data.length > max ? data.slice(-max) : data;
})();
const n = series.length;
const showVolume = variant === 'with-volume' && series.some((d) => d.volume !== undefined);
// Moving average over the full data so the first visible days have a value
const offset = data.length - n;
const ma: (number | null)[] =
variant === 'ohlc'
? series.map((_, i) => {
const end = offset + i;
if (end + 1 < average) return null;
let s = 0;
for (let k = end - average + 1; k <= end; k++) s += data[k].close;
return s / average;
})
: [];
const lo = Math.min(...series.map((d) => d.low));
const hi = Math.max(...series.map((d) => d.high));
const nice = niceTicks(lo - (hi - lo) * 0.04, hi + (hi - lo) * 0.04, 5);
const yLabels = nice.ticks.map(tick);
const priceH = heightProp ?? (phone ? 260 : 300);
const volH = showVolume ? 78 : 0;
const height = priceH + volH + (showVolume ? 14 : 0);
const left = Math.max(...yLabels.map((s) => textWidth(s, 11)), 20) + 14;
const right = width - 8;
const top = 12;
const bottom = priceH - 28;
const Y = linear(nice.min, nice.max, bottom, top);
const band = (right - left) / Math.max(1, n);
const cx = (i: number) => round(left + band * (i + 0.5));
const body = Math.max(1.5, Math.min(11, band * 0.62));
const vTop = priceH + 14;
const vBottom = height - 2;
const vMax = Math.max(1, ...series.map((d) => d.volume ?? 0));
// Month labels on the first day of each month that fits
const monthFmt = new Intl.DateTimeFormat(intl, { month: 'short', timeZone: 'UTC' });
const dayFmt = new Intl.DateTimeFormat(intl, { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' });
const shortDay = new Intl.DateTimeFormat(intl, { day: 'numeric', month: 'short', timeZone: 'UTC' });
const xTicks: { at: number; label: string }[] = [];
let lastX = -Infinity;
series.forEach((d, i) => {
const m = d.date.slice(0, 7);
if (i === 0 || m !== series[i - 1].date.slice(0, 7)) {
const x = cx(i);
const label = monthFmt.format(new Date(`${d.date}T00:00:00Z`));
if (x - lastX > textWidth(label, 11) + 16 && x > left + 10) {
xTicks.push({ at: x, label });
lastX = x;
}
}
});
const weekly_ = variant === 'ranges' && !(ranges[range]?.days);
const dateTitle = (d: Candle) => (weekly_ ? t.week.replace('{d}', shortDay.format(new Date(`${d.date}T00:00:00Z`))) : dayFmt.format(new Date(`${d.date}T00:00:00Z`)));
const rows = (i: number): TooltipRow[] => {
const d = series[i];
const up = d.close >= d.open;
const out: TooltipRow[] = [
{ key: 'c', label: t.close, value: fmt(d.close), color: up ? UP : DOWN, shape: 'rect' },
{ key: 'o', label: t.open, value: fmt(d.open) },
{ key: 'h', label: t.high, value: fmt(d.high) },
{ key: 'l', label: t.low, value: fmt(d.low) },
{ key: 'p', label: t.change, value: pct((d.close - d.open) / d.open) },
];
if (d.volume !== undefined && showVolume) out.push({ key: 'v', label: t.volume, value: vol(d.volume) });
if (variant === 'ohlc' && ma[i] !== null) out.push({ key: 'm', label: t.average.replace('{n}', String(average)), value: fmt(ma[i]!), color: 'var(--primary)', shape: 'line' });
return out;
};
const pick = (clientX: number, el: HTMLElement) => {
const r = el.getBoundingClientRect();
const x = (clientX - r.left) * (width / (r.width || width));
setActive(Math.max(0, Math.min(n - 1, Math.floor((x - left) / band))));
};
const onKeyDown = (e: React.KeyboardEvent) => {
const cur = active ?? n;
let next: number | null = cur;
if (e.key === 'ArrowRight') next = Math.min(n - 1, cur + 1);
else if (e.key === 'ArrowLeft') next = Math.max(0, cur - 1);
else if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = n - 1;
else if (e.key === 'PageUp') next = Math.max(0, cur - 5);
else if (e.key === 'PageDown') next = Math.min(n - 1, cur + 5);
else if (e.key === 'Escape') next = null;
else return;
e.preventDefault();
setActive(next);
};
const first = series[0];
const last = series[n - 1];
const auto = first && last ? t.summary.replace('{a}', fmt(first.open)).replace('{b}', fmt(last.close)).replace('{lo}', fmt(lo)).replace('{hi}', fmt(hi)) : '';
const live = active !== null && series[active] ? `${dateTitle(series[active])}. ${rows(active).map((r) => `${r.label} ${r.value}`).join(', ')}` : '';
const table = <ChartTable caption={name} head={[t.date, t.open, t.high, t.low, t.close, ...(showVolume ? [t.volume] : [])]} rows={series.map((d) => [dateTitle(d), fmt(d.open), fmt(d.high), fmt(d.low), fmt(d.close), ...(showVolume ? [vol(d.volume ?? 0)] : [])])} />;
const delay = (i: number) => (anim ? ({ '--kitch-d': `${round(60 + (i / Math.max(1, n)) * 700, 0)}ms` } as React.CSSProperties) : undefined);
const activeD = shown !== null ? series[shown] : undefined;
// The price tag sits on the axis, tick labels under it step aside
const tagY = activeD ?? series[n - 1] ? Y((activeD ?? series[n - 1]).close) : null;
const actions =
variant === 'ranges' ? (
<div role="radiogroup" aria-label={t.range} className="inline-flex h-9 items-center rounded-md bg-muted p-[3px] shadow-[inset_0_0_0_1px_var(--border)]">
{ranges.map((r, i) => (
<button
key={r.label}
type="button"
role="radio"
aria-checked={range === i}
tabIndex={range === i ? 0 : -1}
onClick={() => {
setRange(i);
setActive(null);
}}
onKeyDown={(e) => {
const step = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : e.key === 'ArrowLeft' || e.key === 'ArrowUp' ? -1 : 0;
if (!step) return;
e.preventDefault();
const k = (range + step + ranges.length) % ranges.length;
setRange(k);
(e.currentTarget.parentElement?.children[k] as HTMLElement | undefined)?.focus();
}}
className={cn("relative h-full min-w-11 rounded-[5px] px-3 text-[13px] font-medium tabular-nums transition-[background-color,color,box-shadow] duration-300 after:absolute after:-inset-y-[7px] after:inset-x-0 after:content-['']", range === i ? 'bg-background text-foreground shadow-[0_0_0_1px_var(--border),var(--shadow-sm)] dark:bg-[color-mix(in_oklab,var(--foreground)_10%,var(--background))]' : 'text-muted-foreground hover:text-foreground')}
>
{r.label}
</button>
))}
</div>
) : undefined;
return (
<ChartFigure title={title} value={value} delta={delta} meta={meta} source={source} framed={framed} actions={actions} className={className}>
<ChartRoot anim={anim} rootRef={rootRef}>
<ul className="mb-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-[12px] text-muted-foreground">
<li className="flex items-center gap-1.5">
<svg width="10" height="14" viewBox="0 0 10 14" aria-hidden>
<path d="M5 0V14" stroke={UP} strokeWidth="1.5" />
<rect x="1.25" y="3.25" width="7.5" height="7.5" rx="1.5" fill="var(--card)" stroke={UP} strokeWidth="1.5" />
</svg>
{t.rise}
</li>
<li className="flex items-center gap-1.5">
<svg width="10" height="14" viewBox="0 0 10 14" aria-hidden>
<path d="M5 0V14" stroke={DOWN} strokeWidth="1.5" />
<rect x="1" y="3" width="8" height="8" rx="1.5" fill={DOWN} />
</svg>
{t.fall}
</li>
{variant === 'ohlc' && (
<li className="flex items-center gap-1.5">
<Swatch shape="line" color="var(--primary)" />
{t.average.replace('{n}', String(average))}
</li>
)}
</ul>
<div ref={wrapRef}>
<ChartSurface label={name} summary={summary ?? auto} labels={labels} live={live} table={table} onKeyDown={onKeyDown} onFocus={(e) => e.target === e.currentTarget && active === null && e.currentTarget.matches(':focus-visible') && setActive(n - 1)} onBlur={() => setActive(null)} onPointerMove={(e) => pick(e.clientX, e.currentTarget)} onPointerDown={(e) => pick(e.clientX, e.currentTarget)} onPointerLeave={(e) => e.pointerType === 'mouse' && setActive(null)}>
<svg key={variant === 'ranges' ? range : 0} width="100%" height={height} viewBox={`0 0 ${width} ${height}`} aria-hidden className={cn('block overflow-visible', variant === 'ranges' && 'animate-[kitch-fade_.45s_var(--ease-kit)] motion-reduce:animate-none')}>
<PlotAxes left={left} right={right} top={top} bottom={bottom} height={priceH} yTicks={nice.ticks.map((v, i) => ({ at: Y(v), label: tagY !== null && Math.abs(Y(v) - tagY) < 14 ? '' : yLabels[i] }))} xTicks={xTicks} />
{/* Volume panel: own scale, same days */}
{showVolume && (
<g>
<line x1={left} x2={right} y1={vBottom} y2={vBottom} stroke="var(--kitch-axis)" strokeWidth="1" shapeRendering="crispEdges" />
<text x={left - 10} y={vTop + 4} dy="0.34em" textAnchor="end" className="text-[11px] tabular-nums" fill="var(--muted-foreground)">
{vol(vMax)}
</text>
<text x={left - 10} y={vBottom} textAnchor="end" className="text-[11px]" fill="var(--muted-foreground)">
{t.volume}
</text>
{series.map((d, i) => {
const h = ((d.volume ?? 0) / vMax) * (vBottom - vTop);
return <rect key={d.date} x={round(cx(i) - body / 2)} y={round(vBottom - h)} width={round(body)} height={round(Math.max(0.5, h))} rx="1" fill={d.close >= d.open ? UP : DOWN} opacity={active === i ? 0.75 : 0.4} className={anim ? 'kitch-grow-y' : undefined} style={delay(i)} />;
})}
</g>
)}
{/* Crosshair */}
{activeD && (
<g className="kitch-glide" style={{ opacity: open ? 1 : 0 }}>
<line x1={cx(shown!)} x2={cx(shown!)} y1={top} y2={showVolume ? vBottom : bottom} stroke="var(--kitch-axis)" strokeWidth="1" />
<line x1={left} x2={right} y1={Y(activeD.close)} y2={Y(activeD.close)} stroke="var(--kitch-axis)" strokeWidth="1" strokeDasharray="3 3" />
</g>
)}
{/* Moving average */}
{variant === 'ohlc' && (
<path
d={ma.map((v, i) => (v === null ? '' : `${i && ma[i - 1] !== null ? 'L' : 'M'}${cx(i)} ${Y(v)}`)).join('')}
pathLength={1}
fill="none"
stroke="var(--primary)"
strokeWidth="2"
strokeLinejoin="round"
className={anim ? 'kitch-draw' : undefined}
style={anim ? ({ '--kitch-d': '600ms', '--kitch-t': '1.2s' } as React.CSSProperties) : undefined}
/>
)}
{/* Candles or bars */}
<g>
{series.map((d, i) => {
const up = d.close >= d.open;
const color = up ? UP : DOWN;
const x = cx(i);
const yo = Y(d.open);
const yc = Y(d.close);
const faded = active !== null && active !== i;
if (variant === 'ohlc')
return (
<g key={d.date} className={anim ? 'kitch-fade' : undefined} style={delay(i)} opacity={faded ? 0.6 : 1}>
<line x1={x} x2={x} y1={Y(d.high)} y2={Y(d.low)} stroke={color} strokeWidth="1.5" />
<line x1={round(x - body / 2)} x2={x} y1={yo} y2={yo} stroke={color} strokeWidth="1.5" />
<line x1={x} x2={round(x + body / 2)} y1={yc} y2={yc} stroke={color} strokeWidth="1.5" />
</g>
);
const top_ = Math.min(yo, yc);
const h = Math.max(1, Math.abs(yc - yo));
return (
<g key={d.date} className={anim ? 'kitch-grow-y' : undefined} style={{ ...delay(i), transformOrigin: `${x}px ${round((Y(d.high) + Y(d.low)) / 2)}px`, transformBox: 'view-box' }} opacity={faded ? 0.6 : 1}>
<line x1={x} x2={x} y1={Y(d.high)} y2={Y(d.low)} stroke={color} strokeWidth="1" />
<rect x={round(x - body / 2 + (up ? 0.6 : 0))} y={round(top_ + (up ? 0.6 : 0))} width={round(body - (up ? 1.2 : 0))} height={round(Math.max(1, h - (up ? 1.2 : 0)))} rx={body > 6 ? 1.5 : 0.5} fill={up ? 'var(--kitch-surface)' : color} stroke={up ? color : 'none'} strokeWidth={up ? 1.2 : 0} />
</g>
);
})}
</g>
{/* Price tag of the active or last day on the axis */}
{(() => {
const d = activeD ?? last;
if (!d) return null;
const y = Y(d.close);
const text = fmt(d.close);
const w = textWidth(text, 11) + 12;
const up = d.close >= d.open;
return (
<g className="transition-transform duration-200" style={{ transform: `translateY(${round(y)}px)` }}>
<rect x={left - w - 4} y={-9} width={w} height={18} rx="4" fill="var(--foreground)" />
<rect x={left - 3} y={-4} width={3} height={8} rx="1" fill={up ? UP : DOWN} />
<text x={left - 4 - w / 2} y={0} dy="0.34em" textAnchor="middle" className="text-[11px] font-semibold tabular-nums" fill="var(--background)">
{text}
</text>
</g>
);
})()}
</svg>
<ChartTooltip open={open} jump={jump} x={shown !== null ? cx(shown) : 0} y={top} width={width} offset={body / 2 + 12} title={activeD ? dateTitle(activeD) : ''} rows={shown !== null && series[shown] ? rows(shown) : []} />
</ChartSurface>
</div>
</ChartRoot>
</ChartFigure>
);
}Häufige Fragen
Wie installiere ich Kerzendiagramm?
Mit aiw add @ai-website/candlestick-chart. Der Befehl legt die Datei components/ui/candlestick-chart.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind chart und chart-figure. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Kerzendiagramm?
4 Varianten, Kerzen, Mit Umsatz, Zeitraum wählen und OHLC-Balken. Du wählst sie mit der Prop variant, zum Beispiel variant="with-volume".
Kann ich die Texte von Kerzendiagramm übersetzen?
Ja. Kerzendiagramm bringt 13 eigene Texte auf Deutsch und Englisch mit, etwa „Eröffnung“, „Hoch“ und „Tief“. Die Sprache folgt dem KitProvider, einzelne Texte ersetzt du über die Prop labels.
Kann ein KI-Assistent Kerzendiagramm einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Kerzendiagramm mit aiw_components und installieren sie mit aiw_add @ai-website/candlestick-chart. Nenn im Prompt die Variante, etwa variant="candles", dann schreibt der Assistent deine Inhalte direkt in die Props.
Kann ich Kerzendiagramm ohne Code anpassen?
Ja, im Studio. Alle 4 Varianten lassen sich dort bearbeiten, Texte, Listen und Schalter. Das Studio schreibt den passenden CandlestickChart-Code zum Kopieren.