@invana/charts 0.0.32

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/index.cjs ADDED
@@ -0,0 +1,1737 @@
1
+ 'use strict';
2
+
3
+ var React2 = require('react');
4
+ var uPlot2 = require('uplot');
5
+ var ui = require('@invana/ui');
6
+ var tables = require('@invana/tables');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
30
+ var uPlot2__default = /*#__PURE__*/_interopDefault(uPlot2);
31
+
32
+ // src/line-chart.tsx
33
+
34
+ // src/base/theme.ts
35
+ var DATA_SLOTS = 8;
36
+ function resolveColor(host, css) {
37
+ const probe = document.createElement("span");
38
+ probe.style.color = css;
39
+ probe.style.display = "none";
40
+ host.appendChild(probe);
41
+ const value = getComputedStyle(probe).color;
42
+ probe.remove();
43
+ return toRgba(value);
44
+ }
45
+ var pixel;
46
+ function toRgba(css) {
47
+ if (/^rgba?\(/.test(css)) return css;
48
+ if (pixel === void 0) {
49
+ const canvas = document.createElement("canvas");
50
+ canvas.width = canvas.height = 1;
51
+ pixel = canvas.getContext("2d", { willReadFrequently: true });
52
+ }
53
+ if (!pixel) return css;
54
+ pixel.clearRect(0, 0, 1, 1);
55
+ pixel.fillStyle = css;
56
+ pixel.fillRect(0, 0, 1, 1);
57
+ const [r, g, b, a] = pixel.getImageData(0, 0, 1, 1).data;
58
+ return `rgba(${r}, ${g}, ${b}, ${Math.round(a / 255 * 1e3) / 1e3})`;
59
+ }
60
+ function resolveSize(host, css) {
61
+ const probe = document.createElement("span");
62
+ probe.style.fontSize = css;
63
+ probe.style.display = "none";
64
+ host.appendChild(probe);
65
+ const value = parseFloat(getComputedStyle(probe).fontSize);
66
+ probe.remove();
67
+ return value;
68
+ }
69
+ function readChartTheme(host) {
70
+ const color = (name) => resolveColor(host, `var(${name})`);
71
+ const family = getComputedStyle(host).fontFamily;
72
+ const size = {
73
+ base: resolveSize(host, "var(--text-base, 1rem)"),
74
+ sm: resolveSize(host, "var(--text-sm, 0.923rem)"),
75
+ xs: resolveSize(host, "var(--text-xs, 0.846rem)")
76
+ };
77
+ return {
78
+ data: Array.from(
79
+ { length: DATA_SLOTS },
80
+ (_, i) => resolveColor(host, `var(--color-data-${i + 1}, var(--color-muted-foreground))`)
81
+ ),
82
+ success: color("--color-success"),
83
+ warning: color("--color-warning"),
84
+ destructive: color("--color-destructive"),
85
+ foreground: color("--color-foreground"),
86
+ mutedForeground: color("--color-muted-foreground"),
87
+ border: color("--color-border"),
88
+ card: color("--color-card"),
89
+ // Whole pixels: uPlot rescales a font for the device by matching
90
+ // `(\d+)px`, which reads `10.998px` as `998px`.
91
+ font: {
92
+ base: `${Math.round(size.base)}px ${family}`,
93
+ sm: `${Math.round(size.sm)}px ${family}`,
94
+ xs: `${Math.round(size.xs)}px ${family}`
95
+ },
96
+ size,
97
+ family
98
+ };
99
+ }
100
+ function withAlpha(rgb, alpha) {
101
+ const parts = rgb.match(/[\d.]+/g);
102
+ if (!parts || parts.length < 3) return rgb;
103
+ return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${alpha})`;
104
+ }
105
+
106
+ // src/base/hooks.ts
107
+ var THEME_ATTRIBUTES = ["class", "style", "data-theme", "data-density"];
108
+ function useChartTheme(ref) {
109
+ const [theme, setTheme] = React2__namespace.useState(null);
110
+ React2__namespace.useLayoutEffect(() => {
111
+ const el = ref.current;
112
+ if (!el) return;
113
+ const read = () => setTheme(readChartTheme(el));
114
+ read();
115
+ const observer = new MutationObserver(read);
116
+ observer.observe(document.documentElement, {
117
+ attributes: true,
118
+ attributeFilter: THEME_ATTRIBUTES
119
+ });
120
+ const scheme = window.matchMedia?.("(prefers-color-scheme: dark)");
121
+ scheme?.addEventListener?.("change", read);
122
+ document.fonts?.ready.then(read).catch(() => {
123
+ });
124
+ return () => {
125
+ observer.disconnect();
126
+ scheme?.removeEventListener?.("change", read);
127
+ };
128
+ }, [ref]);
129
+ return theme;
130
+ }
131
+ function useWidth(ref) {
132
+ const [width, setWidth] = React2__namespace.useState(0);
133
+ React2__namespace.useLayoutEffect(() => {
134
+ const el = ref.current;
135
+ if (!el) return;
136
+ setWidth(Math.floor(el.getBoundingClientRect().width));
137
+ const observer = new ResizeObserver(
138
+ ([entry]) => setWidth(Math.floor(entry.contentRect.width))
139
+ );
140
+ observer.observe(el);
141
+ return () => observer.disconnect();
142
+ }, [ref]);
143
+ return width;
144
+ }
145
+
146
+ // src/base/uplot-styles.ts
147
+ var CSS = `
148
+ .invana-uplot, .invana-uplot * { box-sizing: border-box; }
149
+ .invana-uplot { width: min-content; line-height: 1; }
150
+ .invana-uplot .u-wrap { position: relative; user-select: none; }
151
+ .invana-uplot .u-over, .invana-uplot .u-under { position: absolute; }
152
+ .invana-uplot .u-under { overflow: hidden; }
153
+ .invana-uplot canvas { display: block; position: relative; width: 100%; height: 100%; }
154
+ .invana-uplot .u-axis { position: absolute; }
155
+ .invana-uplot .u-select { position: absolute; pointer-events: none; }
156
+ .invana-uplot .u-cursor-x, .invana-uplot .u-cursor-y {
157
+ position: absolute; left: 0; top: 0; pointer-events: none; will-change: transform;
158
+ }
159
+ .invana-uplot .u-hz .u-cursor-x { height: 100%; border-right: 1px solid var(--color-border); }
160
+ .invana-uplot .u-cursor-pt {
161
+ position: absolute; top: 0; left: 0; border-radius: 50%; border: 0 solid;
162
+ pointer-events: none; will-change: transform; background-clip: padding-box !important;
163
+ }
164
+ .invana-uplot .u-off { display: none; }
165
+ `;
166
+ var ID = "invana-charts-uplot";
167
+ function installUPlotStyles() {
168
+ if (typeof document === "undefined" || document.getElementById(ID)) return;
169
+ const style = document.createElement("style");
170
+ style.id = ID;
171
+ style.textContent = CSS;
172
+ document.head.appendChild(style);
173
+ }
174
+ var measure = /* @__PURE__ */ (() => {
175
+ let ctx;
176
+ return (font, text) => {
177
+ if (ctx === void 0) ctx = document.createElement("canvas").getContext("2d");
178
+ if (!ctx) return text.length * 7;
179
+ ctx.font = font;
180
+ return ctx.measureText(text).width;
181
+ };
182
+ })();
183
+ var ChartFrame = React2__namespace.forwardRef(
184
+ ({
185
+ summary,
186
+ labels,
187
+ height,
188
+ ticks,
189
+ marks = [],
190
+ reference,
191
+ legend = [],
192
+ empty,
193
+ build,
194
+ tooltip,
195
+ table,
196
+ className,
197
+ "aria-label": ariaLabel,
198
+ ...props
199
+ }, ref) => {
200
+ const root = React2__namespace.useRef(null);
201
+ React2__namespace.useImperativeHandle(ref, () => root.current);
202
+ const mount = React2__namespace.useRef(null);
203
+ const plot = React2__namespace.useRef(null);
204
+ const theme = useChartTheme(root);
205
+ const width = useWidth(root);
206
+ const widthRef = React2__namespace.useRef(width);
207
+ React2__namespace.useLayoutEffect(() => {
208
+ widthRef.current = width;
209
+ });
210
+ const [asTable, setAsTable] = React2__namespace.useState(false);
211
+ const [hover, setHover] = React2__namespace.useState(null);
212
+ const [box, setBox] = React2__namespace.useState(null);
213
+ const [crosshair, setCrosshair] = React2__namespace.useState(true);
214
+ const n = labels.length;
215
+ const blank = empty != null || n === 0;
216
+ const marksKey = JSON.stringify(marks);
217
+ const referenceKey = JSON.stringify(reference ?? null);
218
+ const hasWidth = width > 0;
219
+ React2__namespace.useEffect(() => {
220
+ const el = mount.current;
221
+ const host = root.current;
222
+ if (!el || !host || !theme || !hasWidth || blank || asTable) return;
223
+ installUPlotStyles();
224
+ const built = build(theme, host);
225
+ const marksNow = JSON.parse(marksKey);
226
+ const referenceNow = JSON.parse(referenceKey);
227
+ setCrosshair(built.crosshair);
228
+ const labelBand = marksNow.some((m) => m.label) ? Math.ceil(theme.size.xs) + 8 : 0;
229
+ const half = Math.ceil(theme.size.xs / 2) + 1;
230
+ const axisWidth = Math.ceil(Math.max(0, ...built.splits.map((s) => measure(theme.font.xs, built.format(s))))) + 10;
231
+ const referenceBand = referenceNow ? Math.ceil(measure(theme.font.xs, referenceNow.label)) + 10 : 0;
232
+ const readBox = (u2) => setBox({
233
+ left: u2.over.offsetLeft,
234
+ top: u2.over.offsetTop,
235
+ width: u2.over.clientWidth,
236
+ height: u2.over.clientHeight
237
+ });
238
+ const drawMarks = (u2) => {
239
+ const ctx = u2.ctx;
240
+ const pr = uPlot2__default.default.pxRatio;
241
+ const { left, top, width: w, height: h } = u2.bbox;
242
+ ctx.save();
243
+ ctx.font = `${theme.size.xs * pr}px ${theme.family}`;
244
+ ctx.lineWidth = pr;
245
+ ctx.setLineDash([3 * pr, 3 * pr]);
246
+ ctx.strokeStyle = theme.mutedForeground;
247
+ ctx.fillStyle = theme.mutedForeground;
248
+ ctx.textBaseline = "bottom";
249
+ let lastRight = -Infinity;
250
+ for (const m of marksNow) {
251
+ if (m.index < 0 || m.index >= n) continue;
252
+ const x = Math.round(u2.valToPos(m.index, "x", true)) + 0.5;
253
+ ctx.beginPath();
254
+ ctx.moveTo(x, top - 2 * pr);
255
+ ctx.lineTo(x, top + h);
256
+ ctx.stroke();
257
+ if (!m.label) continue;
258
+ const tw = ctx.measureText(m.label).width;
259
+ const flip = x + 4 * pr + tw > left + w;
260
+ const start = flip ? x - 4 * pr - tw : x + 4 * pr;
261
+ if (start < lastRight + 6 * pr) continue;
262
+ ctx.textAlign = "left";
263
+ ctx.fillText(m.label, start, top - 2 * pr);
264
+ lastRight = start + tw;
265
+ }
266
+ if (referenceNow) {
267
+ const y = Math.round(u2.valToPos(referenceNow.value, "y", true)) + 0.5;
268
+ ctx.setLineDash([2 * pr, 4 * pr]);
269
+ ctx.beginPath();
270
+ ctx.moveTo(left, y);
271
+ ctx.lineTo(left + w, y);
272
+ ctx.stroke();
273
+ ctx.textAlign = "left";
274
+ ctx.textBaseline = "middle";
275
+ ctx.fillText(referenceNow.label, left + w + 6 * pr, y);
276
+ }
277
+ ctx.restore();
278
+ };
279
+ const color = (u2, i) => {
280
+ const stroke = u2.series[i].stroke;
281
+ return String(typeof stroke === "function" ? stroke(u2, i) : stroke);
282
+ };
283
+ const opts = {
284
+ class: "invana-uplot",
285
+ width: widthRef.current,
286
+ height,
287
+ padding: [labelBand || half, Math.max(built.padRight ?? 4, referenceBand), half, 0],
288
+ legend: { show: false },
289
+ scales: {
290
+ x: { time: false, range: [-0.5, n - 0.5] },
291
+ y: { range: [built.floor ?? 0, built.ceiling] }
292
+ },
293
+ axes: [
294
+ { show: false },
295
+ {
296
+ scale: "y",
297
+ side: 3,
298
+ size: axisWidth,
299
+ gap: 6,
300
+ font: theme.font.xs,
301
+ stroke: theme.mutedForeground,
302
+ ticks: { show: false },
303
+ grid: { stroke: theme.border, width: 1 },
304
+ splits: () => built.splits,
305
+ values: (_u, splits) => splits.map((s) => built.format(s))
306
+ }
307
+ ],
308
+ series: [{}, ...built.series],
309
+ bands: built.bands,
310
+ cursor: {
311
+ x: built.crosshair,
312
+ y: false,
313
+ drag: { x: false, y: false, setScale: false },
314
+ points: {
315
+ show: built.cursorPoints ?? built.crosshair,
316
+ size: 8,
317
+ width: 2,
318
+ stroke: () => theme.card,
319
+ fill: color
320
+ }
321
+ },
322
+ hooks: {
323
+ ready: [readBox],
324
+ setSize: [readBox],
325
+ setCursor: [(u2) => setHover(u2.cursor.idx ?? null)],
326
+ drawAxes: built.drawUnder ? [built.drawUnder] : [],
327
+ draw: [...built.draw ? [built.draw] : [], drawMarks]
328
+ }
329
+ };
330
+ const xs = Array.from({ length: n }, (_, i) => i);
331
+ const u = new uPlot2__default.default(opts, [xs, ...built.data], el);
332
+ plot.current = u;
333
+ return () => {
334
+ u.destroy();
335
+ plot.current = null;
336
+ setHover(null);
337
+ };
338
+ }, [theme, build, hasWidth, blank, asTable, n, height, marksKey, referenceKey]);
339
+ React2__namespace.useEffect(() => {
340
+ if (hasWidth) plot.current?.setSize({ width, height });
341
+ }, [width, height, hasWidth]);
342
+ const slot = box ? box.width / Math.max(n, 1) : 0;
343
+ const xAt = (i) => box ? box.left + (i + 0.5) * slot : 0;
344
+ const tickStyle = (i) => n > 1 && i === 0 ? { left: xAt(i) - slot / 2 } : n > 1 && i === n - 1 ? { right: (width || 0) - (xAt(i) + slot / 2) } : { left: xAt(i), transform: "translateX(-50%)" };
345
+ const tickSpan = (i) => {
346
+ const w = theme ? measure(theme.font.xs, labels[i] ?? "") : 0;
347
+ if (n > 1 && i === 0) return [xAt(i) - slot / 2, xAt(i) - slot / 2 + w];
348
+ if (n > 1 && i === n - 1) return [xAt(i) + slot / 2 - w, xAt(i) + slot / 2];
349
+ return [xAt(i) - w / 2, xAt(i) + w / 2];
350
+ };
351
+ const wanted = [...new Set(ticks ?? (n > 1 ? [0, n - 1] : [0]))].filter((i) => i >= 0 && i < n);
352
+ const placed = [];
353
+ const named = [
354
+ ...wanted.filter((i) => i === 0 || i === n - 1),
355
+ ...wanted.filter((i) => i !== 0 && i !== n - 1)
356
+ ].filter((i) => {
357
+ const [a, b] = tickSpan(i);
358
+ if (placed.some(([c, d]) => a < d + 8 && b > c - 8)) return false;
359
+ placed.push([a, b]);
360
+ return true;
361
+ });
362
+ const tip = hover != null && hover < n ? tooltip(hover) : null;
363
+ const markHere = hover != null ? marks.flatMap((m) => m.index === hover && m.label ? [m.label] : []) : [];
364
+ const tipLeft = hover != null ? xAt(hover) : 0;
365
+ const tipFlip = box ? tipLeft > box.left + box.width / 2 : false;
366
+ const onKeyDown = (e) => {
367
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
368
+ e.preventDefault();
369
+ const step = e.key === "ArrowLeft" ? -1 : 1;
370
+ setHover((at) => Math.min(n - 1, Math.max(0, (at ?? (step > 0 ? -1 : n)) + step)));
371
+ };
372
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: root, className: ui.cn("flex min-w-0 flex-col gap-1", className), ...props, children: [
373
+ asTable ? /* @__PURE__ */ jsxRuntime.jsx(
374
+ tables.DataTable,
375
+ {
376
+ columns: table.columns,
377
+ data: table.rows,
378
+ enableSorting: false
379
+ }
380
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
381
+ /* @__PURE__ */ jsxRuntime.jsx(
382
+ "div",
383
+ {
384
+ role: "img",
385
+ "aria-label": ariaLabel ?? summary,
386
+ tabIndex: blank ? void 0 : 0,
387
+ onKeyDown,
388
+ onBlur: () => setHover(null),
389
+ className: "relative w-full rounded-sm outline-none focus-visible:ring-1 focus-visible:ring-ring",
390
+ style: { height },
391
+ children: blank ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: empty ?? "Nothing in this period" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
392
+ !crosshair && hover != null && box ? (
393
+ // The hover band sits behind the transparent canvas, so a
394
+ // column lifts without its colour being tinted.
395
+ /* @__PURE__ */ jsxRuntime.jsx(
396
+ "div",
397
+ {
398
+ "aria-hidden": true,
399
+ className: "absolute rounded-sm bg-muted",
400
+ style: { left: xAt(hover) - slot / 2, width: slot, top: box.top, height: box.height }
401
+ }
402
+ )
403
+ ) : null,
404
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: mount, "aria-hidden": true, className: "relative" }),
405
+ tip && box ? /* @__PURE__ */ jsxRuntime.jsxs(
406
+ ui.Card,
407
+ {
408
+ "aria-hidden": true,
409
+ className: "pointer-events-none absolute z-10 flex min-w-32 max-w-64 flex-col gap-1 px-2 py-1.5 shadow-sm",
410
+ style: {
411
+ top: box.top,
412
+ left: tipFlip ? void 0 : tipLeft + 10,
413
+ right: tipFlip ? (width || 0) - tipLeft + 10 : void 0
414
+ },
415
+ children: [
416
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Eyebrow, { children: tip.title }),
417
+ tip.rows.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
418
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "h-0.5 w-3 shrink-0 rounded-full", style: { background: r.color } }),
419
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold tabular-nums", children: r.value }),
420
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate text-muted-foreground", children: r.label })
421
+ ] }, r.key)),
422
+ [...markHere, ...tip.note ? [tip.note] : []].map((note) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-muted-foreground", children: note }, note))
423
+ ]
424
+ }
425
+ ) : null
426
+ ] })
427
+ }
428
+ ),
429
+ !blank && box ? /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, className: "relative h-4 text-xs text-muted-foreground", children: named.map((i) => /* @__PURE__ */ jsxRuntime.jsx(
430
+ "span",
431
+ {
432
+ className: "absolute top-0 whitespace-nowrap tabular-nums",
433
+ style: tickStyle(i),
434
+ children: labels[i]
435
+ },
436
+ i
437
+ )) }) : null
438
+ ] }),
439
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [
440
+ legend.length > 1 && !blank ? /* @__PURE__ */ jsxRuntime.jsx(ui.Legend, { children: legend.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.LegendItem, { color: s.color, label: s.label, kind: s.kind }, s.key)) }) : null,
441
+ blank ? null : /* @__PURE__ */ jsxRuntime.jsx(
442
+ ui.Button,
443
+ {
444
+ variant: "link",
445
+ size: "xs",
446
+ className: "ml-auto px-0 text-muted-foreground",
447
+ "aria-pressed": asTable,
448
+ onClick: () => setAsTable((v) => !v),
449
+ children: asTable ? "View as chart" : "View as table"
450
+ }
451
+ )
452
+ ] })
453
+ ] });
454
+ }
455
+ );
456
+ ChartFrame.displayName = "ChartFrame";
457
+
458
+ // src/base/floors.ts
459
+ var COUNT_FLOOR = 5;
460
+ var MAX_BAR_WIDTH = 24;
461
+ var SEGMENT_GAP = 2;
462
+ var BAR_RADIUS = 4;
463
+ function niceScale(max, { integers = false } = {}) {
464
+ const top = max > 0 ? max : 1;
465
+ let power = 10 ** Math.floor(Math.log10(top / 4));
466
+ if (integers) power = Math.max(1, power);
467
+ for (; ; ) {
468
+ for (const m of [1, 2, 5]) {
469
+ const step = m * power;
470
+ const k = Math.max(1, Math.ceil(top / step - 1e-9));
471
+ if (k <= 4) return { ceiling: step * k, splits: Array.from({ length: k + 1 }, (_, i) => i * step) };
472
+ }
473
+ power *= 10;
474
+ }
475
+ }
476
+ function niceRange(min, max) {
477
+ const span = max > min ? max - min : Math.abs(max) || 1;
478
+ let power = 10 ** Math.floor(Math.log10(span / 4));
479
+ for (; ; ) {
480
+ for (const m of [1, 2, 5]) {
481
+ const step = m * power;
482
+ const floor = Math.floor(min / step + 1e-9) * step;
483
+ const k = Math.max(1, Math.ceil((max - floor) / step - 1e-9));
484
+ if (k <= 4) {
485
+ return {
486
+ floor,
487
+ ceiling: floor + step * k,
488
+ splits: Array.from({ length: k + 1 }, (_, i) => floor + i * step)
489
+ };
490
+ }
491
+ }
492
+ power *= 10;
493
+ }
494
+ }
495
+ function countScale(max) {
496
+ return niceScale(Math.max(max, COUNT_FLOOR), { integers: true });
497
+ }
498
+ function fixedSplits(ceiling) {
499
+ return [0, ceiling / 2, ceiling];
500
+ }
501
+ var compact = new Intl.NumberFormat(void 0, { notation: "compact", maximumFractionDigits: 1 });
502
+ var whole = new Intl.NumberFormat();
503
+ function formatCount(value) {
504
+ return Math.abs(value) < 1e4 ? whole.format(value) : compact.format(value);
505
+ }
506
+ function useLatest(value) {
507
+ const ref = React2__namespace.useRef(value);
508
+ React2__namespace.useLayoutEffect(() => {
509
+ ref.current = value;
510
+ });
511
+ return ref;
512
+ }
513
+ var NONE = [];
514
+ var LineChart = React2__namespace.forwardRef(
515
+ ({
516
+ values,
517
+ name,
518
+ compare = NONE,
519
+ labels,
520
+ format = String,
521
+ max,
522
+ gridlines = NONE,
523
+ marks = NONE,
524
+ reference,
525
+ band,
526
+ highlights = NONE,
527
+ forecastFrom,
528
+ forecastLabel,
529
+ zero = true,
530
+ ticks,
531
+ height = 132,
532
+ color = "var(--color-primary)",
533
+ empty,
534
+ ...props
535
+ }, ref) => {
536
+ const fmt = useLatest(format);
537
+ const referenceValue = reference?.value;
538
+ const n = values.length;
539
+ const [lower, upper] = React2__namespace.useMemo(() => {
540
+ const spread = (edge) => edge == null ? null : Array.from({ length: n }, (_, i) => typeof edge === "number" ? edge : edge[i] ?? null);
541
+ return [spread(band?.lower), spread(band?.upper)];
542
+ }, [band?.lower, band?.upper, n]);
543
+ const forecast = forecastFrom != null && forecastFrom >= 0 && forecastFrom < n ? forecastFrom : null;
544
+ const highlightsKey = JSON.stringify(highlights);
545
+ const compareKey = JSON.stringify(compare);
546
+ const named = compare.length > 0;
547
+ const build = React2__namespace.useCallback(
548
+ (theme, host) => {
549
+ const stroke = resolveColor(host, color);
550
+ const rings = JSON.parse(highlightsKey).filter((h) => h.index >= 0 && h.index < n).map((h) => ({ index: h.index, color: h.color ? resolveColor(host, h.color) : stroke }));
551
+ const ringed = new Set(rings.map((r) => r.index));
552
+ const others = JSON.parse(compareKey).map((c) => ({
553
+ ...c,
554
+ stroke: resolveColor(host, c.color ?? "var(--color-muted-foreground)")
555
+ }));
556
+ const measured2 = [...values, ...others.flatMap((c) => c.values)].filter((v) => v != null);
557
+ const edges = [...lower ?? [], ...upper ?? []].filter((v) => v != null);
558
+ const top = Math.max(...measured2, ...edges, referenceValue ?? -Infinity);
559
+ const bottom = Math.min(...measured2, ...edges, referenceValue ?? Infinity);
560
+ const scale = zero ? { floor: 0, ...niceScale(Math.max(0, top) * 1.05) } : niceRange(bottom, top + (top - bottom) * 0.05);
561
+ const ceiling = max ?? Math.max(scale.ceiling, ...gridlines);
562
+ const last = forecast ?? values.reduce((at, v, i) => v != null ? i : at, -1);
563
+ const points = values.map((v, i) => v != null && (i === last || values[i - 1] == null && values[i + 1] == null) ? i : -1).filter((i) => i >= 0 && !ringed.has(i));
564
+ const line = (dash) => ({
565
+ stroke,
566
+ width: 2,
567
+ dash,
568
+ spanGaps: false,
569
+ points: {
570
+ show: !dash,
571
+ size: 10,
572
+ width: 2,
573
+ stroke: theme.card,
574
+ fill: stroke,
575
+ filter: () => points
576
+ }
577
+ });
578
+ const actual = forecast == null ? values : values.map((v, i) => i <= forecast ? v : null);
579
+ const ahead = forecast == null ? null : values.map((v, i) => i >= forecast ? v : null);
580
+ const bandFill = withAlpha(band?.color ? resolveColor(host, band.color) : stroke, 0.1);
581
+ const bandLabel = band?.label;
582
+ const drawUnder = (u) => {
583
+ if (!lower || !upper) return;
584
+ const ctx = u.ctx;
585
+ const pr = uPlot2__default.default.pxRatio;
586
+ const { left, width: w, top: t, height: h } = u.bbox;
587
+ ctx.save();
588
+ ctx.beginPath();
589
+ ctx.rect(left, t, w, h);
590
+ ctx.clip();
591
+ ctx.fillStyle = bandFill;
592
+ const y = (v) => u.valToPos(v, "y", true);
593
+ if (typeof band?.lower === "number" && typeof band?.upper === "number") {
594
+ ctx.fillRect(left, y(band.upper), w, y(band.lower) - y(band.upper));
595
+ } else {
596
+ let run = [];
597
+ const flush = () => {
598
+ if (run.length) {
599
+ const x = (i) => u.valToPos(i, "x", true);
600
+ ctx.beginPath();
601
+ run.forEach((i, k) => k ? ctx.lineTo(x(i), y(upper[i])) : ctx.moveTo(x(i), y(upper[i])));
602
+ for (const i of [...run].reverse()) ctx.lineTo(x(i), y(lower[i]));
603
+ ctx.closePath();
604
+ ctx.fill();
605
+ }
606
+ run = [];
607
+ };
608
+ for (let i = 0; i < n; i++) {
609
+ if (lower[i] != null && upper[i] != null) run.push(i);
610
+ else flush();
611
+ }
612
+ flush();
613
+ }
614
+ ctx.restore();
615
+ if (bandLabel) {
616
+ const at = n - 1 - [...lower].reverse().findIndex((v, k) => v != null && upper[n - 1 - k] != null);
617
+ if (at < n) {
618
+ ctx.save();
619
+ ctx.font = `${theme.size.xs * pr}px ${theme.family}`;
620
+ ctx.fillStyle = theme.mutedForeground;
621
+ ctx.textAlign = "right";
622
+ const below = y(lower[at]) + 3 * pr;
623
+ const room = below + theme.size.xs * pr <= t + h;
624
+ ctx.textBaseline = room ? "top" : "bottom";
625
+ ctx.fillText(bandLabel, left + w - 4 * pr, room ? below : y(upper[at]) - 3 * pr);
626
+ ctx.restore();
627
+ }
628
+ }
629
+ };
630
+ const lastOf = (vs) => vs.reduce((at, v, i) => v != null ? i : at, -1);
631
+ const endLabels = named ? [{ name: name ?? "", values, stroke, main: true }, ...others.map((c) => ({ ...c, main: false }))] : [];
632
+ const font = `600 ${theme.size.xs}px ${theme.family}`;
633
+ const padRight = named ? Math.ceil(
634
+ Math.max(
635
+ 0,
636
+ ...endLabels.map((l) => {
637
+ const m = document.createElement("canvas").getContext("2d");
638
+ if (!m) return l.name.length * 7;
639
+ m.font = font;
640
+ return m.measureText(l.name).width;
641
+ })
642
+ )
643
+ ) + 12 : void 0;
644
+ const drawLabels = (u) => {
645
+ if (!named) return;
646
+ const ctx = u.ctx;
647
+ const pr = uPlot2__default.default.pxRatio;
648
+ const x = u.bbox.left + u.bbox.width + 6 * pr;
649
+ const step = theme.size.xs * pr + 2 * pr;
650
+ const placed = endLabels.map((l) => {
651
+ const at = lastOf(l.values);
652
+ return at < 0 ? null : { ...l, y: u.valToPos(l.values[at], "y", true) };
653
+ }).filter((l) => l != null).sort((a, b) => a.y - b.y);
654
+ for (let i = 1; i < placed.length; i++) placed[i].y = Math.max(placed[i].y, placed[i - 1].y + step);
655
+ ctx.save();
656
+ ctx.font = `600 ${theme.size.xs * pr}px ${theme.family}`;
657
+ ctx.textBaseline = "middle";
658
+ ctx.textAlign = "left";
659
+ for (const l of placed) {
660
+ ctx.fillStyle = l.main ? theme.foreground : l.stroke;
661
+ ctx.fillText(l.name, x, l.y);
662
+ }
663
+ ctx.restore();
664
+ };
665
+ const draw = (u) => {
666
+ drawLabels(u);
667
+ if (!rings.length) return;
668
+ const ctx = u.ctx;
669
+ const pr = uPlot2__default.default.pxRatio;
670
+ ctx.save();
671
+ ctx.lineWidth = 1.5 * pr;
672
+ for (const r of rings) {
673
+ const v = values[r.index];
674
+ if (v == null) continue;
675
+ ctx.strokeStyle = r.color;
676
+ ctx.beginPath();
677
+ ctx.arc(u.valToPos(r.index, "x", true), u.valToPos(v, "y", true), 4.5 * pr, 0, Math.PI * 2);
678
+ ctx.stroke();
679
+ }
680
+ ctx.restore();
681
+ };
682
+ const behind = others.map(
683
+ (c) => ({ stroke: c.stroke, width: 2, spanGaps: false, points: { show: false } })
684
+ );
685
+ return {
686
+ series: [...behind, ...ahead ? [line(), line([6, 4])] : [line()]],
687
+ data: [...others.map((c) => c.values), ...ahead ? [actual, ahead] : [actual]],
688
+ padRight,
689
+ floor: scale.floor,
690
+ ceiling,
691
+ splits: gridlines.length ? gridlines : max != null ? fixedSplits(max) : scale.splits,
692
+ format: (v) => fmt.current(v),
693
+ crosshair: true,
694
+ cursorPoints: true,
695
+ drawUnder,
696
+ draw
697
+ };
698
+ },
699
+ [values, name, compareKey, named, color, max, gridlines, referenceValue, fmt, zero, lower, upper, band?.lower, band?.upper, band?.color, band?.label, forecast, highlightsKey, n]
700
+ );
701
+ const measured = values.filter((v) => v != null);
702
+ const blank = measured.length === 0;
703
+ const summary = React2__namespace.useMemo(() => {
704
+ if (blank) return "Line chart with no measured periods.";
705
+ const f = format;
706
+ let top = 0;
707
+ values.forEach((v, i) => {
708
+ if (v != null && v > (values[top] ?? -Infinity)) top = i;
709
+ });
710
+ const lastIdx = forecast ?? values.reduce((at, v, i) => v != null ? i : at, -1);
711
+ const parts = [
712
+ `Line chart over ${values.length} periods, ${labels[0]} to ${labels[labels.length - 1]}.`,
713
+ `Latest ${f(values[lastIdx])} on ${labels[lastIdx]}; highest ${f(values[top])} on ${labels[top]}.`
714
+ ];
715
+ const gaps = values.length - measured.length;
716
+ if (gaps) parts.push(`${gaps} periods with nothing measured.`);
717
+ if (forecast != null) parts.push(`Forecast from ${labels[forecast]}.`);
718
+ if (band && typeof band.lower === "number" && typeof band.upper === "number")
719
+ parts.push(`${band.label ?? "Band"} ${f(band.lower)} to ${f(band.upper)}.`);
720
+ else if (band) parts.push(`${band.label ?? "A band"} shaded.`);
721
+ if (highlights.length) parts.push(`Highlighted: ${highlights.map((h) => labels[h.index]).join(", ")}.`);
722
+ const named2 = marks.filter((m) => m.label);
723
+ if (named2.length) parts.push(`Marked: ${named2.map((m) => `${m.label} (${labels[m.index]})`).join(", ")}.`);
724
+ if (reference) parts.push(`Reference ${reference.label} at ${f(reference.value)}.`);
725
+ for (const c of compare) {
726
+ const at = c.values.reduce((last, v, i) => v != null ? i : last, -1);
727
+ if (at >= 0) parts.push(`Against ${c.name}: ${f(c.values[at])} on ${labels[at]}.`);
728
+ }
729
+ return parts.join(" ");
730
+ }, [blank, values, labels, marks, reference, measured.length, format, forecast, band, highlights, compare]);
731
+ const allMarks = React2__namespace.useMemo(
732
+ () => forecast == null ? marks : [...marks, { index: forecast, label: forecastLabel }],
733
+ [marks, forecast, forecastLabel]
734
+ );
735
+ const bandText = (i) => lower?.[i] != null && upper?.[i] != null ? `${format(lower[i])} \u2013 ${format(upper[i])}` : "";
736
+ return /* @__PURE__ */ jsxRuntime.jsx(
737
+ ChartFrame,
738
+ {
739
+ ref,
740
+ summary,
741
+ labels,
742
+ height,
743
+ ticks,
744
+ marks: allMarks,
745
+ reference,
746
+ empty: blank ? empty ?? "Nothing measured in this period" : void 0,
747
+ build,
748
+ tooltip: (i) => ({
749
+ title: labels[i],
750
+ rows: [
751
+ ...values[i] != null ? [{ key: "value", label: named ? name ?? "" : "", color, value: format(values[i]) }] : [],
752
+ ...compare.flatMap(
753
+ (c, k) => c.values[i] != null ? [{ key: `compare-${k}`, label: c.name, color: c.color ?? "var(--color-muted-foreground)", value: format(c.values[i]) }] : []
754
+ )
755
+ ],
756
+ note: values[i] == null ? "nothing ran" : [
757
+ forecast != null && i > forecast ? "forecast" : "",
758
+ bandText(i) ? `${band?.label ?? "band"} ${bandText(i)}` : ""
759
+ ].filter(Boolean).join(" \xB7 ") || void 0
760
+ }),
761
+ table: {
762
+ columns: [
763
+ { accessorKey: "period", header: "Period" },
764
+ { accessorKey: "value", header: name ?? "Value" },
765
+ ...compare.map((c, k) => ({ accessorKey: `compare${k}`, header: c.name })),
766
+ ...band ? [{ accessorKey: "band", header: band.label ?? "Band" }] : []
767
+ ],
768
+ rows: values.map((v, i) => ({
769
+ period: labels[i],
770
+ value: v != null ? `${format(v)}${forecast != null && i > forecast ? " (forecast)" : ""}` : "\u2014",
771
+ ...Object.fromEntries(compare.map((c, k) => [`compare${k}`, c.values[i] != null ? format(c.values[i]) : "\u2014"])),
772
+ band: bandText(i) || "\u2014"
773
+ }))
774
+ },
775
+ ...props
776
+ }
777
+ );
778
+ }
779
+ );
780
+ LineChart.displayName = "LineChart";
781
+
782
+ // src/base/draw.ts
783
+ function topRoundedRect(ctx, x, y, w, h, r) {
784
+ const radius = Math.max(0, Math.min(r, w / 2, h));
785
+ ctx.beginPath();
786
+ ctx.moveTo(x, y + h);
787
+ ctx.lineTo(x, y + radius);
788
+ ctx.arcTo(x, y, x + radius, y, radius);
789
+ ctx.lineTo(x + w - radius, y);
790
+ ctx.arcTo(x + w, y, x + w, y + radius, radius);
791
+ ctx.lineTo(x + w, y + h);
792
+ ctx.closePath();
793
+ ctx.fill();
794
+ }
795
+ var NONE2 = [];
796
+ var StackedBarChartV = React2__namespace.forwardRef(
797
+ ({ data, series, max, gridlines = NONE2, height = 132, ticks, empty, ...props }, ref) => {
798
+ const totals = React2__namespace.useMemo(
799
+ () => data.map((d) => series.reduce((sum, s) => sum + (d.values[s.key] ?? 0), 0)),
800
+ [data, series]
801
+ );
802
+ const blank = totals.every((t) => t === 0);
803
+ const build = React2__namespace.useCallback(
804
+ (_theme, host) => {
805
+ const colors = series.map((s) => resolveColor(host, s.color));
806
+ const scale = countScale(Math.max(0, ...totals));
807
+ const ceiling = max ?? Math.max(scale.ceiling, ...gridlines);
808
+ const draw = (u) => {
809
+ const ctx = u.ctx;
810
+ const pr = uPlot2__default.default.pxRatio;
811
+ const slot = u.bbox.width / Math.max(data.length, 1);
812
+ const width = Math.max(pr, Math.min(MAX_BAR_WIDTH * pr, slot * 0.72));
813
+ const gap = SEGMENT_GAP * pr;
814
+ const baseline = u.valToPos(0, "y", true);
815
+ ctx.save();
816
+ data.forEach((d, i) => {
817
+ const x = Math.round(u.valToPos(i, "x", true) - width / 2);
818
+ const parts = series.map((s, k) => ({ value: d.values[s.key] ?? 0, color: colors[k] })).filter((p) => p.value > 0);
819
+ let sum = 0;
820
+ let bottom = baseline;
821
+ parts.forEach((p, j) => {
822
+ sum += p.value;
823
+ const top = u.valToPos(Math.min(sum, ceiling), "y", true);
824
+ const last = j === parts.length - 1;
825
+ const y = last ? top : Math.min(top + gap, bottom - pr);
826
+ const h = Math.max(pr, bottom - y);
827
+ ctx.fillStyle = p.color;
828
+ if (last) topRoundedRect(ctx, x, y, width, h, BAR_RADIUS * pr);
829
+ else ctx.fillRect(x, y, width, h);
830
+ bottom = top;
831
+ });
832
+ });
833
+ ctx.restore();
834
+ };
835
+ return {
836
+ // One invisible series carries the totals, so the cursor has a value
837
+ // to snap to on every day; the columns themselves are drawn above.
838
+ series: [{ stroke: "transparent", paths: () => null, points: { show: false } }],
839
+ data: [totals],
840
+ ceiling,
841
+ splits: gridlines.length ? gridlines : max != null ? fixedSplits(max) : scale.splits,
842
+ format: formatCount,
843
+ crosshair: false,
844
+ draw
845
+ };
846
+ },
847
+ [data, series, totals, max, gridlines]
848
+ );
849
+ const summary = React2__namespace.useMemo(() => {
850
+ if (blank) return "Stacked column chart with nothing counted in this period.";
851
+ const sums = series.map((s) => data.reduce((sum, d) => sum + (d.values[s.key] ?? 0), 0));
852
+ const busiest = totals.indexOf(Math.max(...totals));
853
+ return [
854
+ `Stacked column chart over ${data.length} periods, ${data[0].label} to ${data[data.length - 1].label}.`,
855
+ `Totals: ${series.map((s, k) => `${s.label} ${formatCount(sums[k])}`).join(", ")}.`,
856
+ `Busiest: ${data[busiest].label} with ${formatCount(totals[busiest])}.`
857
+ ].join(" ");
858
+ }, [blank, data, series, totals]);
859
+ return /* @__PURE__ */ jsxRuntime.jsx(
860
+ ChartFrame,
861
+ {
862
+ ref,
863
+ summary,
864
+ labels: data.map((d) => d.label),
865
+ height,
866
+ ticks,
867
+ legend: series,
868
+ empty: blank ? empty ?? "Nothing counted in this period" : void 0,
869
+ build,
870
+ tooltip: (i) => ({
871
+ title: data[i].label,
872
+ rows: series.map((s) => ({
873
+ key: s.key,
874
+ label: s.label,
875
+ color: s.color,
876
+ value: formatCount(data[i].values[s.key] ?? 0)
877
+ }))
878
+ }),
879
+ table: {
880
+ columns: [
881
+ { accessorKey: "period", header: "Period" },
882
+ ...series.map((s) => ({ accessorKey: s.key, header: s.label })),
883
+ { accessorKey: "__total", header: "Total" }
884
+ ],
885
+ rows: data.map((d, i) => ({
886
+ period: d.label,
887
+ ...Object.fromEntries(series.map((s) => [s.key, formatCount(d.values[s.key] ?? 0)])),
888
+ __total: formatCount(totals[i])
889
+ }))
890
+ },
891
+ ...props
892
+ }
893
+ );
894
+ }
895
+ );
896
+ StackedBarChartV.displayName = "StackedBarChartV";
897
+ var NONE3 = [];
898
+ var FILL_ALPHA = 0.78;
899
+ var StackedAreaChart = React2__namespace.forwardRef(
900
+ ({
901
+ data,
902
+ series,
903
+ marks = NONE3,
904
+ markLegend,
905
+ format = formatCount,
906
+ max,
907
+ gridlines = NONE3,
908
+ ticks,
909
+ height = 200,
910
+ directLabels,
911
+ empty,
912
+ ...props
913
+ }, ref) => {
914
+ const fmt = useLatest(format);
915
+ const labelled = directLabels ?? series.length <= 4;
916
+ const stack = React2__namespace.useMemo(() => {
917
+ const sums = data.map(() => 0);
918
+ return series.map(
919
+ (s) => data.map((d, i) => {
920
+ sums[i] += d.values[s.key] ?? 0;
921
+ return sums[i];
922
+ })
923
+ );
924
+ }, [data, series]);
925
+ const totals = stack.length ? stack[stack.length - 1] : data.map(() => 0);
926
+ const blank = totals.every((t) => t === 0);
927
+ const build = React2__namespace.useCallback(
928
+ (theme, host) => {
929
+ const colors = series.map((s) => resolveColor(host, s.color));
930
+ const scale = niceScale(Math.max(0, ...totals));
931
+ const ceiling = max ?? Math.max(scale.ceiling, ...gridlines);
932
+ const stepped = uPlot2__default.default.paths.stepped({ align: 1 });
933
+ const last = data.length - 1;
934
+ const uSeries = series.map((_, k) => ({
935
+ stroke: theme.card,
936
+ width: 2,
937
+ paths: stepped,
938
+ points: { show: false },
939
+ // The bottom band fills to the baseline; every other band is a
940
+ // `bands` entry filling down to the band beneath it.
941
+ fill: k === 0 ? withAlpha(colors[0], FILL_ALPHA) : void 0
942
+ }));
943
+ const bands = series.slice(1).map((_, j) => ({
944
+ series: [j + 2, j + 1],
945
+ fill: withAlpha(colors[j + 1], FILL_ALPHA)
946
+ }));
947
+ const measure2 = document.createElement("canvas").getContext("2d");
948
+ let padRight = 4;
949
+ if (labelled && measure2) {
950
+ measure2.font = `600 ${theme.font.xs}`;
951
+ padRight = Math.ceil(Math.max(0, ...series.map((s) => measure2.measureText(s.label).width))) + 20;
952
+ }
953
+ const draw = (u) => {
954
+ if (!labelled || last < 0) return;
955
+ const ctx = u.ctx;
956
+ const pr = uPlot2__default.default.pxRatio;
957
+ const x = u.bbox.left + u.bbox.width + 6 * pr;
958
+ const line = theme.size.xs * pr + 2 * pr;
959
+ const placed = series.map((s, k) => {
960
+ const top = stack[k][last];
961
+ const bottom = k ? stack[k - 1][last] : 0;
962
+ return { s, k, empty: top === bottom, y: u.valToPos((top + bottom) / 2, "y", true) };
963
+ }).filter((p) => !p.empty).sort((a, b) => b.y - a.y);
964
+ ctx.save();
965
+ ctx.font = `600 ${theme.size.xs * pr}px ${theme.family}`;
966
+ ctx.textBaseline = "middle";
967
+ ctx.textAlign = "left";
968
+ let prev = Infinity;
969
+ for (const p of placed) {
970
+ if (prev - p.y < line) continue;
971
+ ctx.fillStyle = colors[p.k];
972
+ ctx.fillRect(x, Math.round(p.y - pr), 6 * pr, 2 * pr);
973
+ ctx.fillStyle = theme.foreground;
974
+ ctx.fillText(p.s.label, x + 10 * pr, p.y);
975
+ prev = p.y;
976
+ }
977
+ ctx.restore();
978
+ };
979
+ return {
980
+ series: uSeries,
981
+ data: stack,
982
+ bands,
983
+ ceiling,
984
+ splits: gridlines.length ? gridlines : max != null ? fixedSplits(max) : scale.splits,
985
+ format: (v) => fmt.current(v),
986
+ crosshair: true,
987
+ cursorPoints: false,
988
+ draw,
989
+ padRight
990
+ };
991
+ },
992
+ [data, series, stack, totals, max, gridlines, labelled, fmt]
993
+ );
994
+ const legend = [
995
+ ...series,
996
+ ...marks.length && markLegend ? [{ key: "__mark", label: markLegend, color: "var(--color-muted-foreground)", kind: "dashed" }] : []
997
+ ];
998
+ const summary = React2__namespace.useMemo(() => {
999
+ if (blank) return "Stacked area chart with nothing recorded in this period.";
1000
+ const last = data.length - 1;
1001
+ return [
1002
+ `Stacked area chart over ${data.length} periods, ${data[0].label} to ${data[last].label}.`,
1003
+ `${format(totals[0])} at the start, ${format(totals[last])} at the end.`,
1004
+ `At the end: ${series.map((s) => `${s.label} ${format(data[last].values[s.key] ?? 0)}`).join(", ")}.`,
1005
+ marks.length ? `Writes marked: ${marks.map((m) => `${m.label} (${data[m.index]?.label})`).join(", ")}.` : ""
1006
+ ].filter(Boolean).join(" ");
1007
+ }, [blank, data, series, totals, marks, format]);
1008
+ return /* @__PURE__ */ jsxRuntime.jsx(
1009
+ ChartFrame,
1010
+ {
1011
+ ref,
1012
+ summary,
1013
+ labels: data.map((d) => d.label),
1014
+ height,
1015
+ ticks,
1016
+ marks,
1017
+ legend,
1018
+ empty: blank ? empty ?? "Nothing recorded in this period" : void 0,
1019
+ build,
1020
+ tooltip: (i) => ({
1021
+ title: data[i].label,
1022
+ // Top-down, the order the bands are read in.
1023
+ rows: [...series].reverse().map((s) => ({
1024
+ key: s.key,
1025
+ label: s.label,
1026
+ color: s.color,
1027
+ value: format(data[i].values[s.key] ?? 0)
1028
+ })),
1029
+ note: `${format(totals[i])} in all`
1030
+ }),
1031
+ table: {
1032
+ columns: [
1033
+ { accessorKey: "period", header: "Period" },
1034
+ ...series.map((s) => ({ accessorKey: s.key, header: s.label })),
1035
+ { accessorKey: "__total", header: "Total" }
1036
+ ],
1037
+ rows: data.map((d, i) => ({
1038
+ period: d.label,
1039
+ ...Object.fromEntries(series.map((s) => [s.key, format(d.values[s.key] ?? 0)])),
1040
+ __total: format(totals[i])
1041
+ }))
1042
+ },
1043
+ ...props
1044
+ }
1045
+ );
1046
+ }
1047
+ );
1048
+ StackedAreaChart.displayName = "StackedAreaChart";
1049
+ var Sparkline = React2__namespace.forwardRef(
1050
+ ({
1051
+ values,
1052
+ width = 72,
1053
+ height = 20,
1054
+ color = "var(--color-data-1)",
1055
+ area,
1056
+ strokeWidth = 2,
1057
+ endMarker = true,
1058
+ label,
1059
+ className,
1060
+ ...props
1061
+ }, ref) => {
1062
+ const pad = 3;
1063
+ const min = Math.min(...values);
1064
+ const max = Math.max(...values);
1065
+ const span = max - min || 1;
1066
+ const stepX = values.length > 1 ? (width - pad * 2) / (values.length - 1) : 0;
1067
+ const points = values.map((v, i) => [
1068
+ pad + i * stepX,
1069
+ height - pad - (v - min) / span * (height - pad * 2)
1070
+ ]);
1071
+ const d = points.map((p, i) => `${i ? "L" : "M"}${p[0]} ${p[1]}`).join(" ");
1072
+ const last = points[points.length - 1];
1073
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1074
+ "svg",
1075
+ {
1076
+ ref,
1077
+ width,
1078
+ height,
1079
+ viewBox: `0 0 ${width} ${height}`,
1080
+ className: ui.cn("shrink-0 overflow-visible", className),
1081
+ role: label ? "img" : void 0,
1082
+ "aria-label": label,
1083
+ "aria-hidden": label ? void 0 : true,
1084
+ ...props,
1085
+ children: [
1086
+ area ? /* @__PURE__ */ jsxRuntime.jsx(
1087
+ "path",
1088
+ {
1089
+ d: `${d} L${last[0]} ${height} L${points[0][0]} ${height} Z`,
1090
+ fill: color,
1091
+ opacity: 0.1
1092
+ }
1093
+ ) : null,
1094
+ /* @__PURE__ */ jsxRuntime.jsx(
1095
+ "path",
1096
+ {
1097
+ d,
1098
+ fill: "none",
1099
+ stroke: color,
1100
+ strokeWidth,
1101
+ strokeLinecap: "round",
1102
+ strokeLinejoin: "round"
1103
+ }
1104
+ ),
1105
+ endMarker ? /* @__PURE__ */ jsxRuntime.jsx(
1106
+ "circle",
1107
+ {
1108
+ cx: last[0],
1109
+ cy: last[1],
1110
+ r: 4,
1111
+ fill: color,
1112
+ stroke: "var(--color-card)",
1113
+ strokeWidth: 2
1114
+ }
1115
+ ) : null
1116
+ ]
1117
+ }
1118
+ );
1119
+ }
1120
+ );
1121
+ Sparkline.displayName = "Sparkline";
1122
+ var TONE = {
1123
+ primary: "bg-primary",
1124
+ muted: "bg-muted-foreground",
1125
+ success: "bg-success",
1126
+ warning: "bg-warning",
1127
+ destructive: "bg-destructive"
1128
+ };
1129
+ var percent = (v) => `${Math.round(v * 100)}%`;
1130
+ var InlineMeter = React2__namespace.forwardRef(
1131
+ ({ value, max = 1, format = percent, tone = "primary", color, label, className, ...props }, ref) => {
1132
+ const none = value == null || value <= 0;
1133
+ const share = none ? 0 : Math.min(1, value / (max || 1));
1134
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1135
+ "div",
1136
+ {
1137
+ ref,
1138
+ role: none ? void 0 : "meter",
1139
+ "aria-label": label,
1140
+ "aria-valuenow": none ? void 0 : value,
1141
+ "aria-valuemin": none ? void 0 : 0,
1142
+ "aria-valuemax": none ? void 0 : max,
1143
+ className: ui.cn("flex min-w-0 items-center gap-2", className),
1144
+ ...props,
1145
+ children: [
1146
+ none ? null : /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "h-1.5 w-16 shrink-0 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx(
1147
+ "span",
1148
+ {
1149
+ className: ui.cn("block h-full rounded-full", color ? void 0 : TONE[tone]),
1150
+ style: { width: `${share * 100}%`, background: color }
1151
+ }
1152
+ ) }),
1153
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm tabular-nums text-muted-foreground", children: none ? "\u2014" : format(value) })
1154
+ ]
1155
+ }
1156
+ );
1157
+ }
1158
+ );
1159
+ InlineMeter.displayName = "InlineMeter";
1160
+ var whole2 = new Intl.NumberFormat();
1161
+ var SegmentedBar = React2__namespace.forwardRef(
1162
+ ({ values, series, format = (v) => whole2.format(v), className, ...props }, ref) => {
1163
+ const total = series.reduce((sum, s) => sum + Math.max(0, values[s.key] ?? 0), 0);
1164
+ const parts = series.map((s) => ({ ...s, value: Math.max(0, values[s.key] ?? 0) })).filter((p) => p.value > 0);
1165
+ const pct = (v) => `${Math.round(v / total * 100)}%`;
1166
+ const summary = total ? parts.map((p) => `${p.label} ${pct(p.value)}`).join(", ") : "nothing counted";
1167
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxRuntime.jsx(
1168
+ "div",
1169
+ {
1170
+ ref,
1171
+ role: "img",
1172
+ "aria-label": summary,
1173
+ className: ui.cn(
1174
+ "flex h-2 w-full min-w-16 gap-[2px] overflow-hidden rounded-full",
1175
+ total === 0 && "bg-muted",
1176
+ className
1177
+ ),
1178
+ ...props,
1179
+ children: parts.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Tooltip, { children: [
1180
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1181
+ "span",
1182
+ {
1183
+ className: "h-full min-w-[2px]",
1184
+ style: { flexGrow: p.value, flexBasis: 0, background: p.color }
1185
+ }
1186
+ ) }),
1187
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TooltipContent, { children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums", children: [
1188
+ p.label,
1189
+ " \xB7 ",
1190
+ format(p.value),
1191
+ " \xB7 ",
1192
+ pct(p.value)
1193
+ ] }) })
1194
+ ] }, p.key))
1195
+ }
1196
+ ) });
1197
+ }
1198
+ );
1199
+ SegmentedBar.displayName = "SegmentedBar";
1200
+ var SegmentedBarLegend = React2__namespace.forwardRef(
1201
+ ({ series, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(ui.Legend, { ref, ...props, children: series.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.LegendItem, { color: s.color, label: s.label }, s.key)) })
1202
+ );
1203
+ SegmentedBarLegend.displayName = "SegmentedBarLegend";
1204
+ var MARK = {
1205
+ refused: "bg-destructive",
1206
+ egress: "bg-warning"
1207
+ };
1208
+ var HeatLane = React2__namespace.forwardRef(
1209
+ ({ values, marks, pinned, onPin, cellLabel, fill = "bg-info", stale, className, style, ...props }, ref) => {
1210
+ const markAt = React2__namespace.useMemo(() => new Map((marks ?? []).map((m) => [m.at, m.kind])), [marks]);
1211
+ return /* @__PURE__ */ jsxRuntime.jsx(
1212
+ "div",
1213
+ {
1214
+ ref,
1215
+ className: ui.cn("grid h-3 gap-px", stale && "opacity-40 grayscale", className),
1216
+ style: { gridTemplateColumns: `repeat(${values.length}, minmax(0, 1fr))`, ...style },
1217
+ ...props,
1218
+ children: values.map((v, i) => {
1219
+ const mark = markAt.get(i);
1220
+ const label = `${cellLabel ? cellLabel(i) : i}${mark ? ` \xB7 ${mark}` : v == null ? " \xB7 not touched" : ""}`;
1221
+ const cls = ui.cn(
1222
+ "block h-full min-w-0 rounded-[1px]",
1223
+ mark ? MARK[mark] : v == null ? "border border-border" : v === 0 ? "bg-muted" : fill,
1224
+ pinned === i && "ring-1 ring-foreground ring-offset-1 ring-offset-background"
1225
+ );
1226
+ const opacity = mark || v == null || v === 0 ? void 0 : Math.max(0.18, Math.min(1, v));
1227
+ return onPin && v != null ? /* @__PURE__ */ jsxRuntime.jsx(
1228
+ "button",
1229
+ {
1230
+ type: "button",
1231
+ title: label,
1232
+ "aria-label": label,
1233
+ "aria-pressed": pinned === i,
1234
+ className: ui.cn(cls, "cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"),
1235
+ style: { opacity },
1236
+ onClick: () => onPin(pinned === i ? null : i)
1237
+ },
1238
+ i
1239
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { title: label, className: cls, style: { opacity } }, i);
1240
+ })
1241
+ }
1242
+ );
1243
+ }
1244
+ );
1245
+ HeatLane.displayName = "HeatLane";
1246
+ var BarChartH = React2__namespace.forwardRef(
1247
+ ({
1248
+ data,
1249
+ max,
1250
+ labelWidth,
1251
+ color = "var(--color-data-1)",
1252
+ caption,
1253
+ variant = "tips",
1254
+ negativeColor = "var(--color-destructive)",
1255
+ diverging,
1256
+ className,
1257
+ ...props
1258
+ }, ref) => {
1259
+ if (variant === "ranked") {
1260
+ const ceiling2 = max ?? (Math.max(...data.map((d) => Math.abs(d.value)), 0) || 1);
1261
+ const share = (d) => Math.min(100, Math.abs(d.value) / ceiling2 * 100);
1262
+ const fill = (d) => d.color ?? (d.muted ? "color-mix(in srgb, var(--color-muted-foreground) 40%, transparent)" : d.value < 0 ? negativeColor : color);
1263
+ const columns = labelWidth != null ? { gridTemplateColumns: `${labelWidth}px minmax(0, 1fr) 40px` } : void 0;
1264
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("@container flex flex-col gap-1.5", className), ...props, children: [
1265
+ caption != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: caption }) : null,
1266
+ data.map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1267
+ "div",
1268
+ {
1269
+ className: ui.cn(
1270
+ "grid items-center gap-[7px] text-sm",
1271
+ labelWidth == null && "grid-cols-[96px_minmax(0,1fr)_40px] @max-[290px]:grid-cols-[74px_minmax(0,1fr)_36px]"
1272
+ ),
1273
+ style: columns,
1274
+ children: [
1275
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ui.cn("min-w-0 truncate", d.muted && "text-muted-foreground"), children: d.label }),
1276
+ diverging ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative block h-2 before:absolute before:-inset-y-[3px] before:left-1/2 before:border-l before:border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
1277
+ "span",
1278
+ {
1279
+ title: `${d.label}: ${d.display ?? d.value}`,
1280
+ className: "absolute top-0 h-2 rounded-[1px] opacity-85",
1281
+ style: {
1282
+ left: d.value < 0 ? `${50 - share(d) / 2}%` : "50%",
1283
+ width: `${share(d) / 2}%`,
1284
+ background: fill(d)
1285
+ }
1286
+ }
1287
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1288
+ "span",
1289
+ {
1290
+ title: `${d.label}: ${d.display ?? d.value}`,
1291
+ className: "h-2 rounded-[1px] opacity-85",
1292
+ style: { width: `${share(d)}%`, background: fill(d) }
1293
+ }
1294
+ ),
1295
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-end font-mono text-xs tabular-nums", children: d.display ?? d.value })
1296
+ ]
1297
+ },
1298
+ i
1299
+ )),
1300
+ diverging ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
1301
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: diverging.below }),
1302
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "0" }),
1303
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: diverging.above })
1304
+ ] }) : null
1305
+ ] });
1306
+ }
1307
+ const ceiling = max ?? (Math.max(...data.map((d) => d.value), 0) || 1);
1308
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("flex flex-col gap-1", className), ...props, children: [
1309
+ caption != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: caption }) : null,
1310
+ data.map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-4 items-center gap-2", children: [
1311
+ /* @__PURE__ */ jsxRuntime.jsx(
1312
+ "span",
1313
+ {
1314
+ className: "shrink-0 truncate text-sm text-muted-foreground",
1315
+ style: { width: labelWidth ?? 78 },
1316
+ children: d.label
1317
+ }
1318
+ ),
1319
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [
1320
+ /* @__PURE__ */ jsxRuntime.jsx(
1321
+ "span",
1322
+ {
1323
+ title: `${d.label}: ${d.display ?? d.value}`,
1324
+ className: "h-2.5 shrink-0 rounded-e-[4px]",
1325
+ style: {
1326
+ width: `${Math.max(0, d.value / ceiling * 100)}%`,
1327
+ background: d.color ?? color
1328
+ }
1329
+ }
1330
+ ),
1331
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-sm tabular-nums", children: d.display ?? d.value })
1332
+ ] })
1333
+ ] }, i))
1334
+ ] });
1335
+ }
1336
+ );
1337
+ BarChartH.displayName = "BarChartH";
1338
+ var isGrouped = (d) => "values" in d && Array.isArray(d.values);
1339
+ var BarChartV = React2__namespace.forwardRef(
1340
+ ({
1341
+ data,
1342
+ series,
1343
+ max,
1344
+ gridlines = [],
1345
+ target,
1346
+ height = 160,
1347
+ color = "var(--color-data-1)",
1348
+ highlightColor,
1349
+ highlightIndex,
1350
+ labelMode = "last",
1351
+ caption,
1352
+ variant = "default",
1353
+ planLabel,
1354
+ className,
1355
+ ...props
1356
+ }, ref) => {
1357
+ const grouped = data.some(isGrouped);
1358
+ const seriesColor = (k) => series?.[k]?.color ?? (k === 0 ? color : `var(--color-data-${k + 1})`);
1359
+ const emphasis = highlightIndex === void 0 ? grouped ? void 0 : data.length - 1 : highlightIndex ?? void 0;
1360
+ const marks = data.map(
1361
+ (d, i) => isGrouped(d) ? d.values.map((value, k) => ({
1362
+ value,
1363
+ display: d.display?.[k] ?? value,
1364
+ color: seriesColor(k)
1365
+ })) : [
1366
+ {
1367
+ plan: d.plan,
1368
+ value: d.value,
1369
+ display: d.display ?? d.value,
1370
+ color: i === emphasis ? highlightColor ?? color : color
1371
+ }
1372
+ ]
1373
+ );
1374
+ const largest = Math.max(
1375
+ 0,
1376
+ ...marks.flat().map((m) => Math.max(m.value, m.plan ?? 0)),
1377
+ target?.value ?? 0
1378
+ );
1379
+ const planned = marks.some((g) => g.some((m) => m.plan != null));
1380
+ const ceiling = max ?? (largest || 1);
1381
+ const labelled = (i) => labelMode === "all" || labelMode === "last" && i === emphasis;
1382
+ const at = (v) => `${v / ceiling * 100}%`;
1383
+ const comparison = variant === "comparison";
1384
+ const legend = series != null && series.length > 1 || planned && planLabel != null;
1385
+ const rules = [
1386
+ ...gridlines.map((g) => ({ key: `g${g}`, value: g, label: g })),
1387
+ // With a legend the target is named there, and a gutter label would only
1388
+ // take width from the plot.
1389
+ ...target?.label != null && !legend ? [{ key: "target", value: target.value, label: target.label }] : []
1390
+ ];
1391
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("flex flex-col gap-1", className), ...props, children: [
1392
+ caption != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: caption }) : null,
1393
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 grid grid-cols-[minmax(0,1fr)_auto] gap-y-1", children: [
1394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", style: { height }, children: [
1395
+ gridlines.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
1396
+ "span",
1397
+ {
1398
+ "aria-hidden": true,
1399
+ className: "absolute inset-x-0 h-px bg-border",
1400
+ style: { bottom: at(g) }
1401
+ },
1402
+ g
1403
+ )),
1404
+ comparison ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "absolute inset-x-0 bottom-0 h-px bg-border" }) : null,
1405
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-end justify-between gap-2", children: marks.map((group, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full min-w-0 flex-1 justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
1406
+ "div",
1407
+ {
1408
+ className: ui.cn(
1409
+ "flex h-full min-w-0 items-end justify-center gap-0.5",
1410
+ comparison ? "w-2/3" : "w-full"
1411
+ ),
1412
+ children: group.map((m, k) => /* @__PURE__ */ jsxRuntime.jsxs(
1413
+ "div",
1414
+ {
1415
+ className: ui.cn(
1416
+ "relative flex h-full min-w-0 flex-1 flex-col items-center justify-end gap-1",
1417
+ !comparison && "max-w-6"
1418
+ ),
1419
+ children: [
1420
+ m.plan != null ? /* @__PURE__ */ jsxRuntime.jsx(
1421
+ "span",
1422
+ {
1423
+ "aria-hidden": true,
1424
+ className: "absolute inset-x-0 bottom-0 border border-dashed border-muted-foreground",
1425
+ style: { height: `${Math.max(0, m.plan / ceiling * 100)}%` }
1426
+ }
1427
+ ) : null,
1428
+ labelled(i) ? /* @__PURE__ */ jsxRuntime.jsx(
1429
+ "span",
1430
+ {
1431
+ className: ui.cn(
1432
+ "text-sm whitespace-nowrap tabular-nums",
1433
+ comparison && "text-muted-foreground"
1434
+ ),
1435
+ children: m.display
1436
+ }
1437
+ ) : null,
1438
+ /* @__PURE__ */ jsxRuntime.jsx(
1439
+ "span",
1440
+ {
1441
+ title: `${textOf(data[i].label)}: ${textOf(m.display)}`,
1442
+ className: ui.cn(
1443
+ m.plan != null ? "relative w-2/3" : "w-full",
1444
+ !comparison && "rounded-t-[4px]"
1445
+ ),
1446
+ style: {
1447
+ height: `${Math.max(0, m.value / ceiling * 100)}%`,
1448
+ background: m.color,
1449
+ opacity: emphasis == null || i === emphasis ? 1 : 0.55
1450
+ }
1451
+ }
1452
+ )
1453
+ ]
1454
+ },
1455
+ k
1456
+ ))
1457
+ }
1458
+ ) }, i)) }),
1459
+ target ? /* @__PURE__ */ jsxRuntime.jsx(
1460
+ "span",
1461
+ {
1462
+ "aria-hidden": true,
1463
+ className: "absolute inset-x-0 border-t border-dashed",
1464
+ style: {
1465
+ bottom: at(target.value),
1466
+ borderColor: target.color ?? "var(--color-muted-foreground)"
1467
+ }
1468
+ }
1469
+ ) : null
1470
+ ] }),
1471
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
1472
+ rules.map((r) => /* @__PURE__ */ jsxRuntime.jsx(
1473
+ "span",
1474
+ {
1475
+ "aria-hidden": true,
1476
+ className: "invisible block h-0 pl-1 text-sm whitespace-nowrap",
1477
+ children: r.label
1478
+ },
1479
+ r.key
1480
+ )),
1481
+ rules.map((r) => /* @__PURE__ */ jsxRuntime.jsx(
1482
+ "span",
1483
+ {
1484
+ className: "absolute left-0 translate-y-1/2 pl-1 text-sm whitespace-nowrap tabular-nums text-muted-foreground",
1485
+ style: { bottom: at(r.value) },
1486
+ children: r.label
1487
+ },
1488
+ r.key
1489
+ ))
1490
+ ] }),
1491
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-between gap-2", children: data.map((d, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col items-center", children: [
1492
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm text-muted-foreground", children: d.label }),
1493
+ d.sublabel != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm text-muted-foreground/70", children: d.sublabel }) : null
1494
+ ] }, i)) })
1495
+ ] }),
1496
+ legend ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Legend, { children: [
1497
+ (series ?? []).map((s, k) => /* @__PURE__ */ jsxRuntime.jsx(ui.LegendItem, { kind: "box", color: seriesColor(k), label: s.name }, k)),
1498
+ planned && planLabel != null ? /* @__PURE__ */ jsxRuntime.jsx(ui.LegendItem, { kind: "outline", color: "var(--color-muted-foreground)", label: planLabel }) : null,
1499
+ target?.label != null ? /* @__PURE__ */ jsxRuntime.jsx(
1500
+ ui.LegendItem,
1501
+ {
1502
+ kind: "dashed",
1503
+ color: target.color ?? "var(--color-muted-foreground)",
1504
+ label: target.label
1505
+ }
1506
+ ) : null
1507
+ ] }) : null
1508
+ ] });
1509
+ }
1510
+ );
1511
+ BarChartV.displayName = "BarChartV";
1512
+ function textOf(node) {
1513
+ return typeof node === "string" || typeof node === "number" ? String(node) : "";
1514
+ }
1515
+ var DivergingBar = React2__namespace.forwardRef(
1516
+ ({
1517
+ data,
1518
+ max,
1519
+ labelWidth = 104,
1520
+ positiveColor = "var(--color-success)",
1521
+ negativeColor = "var(--color-destructive)",
1522
+ caption,
1523
+ className,
1524
+ ...props
1525
+ }, ref) => {
1526
+ const ceiling = max ?? (Math.max(...data.map((d) => Math.abs(d.value)), 0) || 1);
1527
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("flex flex-col gap-1", className), ...props, children: [
1528
+ caption != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: caption }) : null,
1529
+ data.map((d, i) => {
1530
+ const positive = d.value >= 0;
1531
+ const width = `${Math.abs(d.value) / ceiling * 50}%`;
1532
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-4 items-center gap-2", children: [
1533
+ /* @__PURE__ */ jsxRuntime.jsx(
1534
+ "span",
1535
+ {
1536
+ className: "shrink-0 truncate text-sm text-muted-foreground",
1537
+ style: { width: labelWidth },
1538
+ children: d.label
1539
+ }
1540
+ ),
1541
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex min-w-0 flex-1 items-center", children: [
1542
+ /* @__PURE__ */ jsxRuntime.jsx(
1543
+ "span",
1544
+ {
1545
+ "aria-hidden": true,
1546
+ className: "absolute left-1/2 h-4 w-px -translate-x-1/2 bg-border"
1547
+ }
1548
+ ),
1549
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex w-1/2 justify-end", children: !positive ? /* @__PURE__ */ jsxRuntime.jsx(
1550
+ "span",
1551
+ {
1552
+ title: `${d.label}: ${d.display ?? d.value}`,
1553
+ className: "h-2.5 rounded-s-[4px]",
1554
+ style: { width, background: negativeColor }
1555
+ }
1556
+ ) : null }),
1557
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex w-1/2 justify-start", children: positive ? /* @__PURE__ */ jsxRuntime.jsx(
1558
+ "span",
1559
+ {
1560
+ title: `${d.label}: ${d.display ?? d.value}`,
1561
+ className: "h-2.5 rounded-e-[4px]",
1562
+ style: { width, background: positiveColor }
1563
+ }
1564
+ ) : null })
1565
+ ] }),
1566
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-8 shrink-0 text-right text-sm tabular-nums", children: d.display ?? (d.value > 0 ? `+${d.value}` : d.value) })
1567
+ ] }, i);
1568
+ })
1569
+ ] });
1570
+ }
1571
+ );
1572
+ DivergingBar.displayName = "DivergingBar";
1573
+ var INDENT = 12;
1574
+ var everyRow = (rows) => rows.flatMap((r) => [r, ...everyRow(r.children ?? [])]);
1575
+ function Cells({
1576
+ cells,
1577
+ byKey,
1578
+ cellSize,
1579
+ className
1580
+ }) {
1581
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("flex gap-1", className), children: cells.map((c, i) => {
1582
+ const s = byKey.get(c.state);
1583
+ return /* @__PURE__ */ jsxRuntime.jsx(
1584
+ "span",
1585
+ {
1586
+ title: `${c.at} \xB7 ${s?.label ?? c.state}${c.detail ? ` \xB7 ${c.detail}` : ""}`,
1587
+ className: ui.cn("shrink-0 rounded-[2px]", s?.hollow && "border"),
1588
+ style: {
1589
+ width: cellSize,
1590
+ height: cellSize,
1591
+ background: s?.hollow ? "transparent" : s?.color ?? "var(--color-muted)",
1592
+ borderColor: s?.hollow ? s.color ?? "var(--color-border)" : void 0
1593
+ }
1594
+ },
1595
+ i
1596
+ );
1597
+ }) });
1598
+ }
1599
+ function Ticks({
1600
+ count,
1601
+ ticks,
1602
+ cellSize,
1603
+ className
1604
+ }) {
1605
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ui.cn("flex gap-1", className), "aria-hidden": true, children: Array.from({ length: count }, (_, i) => {
1606
+ const tick = ticks.find((t) => t.at === i);
1607
+ return /* @__PURE__ */ jsxRuntime.jsx(
1608
+ "span",
1609
+ {
1610
+ className: "shrink-0 text-sm tabular-nums text-muted-foreground",
1611
+ style: { width: cellSize },
1612
+ children: tick ? tick.label : ""
1613
+ },
1614
+ i
1615
+ );
1616
+ }) });
1617
+ }
1618
+ var HeatStrip = React2__namespace.forwardRef(
1619
+ ({
1620
+ cells,
1621
+ rows,
1622
+ states,
1623
+ ticks,
1624
+ cellSize = 14,
1625
+ labelWidth,
1626
+ expanded,
1627
+ defaultExpanded,
1628
+ onExpandedChange,
1629
+ className,
1630
+ ...props
1631
+ }, ref) => {
1632
+ const byKey = React2__namespace.useMemo(
1633
+ () => new Map(states.map((s) => [s.key, s])),
1634
+ [states]
1635
+ );
1636
+ const parents = everyRow(rows ?? []).filter((r) => r.children?.length).map((r) => r.key);
1637
+ const nested = parents.length > 0;
1638
+ const { isOpen, toggle } = ui.useExpandedKeys({
1639
+ expanded,
1640
+ defaultExpanded,
1641
+ onExpandedChange,
1642
+ keys: parents
1643
+ });
1644
+ const shown = [];
1645
+ const walk = (list, depth) => {
1646
+ for (const row of list) {
1647
+ shown.push({ row, depth });
1648
+ if (row.children?.length && isOpen(row.key)) walk(row.children, depth + 1);
1649
+ }
1650
+ };
1651
+ walk(rows ?? [], 0);
1652
+ const count = Math.max(cells?.length ?? 0, ...shown.map((r) => r.row.cells.length));
1653
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: ui.cn("flex flex-col gap-1.5", className), ...props, children: [
1654
+ rows ? /* @__PURE__ */ jsxRuntime.jsxs(
1655
+ "div",
1656
+ {
1657
+ className: "grid items-center gap-x-2 gap-y-1",
1658
+ style: {
1659
+ gridTemplateColumns: `${labelWidth != null ? `${labelWidth}px` : "fit-content(40%)"} minmax(0, 1fr)`
1660
+ },
1661
+ children: [
1662
+ shown.map(({ row, depth }) => {
1663
+ const kids = (row.children?.length ?? 0) > 0;
1664
+ const open = kids && isOpen(row.key);
1665
+ return /* @__PURE__ */ jsxRuntime.jsxs(React2__namespace.Fragment, { children: [
1666
+ /* @__PURE__ */ jsxRuntime.jsxs(
1667
+ "span",
1668
+ {
1669
+ className: ui.cn("flex min-w-0 items-center gap-1 text-sm", depth > 0 && "text-muted-foreground"),
1670
+ style: { paddingLeft: depth * INDENT },
1671
+ children: [
1672
+ kids ? /* @__PURE__ */ jsxRuntime.jsx(
1673
+ ui.ExpandToggle,
1674
+ {
1675
+ open,
1676
+ label: typeof row.label === "string" ? row.label : row.key,
1677
+ onClick: () => toggle(row.key)
1678
+ }
1679
+ ) : nested ? /* @__PURE__ */ jsxRuntime.jsx(ui.ExpandToggle.Spacer, {}) : null,
1680
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: row.label })
1681
+ ]
1682
+ }
1683
+ ),
1684
+ /* @__PURE__ */ jsxRuntime.jsx(
1685
+ Cells,
1686
+ {
1687
+ cells: row.cells,
1688
+ byKey,
1689
+ cellSize,
1690
+ className: "min-w-0 overflow-hidden"
1691
+ }
1692
+ )
1693
+ ] }, row.key);
1694
+ }),
1695
+ ticks?.length ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1696
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
1697
+ /* @__PURE__ */ jsxRuntime.jsx(Ticks, { count, ticks, cellSize, className: "min-w-0 overflow-hidden" })
1698
+ ] }) : null
1699
+ ]
1700
+ }
1701
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1702
+ /* @__PURE__ */ jsxRuntime.jsx(Cells, { cells: cells ?? [], byKey, cellSize, className: "flex-wrap" }),
1703
+ ticks?.length ? /* @__PURE__ */ jsxRuntime.jsx(Ticks, { count, ticks, cellSize, className: "flex-wrap" }) : null
1704
+ ] }),
1705
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", children: states.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
1706
+ /* @__PURE__ */ jsxRuntime.jsx(
1707
+ "span",
1708
+ {
1709
+ "aria-hidden": true,
1710
+ className: ui.cn("size-2.5 shrink-0 rounded-[2px]", s.hollow && "border"),
1711
+ style: {
1712
+ background: s.hollow ? "transparent" : s.color ?? "var(--color-muted)",
1713
+ borderColor: s.hollow ? s.color ?? "var(--color-border)" : void 0
1714
+ }
1715
+ }
1716
+ ),
1717
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: s.label })
1718
+ ] }, s.key)) })
1719
+ ] });
1720
+ }
1721
+ );
1722
+ HeatStrip.displayName = "HeatStrip";
1723
+
1724
+ exports.BarChartH = BarChartH;
1725
+ exports.BarChartV = BarChartV;
1726
+ exports.DivergingBar = DivergingBar;
1727
+ exports.HeatLane = HeatLane;
1728
+ exports.HeatStrip = HeatStrip;
1729
+ exports.InlineMeter = InlineMeter;
1730
+ exports.LineChart = LineChart;
1731
+ exports.SegmentedBar = SegmentedBar;
1732
+ exports.SegmentedBarLegend = SegmentedBarLegend;
1733
+ exports.Sparkline = Sparkline;
1734
+ exports.StackedAreaChart = StackedAreaChart;
1735
+ exports.StackedBarChartV = StackedBarChartV;
1736
+ //# sourceMappingURL=index.cjs.map
1737
+ //# sourceMappingURL=index.cjs.map