@seekalfred/primitives 0.7.1 → 0.7.4
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/README.md +255 -5
- package/dist/alert/alert.d.ts +49 -4
- package/dist/alert/alert.js +66 -38
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +31 -4
- package/dist/alert-dialog/alert-dialog.js +75 -26
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/announcement-bar/announcement-bar.d.ts +15 -4
- package/dist/announcement-bar/announcement-bar.js +31 -6
- package/dist/announcement-bar/announcement-bar.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -0
- package/dist/avatar/avatar.js +33 -12
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart-series.d.ts +75 -0
- package/dist/bar-chart/bar-chart-series.js +401 -0
- package/dist/bar-chart/bar-chart-series.js.map +1 -0
- package/dist/bar-chart/bar-chart.d.ts +25 -1
- package/dist/bar-chart/bar-chart.js +26 -1
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/index.d.ts +2 -1
- package/dist/button/button.d.ts +1 -1
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- package/dist/card/card.js.map +1 -1
- package/dist/carousel/carousel.d.ts +21 -0
- package/dist/carousel/carousel.js +34 -8
- package/dist/carousel/carousel.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +11 -0
- package/dist/chip-group/chip-group.js +50 -34
- package/dist/chip-group/chip-group.js.map +1 -1
- package/dist/chip-group/index.d.ts +2 -2
- package/dist/copy-button/copy-button.d.ts +37 -5
- package/dist/copy-button/copy-button.js +35 -11
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +22 -0
- package/dist/donut-chart/donut-chart.js +35 -18
- package/dist/donut-chart/donut-chart.js.map +1 -1
- package/dist/donut-chart/index.d.ts +2 -2
- package/dist/empty-state/empty-state.d.ts +21 -0
- package/dist/empty-state/empty-state.js +16 -4
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
- package/dist/file-drop-zone/file-drop-zone.js +26 -8
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +4 -1
- package/dist/filter-bar/filter-bar.js +53 -9
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +18 -1
- package/dist/gauge/gauge.js +35 -21
- package/dist/gauge/gauge.js.map +1 -1
- package/dist/gauge/index.d.ts +2 -2
- package/dist/index.d.ts +15 -14
- package/dist/index.js +9 -9
- package/dist/input/input.d.ts +7 -0
- package/dist/input/input.js +7 -5
- package/dist/input/input.js.map +1 -1
- package/dist/lib/field-shell.js +6 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +22 -1
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/required-marker.js +20 -0
- package/dist/lib/required-marker.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +144 -2
- package/dist/line-chart/line-chart.js +314 -46
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -2
- package/dist/money-input/money-input.d.ts +36 -1
- package/dist/money-input/money-input.js +30 -14
- package/dist/money-input/money-input.js.map +1 -1
- package/dist/morph-select/morph-select.d.ts +26 -1
- package/dist/morph-select/morph-select.js +28 -6
- package/dist/morph-select/morph-select.js.map +1 -1
- package/dist/multi-select/index.d.ts +2 -2
- package/dist/multi-select/index.js +2 -2
- package/dist/multi-select/multi-select.d.ts +74 -2
- package/dist/multi-select/multi-select.js +213 -39
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/password-field/password-field.d.ts +16 -0
- package/dist/password-field/password-field.js +37 -8
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/popover/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +39 -2
- package/dist/popover/popover.js +29 -2
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/index.js +2 -2
- package/dist/progress/progress.d.ts +68 -3
- package/dist/progress/progress.js +156 -39
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +109 -9
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.js +4 -3
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/scrub-input.d.ts +11 -1
- package/dist/scrub-input/scrub-input.js +22 -10
- package/dist/scrub-input/scrub-input.js.map +1 -1
- package/dist/search-field/search-field.d.ts +22 -1
- package/dist/search-field/search-field.js +22 -7
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +10 -1
- package/dist/select/select.js +11 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +21 -9
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +1 -1
- package/dist/sparkline/sparkline.d.ts +27 -2
- package/dist/sparkline/sparkline.js +115 -18
- package/dist/sparkline/sparkline.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +65 -1
- package/dist/stat-card/stat-card.js +98 -25
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +115 -5
- package/dist/table/table.js +374 -94
- package/dist/table/table.js.map +1 -1
- package/dist/text-shimmer/text-shimmer.d.ts +18 -2
- package/dist/text-shimmer/text-shimmer.js +5 -2
- package/dist/text-shimmer/text-shimmer.js.map +1 -1
- package/dist/textarea/textarea.d.ts +7 -0
- package/dist/textarea/textarea.js +3 -1
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/time-picker/time-picker.d.ts +36 -1
- package/dist/time-picker/time-picker.js +33 -5
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/usage-meter/index.d.ts +2 -2
- package/dist/usage-meter/usage-meter.d.ts +30 -0
- package/dist/usage-meter/usage-meter.js +56 -38
- package/dist/usage-meter/usage-meter.js.map +1 -1
- package/package.json +3 -3
|
@@ -4,11 +4,13 @@ import { mergeRefs } from "../lib/merge-refs.js";
|
|
|
4
4
|
import { prefersReducedMotion, useIsomorphicLayoutEffect } from "../lib/motion.js";
|
|
5
5
|
import { SMOOTH, SNAPPY, tween } from "../lib/spring-tween.js";
|
|
6
6
|
import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
7
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/line-chart/line-chart.tsx
|
|
9
9
|
/** Room above the top gridline, and the gap between the crosshair and its tooltip. */
|
|
10
10
|
const TOP = 12;
|
|
11
11
|
const GAP = 14;
|
|
12
|
+
/** Between a point and its label. */
|
|
13
|
+
const LABEL_GAP = 6;
|
|
12
14
|
/** The first draw: 480ms x 1.75, slow at both ends. */
|
|
13
15
|
const LINE_CHART_DRAW_MS = 840;
|
|
14
16
|
const PALETTE = [
|
|
@@ -25,9 +27,9 @@ const GRID = Array.from({ length: 241 }, (_, index) => index / 240);
|
|
|
25
27
|
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
26
28
|
const colorOf = (line, index) => line.color ?? PALETTE[Math.min(index, PALETTE.length - 1)];
|
|
27
29
|
const clean = (value) => Number(value.toPrecision(12));
|
|
28
|
-
/** Clean gridlines: the smallest step of 1, 2, 2.5 or 5 that covers the data in four rows or fewer, from zero unless the data dips below it. */
|
|
29
|
-
function niceScale(low, high) {
|
|
30
|
-
const lo = Math.min(0, low);
|
|
30
|
+
/** Clean gridlines: the smallest step of 1, 2, 2.5 or 5 that covers the data in four rows or fewer, from zero unless the data dips below it (or `fromZero` is off). */
|
|
31
|
+
function niceScale(low, high, fromZero = true) {
|
|
32
|
+
const lo = fromZero ? Math.min(0, low) : low;
|
|
31
33
|
const hi = Math.max(high, lo + 1);
|
|
32
34
|
const magnitude = 10 ** Math.floor(Math.log10((hi - lo) / 4));
|
|
33
35
|
for (const factor of [
|
|
@@ -56,6 +58,23 @@ function niceScale(low, high) {
|
|
|
56
58
|
ticks: [lo, hi]
|
|
57
59
|
};
|
|
58
60
|
}
|
|
61
|
+
/** The nice scale with either end pinned: a pinned end is a tick, and the nice ticks within half a step of it give way. */
|
|
62
|
+
function pinScale(scale, min, max) {
|
|
63
|
+
if (min === void 0 && max === void 0) return scale;
|
|
64
|
+
const lo = min ?? scale.min;
|
|
65
|
+
const hi = max ?? scale.max;
|
|
66
|
+
if (!(hi > lo)) return scale;
|
|
67
|
+
const step = scale.ticks.length > 1 ? scale.ticks[1] - scale.ticks[0] : hi - lo;
|
|
68
|
+
return {
|
|
69
|
+
min: lo,
|
|
70
|
+
max: hi,
|
|
71
|
+
ticks: [
|
|
72
|
+
lo,
|
|
73
|
+
...scale.ticks.filter((tick) => tick > lo + step / 2 && tick < hi - step / 2),
|
|
74
|
+
hi
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
59
78
|
/** The line through every value, sampled densely on x from 0 to 1 in data units. Monotone tangents keep a smooth curve from swinging past the data. */
|
|
60
79
|
function curveFor(values, smooth) {
|
|
61
80
|
if (values.length === 0) return [];
|
|
@@ -88,6 +107,65 @@ function curveFor(values, smooth) {
|
|
|
88
107
|
points.push([1, values[last]]);
|
|
89
108
|
return points;
|
|
90
109
|
}
|
|
110
|
+
/** Fills each missing value straight between its present neighbours (or with the nearest one at the ends), so a gapped line still has a shape to morph. */
|
|
111
|
+
function fillGaps(values) {
|
|
112
|
+
const present = values.flatMap((value, index) => value === null ? [] : [index]);
|
|
113
|
+
if (!present.length) return values.map(() => 0);
|
|
114
|
+
return values.map((value, index) => {
|
|
115
|
+
if (value !== null) return value;
|
|
116
|
+
const after = present.find((at) => at > index);
|
|
117
|
+
const before = [...present].reverse().find((at) => at < index);
|
|
118
|
+
if (before === void 0) return values[after];
|
|
119
|
+
if (after === void 0) return values[before];
|
|
120
|
+
return values[before] + (values[after] - values[before]) * (index - before) / (after - before);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Where a line with missing values is not drawn: before its first value, after its last, and (unless it connects across them) between two values with missing ones between them. */
|
|
124
|
+
function breaksFor(values, across = false) {
|
|
125
|
+
const present = values.flatMap((value, index) => value === null ? [] : [index]);
|
|
126
|
+
if (!present.length) return values.length ? [[-Infinity, Infinity]] : [];
|
|
127
|
+
const last = values.length - 1;
|
|
128
|
+
if (last === 0) return [];
|
|
129
|
+
const breaks = [];
|
|
130
|
+
if (present[0] > 0) breaks.push([-Infinity, present[0] / last]);
|
|
131
|
+
for (let at = 1; at < present.length; at++) if (!across && present[at] - present[at - 1] > 1) breaks.push([present[at - 1] / last, present[at] / last]);
|
|
132
|
+
if (present[present.length - 1] < last) breaks.push([present[present.length - 1] / last, Infinity]);
|
|
133
|
+
return breaks;
|
|
134
|
+
}
|
|
135
|
+
function holesFor(values) {
|
|
136
|
+
const last = values.length - 1;
|
|
137
|
+
return values.flatMap((value, index) => value === null && last > 0 ? [index / last] : []);
|
|
138
|
+
}
|
|
139
|
+
const EDGE = 1e-9;
|
|
140
|
+
/** Radius of a lone point's dot, inside its 2px stroke. */
|
|
141
|
+
const DOT = 1.5;
|
|
142
|
+
const inBreak = (breaks, x) => breaks.some(([from, to]) => x > from + EDGE && x < to - EDGE);
|
|
143
|
+
/** The drawn runs of a line, split at its gaps. A run of one point is a dot: a small closed circle the stroke fills out to about 5px. */
|
|
144
|
+
function pathsFor(shape, breaks, toX, toY, bottom) {
|
|
145
|
+
const runs = [];
|
|
146
|
+
let run = null;
|
|
147
|
+
for (const [x, value] of shape) {
|
|
148
|
+
if (breaks.length && inBreak(breaks, x)) {
|
|
149
|
+
run = null;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (!run) runs.push(run = []);
|
|
153
|
+
run.push(`${toX(x).toFixed(1)},${toY(value).toFixed(1)}`);
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
line: runs.map((points) => {
|
|
157
|
+
if (points.length > 1) return `M${points.join("L")}`;
|
|
158
|
+
const [x, y] = points[0].split(",").map(Number);
|
|
159
|
+
return `M${(x - DOT).toFixed(1)},${y.toFixed(1)}a${DOT},${DOT} 0 1,0 ${DOT * 2},0a${DOT},${DOT} 0 1,0 -3,0`;
|
|
160
|
+
}).join(""),
|
|
161
|
+
area: runs.map((points) => {
|
|
162
|
+
const first = points[0].split(",")[0];
|
|
163
|
+
const lastX = points[points.length - 1].split(",")[0];
|
|
164
|
+
return `M${points.join("L")}L${lastX},${bottom}L${first},${bottom}Z`;
|
|
165
|
+
}).join("")
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const isFiniteValue = (reference) => typeof reference.value === "number" && Number.isFinite(reference.value);
|
|
91
169
|
/** Value of a sampled line at x, straight between samples. */
|
|
92
170
|
function valueAt(shape, x) {
|
|
93
171
|
if (!shape.length) return 0;
|
|
@@ -112,7 +190,7 @@ function axisPicks(data, width) {
|
|
|
112
190
|
const stride = Math.ceil(labeled.length / room);
|
|
113
191
|
return labeled.reverse().filter((_, rank) => rank % stride === 0).reverse();
|
|
114
192
|
}
|
|
115
|
-
const LineChart = forwardRef(function LineChart({ data, series, label, unit = "", height = 220, formatValue, formatTick = (value) => COMPACT.format(value), hiddenSeries, defaultHiddenSeries, onHiddenSeriesChange, onActiveChange, loading = false, emptyLabel = "No data for this range", legend, categoryLabel = "Date", curve = "smooth", className }, ref) {
|
|
193
|
+
const LineChart = forwardRef(function LineChart({ data, series, label, unit = "", height = 220, formatValue, formatTick = (value) => COMPACT.format(value), hiddenSeries, defaultHiddenSeries, onHiddenSeriesChange, onActiveChange, loading = false, emptyLabel = "No data for this range", legend, categoryLabel = "Date", curve = "smooth", missing = "zero", missingLabel = "No data", references, markers, reversed = false, valueDomain, bands, renderTooltip, className }, ref) {
|
|
116
194
|
const figure = useRef(null);
|
|
117
195
|
const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
|
|
118
196
|
const plot = useRef(null);
|
|
@@ -131,18 +209,40 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
131
209
|
};
|
|
132
210
|
const visible = series.filter((line) => !hidden.includes(line.key));
|
|
133
211
|
const showLegend = legend ?? series.length > 1;
|
|
134
|
-
const
|
|
135
|
-
const
|
|
212
|
+
const gaps = missing !== "zero";
|
|
213
|
+
const read = (item, key) => {
|
|
214
|
+
const value = item.values[key];
|
|
215
|
+
if (!gaps) return value ?? 0;
|
|
216
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
217
|
+
};
|
|
218
|
+
const signature = `${curve}|${missing}|${series.map((line) => line.key).join(",")}|${data.map((item) => `${item.key}:${series.map((line) => read(item, line.key)).join(",")}`).join(";")}`;
|
|
219
|
+
const targets = useMemo(() => new Map(series.map((line) => {
|
|
220
|
+
const values = data.map((item) => read(item, line.key));
|
|
221
|
+
return [line.key, {
|
|
222
|
+
shape: curveFor(fillGaps(values), curve === "smooth"),
|
|
223
|
+
breaks: breaksFor(values, missing === "connect"),
|
|
224
|
+
holes: holesFor(values)
|
|
225
|
+
}];
|
|
226
|
+
})), [signature]);
|
|
136
227
|
let low = Infinity;
|
|
137
228
|
let high = -Infinity;
|
|
138
229
|
for (const line of visible) for (const item of data) {
|
|
139
|
-
const value = item
|
|
230
|
+
const value = read(item, line.key);
|
|
231
|
+
if (value === null) continue;
|
|
140
232
|
low = Math.min(low, value);
|
|
141
233
|
high = Math.max(high, value);
|
|
142
234
|
}
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
235
|
+
for (const reference of references ?? []) {
|
|
236
|
+
if (!isFiniteValue(reference)) continue;
|
|
237
|
+
low = Math.min(low, reference.value);
|
|
238
|
+
high = Math.max(high, reference.value);
|
|
239
|
+
}
|
|
240
|
+
const pinMin = Number.isFinite(valueDomain?.min) ? valueDomain.min : void 0;
|
|
241
|
+
const pinMax = Number.isFinite(valueDomain?.max) ? valueDomain.max : void 0;
|
|
242
|
+
const fit = (from, to) => pinScale(niceScale(pinMin ?? from, pinMax ?? to, pinMin === void 0), pinMin, pinMax);
|
|
243
|
+
const range = Number.isFinite(low) ? fit(low, high) : pinMin !== void 0 && pinMax !== void 0 ? fit(pinMin, pinMax) : null;
|
|
244
|
+
const [steady, setSteady] = useState(() => range ?? fit(0, 100));
|
|
245
|
+
if (range && (range.min !== steady.min || range.max !== steady.max || range.ticks.join() !== steady.ticks.join())) setSteady(range);
|
|
146
246
|
const [active, setActive] = useState(null);
|
|
147
247
|
const [seen, setSeen] = useState(signature);
|
|
148
248
|
if (seen !== signature) {
|
|
@@ -160,6 +260,15 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
160
260
|
max: steady.max
|
|
161
261
|
});
|
|
162
262
|
const cursor = useRef(1);
|
|
263
|
+
const referenceAt = (reference) => {
|
|
264
|
+
if (typeof reference.value === "number") return isFiniteValue(reference) ? null : void 0;
|
|
265
|
+
const at = data.findIndex((item) => item.key === reference.at);
|
|
266
|
+
return at < 0 ? void 0 : last > 0 ? at / last : .5;
|
|
267
|
+
};
|
|
268
|
+
const drawnReferences = (references ?? []).filter((reference) => referenceAt(reference) !== void 0);
|
|
269
|
+
const markerIndex = (marker) => data.findIndex((item) => item.key === marker.at);
|
|
270
|
+
const drawnMarkers = (markers ?? []).filter((marker) => markerIndex(marker) >= 0);
|
|
271
|
+
const drawnBands = (bands ?? []).filter((band) => Number.isFinite(band.from) && Number.isFinite(band.to));
|
|
163
272
|
const live = useRef({
|
|
164
273
|
series,
|
|
165
274
|
hidden
|
|
@@ -177,25 +286,35 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
177
286
|
if (!w || !root) return;
|
|
178
287
|
const lo = scale.current.min;
|
|
179
288
|
const span = scale.current.max - lo || 1;
|
|
180
|
-
const toY = (value) => TOP + (1 - (value - lo) / span) * (height - TOP);
|
|
289
|
+
const toY = (value) => TOP + (reversed ? (value - lo) / span : 1 - (value - lo) / span) * (height - TOP);
|
|
290
|
+
const base = reversed ? scale.current.max : lo;
|
|
181
291
|
const at = cursor.current;
|
|
182
292
|
const dotYs = [];
|
|
183
293
|
for (const line of live.current.series) {
|
|
184
294
|
const track = tracks.current.get(line.key);
|
|
185
295
|
if (!track) continue;
|
|
186
296
|
const p = track.presence;
|
|
187
|
-
const y = (value) => toY(
|
|
188
|
-
const path = track.shape.length ? `M${track.shape.map(([x, value]) => `${(x * w).toFixed(1)},${y(value).toFixed(1)}`).join("L")}` : "";
|
|
297
|
+
const y = (value) => toY(base + (value - base) * p);
|
|
189
298
|
const group = root.querySelector(`[data-series="${CSS.escape(line.key)}"]`);
|
|
190
|
-
|
|
191
|
-
|
|
299
|
+
if (track.breaks.length) {
|
|
300
|
+
const paths = pathsFor(track.shape, track.breaks, (x) => x * w, y, height);
|
|
301
|
+
group?.querySelector("[data-part=line]")?.setAttribute("d", paths.line);
|
|
302
|
+
group?.querySelector("[data-part=area]")?.setAttribute("d", paths.area);
|
|
303
|
+
} else {
|
|
304
|
+
const path = track.shape.length ? `M${track.shape.map(([x, value]) => `${(x * w).toFixed(1)},${y(value).toFixed(1)}`).join("L")}` : "";
|
|
305
|
+
group?.querySelector("[data-part=line]")?.setAttribute("d", path);
|
|
306
|
+
group?.querySelector("[data-part=area]")?.setAttribute("d", path ? `${path}L${w},${height}L0,${height}Z` : "");
|
|
307
|
+
}
|
|
192
308
|
if (group) group.style.opacity = String(p);
|
|
193
309
|
const dot = root.querySelector(`[data-dot="${CSS.escape(line.key)}"]`);
|
|
194
310
|
if (dot && track.shape.length) {
|
|
311
|
+
const gap = inBreak(track.breaks, at) || track.holes.some((x) => Math.abs(x - at) < 1e-6);
|
|
195
312
|
const cy = y(valueAt(track.shape, at));
|
|
196
313
|
dot.setAttribute("cx", (at * w).toFixed(1));
|
|
197
314
|
dot.setAttribute("cy", cy.toFixed(1));
|
|
198
|
-
if (
|
|
315
|
+
if (gap) dot.setAttribute("data-gap", "");
|
|
316
|
+
else dot.removeAttribute("data-gap");
|
|
317
|
+
if (p > .5 && !gap) dotYs.push(cy);
|
|
199
318
|
}
|
|
200
319
|
}
|
|
201
320
|
for (const node of root.querySelectorAll("[data-grid]")) {
|
|
@@ -204,6 +323,43 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
204
323
|
node.setAttribute("y2", yy);
|
|
205
324
|
}
|
|
206
325
|
for (const node of gutter.current?.querySelectorAll("[data-tick]") ?? []) node.style.translate = `0 ${Math.round(toY(Number(node.dataset.tick)))}px`;
|
|
326
|
+
for (const node of root.querySelectorAll("[data-band]")) {
|
|
327
|
+
const ys = [toY(Number(node.dataset.from)), toY(Number(node.dataset.to))];
|
|
328
|
+
const top = clamp(Math.min(...ys), TOP, height);
|
|
329
|
+
const bottom = clamp(Math.max(...ys), TOP, height);
|
|
330
|
+
node.setAttribute("y", String(Math.round(top)));
|
|
331
|
+
node.setAttribute("height", String(Math.max(0, Math.round(bottom) - Math.round(top))));
|
|
332
|
+
const text = plot.current?.querySelector(`[data-band-label="${CSS.escape(node.dataset.band)}"]`);
|
|
333
|
+
if (text) {
|
|
334
|
+
text.style.translate = `0 ${Math.round(top)}px`;
|
|
335
|
+
text.style.visibility = bottom - top >= text.offsetHeight ? "" : "hidden";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const node of root.querySelectorAll("[data-reference-value]")) {
|
|
339
|
+
const yy = String(Math.round(toY(Number(node.dataset.referenceValue))) + .5);
|
|
340
|
+
node.setAttribute("y1", yy);
|
|
341
|
+
node.setAttribute("y2", yy);
|
|
342
|
+
}
|
|
343
|
+
for (const node of root.querySelectorAll("[data-reference-at]")) {
|
|
344
|
+
const xx = String(Math.round(Number(node.dataset.referenceAt) * w) + .5);
|
|
345
|
+
node.setAttribute("x1", xx);
|
|
346
|
+
node.setAttribute("x2", xx);
|
|
347
|
+
}
|
|
348
|
+
for (const node of plot.current?.querySelectorAll("[data-reference-label]") ?? []) {
|
|
349
|
+
const { referenceValue, referenceAt } = node.dataset;
|
|
350
|
+
node.style.translate = referenceValue !== void 0 ? `0 ${Math.round(toY(Number(referenceValue)))}px` : `${Math.round(Number(referenceAt) * w)}px 0`;
|
|
351
|
+
}
|
|
352
|
+
for (const node of plot.current?.querySelectorAll("[data-point-label]") ?? []) {
|
|
353
|
+
const track = tracks.current.get(node.dataset.pointLabel);
|
|
354
|
+
if (!track?.shape.length) continue;
|
|
355
|
+
const x = Number(node.dataset.x);
|
|
356
|
+
const cy = toY(base + (valueAt(track.shape, x) - base) * track.presence);
|
|
357
|
+
const lw = node.offsetWidth;
|
|
358
|
+
const lh = node.offsetHeight;
|
|
359
|
+
const top = cy - lh - LABEL_GAP < 0 ? cy + LABEL_GAP : cy - lh - LABEL_GAP;
|
|
360
|
+
node.style.translate = `${Math.round(clamp(x * w - lw / 2, 0, Math.max(0, w - lw)))}px ${Math.round(clamp(top, 0, Math.max(0, height - lh)))}px`;
|
|
361
|
+
node.style.opacity = String(track.presence);
|
|
362
|
+
}
|
|
207
363
|
const cx = Math.round(at * w) + .5;
|
|
208
364
|
const cross = root.querySelector("[data-part=crosshair]");
|
|
209
365
|
cross?.setAttribute("x1", String(cx));
|
|
@@ -217,7 +373,7 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
217
373
|
const middle = dotYs.length ? dotYs.reduce((sum, value) => sum + value, 0) / dotYs.length : height / 2;
|
|
218
374
|
bubble.style.translate = `${clamp(x, 0, Math.max(0, w - tw))}px ${clamp(middle - th / 2, 0, Math.max(0, height - th))}px`;
|
|
219
375
|
}
|
|
220
|
-
}, [height]);
|
|
376
|
+
}, [height, reversed]);
|
|
221
377
|
useIsomorphicLayoutEffect(() => {
|
|
222
378
|
const node = plot.current;
|
|
223
379
|
if (!node) return;
|
|
@@ -235,16 +391,20 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
235
391
|
useIsomorphicLayoutEffect(paint);
|
|
236
392
|
useEffect(() => {
|
|
237
393
|
const moves = [];
|
|
238
|
-
for (const [key, target] of targets) {
|
|
394
|
+
for (const [key, { shape: target, breaks, holes }] of targets) {
|
|
239
395
|
const track = tracks.current.get(key);
|
|
240
396
|
if (!track) {
|
|
241
397
|
tracks.current.set(key, {
|
|
242
398
|
shape: target,
|
|
399
|
+
breaks,
|
|
400
|
+
holes,
|
|
243
401
|
presence: live.current.hidden.includes(key) ? 0 : 1,
|
|
244
402
|
stop: null
|
|
245
403
|
});
|
|
246
404
|
continue;
|
|
247
405
|
}
|
|
406
|
+
track.breaks = breaks;
|
|
407
|
+
track.holes = holes;
|
|
248
408
|
if (!track.shape.length || !target.length) {
|
|
249
409
|
track.shape = target;
|
|
250
410
|
continue;
|
|
@@ -375,11 +535,66 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
375
535
|
const onPointerMove = (event) => {
|
|
376
536
|
if (!empty && (event.pointerType === "mouse" || event.buttons)) setActive(pointAt(event.clientX));
|
|
377
537
|
};
|
|
378
|
-
const
|
|
538
|
+
const formatShort = (value, line) => formatValue ? formatValue(value, line) : GROUPED.format(value);
|
|
539
|
+
const formatNumber = (value, line) => `${formatShort(value, line)}${unit ? ` ${unit}` : ""}`;
|
|
540
|
+
const format = (value, line) => value === null ? missingLabel : formatNumber(value, line);
|
|
541
|
+
const formatReference = (value) => series[0] ? formatNumber(value, series[0]) : `${GROUPED.format(value)}${unit ? ` ${unit}` : ""}`;
|
|
379
542
|
const reading = index === null ? null : data[index];
|
|
380
543
|
const shown = reading ?? data[last];
|
|
381
|
-
const
|
|
382
|
-
const
|
|
544
|
+
const markedHere = index === null ? [] : drawnMarkers.filter((marker) => markerIndex(marker) === index);
|
|
545
|
+
const valueText = reading ? `${reading.label}: ${visible.map((line) => `${line.label} ${format(read(reading, line.key), line)}`).join(", ")}${markedHere.map((marker) => `. ${marker.description ?? marker.label}`).join("")}` : empty ? emptyLabel : `${visible.length} of ${series.length} series shown`;
|
|
546
|
+
const summary = empty ? `${label}. ${emptyLabel}.` : `${label}, ${data[0].label} to ${data[last].label}. ${visible.map((line) => `${line.label}, latest ${format(read(data[last], line.key), line)}${typeof line.endLabel === "string" ? `, ${line.endLabel}` : ""}`).join(". ")}.${drawnReferences.map((reference) => typeof reference.value === "number" ? ` ${reference.label}: ${formatReference(reference.value)}.` : ` ${reference.label}: ${data.find((item) => item.key === reference.at).label}.`).join("")}${drawnMarkers.map((marker) => ` ${marker.description ?? marker.label}: ${data[markerIndex(marker)].label}.`).join("")}${drawnBands.map((band) => ` ${band.description ?? `${band.label}: ${formatReference(Math.min(band.from, band.to))} to ${formatReference(Math.max(band.from, band.to))}`}.`).join("")}`;
|
|
547
|
+
const pointLabels = empty ? [] : series.flatMap((line) => {
|
|
548
|
+
if (!line.pointLabels && !line.endLabel) return [];
|
|
549
|
+
const present = data.flatMap((item, at) => {
|
|
550
|
+
const value = read(item, line.key);
|
|
551
|
+
return value === null ? [] : [{
|
|
552
|
+
at,
|
|
553
|
+
value
|
|
554
|
+
}];
|
|
555
|
+
});
|
|
556
|
+
const end = line.endLabel ? present.at(-1) : void 0;
|
|
557
|
+
return [...(line.pointLabels ? present.filter((point) => point !== end) : []).map((point) => ({
|
|
558
|
+
line,
|
|
559
|
+
...point,
|
|
560
|
+
end: false,
|
|
561
|
+
text: formatShort(point.value, line)
|
|
562
|
+
})), ...end ? [{
|
|
563
|
+
line,
|
|
564
|
+
...end,
|
|
565
|
+
end: true,
|
|
566
|
+
text: typeof line.endLabel === "string" ? line.endLabel : formatShort(end.value, line)
|
|
567
|
+
}] : []];
|
|
568
|
+
});
|
|
569
|
+
const readout = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", {
|
|
570
|
+
className: "m-0 mb-0.5 whitespace-nowrap text-xs tabular-nums text-text-secondary",
|
|
571
|
+
children: shown?.label ?? ""
|
|
572
|
+
}), visible.map((line) => /* @__PURE__ */ jsxs("p", {
|
|
573
|
+
className: "m-0 flex min-w-0 items-center gap-2 text-sm",
|
|
574
|
+
style: { "--series": colorOf(line, series.indexOf(line)) },
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ jsx("span", {
|
|
577
|
+
"data-dashed": line.dashed || void 0,
|
|
578
|
+
className: "chart-tip-swatch"
|
|
579
|
+
}),
|
|
580
|
+
/* @__PURE__ */ jsx("span", {
|
|
581
|
+
className: "min-w-0 flex-1 truncate text-text-secondary",
|
|
582
|
+
children: line.label
|
|
583
|
+
}),
|
|
584
|
+
/* @__PURE__ */ jsx("span", {
|
|
585
|
+
className: "ms-2 flex-none whitespace-nowrap font-medium tabular-nums text-foreground",
|
|
586
|
+
children: shown ? format(read(shown, line.key), line) : format(0, line)
|
|
587
|
+
})
|
|
588
|
+
]
|
|
589
|
+
}, line.key))] });
|
|
590
|
+
const tipContent = renderTooltip && shown ? renderTooltip({
|
|
591
|
+
datum: shown,
|
|
592
|
+
index: index ?? last,
|
|
593
|
+
series: visible,
|
|
594
|
+
values: Object.fromEntries(visible.map((line) => [line.key, read(shown, line.key)])),
|
|
595
|
+
format,
|
|
596
|
+
readout
|
|
597
|
+
}) : readout;
|
|
383
598
|
const picks = axisPicks(data, plotWidth);
|
|
384
599
|
const allHidden = !empty && visible.length === 0;
|
|
385
600
|
return /* @__PURE__ */ jsxs("figure", {
|
|
@@ -464,7 +679,16 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
464
679
|
style: { width: drawn ? plotWidth + 16 : 0 }
|
|
465
680
|
})
|
|
466
681
|
}) }),
|
|
467
|
-
|
|
682
|
+
drawnBands.map((band) => /* @__PURE__ */ jsx("rect", {
|
|
683
|
+
"data-band": band.key,
|
|
684
|
+
"data-from": band.from,
|
|
685
|
+
"data-to": band.to,
|
|
686
|
+
"data-tone": band.tone,
|
|
687
|
+
className: "line-chart-band",
|
|
688
|
+
x: 0,
|
|
689
|
+
width: "100%"
|
|
690
|
+
}, band.key)),
|
|
691
|
+
(reversed ? steady.ticks.slice(0, -1) : steady.ticks.slice(1)).map((value) => /* @__PURE__ */ jsx("line", {
|
|
468
692
|
"data-grid": value,
|
|
469
693
|
className: "line-chart-grid",
|
|
470
694
|
x1: 0,
|
|
@@ -477,6 +701,21 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
477
701
|
y1: height - .5,
|
|
478
702
|
y2: height - .5
|
|
479
703
|
}),
|
|
704
|
+
drawnReferences.map((reference) => typeof reference.value === "number" ? /* @__PURE__ */ jsx("line", {
|
|
705
|
+
"data-reference": reference.key,
|
|
706
|
+
"data-reference-value": reference.value,
|
|
707
|
+
"data-tone": reference.tone,
|
|
708
|
+
className: "line-chart-reference",
|
|
709
|
+
x1: 0,
|
|
710
|
+
x2: "100%"
|
|
711
|
+
}, reference.key) : /* @__PURE__ */ jsx("line", {
|
|
712
|
+
"data-reference": reference.key,
|
|
713
|
+
"data-reference-at": referenceAt(reference),
|
|
714
|
+
"data-tone": reference.tone,
|
|
715
|
+
className: "line-chart-reference",
|
|
716
|
+
y1: 8,
|
|
717
|
+
y2: height
|
|
718
|
+
}, reference.key)),
|
|
480
719
|
/* @__PURE__ */ jsx("line", {
|
|
481
720
|
"data-part": "crosshair",
|
|
482
721
|
className: "line-chart-crosshair",
|
|
@@ -508,32 +747,61 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
508
747
|
}, line.key))
|
|
509
748
|
]
|
|
510
749
|
}),
|
|
511
|
-
/* @__PURE__ */
|
|
750
|
+
drawnBands.map((band) => /* @__PURE__ */ jsx("span", {
|
|
751
|
+
"aria-hidden": "true",
|
|
752
|
+
"data-slot": "line-chart-band-label",
|
|
753
|
+
"data-band-label": band.key,
|
|
754
|
+
"data-tone": band.tone,
|
|
755
|
+
className: "line-chart-band-label",
|
|
756
|
+
children: band.label
|
|
757
|
+
}, band.key)),
|
|
758
|
+
drawnReferences.map((reference) => /* @__PURE__ */ jsx("span", {
|
|
759
|
+
"aria-hidden": "true",
|
|
760
|
+
"data-slot": "line-chart-reference-label",
|
|
761
|
+
"data-reference-label": reference.key,
|
|
762
|
+
"data-reference-value": typeof reference.value === "number" ? reference.value : void 0,
|
|
763
|
+
"data-reference-at": typeof reference.value === "number" ? void 0 : referenceAt(reference),
|
|
764
|
+
"data-axis": typeof reference.value === "number" ? "value" : "category",
|
|
765
|
+
"data-tone": reference.tone,
|
|
766
|
+
className: "line-chart-reference-label",
|
|
767
|
+
children: reference.label
|
|
768
|
+
}, reference.key)),
|
|
769
|
+
drawnMarkers.map((marker) => {
|
|
770
|
+
const share = last > 0 ? markerIndex(marker) / last : .5;
|
|
771
|
+
return /* @__PURE__ */ jsx("span", {
|
|
772
|
+
"aria-hidden": "true",
|
|
773
|
+
"data-slot": "line-chart-marker",
|
|
774
|
+
"data-marker": marker.key,
|
|
775
|
+
"data-tone": marker.tone,
|
|
776
|
+
className: "line-chart-marker",
|
|
777
|
+
style: { left: `${share * 100}%` },
|
|
778
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
779
|
+
className: "line-chart-marker-label",
|
|
780
|
+
style: { translate: `${-share * 100}% 0` },
|
|
781
|
+
children: marker.label
|
|
782
|
+
})
|
|
783
|
+
}, marker.key);
|
|
784
|
+
}),
|
|
785
|
+
pointLabels.map(({ line, at, end, text }) => /* @__PURE__ */ jsxs("span", {
|
|
786
|
+
"aria-hidden": "true",
|
|
787
|
+
"data-slot": end ? "line-chart-end-label" : "line-chart-point-label",
|
|
788
|
+
"data-point-label": line.key,
|
|
789
|
+
"data-x": last > 0 ? at / last : .5,
|
|
790
|
+
className: "line-chart-point-label",
|
|
791
|
+
"data-end": end || void 0,
|
|
792
|
+
style: { "--series": colorOf(line, series.indexOf(line)) },
|
|
793
|
+
children: [end ? /* @__PURE__ */ jsx("span", {
|
|
794
|
+
"data-dashed": line.dashed || void 0,
|
|
795
|
+
className: "chart-tip-swatch"
|
|
796
|
+
}) : null, text]
|
|
797
|
+
}, `${line.key}:${data[at].key}`)),
|
|
798
|
+
/* @__PURE__ */ jsx("div", {
|
|
512
799
|
ref: tip,
|
|
513
800
|
"aria-hidden": "true",
|
|
514
801
|
"data-slot": "line-chart-tooltip",
|
|
802
|
+
"data-empty": !tipContent || void 0,
|
|
515
803
|
className: "line-chart-tooltip",
|
|
516
|
-
children:
|
|
517
|
-
className: "m-0 mb-0.5 whitespace-nowrap text-xs tabular-nums text-text-secondary",
|
|
518
|
-
children: shown?.label ?? ""
|
|
519
|
-
}), visible.map((line) => /* @__PURE__ */ jsxs("p", {
|
|
520
|
-
className: "m-0 flex min-w-0 items-center gap-2 text-sm",
|
|
521
|
-
style: { "--series": colorOf(line, series.indexOf(line)) },
|
|
522
|
-
children: [
|
|
523
|
-
/* @__PURE__ */ jsx("span", {
|
|
524
|
-
"data-dashed": line.dashed || void 0,
|
|
525
|
-
className: "chart-tip-swatch"
|
|
526
|
-
}),
|
|
527
|
-
/* @__PURE__ */ jsx("span", {
|
|
528
|
-
className: "min-w-0 flex-1 truncate text-text-secondary",
|
|
529
|
-
children: line.label
|
|
530
|
-
}),
|
|
531
|
-
/* @__PURE__ */ jsx("span", {
|
|
532
|
-
className: "ms-2 flex-none whitespace-nowrap font-medium tabular-nums text-foreground",
|
|
533
|
-
children: format(shown?.values[line.key] ?? 0, line)
|
|
534
|
-
})
|
|
535
|
-
]
|
|
536
|
-
}, line.key))]
|
|
804
|
+
children: tipContent
|
|
537
805
|
}),
|
|
538
806
|
(empty || allHidden) && !loading ? /* @__PURE__ */ jsx("p", {
|
|
539
807
|
className: "absolute inset-0 m-0 grid place-items-center text-sm text-muted-foreground pointer-events-none motion-safe:animate-fade-in",
|
|
@@ -593,7 +861,7 @@ const LineChart = forwardRef(function LineChart({ data, series, label, unit = ""
|
|
|
593
861
|
/* @__PURE__ */ jsx("tbody", { children: data.map((item) => /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
|
|
594
862
|
scope: "row",
|
|
595
863
|
children: item.label
|
|
596
|
-
}), series.map((line) => /* @__PURE__ */ jsx("td", { children: format(item
|
|
864
|
+
}), series.map((line) => /* @__PURE__ */ jsx("td", { children: format(read(item, line.key), line) }, line.key))] }, item.key)) })
|
|
597
865
|
] })
|
|
598
866
|
}),
|
|
599
867
|
/* @__PURE__ */ jsx("p", {
|