Patterns
Charts gallery
Which chart answers which question; token-themed ECharts examples, summary text and a table fallback.
Charts are drawn with the Chart component. Its ECharts theme is generated from the DTCG tokens, so colours, type and motion follow both brands and both modes on their own. This page shows when to pick each chart type.
When to use
Section titled “When to use”- When you need to show a trend over time, a difference between categories or the parts of a whole.
- When one number is enough, use a StatCard instead of a chart.
- When users will read or compare exact values, give a table next to the chart.
Live example
Section titled “Live example”Each chart loads live from Storybook. The brand and mode switches in the page header restyle the charts too; hover a series to see its values.
Line: trend over time
Section titled “Line: trend over time”Area: volume of one series
Section titled “Area: volume of one series”Bar: comparing categories
Section titled “Bar: comparing categories”Stacked bar: composition over time
Section titled “Stacked bar: composition over time”Donut: shares of a whole
Section titled “Donut: shares of a whole”Key figure and change
Section titled “Key figure and change”import { Chart, Grid, StatCard } from '@bigbang/ui';import type { ChartKind, ChartSeries } from '@bigbang/ui';import { type Lang, LangProvider } from '../lang';
const copy = { tr: { months: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz'], categories: ['Kategori A', 'Kategori B', 'Kategori C', 'Kategori D', 'Kategori E'], first: 'Birinci seri', second: 'İkinci seri', third: 'Üçüncü seri', line: { title: 'Aylık kayıt sayısı', description: 'İki serinin altı aylık eğilimi.' }, area: { title: 'Toplam etkinlik', description: 'Tek serinin altı aylık hacmi.' }, bar: { title: 'Kategoriye göre kayıt', description: 'Beş kategorinin karşılaştırması.' }, stacked: { title: 'Aylık dağılım', description: 'Üç serinin aylara göre bileşimi.' }, donut: { title: 'Kategori payları', description: 'Bütünün beş kategoriye dağılımı.' }, kpis: ['Toplam kayıt', 'Açık görev', 'Ortalama süre'], period: 'önceki aya göre', days: '3,5 gün', table: { title: 'Kayıt sayısı, tablo görünümü', description: 'Grafikle aynı veri, tablo olarak.' }, }, en: { months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], categories: ['Category A', 'Category B', 'Category C', 'Category D', 'Category E'], first: 'First series', second: 'Second series', third: 'Third series', line: { title: 'Records per month', description: 'Six-month trend of two series.' }, area: { title: 'Total activity', description: 'Six-month volume of one series.' }, bar: { title: 'Records by category', description: 'A comparison of five categories.' }, stacked: { title: 'Monthly breakdown', description: 'Composition of three series by month.' }, donut: { title: 'Category shares', description: 'How the whole splits across five categories.' }, kpis: ['Total records', 'Open tasks', 'Average cycle time'], period: 'against last month', days: '3.5 days', table: { title: 'Records per month, table view', description: 'The same data as the chart, as a table.' }, },};
// Round, neutral demo figures.const monthly = [[120, 150, 140, 180, 200, 240], [80, 90, 110, 100, 130, 150], [40, 50, 45, 60, 70, 80]] as const;const byCategory = [320, 250, 180, 120, 80];
const seriesOf = (lang: Lang, count: number): ChartSeries[] => { const t = copy[lang]; const labels = [t.first, t.second, t.third]; return monthly.slice(0, count).map((values, index) => ({ id: `s${index + 1}`, label: labels[index] ?? '', data: values.map((y, month) => ({ x: t.months[month] ?? '', y })), }));};
const categorySeries = (lang: Lang): ChartSeries[] => [ { id: 'categories', label: copy[lang].first, data: byCategory.map((y, index) => ({ x: copy[lang].categories[index] ?? '', y })) },];
type ExampleProps = { lang?: Lang };
function Example({ lang = 'tr', kind, part, series, table }: { lang?: Lang; kind: ChartKind; part: 'line' | 'area' | 'bar' | 'stacked' | 'donut' | 'table'; series: ChartSeries[]; table?: 'visible' }) { const t = copy[lang][part]; const number = new Intl.NumberFormat(lang === 'tr' ? 'tr-TR' : 'en-GB'); return ( <LangProvider lang={lang}> <div className="w-full"> <Chart kind={kind} title={t.title} description={t.description} series={series} height="md" formatValue={(value) => number.format(value)} {...(table ? { table } : {})} /> </div> </LangProvider> );}
export const LineExample = ({ lang = 'tr' }: ExampleProps) => <Example lang={lang} kind="line" part="line" series={seriesOf(lang, 2)} />;export const AreaExample = ({ lang = 'tr' }: ExampleProps) => <Example lang={lang} kind="area" part="area" series={seriesOf(lang, 1)} />;export const BarExample = ({ lang = 'tr' }: ExampleProps) => <Example lang={lang} kind="bar" part="bar" series={categorySeries(lang)} />;export const StackedBarExample = ({ lang = 'tr' }: ExampleProps) => <Example lang={lang} kind="stacked-bar" part="stacked" series={seriesOf(lang, 3)} />;export const DonutExample = ({ lang = 'tr' }: ExampleProps) => <Example lang={lang} kind="donut" part="donut" series={categorySeries(lang)} />;export const TableFallbackExample = ({ lang = 'tr' }: ExampleProps) => <Example lang={lang} kind="line" part="table" series={seriesOf(lang, 1)} table="visible" />;
/** A single key figure: StatCard with its change against the period; no chart needed. */export function KpiTrendExample({ lang = 'tr' }: ExampleProps) { const t = copy[lang]; const percent = (value: number) => (lang === 'tr' ? `%${value}` : `${value}%`); return ( <LangProvider lang={lang}> <Grid columns={{ base: 1, md: 3 }} gap="lg" className="w-full"> <StatCard label={t.kpis[0] ?? ''} value={new Intl.NumberFormat(lang === 'tr' ? 'tr-TR' : 'en-GB').format(1200)} delta={{ value: percent(8), direction: 'up' }} period={t.period} /> <StatCard label={t.kpis[1] ?? ''} value="140" delta={{ value: percent(3), direction: 'up', sentiment: 'negative' }} period={t.period} /> <StatCard label={t.kpis[2] ?? ''} value={t.days} delta={{ value: percent(5), direction: 'down', sentiment: 'positive' }} period={t.period} /> </Grid> </LangProvider> );}Table fallback
Section titled “Table fallback”The same data as the chart, as a table.
| Category | First series |
|---|---|
| Jan | 120 |
| Feb | 150 |
| Mar | 140 |
| Apr | 180 |
| May | 200 |
| Jun | 240 |
| Question | Chart | Note |
|---|---|---|
| How did it change over time? | Line (line) |
At most 4 series; use a separate chart for more. |
| How much built up? | Area (area) |
One series; use a line for several. |
| Which is bigger? | Bar (bar) |
Sort categories from largest to smallest. |
| How did the mix change over time? | Stacked bar (stacked-bar) |
At most 4 parts. |
| What share of the whole? | Donut (donut) |
At most 5 parts; use a bar for more. |
| One number and its change | StatCard delta |
The direction is also read as text. |
| Rule | Value |
|---|---|
| Series count | At most 8; colours chart.categorical-1…8, at least 3:1 against the surface. |
| Title and summary | title is required on every chart, description recommended; screen readers read both. |
| Table fallback | table="toggle" by default; visible when exact values matter. |
| Number format | Intl.NumberFormat through formatValue; decimal comma in Turkish. |
| No data | The emptyLabel text; no empty axes are drawn. |
| Hidden values | hideValues turns values into ••••, the shape stays. |
| CSP | The SVG renderer writes inline styles; the page CSP needs style-src-attr 'unsafe-inline'. |
Behaviour
Section titled “Behaviour”- On first render the series enter within 400 ms.
- When the data changes, the transition takes 240 ms; series do not enter again on filter or page changes.
- Hovering or focusing with the keyboard shows the value tooltip.
- While loading, the chart shows its own placeholder; the title keeps its place.
- When the window resizes, the chart redraws to fit its container.
Accessibility
Section titled “Accessibility”- The chart has
role="img"; its name comes from the title and its description from the summary text. - “Show data as a table” opens the same data in a table with
thheaders. - Colour never carries meaning alone: the legend, tooltip and table name each series.
- StatCard reads the direction of change with an arrow and hidden text (“increased”, “decreased”).
Content
Section titled “Content”- The title says what is measured: “Records per month”; avoid names like “Chart 1”.
- Keep axis labels short; abbreviate month names (Jan, Feb).
- Put the unit in the title or the value format, not again on the axis.
- The summary gives the main finding in one sentence.
Motion
Section titled “Motion”| Item | Motion | Preset |
|---|---|---|
| First render | 400 ms, cubicOut |
motionValues |
| Data change | 240 ms; no re-entry on filter | motionValues |
| StatCard value | 110 ms cross-fade; amounts never count | motion.reveal |
With reduced motion, ECharts animation is off (animation: false).
Components used
Section titled “Components used”Chart, StatCard, Grid. Foundation: Data visualisation. Upcoming components: Sparkline exists in the component folder but is not exported from @bigbang/ui; once it is, StatCard will show a small trend line in its trend slot.