@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.
- package/README.md +50 -0
- package/dist/{chart-theme-Cl0IrkKQ.d.cts → ChartCore-BKK2HEfi.d.cts} +77 -50
- package/dist/chart-analytic-guide.d.ts +6 -0
- package/dist/chart-analytic-guide.js +10 -0
- package/dist/chart-analytic-model.d.ts +141 -0
- package/dist/chart-analytic-model.js +252 -0
- package/dist/chart-analytic.cjs +1372 -0
- package/dist/chart-analytic.d.cts +335 -0
- package/dist/chart-analytic.d.ts +19 -0
- package/dist/chart-analytic.js +95 -0
- package/dist/chart-panels-model.d.ts +92 -0
- package/dist/chart-panels-model.js +284 -0
- package/dist/chart-preset.cjs +41 -4
- package/dist/chart-preset.d.cts +2 -2
- package/dist/chart-preset.d.ts +1 -1
- package/dist/chart-stats-o4zDhpVm.d.cts +46 -0
- package/dist/chart-stats.d.ts +46 -0
- package/dist/chart-stats.js +89 -0
- package/dist/chart-theme.d.ts +3 -0
- package/dist/chart-theme.js +2 -0
- package/dist/chart.cjs +41 -4
- package/dist/chart.d.cts +2 -2
- package/dist/chart.d.ts +1 -1
- package/dist/components/Chart.d.ts +1 -1
- package/dist/components/ChartCore.d.ts +23 -3
- package/dist/components/ChartCore.js +40 -5
- package/dist/components/ChartPanels.d.ts +52 -0
- package/dist/components/ChartPanels.js +170 -0
- package/dist/components/InsightCard.d.ts +86 -0
- package/dist/components/InsightCard.js +118 -0
- package/dist/components/Stat.d.ts +7 -0
- package/dist/components/Stat.js +6 -3
- package/dist/correlation-scatter-model.d.ts +68 -0
- package/dist/correlation-scatter-model.js +179 -0
- package/dist/icons/Icon.d.ts +23 -23
- package/dist/index.cjs +1185 -961
- package/dist/index.d.cts +91 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -0
- package/package.json +6 -1
package/dist/chart.cjs
CHANGED
|
@@ -77,6 +77,8 @@ function uixChartTokens(source) {
|
|
|
77
77
|
event: t("chart-event"),
|
|
78
78
|
partial: t("chart-partial"),
|
|
79
79
|
comparison: t("chart-comparison"),
|
|
80
|
+
warning: t("warning"),
|
|
81
|
+
danger: t("danger"),
|
|
80
82
|
surface: t("surface"),
|
|
81
83
|
text: t("text"),
|
|
82
84
|
textMuted: t("text-muted"),
|
|
@@ -159,7 +161,8 @@ function uixChartPalette() {
|
|
|
159
161
|
return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
|
|
160
162
|
}
|
|
161
163
|
function themed(option, theme, element) {
|
|
162
|
-
|
|
164
|
+
const resolved = typeof option === "function" ? option(uixChartTokens(element)) : option;
|
|
165
|
+
return theme === "none" ? resolved : mergeChartTheme(uixChartTheme(element), resolved);
|
|
163
166
|
}
|
|
164
167
|
function motionSafe(option) {
|
|
165
168
|
if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
@@ -190,17 +193,22 @@ function ChartCore({
|
|
|
190
193
|
loading = false,
|
|
191
194
|
empty,
|
|
192
195
|
labels: labelOverrides,
|
|
193
|
-
theme = "uix"
|
|
196
|
+
theme = "uix",
|
|
197
|
+
group,
|
|
198
|
+
onEvents,
|
|
199
|
+
tables
|
|
194
200
|
}) {
|
|
195
201
|
const labels = { ...DEFAULT_CHART_LABELS, ...labelOverrides };
|
|
196
202
|
const containerRef = (0, import_react.useRef)(null);
|
|
197
203
|
const chartRef = (0, import_react.useRef)(null);
|
|
198
204
|
const appliedOptionRef = (0, import_react.useRef)(null);
|
|
205
|
+
const onEventsRef = (0, import_react.useRef)(onEvents);
|
|
206
|
+
onEventsRef.current = onEvents;
|
|
199
207
|
const latestOptionRef = (0, import_react.useRef)(option);
|
|
200
208
|
const onReadyRef = (0, import_react.useRef)(onReady);
|
|
201
209
|
const tableId = (0, import_react.useId)();
|
|
202
210
|
const hasEmpty = empty != null;
|
|
203
|
-
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
|
|
211
|
+
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length || tables?.length);
|
|
204
212
|
const hasTextAlternativeRef = (0, import_react.useRef)(hasTextAlternative);
|
|
205
213
|
const themeRef = (0, import_react.useRef)(theme);
|
|
206
214
|
latestOptionRef.current = option;
|
|
@@ -252,6 +260,29 @@ function ChartCore({
|
|
|
252
260
|
appliedOptionRef.current = null;
|
|
253
261
|
};
|
|
254
262
|
}, [engine, hasEmpty]);
|
|
263
|
+
const eventNames = Object.keys(onEvents ?? {}).sort().join("|");
|
|
264
|
+
(0, import_react.useEffect)(() => {
|
|
265
|
+
const chart = chartRef.current;
|
|
266
|
+
if (!chart || !eventNames) return;
|
|
267
|
+
const bound = eventNames.split("|").map((name) => {
|
|
268
|
+
const handler = (params) => onEventsRef.current?.[name]?.(params);
|
|
269
|
+
chart.on(name, handler);
|
|
270
|
+
return [name, handler];
|
|
271
|
+
});
|
|
272
|
+
return () => {
|
|
273
|
+
if (chart.isDisposed()) return;
|
|
274
|
+
for (const [name, handler] of bound) chart.off(name, handler);
|
|
275
|
+
};
|
|
276
|
+
}, [engine, hasEmpty, eventNames]);
|
|
277
|
+
(0, import_react.useEffect)(() => {
|
|
278
|
+
const chart = chartRef.current;
|
|
279
|
+
if (!chart || !group) return;
|
|
280
|
+
chart.group = group;
|
|
281
|
+
engine.connect?.(group);
|
|
282
|
+
return () => {
|
|
283
|
+
if (!chart.isDisposed()) chart.group = "";
|
|
284
|
+
};
|
|
285
|
+
}, [engine, hasEmpty, group]);
|
|
255
286
|
(0, import_react.useEffect)(() => {
|
|
256
287
|
const chart = chartRef.current;
|
|
257
288
|
const element = containerRef.current;
|
|
@@ -262,6 +293,8 @@ function ChartCore({
|
|
|
262
293
|
const heightValue = typeof height === "number" ? `${height}px` : height;
|
|
263
294
|
const effectiveAriaLabel = ariaLabel ?? title ?? labels.chart;
|
|
264
295
|
const hasTable = Boolean(tableData?.length && tableHeaders?.length);
|
|
296
|
+
const extraTables = (tables ?? []).filter((table) => table.headers.length && table.rows.length);
|
|
297
|
+
const describedBy = [hasTable ? tableId : null, ...extraTables.map((_, index) => `${tableId}-${index}`)].filter(Boolean).join(" ") || void 0;
|
|
265
298
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cx("uix-chart", className), style, children: [
|
|
266
299
|
title != null || subtitle != null || header != null || headerAction != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uix-chart__header", children: [
|
|
267
300
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "uix-chart__heading", children: [
|
|
@@ -280,7 +313,7 @@ function ChartCore({
|
|
|
280
313
|
style: { height: heightValue },
|
|
281
314
|
role: "img",
|
|
282
315
|
"aria-label": effectiveAriaLabel,
|
|
283
|
-
"aria-describedby":
|
|
316
|
+
"aria-describedby": describedBy
|
|
284
317
|
}
|
|
285
318
|
)
|
|
286
319
|
] }),
|
|
@@ -288,6 +321,10 @@ function ChartCore({
|
|
|
288
321
|
/* @__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)) }) }),
|
|
289
322
|
/* @__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)) })
|
|
290
323
|
] }) : null,
|
|
324
|
+
extraTables.map((table, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { id: `${tableId}-${index}`, className: "uix-visually-hidden", "aria-label": table.caption, children: [
|
|
325
|
+
/* @__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)) }) }),
|
|
326
|
+
/* @__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)) })
|
|
327
|
+
] }, index)),
|
|
291
328
|
footer != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "uix-chart__footer", children: footer })
|
|
292
329
|
] });
|
|
293
330
|
}
|
package/dist/chart.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
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
|
/** Full-compatibility adapter. Use `@tensor_1/react/chart/preset` for the lean preset. */
|
package/dist/chart.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Chart } from './components/Chart.js';
|
|
2
|
-
export { ChartLabels, ChartLegend, ChartLegendItem, ChartLegendItemProps, ChartLegendProps, ChartMetric, ChartMetricProps, ChartProps, ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette } from './components/ChartCore.js';
|
|
2
|
+
export { ChartEventHandler, ChartLabels, ChartLegend, ChartLegendItem, ChartLegendItemProps, ChartLegendProps, ChartMetric, ChartMetricProps, ChartOption, ChartProps, ChartTable, ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette } from './components/ChartCore.js';
|
|
3
3
|
export { ChartTokenSource, UixChartTokens, mergeChartTheme, uixChartAreaGradient, uixChartTheme, uixChartTokens } from './chart-theme.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import 'echarts';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ChartProps } from './ChartCore.js';
|
|
3
|
-
export { ChartLabels, ChartLegend, ChartLegendItem, ChartLegendItemProps, ChartLegendProps, ChartMetric, ChartMetricProps, ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette } from './ChartCore.js';
|
|
3
|
+
export { ChartEventHandler, ChartLabels, ChartLegend, ChartLegendItem, ChartLegendItemProps, ChartLegendProps, ChartMetric, ChartMetricProps, ChartOption, ChartTable, ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette } from './ChartCore.js';
|
|
4
4
|
export { ChartTokenSource, UixChartTokens, mergeChartTheme, uixChartAreaGradient, uixChartTheme, uixChartTokens } from '../chart-theme.js';
|
|
5
5
|
import 'echarts';
|
|
6
6
|
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties, ReactNode, HTMLAttributes } from 'react';
|
|
3
3
|
import { EChartsOption, ECharts } from 'echarts';
|
|
4
|
+
import { UixChartTokens } from '../chart-theme.js';
|
|
4
5
|
|
|
5
6
|
interface ChartEngine {
|
|
6
7
|
init(element: HTMLElement, theme?: string | null, options?: {
|
|
7
8
|
renderer?: 'svg' | 'canvas';
|
|
8
9
|
}): ECharts;
|
|
10
|
+
/** Optional: links charts that share a `group` (one crosshair and tooltip across cards). */
|
|
11
|
+
connect?(group: string): void;
|
|
9
12
|
}
|
|
13
|
+
/** An option, or a function that builds it from the chart tokens of the current theme (re-run on a theme flip). */
|
|
14
|
+
type ChartOption = EChartsOption | ((tokens: UixChartTokens) => EChartsOption);
|
|
15
|
+
/** A further text alternative: one visually hidden table (ChartPanels gives one per panel). */
|
|
16
|
+
interface ChartTable {
|
|
17
|
+
caption: string;
|
|
18
|
+
headers: string[];
|
|
19
|
+
rows: ChartTableRow[];
|
|
20
|
+
}
|
|
21
|
+
/** An ECharts event handler; `params` is ECharts' event object. */
|
|
22
|
+
type ChartEventHandler = (params: unknown) => void;
|
|
10
23
|
declare function uixChartPalette(): string[];
|
|
11
24
|
interface ChartTableRow {
|
|
12
25
|
[key: string]: string | number;
|
|
@@ -25,7 +38,8 @@ interface ChartLabels {
|
|
|
25
38
|
}
|
|
26
39
|
declare const DEFAULT_CHART_LABELS: ChartLabels;
|
|
27
40
|
interface ChartProps {
|
|
28
|
-
option
|
|
41
|
+
/** The ECharts option, or a function of the theme's chart tokens (re-run when the theme flips). */
|
|
42
|
+
option: ChartOption;
|
|
29
43
|
title?: string;
|
|
30
44
|
subtitle?: string;
|
|
31
45
|
height?: string | number;
|
|
@@ -47,12 +61,18 @@ interface ChartProps {
|
|
|
47
61
|
* grid and the UIx tooltip — and re-applies it when the page theme changes. `'none'` renders `option` as is.
|
|
48
62
|
*/
|
|
49
63
|
theme?: 'uix' | 'none';
|
|
64
|
+
/** Charts with the same group share one crosshair and tooltip (`echarts.connect`). */
|
|
65
|
+
group?: string;
|
|
66
|
+
/** ECharts event handlers by event name (`click` for drill-down). */
|
|
67
|
+
onEvents?: Record<string, ChartEventHandler>;
|
|
68
|
+
/** Further hidden data tables (after `tableData`), each named in the chart's description. */
|
|
69
|
+
tables?: ChartTable[];
|
|
50
70
|
}
|
|
51
71
|
interface ChartCoreProps extends ChartProps {
|
|
52
72
|
engine: ChartEngine;
|
|
53
73
|
}
|
|
54
74
|
/** Shared chart lifecycle. Runtime adapters only choose the ECharts build. */
|
|
55
|
-
declare function ChartCore({ engine, option, title, subtitle, height, className, style, ariaLabel, tableData, tableHeaders, onReady, header, headerAction, footer, loading, empty, labels: labelOverrides, theme, }: ChartCoreProps): react.JSX.Element;
|
|
75
|
+
declare function ChartCore({ engine, option, title, subtitle, height, className, style, ariaLabel, tableData, tableHeaders, onReady, header, headerAction, footer, loading, empty, labels: labelOverrides, theme, group, onEvents, tables, }: ChartCoreProps): react.JSX.Element;
|
|
56
76
|
interface ChartMetricProps extends HTMLAttributes<HTMLDivElement> {
|
|
57
77
|
value: ReactNode;
|
|
58
78
|
label?: ReactNode;
|
|
@@ -71,4 +91,4 @@ interface ChartLegendItemProps extends HTMLAttributes<HTMLLIElement> {
|
|
|
71
91
|
}
|
|
72
92
|
declare function ChartLegendItem({ label, color, swatch, className, style, ...props }: ChartLegendItemProps): react.JSX.Element;
|
|
73
93
|
|
|
74
|
-
export { ChartCore, type ChartEngine, type ChartLabels, ChartLegend, ChartLegendItem, type ChartLegendItemProps, type ChartLegendProps, ChartMetric, type ChartMetricProps, type ChartProps, type ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette };
|
|
94
|
+
export { ChartCore, type ChartEngine, type ChartEventHandler, type ChartLabels, ChartLegend, ChartLegendItem, type ChartLegendItemProps, type ChartLegendProps, ChartMetric, type ChartMetricProps, type ChartOption, type ChartProps, type ChartTable, type ChartTableRow, DEFAULT_CHART_LABELS, uixChartPalette };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useRef } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
|
-
import { mergeChartTheme, uixChartTheme } from "../chart-theme.js";
|
|
5
|
+
import { mergeChartTheme, uixChartTheme, uixChartTokens } from "../chart-theme.js";
|
|
6
6
|
function readToken(name) {
|
|
7
7
|
if (typeof getComputedStyle === "undefined") return "";
|
|
8
8
|
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
@@ -11,7 +11,8 @@ function uixChartPalette() {
|
|
|
11
11
|
return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
|
|
12
12
|
}
|
|
13
13
|
function themed(option, theme, element) {
|
|
14
|
-
|
|
14
|
+
const resolved = typeof option === "function" ? option(uixChartTokens(element)) : option;
|
|
15
|
+
return theme === "none" ? resolved : mergeChartTheme(uixChartTheme(element), resolved);
|
|
15
16
|
}
|
|
16
17
|
function motionSafe(option) {
|
|
17
18
|
if (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
@@ -42,17 +43,22 @@ function ChartCore({
|
|
|
42
43
|
loading = false,
|
|
43
44
|
empty,
|
|
44
45
|
labels: labelOverrides,
|
|
45
|
-
theme = "uix"
|
|
46
|
+
theme = "uix",
|
|
47
|
+
group,
|
|
48
|
+
onEvents,
|
|
49
|
+
tables
|
|
46
50
|
}) {
|
|
47
51
|
const labels = { ...DEFAULT_CHART_LABELS, ...labelOverrides };
|
|
48
52
|
const containerRef = useRef(null);
|
|
49
53
|
const chartRef = useRef(null);
|
|
50
54
|
const appliedOptionRef = useRef(null);
|
|
55
|
+
const onEventsRef = useRef(onEvents);
|
|
56
|
+
onEventsRef.current = onEvents;
|
|
51
57
|
const latestOptionRef = useRef(option);
|
|
52
58
|
const onReadyRef = useRef(onReady);
|
|
53
59
|
const tableId = useId();
|
|
54
60
|
const hasEmpty = empty != null;
|
|
55
|
-
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length);
|
|
61
|
+
const hasTextAlternative = Boolean(ariaLabel || title || tableData?.length || tables?.length);
|
|
56
62
|
const hasTextAlternativeRef = useRef(hasTextAlternative);
|
|
57
63
|
const themeRef = useRef(theme);
|
|
58
64
|
latestOptionRef.current = option;
|
|
@@ -104,6 +110,29 @@ function ChartCore({
|
|
|
104
110
|
appliedOptionRef.current = null;
|
|
105
111
|
};
|
|
106
112
|
}, [engine, hasEmpty]);
|
|
113
|
+
const eventNames = Object.keys(onEvents ?? {}).sort().join("|");
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
const chart = chartRef.current;
|
|
116
|
+
if (!chart || !eventNames) return;
|
|
117
|
+
const bound = eventNames.split("|").map((name) => {
|
|
118
|
+
const handler = (params) => onEventsRef.current?.[name]?.(params);
|
|
119
|
+
chart.on(name, handler);
|
|
120
|
+
return [name, handler];
|
|
121
|
+
});
|
|
122
|
+
return () => {
|
|
123
|
+
if (chart.isDisposed()) return;
|
|
124
|
+
for (const [name, handler] of bound) chart.off(name, handler);
|
|
125
|
+
};
|
|
126
|
+
}, [engine, hasEmpty, eventNames]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const chart = chartRef.current;
|
|
129
|
+
if (!chart || !group) return;
|
|
130
|
+
chart.group = group;
|
|
131
|
+
engine.connect?.(group);
|
|
132
|
+
return () => {
|
|
133
|
+
if (!chart.isDisposed()) chart.group = "";
|
|
134
|
+
};
|
|
135
|
+
}, [engine, hasEmpty, group]);
|
|
107
136
|
useEffect(() => {
|
|
108
137
|
const chart = chartRef.current;
|
|
109
138
|
const element = containerRef.current;
|
|
@@ -114,6 +143,8 @@ function ChartCore({
|
|
|
114
143
|
const heightValue = typeof height === "number" ? `${height}px` : height;
|
|
115
144
|
const effectiveAriaLabel = ariaLabel ?? title ?? labels.chart;
|
|
116
145
|
const hasTable = Boolean(tableData?.length && tableHeaders?.length);
|
|
146
|
+
const extraTables = (tables ?? []).filter((table) => table.headers.length && table.rows.length);
|
|
147
|
+
const describedBy = [hasTable ? tableId : null, ...extraTables.map((_, index) => `${tableId}-${index}`)].filter(Boolean).join(" ") || void 0;
|
|
117
148
|
return /* @__PURE__ */ jsxs("div", { className: cx("uix-chart", className), style, children: [
|
|
118
149
|
title != null || subtitle != null || header != null || headerAction != null ? /* @__PURE__ */ jsxs("div", { className: "uix-chart__header", children: [
|
|
119
150
|
/* @__PURE__ */ jsxs("div", { className: "uix-chart__heading", children: [
|
|
@@ -132,7 +163,7 @@ function ChartCore({
|
|
|
132
163
|
style: { height: heightValue },
|
|
133
164
|
role: "img",
|
|
134
165
|
"aria-label": effectiveAriaLabel,
|
|
135
|
-
"aria-describedby":
|
|
166
|
+
"aria-describedby": describedBy
|
|
136
167
|
}
|
|
137
168
|
)
|
|
138
169
|
] }),
|
|
@@ -140,6 +171,10 @@ function ChartCore({
|
|
|
140
171
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("th", { scope: "col", children: label }, label)) }) }),
|
|
141
172
|
/* @__PURE__ */ jsx("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("td", { children: row[label] }, label)) }, index)) })
|
|
142
173
|
] }) : null,
|
|
174
|
+
extraTables.map((table, index) => /* @__PURE__ */ jsxs("table", { id: `${tableId}-${index}`, className: "uix-visually-hidden", "aria-label": table.caption, children: [
|
|
175
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: table.headers.map((label) => /* @__PURE__ */ jsx("th", { scope: "col", children: label }, label)) }) }),
|
|
176
|
+
/* @__PURE__ */ jsx("tbody", { children: table.rows.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: table.headers.map((label) => /* @__PURE__ */ jsx("td", { children: row[label] }, label)) }, rowIndex)) })
|
|
177
|
+
] }, index)),
|
|
143
178
|
footer != null && /* @__PURE__ */ jsx("div", { className: "uix-chart__footer", children: footer })
|
|
144
179
|
] });
|
|
145
180
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { ChartLabels, ChartEngine } from './ChartCore.js';
|
|
4
|
+
import { ChartPanelsSpec, ChartPanelsLabels } from '../chart-panels-model.js';
|
|
5
|
+
import { ChartX, ChartEvent } from '../chart-analytic-model.js';
|
|
6
|
+
import { CorrelationScatterSpec, CorrelationScatterLabels, CorrelationPoint } from '../correlation-scatter-model.js';
|
|
7
|
+
import 'echarts';
|
|
8
|
+
import '../chart-theme.js';
|
|
9
|
+
import '../chart-stats.js';
|
|
10
|
+
|
|
11
|
+
interface ChartFrameProps {
|
|
12
|
+
/** Accessible name of the whole chart. Required: a chart without one tells a screen reader nothing. */
|
|
13
|
+
ariaLabel: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
header?: ReactNode;
|
|
17
|
+
headerAction?: ReactNode;
|
|
18
|
+
footer?: ReactNode;
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
empty?: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
/** Charts with the same group share one crosshair across cards. */
|
|
23
|
+
group?: string;
|
|
24
|
+
}
|
|
25
|
+
interface ChartPanelsProps extends ChartPanelsSpec, ChartFrameProps {
|
|
26
|
+
labels?: Partial<ChartPanelsLabels> & Partial<ChartLabels>;
|
|
27
|
+
/** A click on a panel's point. */
|
|
28
|
+
onPointClick?: (panelId: string, x: ChartX) => void;
|
|
29
|
+
/** A click on an event pin. Without a handler, an event with `href` navigates there. */
|
|
30
|
+
onEventClick?: (event: ChartEvent) => void;
|
|
31
|
+
/** Show the legend (series of multi-series panels, comparison, projection). Default: when there is one. */
|
|
32
|
+
legend?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Aligned panels on one time axis with one crosshair (HAR-1554). Different units never share an axis. */
|
|
35
|
+
declare function ChartPanelsCore({ engine, x, panels, events, partialLast, zoom, ariaLabel, title, subtitle, header, headerAction, footer, loading, empty, className, group, labels, onPointClick, onEventClick, legend, }: ChartPanelsProps & {
|
|
36
|
+
engine: ChartEngine;
|
|
37
|
+
}): react.JSX.Element;
|
|
38
|
+
interface CorrelationScatterProps extends CorrelationScatterSpec, ChartFrameProps {
|
|
39
|
+
labels?: Partial<CorrelationScatterLabels> & Partial<ChartLabels>;
|
|
40
|
+
/** Show r, n and "association, not cause" under the plot. Default true. */
|
|
41
|
+
showStats?: boolean;
|
|
42
|
+
/** Plot height in px. Default 280. */
|
|
43
|
+
height?: number;
|
|
44
|
+
/** A click on a point. Without a handler, a point with `href` navigates there. */
|
|
45
|
+
onPointClick?: (point: CorrelationPoint) => void;
|
|
46
|
+
}
|
|
47
|
+
/** How two variables move together: dots, a trend line, r and n, and an optional breaking point (HAR-1554). */
|
|
48
|
+
declare function CorrelationScatterCore({ engine, points, xLabel, yLabel, trend, percentiles, thresholds, breakpoint, minPoints, ariaLabel, title, subtitle, header, headerAction, footer, loading, empty, className, group, labels, showStats, height, onPointClick, }: CorrelationScatterProps & {
|
|
49
|
+
engine: ChartEngine;
|
|
50
|
+
}): react.JSX.Element;
|
|
51
|
+
|
|
52
|
+
export { ChartPanelsCore, type ChartPanelsProps, CorrelationScatterCore, type CorrelationScatterProps };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { ChartCore, ChartLegend, ChartLegendItem } from "./ChartCore.js";
|
|
5
|
+
import { chartPanelsModel } from "../chart-panels-model.js";
|
|
6
|
+
import { DEFAULT_CORRELATION_SCATTER_LABELS, correlationScatterModel } from "../correlation-scatter-model.js";
|
|
7
|
+
const CSS_VAR_TOKENS = {
|
|
8
|
+
palette: [1, 2, 3, 4, 5, 6, 7, 8].map((i) => `var(--uix-chart-${i})`),
|
|
9
|
+
neutral: "var(--uix-chart-neutral)",
|
|
10
|
+
grid: "var(--uix-chart-grid)",
|
|
11
|
+
axis: "var(--uix-chart-axis)",
|
|
12
|
+
reference: "var(--uix-chart-reference)",
|
|
13
|
+
zoneWarning: "var(--uix-chart-zone-warning)",
|
|
14
|
+
zoneDanger: "var(--uix-chart-zone-danger)",
|
|
15
|
+
forecastBand: "var(--uix-chart-forecast-band)",
|
|
16
|
+
event: "var(--uix-chart-event)",
|
|
17
|
+
partial: "var(--uix-chart-partial)",
|
|
18
|
+
comparison: "var(--uix-chart-comparison)",
|
|
19
|
+
warning: "var(--uix-warning)",
|
|
20
|
+
danger: "var(--uix-danger)",
|
|
21
|
+
surface: "var(--uix-surface)",
|
|
22
|
+
text: "var(--uix-text)",
|
|
23
|
+
textMuted: "var(--uix-text-muted)",
|
|
24
|
+
border: "var(--uix-border-strong)",
|
|
25
|
+
fontSans: "inherit",
|
|
26
|
+
fontMono: "inherit"
|
|
27
|
+
};
|
|
28
|
+
function ChartPanelsCore({
|
|
29
|
+
engine,
|
|
30
|
+
x,
|
|
31
|
+
panels,
|
|
32
|
+
events,
|
|
33
|
+
partialLast,
|
|
34
|
+
zoom,
|
|
35
|
+
ariaLabel,
|
|
36
|
+
title,
|
|
37
|
+
subtitle,
|
|
38
|
+
header,
|
|
39
|
+
headerAction,
|
|
40
|
+
footer,
|
|
41
|
+
loading,
|
|
42
|
+
empty,
|
|
43
|
+
className,
|
|
44
|
+
group,
|
|
45
|
+
labels,
|
|
46
|
+
onPointClick,
|
|
47
|
+
onEventClick,
|
|
48
|
+
legend
|
|
49
|
+
}) {
|
|
50
|
+
const spec = useMemo(() => ({ x, panels, events, partialLast, zoom }), [x, panels, events, partialLast, zoom]);
|
|
51
|
+
const layout = useMemo(() => chartPanelsModel(spec, CSS_VAR_TOKENS, labels), [spec, labels]);
|
|
52
|
+
const option = useMemo(() => (tokens) => chartPanelsModel(spec, tokens, labels).option, [spec, labels]);
|
|
53
|
+
const onEvents = useMemo(() => ({
|
|
54
|
+
click: (raw) => {
|
|
55
|
+
const params = raw;
|
|
56
|
+
const value = Array.isArray(params.value) ? params.value : [];
|
|
57
|
+
if (params.seriesId && params.seriesId === layout.laneSeriesId) {
|
|
58
|
+
const event = spec.events?.[Number(value[2])];
|
|
59
|
+
if (!event) return;
|
|
60
|
+
if (onEventClick) onEventClick(event);
|
|
61
|
+
else if (event.href && typeof window !== "undefined") window.location.assign(event.href);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const panelId = params.seriesId ? layout.seriesPanel[params.seriesId] : void 0;
|
|
65
|
+
if (panelId && onPointClick) onPointClick(panelId, value[0] ?? params.name);
|
|
66
|
+
}
|
|
67
|
+
}), [layout, spec.events, onEventClick, onPointClick]);
|
|
68
|
+
const showLegend = legend ?? layout.legend.length > 0;
|
|
69
|
+
const legendNode = showLegend && layout.legend.length ? /* @__PURE__ */ jsx(ChartLegend, { children: layout.legend.map((item) => /* @__PURE__ */ jsx(ChartLegendItem, { label: item.label, color: item.color, swatch: item.swatch }, `${item.swatch}:${item.label}`)) }) : null;
|
|
70
|
+
const footerNode = legendNode || footer != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
71
|
+
legendNode,
|
|
72
|
+
footer
|
|
73
|
+
] }) : void 0;
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
ChartCore,
|
|
76
|
+
{
|
|
77
|
+
engine,
|
|
78
|
+
option,
|
|
79
|
+
height: layout.height,
|
|
80
|
+
ariaLabel,
|
|
81
|
+
title,
|
|
82
|
+
subtitle,
|
|
83
|
+
header,
|
|
84
|
+
headerAction,
|
|
85
|
+
footer: footerNode,
|
|
86
|
+
loading,
|
|
87
|
+
empty,
|
|
88
|
+
className,
|
|
89
|
+
group,
|
|
90
|
+
labels,
|
|
91
|
+
onEvents,
|
|
92
|
+
tables: layout.tables
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function CorrelationScatterCore({
|
|
97
|
+
engine,
|
|
98
|
+
points,
|
|
99
|
+
xLabel,
|
|
100
|
+
yLabel,
|
|
101
|
+
trend,
|
|
102
|
+
percentiles,
|
|
103
|
+
thresholds,
|
|
104
|
+
breakpoint,
|
|
105
|
+
minPoints,
|
|
106
|
+
ariaLabel,
|
|
107
|
+
title,
|
|
108
|
+
subtitle,
|
|
109
|
+
header,
|
|
110
|
+
headerAction,
|
|
111
|
+
footer,
|
|
112
|
+
loading,
|
|
113
|
+
empty,
|
|
114
|
+
className,
|
|
115
|
+
group,
|
|
116
|
+
labels,
|
|
117
|
+
showStats = true,
|
|
118
|
+
height = 280,
|
|
119
|
+
onPointClick
|
|
120
|
+
}) {
|
|
121
|
+
const spec = useMemo(
|
|
122
|
+
() => ({ points, xLabel, yLabel, trend, percentiles, thresholds, breakpoint, minPoints }),
|
|
123
|
+
[points, xLabel, yLabel, trend, percentiles, thresholds, breakpoint, minPoints]
|
|
124
|
+
);
|
|
125
|
+
const layout = useMemo(() => correlationScatterModel(spec, CSS_VAR_TOKENS, labels), [spec, labels]);
|
|
126
|
+
const option = useMemo(() => (tokens) => correlationScatterModel(spec, tokens, labels).option, [spec, labels]);
|
|
127
|
+
const usable = useMemo(() => spec.points.filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y)), [spec.points]);
|
|
128
|
+
const onEvents = useMemo(() => ({
|
|
129
|
+
click: (raw) => {
|
|
130
|
+
const params = raw;
|
|
131
|
+
if (params.seriesId !== layout.hitSeriesId || !Array.isArray(params.value)) return;
|
|
132
|
+
const point = usable[Number(params.value[2])];
|
|
133
|
+
if (!point) return;
|
|
134
|
+
if (onPointClick) onPointClick(point);
|
|
135
|
+
else if (point.href && typeof window !== "undefined") window.location.assign(point.href);
|
|
136
|
+
}
|
|
137
|
+
}), [layout.hitSeriesId, usable, onPointClick]);
|
|
138
|
+
const notEnough = !layout.enough ? /* @__PURE__ */ jsx("span", { className: "uix-chart__insufficient", children: (labels?.notEnough ?? DEFAULT_CORRELATION_SCATTER_LABELS.notEnough)(layout.n, layout.minPoints) }) : null;
|
|
139
|
+
const stats = showStats && layout.enough ? /* @__PURE__ */ jsx("p", { className: "uix-chart__stats", children: layout.statsText }) : null;
|
|
140
|
+
const footerNode = stats || footer != null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
141
|
+
stats,
|
|
142
|
+
footer
|
|
143
|
+
] }) : void 0;
|
|
144
|
+
return /* @__PURE__ */ jsx(
|
|
145
|
+
ChartCore,
|
|
146
|
+
{
|
|
147
|
+
engine,
|
|
148
|
+
option,
|
|
149
|
+
height,
|
|
150
|
+
ariaLabel,
|
|
151
|
+
title,
|
|
152
|
+
subtitle,
|
|
153
|
+
header,
|
|
154
|
+
headerAction,
|
|
155
|
+
footer: footerNode,
|
|
156
|
+
loading,
|
|
157
|
+
empty: empty ?? notEnough ?? void 0,
|
|
158
|
+
className,
|
|
159
|
+
group,
|
|
160
|
+
labels,
|
|
161
|
+
onEvents,
|
|
162
|
+
tableHeaders: layout.table.headers,
|
|
163
|
+
tableData: layout.table.rows
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
ChartPanelsCore,
|
|
169
|
+
CorrelationScatterCore
|
|
170
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type InsightStatusTone = 'ok' | 'warning' | 'critical' | 'unknown';
|
|
5
|
+
interface InsightStatus {
|
|
6
|
+
tone: InsightStatusTone;
|
|
7
|
+
/** The words: the status is always readable without its colour or icon. */
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
interface InsightComparison {
|
|
11
|
+
text: ReactNode;
|
|
12
|
+
/** Direction × whether that direction is good; `neutral` when it is neither. */
|
|
13
|
+
tone?: 'neutral' | 'positive' | 'negative' | 'warning';
|
|
14
|
+
}
|
|
15
|
+
interface InsightTarget {
|
|
16
|
+
actual: number;
|
|
17
|
+
target: number;
|
|
18
|
+
/** Qualitative bands from the start of the scale, e.g. [[0, 10], [10, 15], [15, 24]]. */
|
|
19
|
+
ranges?: [number, number][];
|
|
20
|
+
/** The scale's end. Default: the larger of the last range, the actual and the target, plus 10 %. */
|
|
21
|
+
max?: number;
|
|
22
|
+
}
|
|
23
|
+
interface InsightKpi {
|
|
24
|
+
value: ReactNode;
|
|
25
|
+
label: ReactNode;
|
|
26
|
+
comparison?: InsightComparison;
|
|
27
|
+
target?: InsightTarget;
|
|
28
|
+
}
|
|
29
|
+
interface InsightCoverage {
|
|
30
|
+
covered: number;
|
|
31
|
+
total: number;
|
|
32
|
+
/** What was counted, e.g. "changes classified". */
|
|
33
|
+
note: ReactNode;
|
|
34
|
+
/** Shown when the last period is still filling up. */
|
|
35
|
+
partialPeriod?: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/** Every word the card writes itself. English defaults in {@link DEFAULT_INSIGHT_CARD_LABELS}. */
|
|
38
|
+
interface InsightCardLabels {
|
|
39
|
+
/** "412 of 431 changes classified (96%)". */
|
|
40
|
+
coverage: (covered: number, total: number, percent: number, note: ReactNode) => ReactNode;
|
|
41
|
+
/** The bullet's accessible description. */
|
|
42
|
+
bullet: (actual: string, target: string) => string;
|
|
43
|
+
source: string;
|
|
44
|
+
updated: string;
|
|
45
|
+
kpis: string;
|
|
46
|
+
formatNumber: (value: number) => string;
|
|
47
|
+
}
|
|
48
|
+
declare const DEFAULT_INSIGHT_CARD_LABELS: InsightCardLabels;
|
|
49
|
+
interface InsightCardProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
|
|
50
|
+
eyebrow?: ReactNode;
|
|
51
|
+
/** The answer, as a sentence (≤ 10 words reads best). */
|
|
52
|
+
headline: ReactNode;
|
|
53
|
+
/** The implication and the suggested decision. */
|
|
54
|
+
sowhat?: ReactNode;
|
|
55
|
+
status?: InsightStatus;
|
|
56
|
+
kpis?: InsightKpi[];
|
|
57
|
+
coverage?: InsightCoverage;
|
|
58
|
+
/** One drill-down action (a link or button). */
|
|
59
|
+
actions?: ReactNode;
|
|
60
|
+
source?: ReactNode;
|
|
61
|
+
updated?: ReactNode;
|
|
62
|
+
/** `split` puts the aside on the right when the card is ≥ 60rem (960 px) wide; below, it stacks. */
|
|
63
|
+
layout?: 'stack' | 'split';
|
|
64
|
+
/**
|
|
65
|
+
* `card` (default) draws its own border and surface. `plain` draws none, for a card that sits
|
|
66
|
+
* inside another frame (a dashboard widget), so there is no card inside a card.
|
|
67
|
+
*/
|
|
68
|
+
variant?: 'card' | 'plain';
|
|
69
|
+
aside?: ReactNode;
|
|
70
|
+
/** The headline's heading level. Default 3. */
|
|
71
|
+
headingLevel?: 2 | 3 | 4;
|
|
72
|
+
labels?: Partial<InsightCardLabels>;
|
|
73
|
+
children?: ReactNode;
|
|
74
|
+
}
|
|
75
|
+
declare function InsightCard({ eyebrow, headline, sowhat, status, kpis, coverage, actions, source, updated, layout, variant, aside, headingLevel, labels: labelOverrides, className, children, ...props }: InsightCardProps): react.JSX.Element;
|
|
76
|
+
interface BulletProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role'>, InsightTarget {
|
|
77
|
+
/** The accessible description, e.g. "18.7 h against a target of 15 h". */
|
|
78
|
+
label: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* A bullet graph (Few): grey qualitative bands, a dark bar for the actual, a tick for the target.
|
|
82
|
+
* It replaces a gauge: the comparison is a length on one scale, not an angle.
|
|
83
|
+
*/
|
|
84
|
+
declare function Bullet({ actual, target, ranges, max, label, className, style, ...props }: BulletProps): react.JSX.Element;
|
|
85
|
+
|
|
86
|
+
export { Bullet, type BulletProps, DEFAULT_INSIGHT_CARD_LABELS, InsightCard, type InsightCardLabels, type InsightCardProps, type InsightComparison, type InsightCoverage, type InsightKpi, type InsightStatus, type InsightStatusTone, type InsightTarget };
|