@siyahi/ui 0.1.9-next.23 → 0.1.9-next.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/agents/examples/change-grid-money.tsx +36 -0
  2. package/agents/examples/change-grid.tsx +42 -0
  3. package/agents/examples/delta.tsx +11 -6
  4. package/agents/examples/drawdown-chart.tsx +35 -0
  5. package/agents/examples/heatmap-calendar-signed.tsx +67 -0
  6. package/agents/examples/heatmap-status.tsx +6 -4
  7. package/agents/examples/histogram-chart.tsx +28 -0
  8. package/agents/examples/scatter-chart-quadrants.tsx +63 -0
  9. package/agents/examples/scatter-chart.tsx +75 -0
  10. package/agents/examples/slope-chart-market.tsx +33 -0
  11. package/agents/examples/slope-chart.tsx +26 -0
  12. package/agents/examples/waterfall-chart-stores.tsx +27 -0
  13. package/agents/examples/waterfall-chart.tsx +37 -0
  14. package/agents/manifest.json +160 -0
  15. package/dist/components/registry/heatmap/calendar-heatmap.d.ts +8 -2
  16. package/dist/components/registry/heatmap/calendar-heatmap.js +96 -18
  17. package/dist/components/registry/heatmap/status-heatmap.js +86 -47
  18. package/dist/components/siyahi/change-grid.d.ts +31 -0
  19. package/dist/components/siyahi/change-grid.js +74 -0
  20. package/dist/components/siyahi/delta.js +2 -2
  21. package/dist/components/siyahi/drawdown-chart.d.ts +34 -0
  22. package/dist/components/siyahi/drawdown-chart.js +102 -0
  23. package/dist/components/siyahi/histogram-chart.d.ts +57 -0
  24. package/dist/components/siyahi/histogram-chart.js +134 -0
  25. package/dist/components/siyahi/scatter-chart.d.ts +59 -0
  26. package/dist/components/siyahi/scatter-chart.js +367 -0
  27. package/dist/components/siyahi/slope-chart.d.ts +35 -0
  28. package/dist/components/siyahi/slope-chart.js +159 -0
  29. package/dist/components/siyahi/treemap.js +3 -7
  30. package/dist/components/siyahi/waterfall-chart.d.ts +51 -0
  31. package/dist/components/siyahi/waterfall-chart.js +147 -0
  32. package/dist/index.d.ts +9 -2
  33. package/dist/index.js +7 -1
  34. package/dist/lib/chart-frame.d.ts +7 -2
  35. package/dist/lib/chart-frame.js +44 -10
  36. package/dist/lib/chart-series.d.ts +2 -3
  37. package/dist/lib/fit-text.d.ts +6 -0
  38. package/dist/lib/fit-text.js +11 -0
  39. package/dist/lib/heat.d.ts +7 -0
  40. package/dist/lib/heat.js +14 -0
  41. package/dist/lib/scatter.d.ts +98 -0
  42. package/dist/lib/scatter.js +218 -0
  43. package/dist/lib/treemap.d.ts +0 -5
  44. package/dist/lib/treemap.js +1 -16
  45. package/package.json +1 -1
  46. package/report.css +9 -0
  47. package/styles/siyahi.css +23 -5
