@tensor_1/react 2.37.0 → 2.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +50 -0
  2. package/dist/{chart-theme-Cl0IrkKQ.d.cts → ChartCore-BKK2HEfi.d.cts} +77 -50
  3. package/dist/chart-analytic-guide.d.ts +6 -0
  4. package/dist/chart-analytic-guide.js +10 -0
  5. package/dist/chart-analytic-model.d.ts +141 -0
  6. package/dist/chart-analytic-model.js +252 -0
  7. package/dist/chart-analytic.cjs +1372 -0
  8. package/dist/chart-analytic.d.cts +335 -0
  9. package/dist/chart-analytic.d.ts +19 -0
  10. package/dist/chart-analytic.js +95 -0
  11. package/dist/chart-panels-model.d.ts +92 -0
  12. package/dist/chart-panels-model.js +284 -0
  13. package/dist/chart-preset.cjs +41 -4
  14. package/dist/chart-preset.d.cts +2 -2
  15. package/dist/chart-preset.d.ts +1 -1
  16. package/dist/chart-stats-o4zDhpVm.d.cts +46 -0
  17. package/dist/chart-stats.d.ts +46 -0
  18. package/dist/chart-stats.js +89 -0
  19. package/dist/chart-theme.d.ts +3 -0
  20. package/dist/chart-theme.js +2 -0
  21. package/dist/chart.cjs +41 -4
  22. package/dist/chart.d.cts +2 -2
  23. package/dist/chart.d.ts +1 -1
  24. package/dist/components/Chart.d.ts +1 -1
  25. package/dist/components/ChartCore.d.ts +23 -3
  26. package/dist/components/ChartCore.js +40 -5
  27. package/dist/components/ChartPanels.d.ts +52 -0
  28. package/dist/components/ChartPanels.js +170 -0
  29. package/dist/components/InsightCard.d.ts +86 -0
  30. package/dist/components/InsightCard.js +118 -0
  31. package/dist/components/Stat.d.ts +7 -0
  32. package/dist/components/Stat.js +6 -3
  33. package/dist/correlation-scatter-model.d.ts +68 -0
  34. package/dist/correlation-scatter-model.js +179 -0
  35. package/dist/icons/Icon.d.ts +23 -23
  36. package/dist/index.cjs +1185 -961
  37. package/dist/index.d.cts +91 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +10 -0
  40. package/package.json +6 -1
