@alpic-ai/ui 0.0.0-dev.g2d48d44 → 0.0.0-dev.g2eefcc2
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.
- package/dist/components/accordion-card.d.mts +5 -6
- package/dist/components/accordion.d.mts +5 -6
- package/dist/components/alert.d.mts +9 -11
- package/dist/components/area-chart.d.mts +62 -0
- package/dist/components/area-chart.mjs +269 -0
- package/dist/components/attachment-tile.d.mts +1 -3
- package/dist/components/avatar.d.mts +8 -10
- package/dist/components/badge.d.mts +2 -4
- package/dist/components/bar-chart.d.mts +48 -0
- package/dist/components/bar-chart.mjs +256 -0
- package/dist/components/bar-list.d.mts +28 -0
- package/dist/components/bar-list.mjs +98 -0
- package/dist/components/breadcrumb.d.mts +10 -11
- package/dist/components/button.d.mts +6 -8
- package/dist/components/card.d.mts +9 -10
- package/dist/components/chart-card.d.mts +25 -0
- package/dist/components/chart-card.mjs +48 -0
- package/dist/components/chart-container.d.mts +20 -0
- package/dist/components/chart-container.mjs +37 -0
- package/dist/components/chart-legend.d.mts +16 -0
- package/dist/components/chart-legend.mjs +26 -0
- package/dist/components/chart-tooltip.d.mts +33 -0
- package/dist/components/chart-tooltip.mjs +52 -0
- package/dist/components/checkbox.d.mts +2 -3
- package/dist/components/collapsible.d.mts +4 -5
- package/dist/components/combobox.d.mts +12 -11
- package/dist/components/combobox.mjs +7 -4
- package/dist/components/command.d.mts +9 -10
- package/dist/components/copyable.d.mts +2 -3
- package/dist/components/description-list.d.mts +5 -6
- package/dist/components/dialog.d.mts +15 -17
- package/dist/components/donut-chart.d.mts +46 -0
- package/dist/components/donut-chart.mjs +185 -0
- package/dist/components/dropdown-menu.d.mts +18 -20
- package/dist/components/form.d.mts +38 -21
- package/dist/components/form.mjs +6 -6
- package/dist/components/github-button.d.mts +1 -2
- package/dist/components/heatmap-chart.d.mts +40 -0
- package/dist/components/heatmap-chart.mjs +198 -0
- package/dist/components/input-group.d.mts +5 -7
- package/dist/components/input.d.mts +4 -5
- package/dist/components/input.mjs +2 -2
- package/dist/components/label.d.mts +2 -3
- package/dist/components/line-chart.d.mts +55 -0
- package/dist/components/line-chart.mjs +211 -0
- package/dist/components/page-loader.d.mts +1 -3
- package/dist/components/pagination.d.mts +3 -4
- package/dist/components/popover.d.mts +5 -6
- package/dist/components/radio-group.d.mts +3 -4
- package/dist/components/scroll-area.d.mts +3 -4
- package/dist/components/select-trigger-variants.d.mts +1 -3
- package/dist/components/select.d.mts +9 -10
- package/dist/components/separator.d.mts +2 -3
- package/dist/components/sheet.d.mts +11 -12
- package/dist/components/shimmer-text.d.mts +2 -2
- package/dist/components/sidebar.d.mts +34 -36
- package/dist/components/sidebar.mjs +10 -10
- package/dist/components/skeleton.d.mts +2 -4
- package/dist/components/sonner.d.mts +5 -6
- package/dist/components/spinner.d.mts +3 -5
- package/dist/components/stat.d.mts +30 -0
- package/dist/components/stat.mjs +107 -0
- package/dist/components/status-dot.d.mts +2 -4
- package/dist/components/switch.d.mts +2 -3
- package/dist/components/table.d.mts +10 -11
- package/dist/components/tabs.d.mts +12 -14
- package/dist/components/tag.d.mts +3 -5
- package/dist/components/task-progress.d.mts +1 -3
- package/dist/components/textarea.d.mts +3 -4
- package/dist/components/textarea.mjs +2 -2
- package/dist/components/toggle-group.d.mts +4 -6
- package/dist/components/toggle-group.mjs +3 -3
- package/dist/components/tooltip-icon-button.d.mts +1 -2
- package/dist/components/tooltip.d.mts +5 -6
- package/dist/components/typography.d.mts +4 -5
- package/dist/components/wizard.d.mts +4 -23
- package/dist/components/wizard.mjs +1 -19
- package/dist/hooks/use-chart-theme.d.mts +18 -0
- package/dist/hooks/use-chart-theme.mjs +57 -0
- package/dist/hooks/use-mobile.mjs +3 -3
- package/dist/hooks/use-reduced-motion.d.mts +4 -0
- package/dist/hooks/use-reduced-motion.mjs +16 -0
- package/dist/lib/chart-palette.d.mts +4 -0
- package/dist/lib/chart-palette.mjs +95 -0
- package/dist/lib/chart.d.mts +14 -0
- package/dist/lib/chart.mjs +27 -0
- package/package.json +30 -29
- package/src/components/area-chart.tsx +339 -0
- package/src/components/bar-chart.tsx +309 -0
- package/src/components/bar-list.tsx +150 -0
- package/src/components/chart-card.tsx +63 -0
- package/src/components/chart-container.tsx +49 -0
- package/src/components/chart-legend.tsx +41 -0
- package/src/components/chart-tooltip.tsx +93 -0
- package/src/components/combobox.tsx +9 -2
- package/src/components/donut-chart.tsx +217 -0
- package/src/components/heatmap-chart.tsx +287 -0
- package/src/components/line-chart.tsx +264 -0
- package/src/components/stat.tsx +109 -0
- package/src/components/wizard.tsx +1 -35
- package/src/hooks/use-chart-theme.ts +75 -0
- package/src/hooks/use-reduced-motion.ts +17 -0
- package/src/lib/chart-palette.ts +110 -0
- package/src/lib/chart.ts +56 -0
- package/src/stories/area-chart.stories.tsx +198 -0
- package/src/stories/bar-chart.stories.tsx +167 -0
- package/src/stories/bar-list.stories.tsx +83 -0
- package/src/stories/donut-chart.stories.tsx +110 -0
- package/src/stories/heatmap-chart.stories.tsx +105 -0
- package/src/stories/line-chart.stories.tsx +144 -0
- package/src/stories/stat.stories.tsx +64 -0
- package/src/stories/wizard.stories.tsx +22 -4
- package/src/styles/tokens.css +63 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
|
|
3
|
+
import { AreaChart } from "../components/area-chart";
|
|
4
|
+
import { ChartCard } from "../components/chart-card";
|
|
5
|
+
import { Stat } from "../components/stat";
|
|
6
|
+
|
|
7
|
+
export default { title: "Charts/Area Chart" };
|
|
8
|
+
|
|
9
|
+
const mulberry32 = (seed: number) => () => {
|
|
10
|
+
seed |= 0;
|
|
11
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
12
|
+
let hash = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
13
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
14
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const CLIENTS = ["ChatGPT", "Claude Code", "Claude", "Anthropic", "Goose", "VS Code"];
|
|
18
|
+
const WEIGHTS = [0.42, 0.24, 0.12, 0.09, 0.07, 0.06];
|
|
19
|
+
|
|
20
|
+
const hourLabel = (index: number, length: number) =>
|
|
21
|
+
`${String(Math.round((index / (length - 1)) * 24)).padStart(2, "0")}:00`;
|
|
22
|
+
|
|
23
|
+
const genStacked = (seed: number) => {
|
|
24
|
+
const rnd = mulberry32(seed);
|
|
25
|
+
const length = 25;
|
|
26
|
+
return Array.from({ length }, (_, index) => {
|
|
27
|
+
const tide = 0.55 + 0.45 * Math.sin((index / length) * Math.PI * 1.6 - 0.4);
|
|
28
|
+
const row: Record<string, number | string> = { t: hourLabel(index, length) };
|
|
29
|
+
CLIENTS.forEach((client, clientIndex) => {
|
|
30
|
+
// biome-ignore lint/style/noNonNullAssertion: index aligned with CLIENTS
|
|
31
|
+
row[client] = Math.max(2, Math.round(120 * WEIGHTS[clientIndex]! * tide * (0.7 + rnd() * 0.6)));
|
|
32
|
+
});
|
|
33
|
+
return row;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const genSeries = (seed: number, scale: number) => {
|
|
38
|
+
const rnd = mulberry32(seed);
|
|
39
|
+
const length = 30;
|
|
40
|
+
let value = scale * 0.6;
|
|
41
|
+
return Array.from({ length }, (_, index) => {
|
|
42
|
+
value = Math.max(scale * 0.12, value + (rnd() - 0.46) * scale * 0.18);
|
|
43
|
+
return { t: hourLabel(index, length), v: Math.round(value * (1 + Math.sin(index / 5) * 0.16)) };
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const genLatency = (seed: number) => {
|
|
48
|
+
const rnd = mulberry32(seed);
|
|
49
|
+
const length = 30;
|
|
50
|
+
let p50 = 240;
|
|
51
|
+
let p95 = 720;
|
|
52
|
+
return Array.from({ length }, (_, index) => {
|
|
53
|
+
p50 = Math.max(120, p50 + (rnd() - 0.48) * 40);
|
|
54
|
+
p95 = Math.max(p50 + 200, p95 + (rnd() - 0.46) * 120);
|
|
55
|
+
return { t: hourLabel(index, length), p50: Math.round(p50), p95: Math.round(p95 * (index === 19 ? 1.7 : 1)) };
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const genErrors = (seed: number) => {
|
|
60
|
+
const rnd = mulberry32(seed);
|
|
61
|
+
const length = 30;
|
|
62
|
+
return Array.from({ length }, (_, index) => {
|
|
63
|
+
const burst = index > 14 && index < 20 ? 3.4 : 1;
|
|
64
|
+
return {
|
|
65
|
+
t: hourLabel(index, length),
|
|
66
|
+
mcp: Math.max(0, Math.round(4 * (0.3 + rnd()) * burst)),
|
|
67
|
+
tool: Math.max(0, Math.round(6 * (0.3 + rnd()) * burst)),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const stacked = genStacked(7);
|
|
73
|
+
const tokens = genSeries(11, 84000);
|
|
74
|
+
const latency = genLatency(31);
|
|
75
|
+
const errors = genErrors(41);
|
|
76
|
+
|
|
77
|
+
const sessionsSeries = CLIENTS.map((key) => ({ key }));
|
|
78
|
+
const nf = (value: number) => value.toLocaleString("en-US");
|
|
79
|
+
const fmtK = (value: number) => (value >= 1000 ? `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k` : `${value}`);
|
|
80
|
+
const ms = (value: number) => `${value}ms`;
|
|
81
|
+
|
|
82
|
+
const sessionsTotal = stacked.reduce(
|
|
83
|
+
(sum, row) => sum + CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0),
|
|
84
|
+
0,
|
|
85
|
+
);
|
|
86
|
+
const sessionsSpark = stacked.map((row) => CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0));
|
|
87
|
+
|
|
88
|
+
const latencyPeak = latency.reduce((best, row) => (row.p95 > best.p95 ? row : best));
|
|
89
|
+
const errorsPeak = errors.reduce((best, row) => (row.mcp + row.tool > best.mcp + best.tool ? row : best));
|
|
90
|
+
|
|
91
|
+
export const AllVariants: Story = () => (
|
|
92
|
+
<div className="chart-canvas mx-auto max-w-[1600px] p-8">
|
|
93
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
94
|
+
<ChartCard
|
|
95
|
+
palette="magenta"
|
|
96
|
+
accent="left"
|
|
97
|
+
kicker="Last 24h"
|
|
98
|
+
title="Sessions"
|
|
99
|
+
description="Full-width · left accent"
|
|
100
|
+
className="md:col-span-2 xl:col-span-3"
|
|
101
|
+
>
|
|
102
|
+
<AreaChart data={stacked} index="t" series={sessionsSeries} variant="stacked" legend valueFormatter={nf} />
|
|
103
|
+
</ChartCard>
|
|
104
|
+
|
|
105
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Sessions" description="By client">
|
|
106
|
+
<Stat
|
|
107
|
+
value={nf(sessionsTotal)}
|
|
108
|
+
unit="sessions"
|
|
109
|
+
delta={{ value: 12.4, direction: "up" }}
|
|
110
|
+
sparkline={sessionsSpark}
|
|
111
|
+
/>
|
|
112
|
+
<AreaChart data={stacked} index="t" series={sessionsSeries} variant="stacked" legend valueFormatter={nf} />
|
|
113
|
+
</ChartCard>
|
|
114
|
+
|
|
115
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Sessions share" description="Composition over time">
|
|
116
|
+
<AreaChart data={stacked} index="t" series={sessionsSeries} variant="expand" legend valueFormatter={nf} />
|
|
117
|
+
</ChartCard>
|
|
118
|
+
|
|
119
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Sessions" description="Grouped overlay · last values">
|
|
120
|
+
<AreaChart
|
|
121
|
+
data={stacked}
|
|
122
|
+
index="t"
|
|
123
|
+
series={sessionsSeries.slice(0, 3)}
|
|
124
|
+
variant="grouped"
|
|
125
|
+
lastValueLabel
|
|
126
|
+
legend
|
|
127
|
+
valueFormatter={nf}
|
|
128
|
+
/>
|
|
129
|
+
</ChartCard>
|
|
130
|
+
|
|
131
|
+
<ChartCard palette="cyan" kicker="Last 24h" title="Output tokens" description="Value flags · last value">
|
|
132
|
+
<Stat value="1.4M" unit="tokens" delta={{ value: 8.1, direction: "up" }} />
|
|
133
|
+
<AreaChart
|
|
134
|
+
data={tokens}
|
|
135
|
+
index="t"
|
|
136
|
+
series={[{ key: "v", name: "tokens" }]}
|
|
137
|
+
variant="grouped"
|
|
138
|
+
lastValueLabel
|
|
139
|
+
valueFlags
|
|
140
|
+
valueFormatter={fmtK}
|
|
141
|
+
/>
|
|
142
|
+
</ChartCard>
|
|
143
|
+
|
|
144
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Request latency" description="p50 vs p95 · SLA band + peak">
|
|
145
|
+
<Stat value="240ms" unit="p50 · current" delta={{ value: 4.2, direction: "down", invert: true }} />
|
|
146
|
+
<AreaChart
|
|
147
|
+
data={latency}
|
|
148
|
+
index="t"
|
|
149
|
+
series={[
|
|
150
|
+
{ key: "p50", name: "p50" },
|
|
151
|
+
{ key: "p95", name: "p95", dashed: true, color: "#9b5de5" },
|
|
152
|
+
]}
|
|
153
|
+
variant="grouped"
|
|
154
|
+
referenceLine={{ y: 1000, label: "SLA 1s", band: true }}
|
|
155
|
+
markers={[{ x: latencyPeak.t, y: latencyPeak.p95, label: "peak", color: "#9b5de5" }]}
|
|
156
|
+
legend
|
|
157
|
+
valueFormatter={ms}
|
|
158
|
+
/>
|
|
159
|
+
</ChartCard>
|
|
160
|
+
|
|
161
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Errors" description="Semantic red · palette-independent">
|
|
162
|
+
<Stat value="312" unit="errors" delta={{ value: 0, direction: "up", invert: true, label: "burst 16:00" }} />
|
|
163
|
+
<AreaChart
|
|
164
|
+
data={errors}
|
|
165
|
+
index="t"
|
|
166
|
+
series={[
|
|
167
|
+
{ key: "tool", name: "Tool errors", semantic: "warning" },
|
|
168
|
+
{ key: "mcp", name: "MCP errors", semantic: "error" },
|
|
169
|
+
]}
|
|
170
|
+
variant="stacked"
|
|
171
|
+
markers={[{ x: errorsPeak.t, y: errorsPeak.mcp + errorsPeak.tool, label: "burst" }]}
|
|
172
|
+
legend
|
|
173
|
+
valueFormatter={nf}
|
|
174
|
+
/>
|
|
175
|
+
</ChartCard>
|
|
176
|
+
|
|
177
|
+
<ChartCard palette="cyan" kicker="Last 24h" title="Tasks" description="Hatch lead · stepped">
|
|
178
|
+
<AreaChart
|
|
179
|
+
data={tokens}
|
|
180
|
+
index="t"
|
|
181
|
+
series={[{ key: "v", name: "tokens" }]}
|
|
182
|
+
curve="step"
|
|
183
|
+
variant="grouped"
|
|
184
|
+
texture
|
|
185
|
+
valueFormatter={fmtK}
|
|
186
|
+
/>
|
|
187
|
+
</ChartCard>
|
|
188
|
+
|
|
189
|
+
<ChartCard palette="magenta" kicker="State" title="Loading" description="Mono spinner placeholder">
|
|
190
|
+
<AreaChart data={[]} index="t" series={sessionsSeries} loading />
|
|
191
|
+
</ChartCard>
|
|
192
|
+
|
|
193
|
+
<ChartCard palette="magenta" kicker="State" title="Empty" description="No data in range">
|
|
194
|
+
<AreaChart data={[]} index="t" series={sessionsSeries} />
|
|
195
|
+
</ChartCard>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
|
|
3
|
+
import { BarChart } from "../components/bar-chart";
|
|
4
|
+
import { ChartCard } from "../components/chart-card";
|
|
5
|
+
import { Stat } from "../components/stat";
|
|
6
|
+
|
|
7
|
+
export default { title: "Charts/Bar Chart" };
|
|
8
|
+
|
|
9
|
+
const mulberry32 = (seed: number) => () => {
|
|
10
|
+
seed |= 0;
|
|
11
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
12
|
+
let hash = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
13
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
14
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const CLIENTS = ["ChatGPT", "Claude Code", "Claude", "Anthropic", "Goose", "VS Code"];
|
|
18
|
+
const WEIGHTS = [0.42, 0.24, 0.12, 0.09, 0.07, 0.06];
|
|
19
|
+
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
20
|
+
|
|
21
|
+
const genDailyStacked = (seed: number) => {
|
|
22
|
+
const rnd = mulberry32(seed);
|
|
23
|
+
return DAYS.map((day, dayIndex) => {
|
|
24
|
+
const tide = 0.6 + 0.4 * Math.sin((dayIndex / DAYS.length) * Math.PI * 1.4);
|
|
25
|
+
const row: Record<string, number | string> = { t: day };
|
|
26
|
+
CLIENTS.forEach((client, clientIndex) => {
|
|
27
|
+
// biome-ignore lint/style/noNonNullAssertion: index aligned with CLIENTS
|
|
28
|
+
row[client] = Math.max(4, Math.round(900 * WEIGHTS[clientIndex]! * tide * (0.7 + rnd() * 0.6)));
|
|
29
|
+
});
|
|
30
|
+
return row;
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const genDuration = (seed: number) => {
|
|
35
|
+
const rnd = mulberry32(seed);
|
|
36
|
+
return DAYS.map((day, dayIndex) => ({
|
|
37
|
+
t: day,
|
|
38
|
+
p50: Math.round(240 + rnd() * 120),
|
|
39
|
+
p95: Math.round(820 + rnd() * 360 * (dayIndex === 4 ? 1.6 : 1)),
|
|
40
|
+
}));
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const genErrors = (seed: number) => {
|
|
44
|
+
const rnd = mulberry32(seed);
|
|
45
|
+
return DAYS.map((day, dayIndex) => {
|
|
46
|
+
const burst = dayIndex === 4 ? 3 : 1;
|
|
47
|
+
return {
|
|
48
|
+
t: day,
|
|
49
|
+
tool: Math.max(0, Math.round(6 * (0.4 + rnd()) * burst)),
|
|
50
|
+
mcp: Math.max(0, Math.round(4 * (0.4 + rnd()) * burst)),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const genThroughput = (seed: number) => {
|
|
56
|
+
const rnd = mulberry32(seed);
|
|
57
|
+
return DAYS.map((day) => ({ t: day, v: Math.round(38000 + rnd() * 46000) }));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const stacked = genDailyStacked(7);
|
|
61
|
+
const duration = genDuration(19);
|
|
62
|
+
const errors = genErrors(41);
|
|
63
|
+
const throughput = genThroughput(11);
|
|
64
|
+
|
|
65
|
+
const sessionsSeries = CLIENTS.map((key) => ({ key }));
|
|
66
|
+
const nf = (value: number) => value.toLocaleString("en-US");
|
|
67
|
+
const fmtK = (value: number) => (value >= 1000 ? `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k` : `${value}`);
|
|
68
|
+
const ms = (value: number) => `${value}ms`;
|
|
69
|
+
|
|
70
|
+
const sessionsTotal = stacked.reduce(
|
|
71
|
+
(sum, row) => sum + CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0),
|
|
72
|
+
0,
|
|
73
|
+
);
|
|
74
|
+
const sessionsSpark = stacked.map((row) => CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0));
|
|
75
|
+
|
|
76
|
+
const errorsPeak = errors.reduce((best, row) => (row.mcp + row.tool > best.mcp + best.tool ? row : best));
|
|
77
|
+
|
|
78
|
+
export const AllVariants: Story = () => (
|
|
79
|
+
<div className="chart-canvas mx-auto max-w-[1600px] p-8">
|
|
80
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
81
|
+
<ChartCard
|
|
82
|
+
palette="magenta"
|
|
83
|
+
accent="left"
|
|
84
|
+
kicker="Last 7d"
|
|
85
|
+
title="Sessions"
|
|
86
|
+
description="Full-width · left accent"
|
|
87
|
+
className="md:col-span-2 xl:col-span-3"
|
|
88
|
+
>
|
|
89
|
+
<BarChart data={stacked} index="t" series={sessionsSeries} variant="stacked" legend valueFormatter={nf} />
|
|
90
|
+
</ChartCard>
|
|
91
|
+
|
|
92
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Sessions" description="By client · stacked">
|
|
93
|
+
<Stat
|
|
94
|
+
value={nf(sessionsTotal)}
|
|
95
|
+
unit="sessions"
|
|
96
|
+
delta={{ value: 9.3, direction: "up" }}
|
|
97
|
+
sparkline={sessionsSpark}
|
|
98
|
+
/>
|
|
99
|
+
<BarChart data={stacked} index="t" series={sessionsSeries} variant="stacked" legend valueFormatter={nf} />
|
|
100
|
+
</ChartCard>
|
|
101
|
+
|
|
102
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Sessions share" description="Composition · 100% stacked">
|
|
103
|
+
<BarChart data={stacked} index="t" series={sessionsSeries} variant="expand" legend valueFormatter={nf} />
|
|
104
|
+
</ChartCard>
|
|
105
|
+
|
|
106
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Request duration" description="p50 vs p95 · grouped">
|
|
107
|
+
<BarChart
|
|
108
|
+
data={duration}
|
|
109
|
+
index="t"
|
|
110
|
+
series={[
|
|
111
|
+
{ key: "p50", name: "p50" },
|
|
112
|
+
{ key: "p95", name: "p95", color: "#9b5de5" },
|
|
113
|
+
]}
|
|
114
|
+
variant="grouped"
|
|
115
|
+
legend
|
|
116
|
+
valueFormatter={ms}
|
|
117
|
+
/>
|
|
118
|
+
</ChartCard>
|
|
119
|
+
|
|
120
|
+
<ChartCard palette="cyan" kicker="Last 7d" title="Output tokens" description="Single series · value labels">
|
|
121
|
+
<Stat value="1.4M" unit="tokens" delta={{ value: 8.1, direction: "up" }} />
|
|
122
|
+
<BarChart
|
|
123
|
+
data={throughput}
|
|
124
|
+
index="t"
|
|
125
|
+
series={[{ key: "v", name: "tokens" }]}
|
|
126
|
+
valueLabels
|
|
127
|
+
valueFormatter={fmtK}
|
|
128
|
+
/>
|
|
129
|
+
</ChartCard>
|
|
130
|
+
|
|
131
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Errors" description="Semantic red · burst marker">
|
|
132
|
+
<Stat value="312" unit="errors" delta={{ value: 0, direction: "up", invert: true, label: "burst Fri" }} />
|
|
133
|
+
<BarChart
|
|
134
|
+
data={errors}
|
|
135
|
+
index="t"
|
|
136
|
+
series={[
|
|
137
|
+
{ key: "tool", name: "Tool errors", semantic: "warning" },
|
|
138
|
+
{ key: "mcp", name: "MCP errors", semantic: "error" },
|
|
139
|
+
]}
|
|
140
|
+
variant="stacked"
|
|
141
|
+
markers={[{ x: errorsPeak.t, y: errorsPeak.mcp + errorsPeak.tool, label: "burst" }]}
|
|
142
|
+
legend
|
|
143
|
+
valueFormatter={nf}
|
|
144
|
+
/>
|
|
145
|
+
</ChartCard>
|
|
146
|
+
|
|
147
|
+
<ChartCard palette="cyan" kicker="Last 7d" title="Throughput" description="Hatch lead · SLA band">
|
|
148
|
+
<BarChart
|
|
149
|
+
data={throughput}
|
|
150
|
+
index="t"
|
|
151
|
+
series={[{ key: "v", name: "tokens" }]}
|
|
152
|
+
texture
|
|
153
|
+
referenceLine={{ y: 80000, label: "capacity", band: true }}
|
|
154
|
+
valueFormatter={fmtK}
|
|
155
|
+
/>
|
|
156
|
+
</ChartCard>
|
|
157
|
+
|
|
158
|
+
<ChartCard palette="magenta" kicker="State" title="Loading" description="Mono spinner placeholder">
|
|
159
|
+
<BarChart data={[]} index="t" series={sessionsSeries} loading />
|
|
160
|
+
</ChartCard>
|
|
161
|
+
|
|
162
|
+
<ChartCard palette="magenta" kicker="State" title="Empty" description="No data in range">
|
|
163
|
+
<BarChart data={[]} index="t" series={sessionsSeries} />
|
|
164
|
+
</ChartCard>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
|
|
3
|
+
import { BarList } from "../components/bar-list";
|
|
4
|
+
import { ChartCard } from "../components/chart-card";
|
|
5
|
+
import { Stat } from "../components/stat";
|
|
6
|
+
|
|
7
|
+
export default { title: "Charts/Bar List" };
|
|
8
|
+
|
|
9
|
+
const TOP_TOOLS = [
|
|
10
|
+
{ tool: "search_codebase", calls: 18420 },
|
|
11
|
+
{ tool: "read_file", calls: 14110 },
|
|
12
|
+
{ tool: "run_command", calls: 9870 },
|
|
13
|
+
{ tool: "edit_file", calls: 8240 },
|
|
14
|
+
{ tool: "list_dir", calls: 5130 },
|
|
15
|
+
{ tool: "grep", calls: 3960 },
|
|
16
|
+
{ tool: "web_fetch", calls: 2210 },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const TOP_CLIENTS = [
|
|
20
|
+
{ client: "ChatGPT", sessions: 16222 },
|
|
21
|
+
{ client: "Claude Code", sessions: 9502 },
|
|
22
|
+
{ client: "Claude", sessions: 6859 },
|
|
23
|
+
{ client: "Anthropic", sessions: 4664 },
|
|
24
|
+
{ client: "Goose", sessions: 3389 },
|
|
25
|
+
{ client: "VS Code", sessions: 2771 },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const TOP_ROUTES = [
|
|
29
|
+
{ route: "POST /v1/messages", hits: 482000 },
|
|
30
|
+
{ route: "GET /v1/models", hits: 211400 },
|
|
31
|
+
{ route: "POST /v1/embeddings", hits: 98700 },
|
|
32
|
+
{ route: "GET /v1/usage", hits: 64200 },
|
|
33
|
+
{ route: "POST /v1/files", hits: 41900 },
|
|
34
|
+
{ route: "GET /v1/health", hits: 28800 },
|
|
35
|
+
{ route: "DELETE /v1/files", hits: 12400 },
|
|
36
|
+
{ route: "POST /v1/batches", hits: 8600 },
|
|
37
|
+
{ route: "GET /v1/batches", hits: 5300 },
|
|
38
|
+
{ route: "POST /v1/moderations", hits: 2100 },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const nf = (value: number) => value.toLocaleString("en-US");
|
|
42
|
+
const fmtK = (value: number) => {
|
|
43
|
+
if (value >= 1_000_000) {
|
|
44
|
+
return `${(value / 1_000_000).toFixed(2)}M`;
|
|
45
|
+
}
|
|
46
|
+
if (value >= 1000) {
|
|
47
|
+
return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k`;
|
|
48
|
+
}
|
|
49
|
+
return `${value}`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const toolCallsTotal = TOP_TOOLS.reduce((sum, row) => sum + row.calls, 0);
|
|
53
|
+
|
|
54
|
+
export const AllVariants: Story = () => (
|
|
55
|
+
<div className="chart-canvas mx-auto max-w-[1600px] p-8">
|
|
56
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
57
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Top tools" description="Ranked · magenta ramp">
|
|
58
|
+
<Stat value={fmtK(toolCallsTotal)} unit="calls" delta={{ value: 12.4, direction: "up" }} />
|
|
59
|
+
<BarList data={TOP_TOOLS} index="tool" dataKey="calls" valueFormatter={fmtK} />
|
|
60
|
+
</ChartCard>
|
|
61
|
+
|
|
62
|
+
<ChartCard palette="cyan" kicker="Last 7d" title="Top clients" description="Ranked · cyan ramp">
|
|
63
|
+
<BarList data={TOP_CLIENTS} index="client" dataKey="sessions" valueFormatter={nf} />
|
|
64
|
+
</ChartCard>
|
|
65
|
+
|
|
66
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Top routes" description="Long list · capped to 6">
|
|
67
|
+
<BarList data={TOP_ROUTES} index="route" dataKey="hits" maxItems={6} valueFormatter={fmtK} />
|
|
68
|
+
</ChartCard>
|
|
69
|
+
|
|
70
|
+
<ChartCard palette="cyan" kicker="Last 24h" title="Top routes" description="Full list · cyan ramp">
|
|
71
|
+
<BarList data={TOP_ROUTES} index="route" dataKey="hits" valueFormatter={fmtK} />
|
|
72
|
+
</ChartCard>
|
|
73
|
+
|
|
74
|
+
<ChartCard palette="magenta" kicker="State" title="Loading" description="Mono spinner placeholder">
|
|
75
|
+
<BarList data={[]} index="tool" dataKey="calls" loading />
|
|
76
|
+
</ChartCard>
|
|
77
|
+
|
|
78
|
+
<ChartCard palette="magenta" kicker="State" title="Empty" description="No data in range">
|
|
79
|
+
<BarList data={[]} index="tool" dataKey="calls" />
|
|
80
|
+
</ChartCard>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
|
|
3
|
+
import { ChartCard } from "../components/chart-card";
|
|
4
|
+
import { DonutChart } from "../components/donut-chart";
|
|
5
|
+
import { Stat } from "../components/stat";
|
|
6
|
+
|
|
7
|
+
export default { title: "Charts/Donut Chart" };
|
|
8
|
+
|
|
9
|
+
const mulberry32 = (seed: number) => () => {
|
|
10
|
+
seed |= 0;
|
|
11
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
12
|
+
let hash = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
13
|
+
hash = (hash + Math.imul(hash ^ (hash >>> 7), 61 | hash)) ^ hash;
|
|
14
|
+
return ((hash ^ (hash >>> 14)) >>> 0) / 4294967296;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const CLIENTS = ["ChatGPT", "Claude Code", "Claude", "Anthropic", "Goose", "VS Code"];
|
|
18
|
+
const WEIGHTS = [0.42, 0.24, 0.12, 0.09, 0.07, 0.06];
|
|
19
|
+
|
|
20
|
+
const genClients = (seed: number) => {
|
|
21
|
+
const rnd = mulberry32(seed);
|
|
22
|
+
return CLIENTS.map((client, clientIndex) => ({
|
|
23
|
+
client,
|
|
24
|
+
sessions: Math.round(48000 * (WEIGHTS[clientIndex] ?? 0) * (0.8 + rnd() * 0.4)),
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const MODELS = [
|
|
29
|
+
"claude-opus-4-8",
|
|
30
|
+
"claude-sonnet-4-6",
|
|
31
|
+
"claude-haiku-4-5",
|
|
32
|
+
"gpt-5.2",
|
|
33
|
+
"gpt-5.2-mini",
|
|
34
|
+
"gemini-3-pro",
|
|
35
|
+
"gemini-3-flash",
|
|
36
|
+
"llama-4-405b",
|
|
37
|
+
"mistral-large-3",
|
|
38
|
+
"command-r-plus",
|
|
39
|
+
"qwen-3-72b",
|
|
40
|
+
"deepseek-v4",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const genModels = (seed: number) => {
|
|
44
|
+
const rnd = mulberry32(seed);
|
|
45
|
+
return MODELS.map((model) => ({ model, tokens: Math.round(40000 + rnd() * 920000) }));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const genStatus = (seed: number) => {
|
|
49
|
+
const rnd = mulberry32(seed);
|
|
50
|
+
return [
|
|
51
|
+
{ state: "Success", count: Math.round(8200 + rnd() * 600) },
|
|
52
|
+
{ state: "Errored", count: Math.round(180 + rnd() * 90) },
|
|
53
|
+
{ state: "Timed out", count: Math.round(60 + rnd() * 40) },
|
|
54
|
+
];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const clients = genClients(7);
|
|
58
|
+
const models = genModels(23);
|
|
59
|
+
const status = genStatus(41);
|
|
60
|
+
|
|
61
|
+
const nf = (value: number) => value.toLocaleString("en-US");
|
|
62
|
+
const fmtK = (value: number) => {
|
|
63
|
+
if (value >= 1_000_000) {
|
|
64
|
+
return `${(value / 1_000_000).toFixed(2)}M`;
|
|
65
|
+
}
|
|
66
|
+
if (value >= 1000) {
|
|
67
|
+
return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1)}k`;
|
|
68
|
+
}
|
|
69
|
+
return `${value}`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const clientsTotal = clients.reduce((sum, row) => sum + row.sessions, 0);
|
|
73
|
+
|
|
74
|
+
export const AllVariants: Story = () => (
|
|
75
|
+
<div className="chart-canvas mx-auto max-w-[1600px] p-8">
|
|
76
|
+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
77
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Sessions by client" description="Donut · share readout">
|
|
78
|
+
<Stat value={fmtK(clientsTotal)} unit="sessions" delta={{ value: 6.2, direction: "up" }} />
|
|
79
|
+
<DonutChart
|
|
80
|
+
data={clients}
|
|
81
|
+
index="client"
|
|
82
|
+
dataKey="sessions"
|
|
83
|
+
legend
|
|
84
|
+
centerLabel="sessions"
|
|
85
|
+
valueFormatter={nf}
|
|
86
|
+
/>
|
|
87
|
+
</ChartCard>
|
|
88
|
+
|
|
89
|
+
<ChartCard palette="cyan" kicker="Last 7d" title="Tokens by model" description="Many slices · scrollable table">
|
|
90
|
+
<DonutChart data={models} index="model" dataKey="tokens" legend centerLabel="tokens" valueFormatter={fmtK} />
|
|
91
|
+
</ChartCard>
|
|
92
|
+
|
|
93
|
+
<ChartCard palette="magenta" kicker="Last 24h" title="Request outcome" description="Ring · prominent total">
|
|
94
|
+
<DonutChart data={status} index="state" dataKey="count" variant="ring" legend valueFormatter={nf} />
|
|
95
|
+
</ChartCard>
|
|
96
|
+
|
|
97
|
+
<ChartCard palette="magenta" kicker="Last 7d" title="Tokens by model" description="Ring only · no readout">
|
|
98
|
+
<DonutChart data={models} index="model" dataKey="tokens" centerLabel="tokens" valueFormatter={fmtK} />
|
|
99
|
+
</ChartCard>
|
|
100
|
+
|
|
101
|
+
<ChartCard palette="cyan" kicker="State" title="Loading" description="Mono spinner placeholder">
|
|
102
|
+
<DonutChart data={[]} index="client" dataKey="sessions" loading />
|
|
103
|
+
</ChartCard>
|
|
104
|
+
|
|
105
|
+
<ChartCard palette="magenta" kicker="State" title="Empty" description="No data in range">
|
|
106
|
+
<DonutChart data={[]} index="client" dataKey="sessions" legend />
|
|
107
|
+
</ChartCard>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Story } from "@ladle/react";
|
|
2
|
+
|
|
3
|
+
import { ChartCard } from "../components/chart-card";
|
|
4
|
+
import { HeatmapChart } from "../components/heatmap-chart";
|
|
5
|
+
|
|
6
|
+
export default { title: "Charts/Heatmap" };
|
|
7
|
+
|
|
8
|
+
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
9
|
+
|
|
10
|
+
function mulberry32(seed: number) {
|
|
11
|
+
let state = seed;
|
|
12
|
+
return () => {
|
|
13
|
+
state |= 0;
|
|
14
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
15
|
+
let mixed = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
16
|
+
mixed = (mixed + Math.imul(mixed ^ (mixed >>> 7), 61 | mixed)) ^ mixed;
|
|
17
|
+
return ((mixed ^ (mixed >>> 14)) >>> 0) / 4294967296;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Long-format activity rows: one record per (day, hour) with a working-hours hump.
|
|
22
|
+
const ACTIVITY = (() => {
|
|
23
|
+
const random = mulberry32(99);
|
|
24
|
+
const rows: Array<{ day: string; hour: string; sessions: number }> = [];
|
|
25
|
+
DAYS.forEach((day, dayIndex) => {
|
|
26
|
+
const weekend = dayIndex >= 5;
|
|
27
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
28
|
+
const work = Math.exp(-((hour - 14) ** 2) / 26) * (weekend ? 0.45 : 1);
|
|
29
|
+
const evening = Math.exp(-((hour - 21) ** 2) / 10) * 0.5;
|
|
30
|
+
const intensity = Math.max(0.02, Math.min(1, work + evening + random() * 0.28));
|
|
31
|
+
rows.push({ day, hour: String(hour).padStart(2, "0"), sessions: Math.round(intensity * 320) });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return rows;
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
const HOURS = Array.from({ length: 24 }, (_, hour) => String(hour).padStart(2, "0"));
|
|
38
|
+
|
|
39
|
+
const nf = (value: number) => value.toLocaleString("en-US");
|
|
40
|
+
|
|
41
|
+
export const AllVariants: Story = () => (
|
|
42
|
+
<div className="chart-canvas mx-auto max-w-[1600px] p-8">
|
|
43
|
+
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
44
|
+
<ChartCard palette="magenta" accent="left" kicker="Last 7d" title="Activity" description="Square · hour × day">
|
|
45
|
+
<HeatmapChart
|
|
46
|
+
data={ACTIVITY}
|
|
47
|
+
xKey="hour"
|
|
48
|
+
yKey="day"
|
|
49
|
+
dataKey="sessions"
|
|
50
|
+
variant="square"
|
|
51
|
+
xLabels={HOURS}
|
|
52
|
+
yLabels={DAYS}
|
|
53
|
+
valueFormatter={nf}
|
|
54
|
+
/>
|
|
55
|
+
</ChartCard>
|
|
56
|
+
|
|
57
|
+
<ChartCard palette="cyan" accent="left" kicker="Last 7d" title="Activity" description="Dots · hour × day">
|
|
58
|
+
<HeatmapChart
|
|
59
|
+
data={ACTIVITY}
|
|
60
|
+
xKey="hour"
|
|
61
|
+
yKey="day"
|
|
62
|
+
dataKey="sessions"
|
|
63
|
+
variant="dot"
|
|
64
|
+
xLabels={HOURS}
|
|
65
|
+
yLabels={DAYS}
|
|
66
|
+
valueFormatter={nf}
|
|
67
|
+
/>
|
|
68
|
+
</ChartCard>
|
|
69
|
+
|
|
70
|
+
<ChartCard palette="cyan" accent="left" kicker="Last 7d" title="Activity" description="Square · cyan ramp">
|
|
71
|
+
<HeatmapChart
|
|
72
|
+
data={ACTIVITY}
|
|
73
|
+
xKey="hour"
|
|
74
|
+
yKey="day"
|
|
75
|
+
dataKey="sessions"
|
|
76
|
+
variant="square"
|
|
77
|
+
xLabels={HOURS}
|
|
78
|
+
yLabels={DAYS}
|
|
79
|
+
valueFormatter={nf}
|
|
80
|
+
/>
|
|
81
|
+
</ChartCard>
|
|
82
|
+
|
|
83
|
+
<ChartCard palette="magenta" accent="left" kicker="Last 7d" title="Activity" description="Dots · magenta ramp">
|
|
84
|
+
<HeatmapChart
|
|
85
|
+
data={ACTIVITY}
|
|
86
|
+
xKey="hour"
|
|
87
|
+
yKey="day"
|
|
88
|
+
dataKey="sessions"
|
|
89
|
+
variant="dot"
|
|
90
|
+
xLabels={HOURS}
|
|
91
|
+
yLabels={DAYS}
|
|
92
|
+
valueFormatter={nf}
|
|
93
|
+
/>
|
|
94
|
+
</ChartCard>
|
|
95
|
+
|
|
96
|
+
<ChartCard palette="magenta" accent="left" kicker="State" title="Loading" description="Mono spinner placeholder">
|
|
97
|
+
<HeatmapChart data={[]} xKey="hour" yKey="day" dataKey="sessions" loading />
|
|
98
|
+
</ChartCard>
|
|
99
|
+
|
|
100
|
+
<ChartCard palette="magenta" accent="left" kicker="State" title="Empty" description="No data in range">
|
|
101
|
+
<HeatmapChart data={[]} xKey="hour" yKey="day" dataKey="sessions" />
|
|
102
|
+
</ChartCard>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|