@@ -0,0 +1,36 @@
1
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, ChangeGrid, type ChangeGridRow } from "@siyahi/ui";
2
+
3
+ // Profit or loss per store per quarter; made-up figures.
4
+ const quarters = ["Q1", "Q2", "Q3", "Q4"];
5
+ const stores: [string, number[]][] = [
6
+ ["Koramangala", [412_000, 386_000, 455_000, 520_000]],
7
+ ["Indiranagar", [218_000, -42_000, 131_000, 264_000]],
8
+ ["Whitefield", [-86_000, -124_000, 38_000, 61_000]],
9
+ ["Online", [305_000, 344_000, 398_000, 471_000]],
10
+ ["Jayanagar", [96_000, -158_000, -212_000, -64_000]],
11
+ ];
12
+ const rows: ChangeGridRow[] = stores.map(([label, values]) => ({
13
+ label,
14
+ values,
15
+ total: values.reduce((sum, value) => sum + value, 0),
16
+ }));
17
+
18
+ export default function ChangeGridMoneyExample() {
19
+ return (
20
+ <Card>
21
+ <CardHeader>
22
+ <CardTitle>Profit by store and quarter</CardTitle>
23
+ <CardDescription>After rent and wages. The year adds its quarters.</CardDescription>
24
+ </CardHeader>
25
+ <CardContent>
26
+ <ChangeGrid
27
+ label="Profit by store and quarter"
28
+ columns={quarters}
29
+ rows={rows}
30
+ format="money"
31
+ totalLabel="Year"
32
+ />
33
+ </CardContent>
34
+ </Card>
35
+ );
36
+ }
@@ -0,0 +1,42 @@
1
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, ChangeGrid, type ChangeGridRow } from "@siyahi/ui";
2
+
3
+ // A café chain's sales growth on the month before, per financial year (April to March); made-up figures.
4
+ const months = ["Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar"];
5
+ const growth: Omit<ChangeGridRow, "total">[] = [
6
+ {
7
+ label: "FY24",
8
+ values: [0.031, 0.012, -0.024, 0.008, 0.017, -0.006, 0.041, 0.052, 0.063, -0.038, 0.004, 0.022],
9
+ },
10
+ {
11
+ label: "FY25",
12
+ values: [0.018, -0.011, -0.032, 0.014, 0.009, 0, 0.036, 0.047, 0.071, -0.044, -0.008, 0.019],
13
+ },
14
+ // The year is under way: months still to come have no figure.
15
+ {
16
+ label: "FY26",
17
+ values: [0.026, 0.015, -0.018, 0.021, 0.012, 0.007, 0.044, null, null, null, null, null],
18
+ },
19
+ ];
20
+
21
+ // Months compound: a year's growth is the product of (1 + each month), less one.
22
+ const rows: ChangeGridRow[] = growth.map(({ label, values }) => ({
23
+ label,
24
+ values,
25
+ total: values.reduce<number>((year, month) => year * (1 + (month ?? 0)), 1) - 1,
26
+ }));
27
+
28
+ export default function ChangeGridExample() {
29
+ return (
30
+ <Card>
31
+ <CardHeader>
32
+ <CardTitle>Sales growth by month</CardTitle>
33
+ <CardDescription>
34
+ Each month against the one before. Deeper tints are larger changes; the year compounds its months.
35
+ </CardDescription>
36
+ </CardHeader>
37
+ <CardContent>
38
+ <ChangeGrid label="Sales growth by month and financial year" columns={months} rows={rows} totalLabel="Year" />
39
+ </CardContent>
40
+ </Card>
41
+ );
42
+ }
@@ -2,12 +2,17 @@ import { Delta } from "@siyahi/ui";
2
2
 
3
3
  export default function DeltaExample() {
4
4
  return (
5
- <div className="flex flex-wrap items-center gap-6">
6
- <Delta value={0.0234} />
7
- <Delta value={-0.0108} />
8
- <Delta value={0} />
9
- <Delta value={18250} format="money" />
10
- <Delta value={-4300} format="money" />
5
+ <div className="flex flex-col gap-4">
6
+ <div className="flex flex-wrap items-center gap-6">
7
+ <Delta value={0.0234} />
8
+ <Delta value={-0.0108} />
9
+ <Delta value={0} />
10
+ <Delta value={18250} format="money" />
11
+ <Delta value={-4300} format="money" />
12
+ </div>
13
+ <p>
14
+ Revenue was <Delta value={0.031} /> on last month, and refunds <Delta value={-1200} format="money" />.
15
+ </p>
11
16
  </div>
12
17
  );
13
18
  }