@@ -0,0 +1,284 @@
1
+ import { uixChartAreaGradient } from "./chart-theme.js";
2
+ import {
3
+ DEFAULT_ANALYTIC_LABELS,
4
+ combineMarks,
5
+ comparisonSeries,
6
+ crossingPoints,
7
+ eventMarks,
8
+ forecastSeries,
9
+ partialPeriod,
10
+ thresholdMarks
11
+ } from "./chart-analytic-model.js";
12
+ const defaultNumber = new Intl.NumberFormat("en", { maximumFractionDigits: 1 });
13
+ const DEFAULT_CHART_PANELS_LABELS = {
14
+ ...DEFAULT_ANALYTIC_LABELS,
15
+ x: "Period",
16
+ table: (title) => `${title} \u2014 data table`,
17
+ formatValue: (value, unit) => {
18
+ const text = defaultNumber.format(value);
19
+ if (!unit) return text;
20
+ return unit === "%" ? `${text}%` : `${text} ${unit}`;
21
+ }
22
+ };
23
+ const CHART_PANELS_LAYOUT = {
24
+ top: 8,
25
+ lane: 14,
26
+ laneGap: 10,
27
+ title: 28,
28
+ panelHeight: 112,
29
+ panelGap: 14,
30
+ axisBand: 30,
31
+ left: 52,
32
+ right: 16,
33
+ gutter: 100
34
+ };
35
+ function niceCeil(value) {
36
+ if (!(value > 0)) return value === 0 ? 1 : value;
37
+ const exponent = Math.floor(Math.log10(value));
38
+ const base = 10 ** exponent;
39
+ for (const step of [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]) {
40
+ if (step * base >= value - 1e-12) return step * base;
41
+ }
42
+ return 10 * base;
43
+ }
44
+ const escapeHtml = (text) => text.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c] ?? c);
45
+ const finite = (v) => typeof v === "number" && Number.isFinite(v);
46
+ const sameX = (a, b) => String(a) === String(b);
47
+ function chartPanelsModel(spec, tokens, labelOverrides) {
48
+ const labels = { ...DEFAULT_CHART_PANELS_LABELS, ...labelOverrides };
49
+ const L = CHART_PANELS_LAYOUT;
50
+ const events = spec.events ?? [];
51
+ const hasLane = events.length > 0;
52
+ const hasGutter = spec.panels.some((panel) => panel.thresholds?.length);
53
+ const right = hasGutter ? L.gutter : L.right;
54
+ const anyBar = spec.panels.some((panel) => panel.kind === "bar");
55
+ const categories = [...spec.x.values];
56
+ for (const panel of spec.panels) {
57
+ for (const point of panel.forecast?.points ?? []) {
58
+ if (!categories.some((x) => sameX(x, point.x))) categories.push(point.x);
59
+ }
60
+ }
61
+ const lastActual = spec.x.values[spec.x.values.length - 1];
62
+ const previousActual = spec.x.values[spec.x.values.length - 2];
63
+ const grids = [];
64
+ const xAxes = [];
65
+ const yAxes = [];
66
+ const series = [];
67
+ const seriesPanel = {};
68
+ const legend = [];
69
+ let cursor = L.top;
70
+ const xAxisBase = (gridIndex, showLabels) => {
71
+ const common = {
72
+ gridIndex,
73
+ axisLabel: {
74
+ show: showLabels,
75
+ hideOverlap: true,
76
+ showMaxLabel: true,
77
+ // the first and last labels align inward, so neither runs into the y-axis labels or the gutter
78
+ alignMinLabel: "left",
79
+ alignMaxLabel: "right",
80
+ formatter: (value) => labels.formatX(value)
81
+ },
82
+ axisLine: { show: true },
83
+ splitLine: { show: false }
84
+ };
85
+ return spec.x.type === "time" ? { type: "time", ...common } : { type: "category", data: categories.map(String), boundaryGap: anyBar, ...common };
86
+ };
87
+ let laneSeriesId = "";
88
+ let eventFragment = null;
89
+ if (hasLane) {
90
+ grids.push({ top: cursor, height: L.lane, left: L.left, right, containLabel: false });
91
+ xAxes.push({ ...xAxisBase(0, false), axisLine: { show: false } });
92
+ yAxes.push({ type: "value", gridIndex: 0, min: -1, max: 1, show: false });
93
+ const marks = eventMarks(events, { tokens, xAxisIndex: 0, yAxisIndex: 0, seriesId: "uix-event-lane" });
94
+ laneSeriesId = "uix-event-lane";
95
+ series.push(marks.lane.series);
96
+ eventFragment = { markLine: marks.markLine, markArea: marks.markArea };
97
+ cursor += L.lane + L.laneGap;
98
+ }
99
+ const tables = [];
100
+ spec.panels.forEach((panel, panelIndex) => {
101
+ const gridIndex = grids.length;
102
+ const isLast = panelIndex === spec.panels.length - 1;
103
+ const height = panel.height ?? L.panelHeight;
104
+ cursor += L.title;
105
+ grids.push({ top: cursor, height, left: L.left, right, containLabel: false });
106
+ cursor += height + (isLast ? L.axisBand : L.panelGap);
107
+ const values = [];
108
+ for (const s of panel.series) values.push(...s.values.filter(finite));
109
+ values.push(...panel.comparison?.values.filter(finite) ?? []);
110
+ for (const p of panel.forecast?.points ?? []) values.push(p.lower, p.upper, p.y);
111
+ for (const t of panel.thresholds ?? []) values.push(t.value, ...finite(t.to) ? [t.to] : []);
112
+ const usable = values.filter(Number.isFinite);
113
+ const dataMax = usable.length ? Math.max(...usable) : 1;
114
+ const dataMin = usable.length ? Math.min(...usable) : 0;
115
+ const max = niceCeil(dataMax * 1.08);
116
+ const min = dataMin < 0 ? -niceCeil(-dataMin * 1.08) : 0;
117
+ xAxes.push(xAxisBase(gridIndex, isLast));
118
+ yAxes.push({
119
+ type: "value",
120
+ gridIndex,
121
+ min,
122
+ max,
123
+ // three ticks (min, half way, max): a short panel never prints a crowded extra max label
124
+ interval: (max - min) / 2,
125
+ name: panel.unit ? `${panel.title} (${panel.unit})` : panel.title,
126
+ nameLocation: "end",
127
+ nameGap: 14,
128
+ nameTextStyle: { align: "left", padding: [0, 0, 0, -L.left + 4], color: tokens.text, fontFamily: tokens.fontSans, fontSize: 12, fontWeight: 600, width: 320, overflow: "truncate" },
129
+ axisLabel: { formatter: (value) => labels.formatValue(value, panel.unit === "%" ? "%" : void 0) }
130
+ });
131
+ const axes = { xAxisIndex: gridIndex, yAxisIndex: gridIndex };
132
+ const pairs = (vals) => spec.x.values.map((x, i) => [x, vals[i] ?? null]);
133
+ panel.series.forEach((s, seriesIndex) => {
134
+ const colorIndex = s.colorIndex ?? seriesIndex;
135
+ const color = tokens.palette[colorIndex % tokens.palette.length];
136
+ const id = `${panel.id}:${s.id}`;
137
+ seriesPanel[id] = panel.id;
138
+ const isPrimary = seriesIndex === 0;
139
+ let marks = {};
140
+ if (isPrimary) {
141
+ const crossings = (panel.thresholds ?? []).filter((t) => t.kind !== "target" && (t.badSide || t.kind === "limit" || t.kind === "budget" || t.kind === "floor")).slice(0, 1).map((t) => crossingPoints(s.values.slice(0, spec.x.values.length), t, { x: spec.x.values, tokens, labels }));
142
+ marks = combineMarks(
143
+ panel.thresholds?.length ? thresholdMarks(panel.thresholds, { tokens, extent: [min, max] }) : null,
144
+ eventFragment,
145
+ spec.partialLast && lastActual != null ? partialWash(partialPeriod(lastActual, { tokens, from: previousActual, labels }), panelIndex === 0) : null,
146
+ ...crossings
147
+ );
148
+ }
149
+ if (panel.kind === "bar") {
150
+ series.push({
151
+ id,
152
+ name: s.label,
153
+ type: "bar",
154
+ ...axes,
155
+ data: pairs(s.values),
156
+ barMaxWidth: 24,
157
+ itemStyle: { color, borderRadius: [4, 4, 0, 0], borderColor: tokens.surface, borderWidth: 1 },
158
+ animation: false,
159
+ z: 2,
160
+ ...marks
161
+ });
162
+ } else {
163
+ series.push({
164
+ id,
165
+ name: s.label,
166
+ type: "line",
167
+ ...axes,
168
+ data: pairs(s.values),
169
+ symbol: "circle",
170
+ symbolSize: 8,
171
+ showSymbol: false,
172
+ connectNulls: false,
173
+ lineStyle: { color, width: 2, type: "solid" },
174
+ itemStyle: { color, borderColor: tokens.surface, borderWidth: 2 },
175
+ ...panel.kind === "area" ? { areaStyle: { color: uixChartAreaGradient(color) } } : {},
176
+ emphasis: { focus: "none" },
177
+ animation: false,
178
+ z: 2,
179
+ ...marks
180
+ });
181
+ }
182
+ if (panel.series.length > 1 || panel.comparison || panel.forecast) legend.push({ label: s.label, color, swatch: panel.kind === "bar" ? "block" : "line" });
183
+ });
184
+ if (panel.comparison) {
185
+ const id = `${panel.id}:comparison`;
186
+ seriesPanel[id] = panel.id;
187
+ series.push(comparisonSeries(panel.comparison, { x: spec.x.values, tokens, ...axes, id }));
188
+ if (!legend.some((item) => item.label === panel.comparison.label)) legend.push({ label: panel.comparison.label, color: tokens.comparison, swatch: "line" });
189
+ }
190
+ if (panel.forecast && panel.series[0]) {
191
+ const primary = panel.series[0];
192
+ const fromIndex = spec.x.values.findIndex((x) => sameX(x, panel.forecast.from));
193
+ const fromValue = fromIndex >= 0 ? primary.values[fromIndex] ?? null : null;
194
+ const colorIndex = primary.colorIndex ?? 0;
195
+ const fc = forecastSeries(panel.forecast, { name: primary.label, colorIndex, tokens, labels, fromValue, ...axes, idPrefix: `${panel.id}:forecast` });
196
+ for (const s of fc) seriesPanel[String(s.id)] = panel.id;
197
+ series.push(...fc);
198
+ if (!legend.some((item) => item.swatch === "dash")) {
199
+ legend.push({ label: labels.projection, color: tokens.palette[colorIndex % tokens.palette.length], swatch: "dash" });
200
+ legend.push({ label: labels.range(panel.forecast.level), color: tokens.forecastBand, swatch: "block" });
201
+ }
202
+ }
203
+ tables.push(panelTable(spec, panel, categories, events, labels));
204
+ });
205
+ const allX = xAxes.map((_, i) => i);
206
+ const option = {
207
+ animation: false,
208
+ grid: grids,
209
+ xAxis: xAxes,
210
+ yAxis: yAxes,
211
+ axisPointer: { link: [{ xAxisIndex: "all" }], label: { show: false } },
212
+ tooltip: {
213
+ trigger: "axis",
214
+ confine: true,
215
+ axisPointer: { type: "line" },
216
+ formatter: (params) => tooltipHtml(params, spec, categories, events, labels)
217
+ },
218
+ series,
219
+ ...spec.zoom ? { dataZoom: [{ type: "inside", xAxisIndex: allX, filterMode: "none" }] } : {}
220
+ };
221
+ return { option, height: cursor, legend, tables, categories, seriesPanel, laneSeriesId };
222
+ }
223
+ function partialWash(marks, labelled) {
224
+ if (labelled || !marks.markArea?.data) return marks;
225
+ const data = marks.markArea.data.map(([from, to]) => [{ ...from, label: { show: false } }, to]);
226
+ return { markArea: { ...marks.markArea, data } };
227
+ }
228
+ function panelTable(spec, panel, categories, events, labels) {
229
+ const headers = [labels.x, ...panel.series.map((s) => s.label)];
230
+ if (panel.comparison) headers.push(panel.comparison.label);
231
+ if (panel.forecast) headers.push(labels.projection, labels.lower, labels.upper);
232
+ for (const t of panel.thresholds ?? []) headers.push(`${labels.threshold}: ${t.label}`);
233
+ if (events.length) headers.push(labels.event);
234
+ const unit = panel.unit;
235
+ const fmt = (v) => finite(v) ? labels.formatValue(v, unit) : "";
236
+ const rows = categories.map((x, i) => {
237
+ const row = { [labels.x]: labels.formatX(x) };
238
+ for (const s of panel.series) row[s.label] = i < spec.x.values.length ? fmt(s.values[i]) : "";
239
+ if (panel.comparison) row[panel.comparison.label] = i < spec.x.values.length ? fmt(panel.comparison.values[i]) : "";
240
+ if (panel.forecast) {
241
+ const p = panel.forecast.points.find((point) => sameX(point.x, x));
242
+ row[labels.projection] = fmt(p?.y);
243
+ row[labels.lower] = fmt(p?.lower);
244
+ row[labels.upper] = fmt(p?.upper);
245
+ }
246
+ for (const t of panel.thresholds ?? []) row[`${labels.threshold}: ${t.label}`] = finite(t.to) ? `${fmt(t.value)}\u2013${fmt(t.to)}` : fmt(t.value);
247
+ if (events.length) row[labels.event] = events.filter((e) => sameX(e.at, x)).map((e) => e.label).join("; ");
248
+ return row;
249
+ });
250
+ return { caption: labels.table(panel.title), headers, rows };
251
+ }
252
+ function tooltipHtml(params, spec, categories, events, labels) {
253
+ const list = Array.isArray(params) ? params : [params];
254
+ const first = list[0];
255
+ if (!first) return "";
256
+ const axisValue = first.axisValue ?? (Array.isArray(first.value) ? first.value[0] : void 0);
257
+ const index = categories.findIndex((x2) => sameX(x2, axisValue));
258
+ if (index < 0) return "";
259
+ const x = categories[index];
260
+ const lines = [`<div style="font-weight:600;margin-bottom:4px">${escapeHtml(labels.formatX(x))}</div>`];
261
+ const row = (name, value) => `<div style="display:flex;justify-content:space-between;gap:16px"><span>${escapeHtml(name)}</span><span style="font-variant-numeric:tabular-nums">${escapeHtml(value)}</span></div>`;
262
+ for (const panel of spec.panels) {
263
+ for (const s of panel.series) {
264
+ const v = index < spec.x.values.length ? s.values[index] : null;
265
+ if (finite(v)) lines.push(row(s.label, labels.formatValue(v, panel.unit)));
266
+ }
267
+ const c = panel.comparison && index < spec.x.values.length ? panel.comparison.values[index] : null;
268
+ if (panel.comparison && finite(c)) lines.push(row(panel.comparison.label, labels.formatValue(c, panel.unit)));
269
+ const p = panel.forecast?.points.find((point) => sameX(point.x, x));
270
+ if (p && panel.forecast) {
271
+ lines.push(row(`${labels.projection} (${panel.forecast.method})`, labels.formatValue(p.y, panel.unit)));
272
+ lines.push(row(labels.range(panel.forecast.level), `${labels.formatValue(p.lower, panel.unit)} \u2013 ${labels.formatValue(p.upper, panel.unit)}`));
273
+ }
274
+ }
275
+ for (const e of events.filter((event) => sameX(event.at, x))) lines.push(row(e.kind, e.label));
276
+ if (spec.partialLast && index === spec.x.values.length - 1) lines.push(`<div style="opacity:.75">${escapeHtml(labels.partial)}</div>`);
277
+ return lines.join("");
278
+ }
279
+ export {
280
+ CHART_PANELS_LAYOUT,
281
+ DEFAULT_CHART_PANELS_LABELS,
282
+ chartPanelsModel,
283
+ niceCeil
284
+ };
@@ -79,6 +79,8 @@ function uixChartTokens(source) {
79
79
  event: t("chart-event"),
80
80
  partial: t("chart-partial"),
81
81
  comparison: t("chart-comparison"),
82
+ warning: t("warning"),
83
+ danger: t("danger"),
82
84
  surface: t("surface"),
83
85
  text: t("text"),
84
86
  textMuted: t("text-muted"),
@@ -161,7 +163,8 @@ function uixChartPalette() {
161
163
  return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
162
164
  }
163
165
  function themed(option, theme, element) {
164
- return theme === "none" ? option : mergeChartTheme(uixChartTheme(element), option);
166
+ const resolved = typeof option === "function" ? option(uixChartTokens(element)) : option;
167
+ return theme === "none" ? resolved : mergeChartTheme(uixChartTheme(element), resolved);
165
168
  }
166
169
  function motionSafe(option) {
167
170
  if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
@@ -192,17 +195,22 @@ function ChartCore({
192
195
  loading = false,
193
196
  empty,
194
197
  labels: labelOverrides,
195
- theme = "uix"
198
+ theme = "uix",
199
+ group,
200
+ onEvents,
201
+ tables
196
202
  }) {
197
203
  const labels = { ...DEFAULT_CHART_LABELS, ...labelOverrides };
198
204
  const containerRef = (0, import_react.useRef)(null);
199
205
  const chartRef = (0, import_react.useRef)(null);
200
206
  const appliedOptionRef = (0, import_react.useRef)(null);
207
+ const onEventsRef = (0, import_react.useRef)(onEvents);
208
+ onEventsRef.current = onEvents;
201
209
  const latestOptionRef = (0, import_react.useRef)(option);
202
210
  const onReadyRef = (0, import_react.useRef)(onReady);
203
211
  const tableId = (0, import_react.useId)();
204
212
  const hasEmpty = empty != null;
205
- const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
213
+ const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length || tables?.length);
206
214
  const hasTextAlternativeRef = (0, import_react.useRef)(hasTextAlternative);
207
215
  const themeRef = (0, import_react.useRef)(theme);
208
216
  latestOptionRef.current = option;
@@ -254,6 +262,29 @@ function ChartCore({
254
262
  appliedOptionRef.current = null;
255
263
  };
256
264
  }, [engine, hasEmpty]);
265
+ const eventNames = Object.keys(onEvents ?? {}).sort().join("|");
266
+ (0, import_react.useEffect)(() => {
267
+ const chart = chartRef.current;
268
+ if (!chart || !eventNames) return;
269
+ const bound = eventNames.split("|").map((name) => {
270
+ const handler = (params) => onEventsRef.current?.[name]?.(params);
271
+ chart.on(name, handler);
272
+ return [name, handler];
273
+ });
274
+ return () => {
275
+ if (chart.isDisposed()) return;
276
+ for (const [name, handler] of bound) chart.off(name, handler);
277
+ };
278
+ }, [engine, hasEmpty, eventNames]);
279
+ (0, import_react.useEffect)(() => {
280
+ const chart = chartRef.current;
281
+ if (!chart || !group) return;
282
+ chart.group = group;
283
+ engine.connect?.(group);
284
+ return () => {
285
+ if (!chart.isDisposed()) chart.group = "";
286
+ };
287
+ }, [engine, hasEmpty, group]);
257
288
  (0, import_react.useEffect)(() => {
258
289
  const chart = chartRef.current;
259
290
  const element = containerRef.current;
@@ -264,6 +295,8 @@ function ChartCore({
264
295
  const heightValue = typeof height === "number" ? `${height}px` : height;
265
296
  const effectiveAriaLabel = ariaLabel ?? title ?? labels.chart;
266
297
  const hasTable = Boolean(tableData?.length && tableHeaders?.length);
298
+ const extraTables = (tables ?? []).filter((table) => table.headers.length && table.rows.length);
299
+ const describedBy = [hasTable ? tableId : null, ...extraTables.map((_, index) => `${tableId}-${index}`)].filter(Boolean).join(" ") || void 0;
267
300
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cx("uix-chart", className), style, children: [
268
301
  title != null || subtitle != null || header != null || headerAction != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uix-chart__header", children: [
269
302
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uix-chart__heading", children: [
@@ -282,7 +315,7 @@ function ChartCore({
282
315
  style: { height: heightValue },
283
316
  role: "img",
284
317
  "aria-label": effectiveAriaLabel,
285
- "aria-describedby": hasTable ? tableId : void 0
318
+ "aria-describedby": describedBy
286
319
  }
287
320
  )
288
321
  ] }),
@@ -290,6 +323,10 @@ function ChartCore({
290
323
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: label }, label)) }) }),
291
324
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: row[label] }, label)) }, index)) })
292
325
  ] }) : null,
326
+ extraTables.map((table, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { id: `${tableId}-${index}`, className: "uix-visually-hidden", "aria-label": table.caption, children: [
327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: table.headers.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { scope: "col", children: label }, label)) }) }),
328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: table.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: table.headers.map((label) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: row[label] }, label)) }, rowIndex)) })
329
+ ] }, index)),
293
330
  footer != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uix-chart__footer", children: footer })
294
331
  ] });
295
332
  }
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { C as ChartProps } from './chart-theme-Cl0IrkKQ.cjs';
3
- export { a as ChartLabels, b as ChartLegend, c as ChartLegendItem, d as ChartLegendItemProps, e as ChartLegendProps, f as ChartMetric, g as ChartMetricProps, h as ChartTableRow, i as ChartTokenSource, D as DEFAULT_CHART_LABELS, U as UixChartTokens, m as mergeChartTheme, u as uixChartAreaGradient, j as uixChartPalette, k as uixChartTheme, l as uixChartTokens } from './chart-theme-Cl0IrkKQ.cjs';
2
+ import { a as ChartProps } from './ChartCore-BKK2HEfi.cjs';
3
+ export { c as ChartEventHandler, C as ChartLabels, d as ChartLegend, e as ChartLegendItem, f as ChartLegendItemProps, g as ChartLegendProps, h as ChartMetric, i as ChartMetricProps, j as ChartOption, k as ChartTable, l as ChartTableRow, m as ChartTokenSource, D as DEFAULT_CHART_LABELS, U as UixChartTokens, n as mergeChartTheme, u as uixChartAreaGradient, o as uixChartPalette, p as uixChartTheme, q as uixChartTokens } from './ChartCore-BKK2HEfi.cjs';
4
4
  import 'echarts';