@@ -0,0 +1,35 @@
1
+ import { DrawdownChartCard, useFormat } from "@siyahi/ui";
2
+
3
+ // A bakery's cash balance at the end of each week of 2026: it climbs, falls through the summer when an oven is
4
+ // replaced, and falls again in the quiet weeks of the new year; made-up figures.
5
+ const weeks = Array.from({ length: 52 }, (_, i) => {
6
+ const date = new Date(Date.UTC(2026, 0, 4 + i * 7));
7
+ const trend = 600_000 + i * 9_000;
8
+ const oven = i >= 22 && i < 34 ? -180_000 + (i - 22) * 12_000 : 0;
9
+ const quiet = i >= 46 ? -(i - 45) * 22_000 : 0;
10
+ const wobble = ((i * 53) % 17) * 3_000 - 24_000;
11
+ return { week: date.toISOString().slice(0, 10), balance: trend + oven + quiet + wobble };
12
+ });
13
+
14
+ const month = (iso: string) => new Date(iso).toLocaleString("en-IN", { month: "short", timeZone: "UTC" });
15
+ const day = (iso: string) =>
16
+ new Date(iso).toLocaleString("en-IN", { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" });
17
+
18
+ export default function DrawdownChartExample() {
19
+ const { money } = useFormat();
20
+ return (
21
+ <DrawdownChartCard
22
+ title="Cash below its best"
23
+ description="Each week's balance against the highest it had been so far. At zero the bakery was at a new high."
24
+ data={weeks}
25
+ xKey="week"
26
+ levelKey="balance"
27
+ levelFormatter={money}
28
+ label="Below its best"
29
+ xFormatter={month}
30
+ xTooltipFormatter={(iso) => `Week ending ${day(iso)}`}
31
+ xTicks={weeks.filter((_, i) => i % 9 === 0).map(({ week }) => week)}
32
+ height={260}
33
+ />
34
+ );
35
+ }
@@ -0,0 +1,67 @@
1
+ import { useState } from "react";
2
+ import {
3
+ CalendarHeatmap,
4
+ CalendarHeatmapBlock,
5
+ CalendarHeatmapBody,
6
+ CalendarHeatmapFooter,
7
+ CalendarHeatmapLegend,
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardHeader,
12
+ CardTitle,
13
+ useFormat,
14
+ } from "@siyahi/ui";
15
+
16
+ // A café's profit or loss each day for six months: weekends earn most, rainy Mondays lose; made-up figures.
17
+ const data = Array.from({ length: 182 }, (_, i) => {
18
+ const day = new Date(Date.UTC(2026, 4, 1 + i));
19
+ const weekday = day.getUTCDay();
20
+ const base = weekday === 0 || weekday === 6 ? 9000 : weekday === 1 ? -2500 : 3000;
21
+ const swing = ((i * 37) % 23) * 400 - 4400;
22
+ return { date: day.toISOString().slice(0, 10), value: base + swing };
23
+ });
24
+
25
+ export default function HeatmapCalendarSignedExample() {
26
+ const [picked, setPicked] = useState<(typeof data)[number] | null>(null);
27
+ const { money } = useFormat();
28
+ const signed = (value: number) => `${value > 0 ? "+" : value < 0 ? "−" : ""}${money(Math.abs(value))}`;
29
+ return (
30
+ <Card>
31
+ <CardHeader>
32
+ <CardTitle>Profit by day</CardTitle>
33
+ <CardDescription>
34
+ May to October. Square green days made money and round red days lost it; deeper is more.
35
+ </CardDescription>
36
+ </CardHeader>
37
+ <CardContent>
38
+ <CalendarHeatmap
39
+ data={data}
40
+ signed
41
+ weekStart={1}
42
+ labels={{ heatmapLabel: "Profit by day, May to October", cellLabel: "{{date}}: {{value}} rupees" }}
43
+ >
44
+ <CalendarHeatmapBody>
45
+ {({ activity, dayIndex, weekIndex }) => (
46
+ <CalendarHeatmapBlock
47
+ activity={activity}
48
+ dayIndex={dayIndex}
49
+ weekIndex={weekIndex}
50
+ selected={activity.date === picked?.date}
51
+ onCellClick={(cell) => setPicked(cell.date === picked?.date ? null : cell)}
52
+ />
53
+ )}
54
+ </CalendarHeatmapBody>
55
+ <CalendarHeatmapFooter className="whitespace-normal">
56
+ <div className="flex w-full flex-wrap items-center justify-between gap-x-6 gap-y-2">
57
+ <p aria-live="polite" className="text-sm tabular-nums">
58
+ {picked ? `${picked.date}: ${signed(picked.value)}` : "Select a day for its profit or loss."}
59
+ </p>
60
+ <CalendarHeatmapLegend labels={{ less: "Loss", more: "Profit" }} valueFormatter={money} />
61
+ </div>
62
+ </CalendarHeatmapFooter>
63
+ </CalendarHeatmap>
64
+ </CardContent>
65
+ </Card>
66
+ );
67
+ }
@@ -13,10 +13,13 @@ import {
13
13
  } from "@siyahi/ui";
14
14
 
15
15
  // One cell per day for the last 60 days, as a status the heatmap colours:
16
- // 3 healthy (quiet grey), 2 degraded (--warning), 1 critical (--destructive).
16
+ // 3 healthy (quiet grey), 2 degraded (a half bar in --warning), 1 critical (a full bar in --destructive),
17
+ // 0 no data (a hollow outline).
17
18
  const data = Array.from({ length: 60 }, (_, i) => {
18
19
  const day = new Date(Date.UTC(2026, 7, 22 + i));
19
- return { date: day.toISOString().slice(0, 10), value: i % 17 === 5 ? 1 : i % 11 === 3 ? 2 : 3 };
20
+ // Two days in September the monitor itself was down, so there is no reading: 0.
21
+ const value = i === 30 || i === 31 ? 0 : i % 17 === 5 ? 1 : i % 11 === 3 ? 2 : 3;
22
+ return { date: day.toISOString().slice(0, 10), value };
20
23
  });
21
24
 
22
25
  export default function HeatmapStatusExample() {
@@ -30,7 +33,7 @@ export default function HeatmapStatusExample() {
30
33
  <CardDescription>Daily health: healthy, degraded or critical</CardDescription>
31
34
  </CardHeader>
32
35
  <CardContent>
33
- <StatusHeatmap data={data} statusValues={[3, 2, 1]}>
36
+ <StatusHeatmap data={data} statusValues={[3, 2, 1, 0]}>
34
37
  <StatusHeatmapBody>
35
38
  {({ activity, dayIndex }) => (
36
39
  <StatusHeatmapBlock
@@ -48,7 +51,6 @@ export default function HeatmapStatusExample() {
48
51
  ? `${picked.date}: ${["unknown", "critical", "degraded", "healthy"][picked.value] ?? "unknown"}`
49
52
  : "Select a day for its health. Use arrow keys to move between days."}
50
53
  </p>
51
- {/* No day lacks data, so the legend leaves out "No Data". */}
52
54
  <StatusHeatmapLegend />
53
55
  </div>
54
56
  </StatusHeatmapFooter>
@@ -0,0 +1,28 @@
1
+ import { HistogramChartCard, useFormat } from "@siyahi/ui";
2
+
3
+ // 2,000 checkout requests' response times in milliseconds: most are quick, a long tail is slow. Made-up figures from a
4
+ // fixed sequence, so the page draws the same every time.
5
+ let seed = 7;
6
+ const next = () => {
7
+ seed = (seed * 16807) % 2147483647;
8
+ return seed / 2147483647;
9
+ };
10
+ const times = Array.from({ length: 2000 }, () => Math.round(80 + 900 * next() ** 3 + 120 * next()));
11
+
12
+ export default function HistogramChartExample() {
13
+ const { count } = useFormat();
14
+ return (
15
+ <HistogramChartCard
16
+ title="Checkout response times"
17
+ description="The last 2,000 requests, counted in 100 ms buckets. Lines mark the median and the 95th percentile."
18
+ values={times}
19
+ valueFormatter={(ms) => `${count(ms)} ms`}
20
+ tickFormatter={count}
21
+ countLabel="Requests"
22
+ marks={["median", "p95"]}
23
+ xLabel="Response time (ms)"
24
+ yLabel="Requests"
25
+ height={280}
26
+ />
27
+ );
28
+ }
@@ -0,0 +1,63 @@
1
+ import { useState } from "react";
2
+ import { ScatterChartCard, useFormat, type ScatterPoint } from "@siyahi/ui";
3
+
4
+ // A grocer's products this quarter against last: x is the change in shelf price, y the change in units sold, and
5
+ // size the quarter's revenue; made-up figures. Largest first, so the biggest sellers take the labels.
6
+ const products: ScatterPoint[] = [
7
+ { name: "Basmati rice 5 kg", x: 0.06, y: -0.04, size: 1_840_000 },
8
+ { name: "Sunflower oil 1 L", x: -0.08, y: 0.12, size: 1_520_000 },
9
+ { name: "Full-cream milk", x: 0.03, y: 0.05, size: 1_310_000 },
10
+ { name: "Atta 10 kg", x: 0.04, y: -0.09, size: 1_120_000 },
11
+ { name: "Tea 500 g", x: -0.02, y: 0.08, size: 760_000 },
12
+ { name: "Paneer 200 g", x: 0.09, y: 0.11, size: 640_000 },
13
+ { name: "Toor dal 1 kg", x: 0.12, y: -0.18, size: 590_000 },
14
+ { name: "Biscuits family pack", x: -0.05, y: -0.03, size: 420_000 },
15
+ { name: "Instant noodles", x: 0.01, y: 0.21, size: 380_000 },
16
+ { name: "Dish soap", x: -0.1, y: -0.07, size: 260_000 },
17
+ { name: "Ghee 500 ml", x: 0.07, y: 0.02, size: 240_000 },
18
+ { name: "Coffee 200 g", x: -0.04, y: 0.15, size: 190_000 },
19
+ ];
20
+
21
+ export default function ScatterChartQuadrantsExample() {
22
+ const [selected, setSelected] = useState<ScatterPoint | null>(null);
23
+ const { moneyCompact, pct } = useFormat();
24
+ const signed = (value: number) => `${value > 0 ? "+" : value < 0 ? "−" : ""}${pct(Math.abs(value))}`;
25
+ return (
26
+ <ScatterChartCard
27
+ title="Products by price and sales"
28
+ description="This quarter against last. Across is the change in shelf price, up the change in units sold; a dot's area is the quarter's revenue."
29
+ points={products}
30
+ height={380}
31
+ xLabel="Change in price"
32
+ yLabel="Change in units sold"
33
+ xFormatter={signed}
34
+ yFormatter={signed}
35
+ sizeLabel="Revenue"
36
+ sizeFormatter={(value) => moneyCompact(value)}
37
+ selected={selected}
38
+ onSelect={setSelected}
39
+ footer={
40
+ <p data-slot="scatter-selection" aria-live="polite" className="tabular-nums">
41
+ {selected ? (
42
+ <>
43
+ <span className="font-medium">{selected.name}</span> · price {signed(selected.x)} · units{" "}
44
+ {signed(selected.y ?? 0)} · {moneyCompact(selected.size ?? 0)} revenue
45
+ </>
46
+ ) : (
47
+ <span className="text-muted-foreground">Tap a dot, or Tab to the chart and use the arrow keys</span>
48
+ )}
49
+ </p>
50
+ }
51
+ quadrants={{
52
+ x: 0,
53
+ y: 0,
54
+ labels: {
55
+ topLeft: "Cheaper, selling more",
56
+ topRight: "Dearer, still selling more",
57
+ bottomLeft: "Cheaper, selling less",
58
+ bottomRight: "Dearer, selling less",
59
+ },
60
+ }}
61
+ />
62
+ );
63
+ }
@@ -0,0 +1,75 @@
1
+ import { ScatterChartCard, useFormat, type ScatterPoint } from "@siyahi/ui";
2
+ import { useState } from "react";
3
+
4
+ interface Store extends ScatterPoint {
5
+ region: string;
6
+ sales: number;
7
+ }
8
+
9
+ // x is the store's share of the month's sales and y its growth on the same month last year; the numbers are made up
10
+ // for the catalogue. The Guwahati store opened this year, so it has no growth to plot. Largest first, so the biggest
11
+ // stores take the labels.
12
+ const stores: Store[] = [
13
+ { name: "Delhi Saket", region: "North", x: 0.112, y: 0.27, sales: 2760000 },
14
+ { name: "Bengaluru MG", region: "South", x: 0.106, y: -0.06, sales: 2625000 },
15
+ { name: "Chennai T Nagar", region: "South", x: 0.104, y: 0.44, sales: 2580000 },
16
+ { name: "Kolkata Park St", region: "East", x: 0.103, y: 0.16, sales: 2556000 },
17
+ { name: "Mumbai Online", key: "mumbai-online", region: "West", x: 0.091, y: 0.38, sales: 2244000 },
18
+ { name: "Pune Camp", region: "West", x: 0.067, y: 0.09, sales: 1660000 },
19
+ { name: "Mumbai Bandra", key: "mumbai-bandra", region: "West", x: 0.061, y: 0.12, sales: 1520000 },
20
+ { name: "Hyderabad", region: "South", x: 0.052, y: -0.14, sales: 1290000 },
21
+ { name: "Ahmedabad", region: "West", x: 0.044, y: 0.05, sales: 1090000 },
22
+ { name: "Surat", region: "West", x: 0.04, y: -0.21, sales: 980000 },
23
+ { name: "Kochi", region: "South", x: 0.035, y: 0.58, sales: 870000 },
24
+ { name: "Lucknow", region: "North", x: 0.031, y: -0.02, sales: 770000 },
25
+ { name: "Jaipur", region: "North", x: 0.026, y: -0.33, sales: 640000 },
26
+ { name: "Bhubaneswar", region: "East", x: 0.021, y: 0.22, sales: 520000 },
27
+ { name: "Indore", region: "Central", x: 0.018, y: 0.31, sales: 450000 },
28
+ { name: "Bhopal", region: "Central", x: 0.012, y: -0.09, sales: 300000 },
29
+ { name: "Patna", region: "East", x: 0.009, y: 0.47, sales: 220000 },
30
+ { name: "Nagpur", region: "Central", x: 0.007, y: 0.02, sales: 170000 },
31
+ { name: "Shillong", region: "Northeast", x: 0.004, y: -0.18, sales: 100000 },
32
+ { name: "Guwahati", region: "Northeast", x: 0.021, y: null, sales: 520000 },
33
+ ];
34
+
35
+ export default function ScatterChartExample() {
36
+ const [selected, setSelected] = useState<Store | null>(null);
37
+ const { money, pct } = useFormat();
38
+ const growth = (y: number | null) => (y === null ? "new this year" : `${y < 0 ? "−" : "+"}${pct(Math.abs(y))}`);
39
+ const missing = stores.filter((s) => s.y === null).length;
40
+ return (
41
+ <ScatterChartCard
42
+ title="Stores by size and growth"
43
+ description="Across is the store's share of sales (log scale), up its growth on last year. Hover or tap a dot."
44
+ points={stores}
45
+ height={360}
46
+ xLabel="Share of sales"
47
+ yLabel="Growth"
48
+ xScale="log"
49
+ xFormatter={(x) => pct(x)}
50
+ yFormatter={(y) => growth(y)}
51
+ details={(s) => [
52
+ ["Region", s.region],
53
+ ["Sales", money(s.sales)],
54
+ ["Growth", growth(s.y)],
55
+ ]}
56
+ selected={selected}
57
+ onSelect={setSelected}
58
+ footer={
59
+ <>
60
+ <p data-slot="scatter-selection" aria-live="polite" className="tabular-nums">
61
+ {selected ? (
62
+ <>
63
+ <span className="font-medium">{selected.name}</span> · {selected.region} · {money(selected.sales)} ·{" "}
64
+ {growth(selected.y)}
65
+ </>
66
+ ) : (
67
+ <span className="text-muted-foreground">Tap a dot for its figures</span>
68
+ )}
69
+ </p>
70
+ <p className="text-muted-foreground">{missing} store opened this year and has no growth to plot.</p>
71
+ </>
72
+ }
73
+ />
74
+ );
75
+ }
@@ -0,0 +1,33 @@
1
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, SlopeChart, type SlopeItem } from "@siyahi/ui";
2
+
3
+ // Each aisle's share of one grocery store's sales against its share across the region's stores; made-up figures.
4
+ const items: SlopeItem[] = [
5
+ { name: "Fresh fruit and vegetables", from: 0.18, to: 0.24 },
6
+ { name: "Dairy and eggs", from: 0.14, to: 0.15 },
7
+ { name: "Bakery", from: 0.07, to: 0.11 },
8
+ { name: "Rice, flour and pulses", from: 0.16, to: 0.12 },
9
+ { name: "Snacks and drinks", from: 0.15, to: 0.13 },
10
+ { name: "Household and cleaning", from: 0.11, to: 0.08 },
11
+ { name: "Personal care", from: 0.09, to: 0.08 },
12
+ { name: "Frozen food", from: 0.06, to: 0.05 },
13
+ { name: "Baby care", from: 0.04, to: 0.04 },
14
+ ];
15
+
16
+ // The aisles at least three points away from the region, either way.
17
+ const apart = items.filter(({ from, to }) => Math.abs(to - from) >= 0.03).map(({ name }) => name);
18
+
19
+ export default function SlopeChartMarketExample() {
20
+ return (
21
+ <Card>
22
+ <CardHeader>
23
+ <CardTitle>Our aisles against the region</CardTitle>
24
+ <CardDescription>
25
+ Share of sales by aisle. Aisles three points or more away from the region are drawn in colour.
26
+ </CardDescription>
27
+ </CardHeader>
28
+ <CardContent>
29
+ <SlopeChart items={items} fromLabel="Region" toLabel="This store, this year" highlighted={apart} />
30
+ </CardContent>
31
+ </Card>
32
+ );
33
+ }
@@ -0,0 +1,26 @@
1
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, SlopeChart, type SlopeItem } from "@siyahi/ui";
2
+
3
+ // Each channel's share of a bookshop's sales, last year and this; made-up figures.
4
+ const items: SlopeItem[] = [
5
+ { name: "In store", from: 0.46, to: 0.38 },
6
+ { name: "Own website", from: 0.18, to: 0.27 },
7
+ { name: "Marketplaces", from: 0.21, to: 0.2 },
8
+ { name: "Schools and libraries", from: 0.09, to: 0.1 },
9
+ { name: "Events and fairs", from: 0.06, to: 0.05 },
10
+ ];
11
+
12
+ export default function SlopeChartExample() {
13
+ return (
14
+ <Card>
15
+ <CardHeader>
16
+ <CardTitle>Sales by channel</CardTitle>
17
+ <CardDescription>
18
+ Each channel's share of the year's sales. The website gained what the shop lost.
19
+ </CardDescription>
20
+ </CardHeader>
21
+ <CardContent>
22
+ <SlopeChart items={items} fromLabel="2025" toLabel="2026" highlighted={["In store", "Own website"]} />
23
+ </CardContent>
24
+ </Card>
25
+ );
26
+ }
@@ -0,0 +1,27 @@
1
+ import { WaterfallChartCard, useFormat, type WaterfallStep } from "@siyahi/ui";
2
+
3
+ // Revenue from last year to this, store by store; made-up figures.
4
+ const steps: WaterfallStep[] = [
5
+ { kind: "total", label: "Last year", value: 4_820_000 },
6
+ { kind: "change", label: "Koramangala flagship", value: 640_000 },
7
+ { kind: "change", label: "Indiranagar high street", value: 310_000 },
8
+ { kind: "change", label: "Whitefield mall kiosk", value: -420_000 },
9
+ { kind: "change", label: "Online orders and delivery", value: 880_000 },
10
+ { kind: "change", label: "Jayanagar (closed in June)", value: -560_000 },
11
+ { kind: "total", label: "This year" },
12
+ ];
13
+
14
+ export default function WaterfallChartStoresExample() {
15
+ const { money, moneyCompact } = useFormat();
16
+ return (
17
+ <WaterfallChartCard
18
+ title="Revenue, last year to this"
19
+ description="What each store added or lost on the way from last year's total to this year's"
20
+ steps={steps}
21
+ horizontal
22
+ valueFormatter={money}
23
+ yTickFormatter={moneyCompact}
24
+ height={steps.length * 48 + 64}
25
+ />
26
+ );
27
+ }
@@ -0,0 +1,37 @@
1
+ import { WaterfallChartCard, useFormat, type WaterfallStep } from "@siyahi/ui";
2
+
3
+ // A bakery's profit for the year, April to March, month by month; made-up figures.
4
+ const months: [string, number][] = [
5
+ ["Apr", 42000],
6
+ ["May", 38000],
7
+ ["Jun", -12000],
8
+ ["Jul", 26000],
9
+ ["Aug", 31000],
10
+ ["Sep", -8000],
11
+ ["Oct", 54000],
12
+ ["Nov", 61000],
13
+ ["Dec", 72000],
14
+ ["Jan", -21000],
15
+ ["Feb", 18000],
16
+ ["Mar", 35000],
17
+ ];
18
+ const steps: WaterfallStep[] = [
19
+ ...months.map(([label, value]): WaterfallStep => ({ kind: "change", label, value })),
20
+ // No value: the year's total is wherever the months left it.
21
+ { kind: "total", label: "Year" },
22
+ ];
23
+
24
+ export default function WaterfallChartExample() {
25
+ const { money, moneyCompact } = useFormat();
26
+ return (
27
+ <WaterfallChartCard
28
+ title="Profit for the year, month by month"
29
+ description="April to March, before tax. Each bar starts where the month before left off."
30
+ steps={steps}
31
+ labels={{ rise: "Profit", fall: "Loss", total: "Year to date" }}
32
+ valueFormatter={money}
33
+ yTickFormatter={moneyCompact}
34
+ height={300}
35
+ />
36
+ );
37
+ }