5
5
 
6
6
  /** Lean SVG adapter: line, bar, pie, dataset, grid, legend, title, and tooltip. */
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ChartProps } from './components/ChartCore.js';
3
- export { ChartLabels, ChartLegend, ChartLegendItem, ChartLegendItemProps, ChartLegendProps, ChartMetric, ChartMetricProps, ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette } from './components/ChartCore.js';
3
+ export { ChartEventHandler, ChartLabels, ChartLegend, ChartLegendItem, ChartLegendItemProps, ChartLegendProps, ChartMetric, ChartMetricProps, ChartOption, ChartTable, ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette } from './components/ChartCore.js';
4
4
  export { ChartTokenSource, UixChartTokens, mergeChartTheme, uixChartAreaGradient, uixChartTheme, uixChartTokens } from './chart-theme.js';
5
5
  import 'echarts';
6
6
 
@@ -0,0 +1,46 @@
1
+ /** One observation for {@link linearFit}. */
2
+ interface ChartStatPoint {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /** An ordinary least-squares line through the points, with what a prediction band needs. */
7
+ interface LinearFit {
8
+ slope: number;
9
+ intercept: number;
10
+ /** Pearson correlation; `null` when y is constant (undefined, not zero). */
11
+ r: number | null;
12
+ /** Points used (non-finite x or y are skipped). */
13
+ n: number;
14
+ /** Mean of x. */
15
+ xMean: number;
16
+ /** Sum of squared deviations of x from its mean. */
17
+ sxx: number;
18
+ /** Residual standard error, √(SSE / (n − 2)); 0 when n = 2. */
19
+ residualStdError: number;
20
+ }
21
+ /** A band around the fitted line at each requested x. */
22
+ interface PredictionBandPoint {
23
+ x: number;
24
+ y: number;
25
+ lower: number;
26
+ upper: number;
27
+ }
28
+ /** Least-squares line through `points`; `null` below two usable points or when every x is equal. */
29
+ declare function linearFit(points: readonly ChartStatPoint[]): LinearFit | null;
30
+ /**
31
+ * Quantiles by linear interpolation between order statistics (the "type 7" rule of R and NumPy).
32
+ * `ps` are fractions in [0, 1]; non-finite values are skipped. An empty input gives `null`s.
33
+ */
34
+ declare function quantiles(values: readonly number[], ps: readonly number[]): (number | null)[];
35
+ /** Inverse of the standard normal CDF (Acklam's rational approximation, |error| < 1.2e-9). */
36
+ declare function normalQuantile(p: number): number;
37
+ /** Student-t quantile by the Cornish–Fisher expansion around the normal (good to ~1e-3 from 3 df). */
38
+ declare function studentTQuantile(p: number, df: number): number;
39
+ /**
40
+ * A prediction band for new observations at `xs`: ŷ ± t·s·√(1 + 1/n + (x − x̄)² / Sxx), with `level`
41
+ * the central coverage (0.8 = 80 %). With n = 2 there is no residual error to widen by, so the band
42
+ * collapses onto the line.
43
+ */
44
+ declare function predictionBand(fit: LinearFit, xs: readonly number[], level?: number): PredictionBandPoint[];
45
+
46
+ export { type ChartStatPoint as C, type LinearFit as L, type PredictionBandPoint as P, linearFit as l, normalQuantile as n, predictionBand as p, quantiles as q, studentTQuantile as s };
@@ -0,0 +1,46 @@
1
+ /** One observation for {@link linearFit}. */
2
+ interface ChartStatPoint {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /** An ordinary least-squares line through the points, with what a prediction band needs. */
7
+ interface LinearFit {
8
+ slope: number;
9
+ intercept: number;
10
+ /** Pearson correlation; `null` when y is constant (undefined, not zero). */
11
+ r: number | null;
12
+ /** Points used (non-finite x or y are skipped). */
13
+ n: number;
14
+ /** Mean of x. */
15
+ xMean: number;
16
+ /** Sum of squared deviations of x from its mean. */
17
+ sxx: number;
18
+ /** Residual standard error, √(SSE / (n − 2)); 0 when n = 2. */
19
+ residualStdError: number;
20
+ }
21
+ /** A band around the fitted line at each requested x. */
22
+ interface PredictionBandPoint {
23
+ x: number;
24
+ y: number;
25
+ lower: number;
26
+ upper: number;
27
+ }
28
+ /** Least-squares line through `points`; `null` below two usable points or when every x is equal. */
29
+ declare function linearFit(points: readonly ChartStatPoint[]): LinearFit | null;
30
+ /**
31
+ * Quantiles by linear interpolation between order statistics (the "type 7" rule of R and NumPy).
32
+ * `ps` are fractions in [0, 1]; non-finite values are skipped. An empty input gives `null`s.
33
+ */
34
+ declare function quantiles(values: readonly number[], ps: readonly number[]): (number | null)[];
35
+ /** Inverse of the standard normal CDF (Acklam's rational approximation, |error| < 1.2e-9). */
36
+ declare function normalQuantile(p: number): number;
37
+ /** Student-t quantile by the Cornish–Fisher expansion around the normal (good to ~1e-3 from 3 df). */
38
+ declare function studentTQuantile(p: number, df: number): number;
39
+ /**
40
+ * A prediction band for new observations at `xs`: ŷ ± t·s·√(1 + 1/n + (x − x̄)² / Sxx), with `level`
41
+ * the central coverage (0.8 = 80 %). With n = 2 there is no residual error to widen by, so the band
42
+ * collapses onto the line.
43
+ */
44
+ declare function predictionBand(fit: LinearFit, xs: readonly number[], level?: number): PredictionBandPoint[];
45
+
46
+ export { type ChartStatPoint, type LinearFit, type PredictionBandPoint, linearFit, normalQuantile, predictionBand, quantiles, studentTQuantile };
@@ -0,0 +1,89 @@
1
+ const finite = (value) => typeof value === "number" && Number.isFinite(value);
2
+ function linearFit(points) {
3
+ const usable = points.filter((p) => finite(p.x) && finite(p.y));
4
+ const n = usable.length;
5
+ if (n < 2) return null;
6
+ let sumX = 0;
7
+ let sumY = 0;
8
+ for (const p of usable) {
9
+ sumX += p.x;
10
+ sumY += p.y;
11
+ }
12
+ const xMean = sumX / n;
13
+ const yMean = sumY / n;
14
+ let sxx = 0;
15
+ let syy = 0;
16
+ let sxy = 0;
17
+ for (const p of usable) {
18
+ const dx = p.x - xMean;
19
+ const dy = p.y - yMean;
20
+ sxx += dx * dx;
21
+ syy += dy * dy;
22
+ sxy += dx * dy;
23
+ }
24
+ if (sxx === 0) return null;
25
+ const slope = sxy / sxx;
26
+ const intercept = yMean - slope * xMean;
27
+ let sse = 0;
28
+ for (const p of usable) {
29
+ const residual = p.y - (intercept + slope * p.x);
30
+ sse += residual * residual;
31
+ }
32
+ const r = syy === 0 ? null : Math.max(-1, Math.min(1, sxy / Math.sqrt(sxx * syy)));
33
+ const residualStdError = n > 2 ? Math.sqrt(sse / (n - 2)) : 0;
34
+ return { slope, intercept, r, n, xMean, sxx, residualStdError };
35
+ }
36
+ function quantiles(values, ps) {
37
+ const sorted = values.filter(finite).slice().sort((a, b) => a - b);
38
+ return ps.map((p) => {
39
+ if (!sorted.length || !finite(p)) return null;
40
+ const clamped = Math.max(0, Math.min(1, p));
41
+ const h = (sorted.length - 1) * clamped;
42
+ const lo = Math.floor(h);
43
+ const hi = Math.ceil(h);
44
+ return sorted[lo] + (h - lo) * (sorted[hi] - sorted[lo]);
45
+ });
46
+ }
47
+ function normalQuantile(p) {
48
+ if (!(p > 0 && p < 1)) return p === 0 ? -Infinity : p === 1 ? Infinity : Number.NaN;
49
+ const a = [-39.69683028665376, 220.9460984245205, -275.9285104469687, 138.357751867269, -30.66479806614716, 2.506628277459239];
50
+ const b = [-54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572];
51
+ const c = [-0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783];
52
+ const d = [0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416];
53
+ const low = 0.02425;
54
+ if (p < low) {
55
+ const q2 = Math.sqrt(-2 * Math.log(p));
56
+ return (((((c[0] * q2 + c[1]) * q2 + c[2]) * q2 + c[3]) * q2 + c[4]) * q2 + c[5]) / ((((d[0] * q2 + d[1]) * q2 + d[2]) * q2 + d[3]) * q2 + 1);
57
+ }
58
+ if (p > 1 - low) {
59
+ const q2 = Math.sqrt(-2 * Math.log(1 - p));
60
+ return -(((((c[0] * q2 + c[1]) * q2 + c[2]) * q2 + c[3]) * q2 + c[4]) * q2 + c[5]) / ((((d[0] * q2 + d[1]) * q2 + d[2]) * q2 + d[3]) * q2 + 1);
61
+ }
62
+ const q = p - 0.5;
63
+ const r = q * q;
64
+ return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
65
+ }
66
+ function studentTQuantile(p, df) {
67
+ const z = normalQuantile(p);
68
+ if (!Number.isFinite(z) || !(df > 0)) return z;
69
+ const z3 = z ** 3;
70
+ const z5 = z ** 5;
71
+ const z7 = z ** 7;
72
+ return z + (z3 + z) / (4 * df) + (5 * z5 + 16 * z3 + 3 * z) / (96 * df ** 2) + (3 * z7 + 19 * z5 + 17 * z3 - 15 * z) / (384 * df ** 3);
73
+ }
74
+ function predictionBand(fit, xs, level = 0.8) {
75
+ const coverage = Math.max(0.01, Math.min(0.999, level));
76
+ const t = fit.n > 2 ? studentTQuantile(1 - (1 - coverage) / 2, fit.n - 2) : 0;
77
+ return xs.filter(finite).map((x) => {
78
+ const y = fit.intercept + fit.slope * x;
79
+ const spread = t * fit.residualStdError * Math.sqrt(1 + 1 / fit.n + (x - fit.xMean) ** 2 / fit.sxx);
80
+ return { x, y, lower: y - spread, upper: y + spread };
81
+ });
82
+ }
83
+ export {
84
+ linearFit,
85
+ normalQuantile,
86
+ predictionBand,
87
+ quantiles,
88
+ studentTQuantile
89
+ };
@@ -15,6 +15,9 @@ interface UixChartTokens {
15
15
  event: string;
16
16
  partial: string;
17
17
  comparison: string;
18
+ /** Status hues for an emphasised mark (a point that breaches); never a series colour. */
19
+ warning: string;
20
+ danger: string;
18
21
  surface: string;
19
22
  text: string;
20
23
  textMuted: string;
@@ -20,6 +20,8 @@ function uixChartTokens(source) {
20
20
  event: t("chart-event"),
21
21
  partial: t("chart-partial"),
22
22
  comparison: t("chart-comparison"),
23
+ warning: t("warning"),
24
+ danger: t("danger"),
23
25
  surface: t("surface"),
24
26
  text: t("text"),
25
27
  textMuted: t("text-muted"),