@arcfusionz/arc-analytic-ui 0.0.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/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/components/AnomalyCard/AnomalyCard.chart.d.ts +64 -0
- package/dist/components/AnomalyCard/AnomalyCard.chart.js +341 -0
- package/dist/components/AnomalyCard/AnomalyCard.d.ts +169 -0
- package/dist/components/AnomalyCard/AnomalyCard.js +1019 -0
- package/dist/components/AnomalyCard/AnomalyCard.model.d.ts +508 -0
- package/dist/components/AnomalyCard/AnomalyCard.model.js +553 -0
- package/dist/components/AnomalyCard/index.d.ts +4 -0
- package/dist/components/AnomalyCard/index.js +4 -0
- package/dist/components/ChartCard/ChartCard.chart.d.ts +72 -0
- package/dist/components/ChartCard/ChartCard.chart.js +1333 -0
- package/dist/components/ChartCard/ChartCard.d.ts +206 -0
- package/dist/components/ChartCard/ChartCard.js +718 -0
- package/dist/components/ChartCard/ChartCard.model.d.ts +452 -0
- package/dist/components/ChartCard/ChartCard.model.js +1223 -0
- package/dist/components/ChartCard/index.d.ts +4 -0
- package/dist/components/ChartCard/index.js +4 -0
- package/dist/components/ComparisonCard/ComparisonCard.chart.d.ts +80 -0
- package/dist/components/ComparisonCard/ComparisonCard.chart.js +337 -0
- package/dist/components/ComparisonCard/ComparisonCard.d.ts +200 -0
- package/dist/components/ComparisonCard/ComparisonCard.js +1325 -0
- package/dist/components/ComparisonCard/ComparisonCard.model.d.ts +309 -0
- package/dist/components/ComparisonCard/ComparisonCard.model.js +263 -0
- package/dist/components/ComparisonCard/index.d.ts +4 -0
- package/dist/components/ComparisonCard/index.js +4 -0
- package/dist/components/ForecastCard/ForecastCard.chart.d.ts +82 -0
- package/dist/components/ForecastCard/ForecastCard.chart.js +416 -0
- package/dist/components/ForecastCard/ForecastCard.d.ts +148 -0
- package/dist/components/ForecastCard/ForecastCard.js +1029 -0
- package/dist/components/ForecastCard/ForecastCard.model.d.ts +497 -0
- package/dist/components/ForecastCard/ForecastCard.model.js +554 -0
- package/dist/components/ForecastCard/index.d.ts +4 -0
- package/dist/components/ForecastCard/index.js +4 -0
- package/dist/components/HighlightCard/HighlightCard.chart.d.ts +122 -0
- package/dist/components/HighlightCard/HighlightCard.chart.js +660 -0
- package/dist/components/HighlightCard/HighlightCard.d.ts +202 -0
- package/dist/components/HighlightCard/HighlightCard.js +1286 -0
- package/dist/components/HighlightCard/HighlightCard.model.d.ts +692 -0
- package/dist/components/HighlightCard/HighlightCard.model.js +875 -0
- package/dist/components/HighlightCard/index.d.ts +4 -0
- package/dist/components/HighlightCard/index.js +4 -0
- package/dist/components/InsightCard/InsightCard.chart.d.ts +91 -0
- package/dist/components/InsightCard/InsightCard.chart.js +471 -0
- package/dist/components/InsightCard/InsightCard.d.ts +161 -0
- package/dist/components/InsightCard/InsightCard.js +961 -0
- package/dist/components/InsightCard/InsightCard.model.d.ts +405 -0
- package/dist/components/InsightCard/InsightCard.model.js +421 -0
- package/dist/components/InsightCard/index.d.ts +4 -0
- package/dist/components/InsightCard/index.js +4 -0
- package/dist/components/MetricCard/MetricCard.chart.d.ts +79 -0
- package/dist/components/MetricCard/MetricCard.chart.js +435 -0
- package/dist/components/MetricCard/MetricCard.d.ts +227 -0
- package/dist/components/MetricCard/MetricCard.js +1293 -0
- package/dist/components/MetricCard/MetricCard.model.d.ts +521 -0
- package/dist/components/MetricCard/MetricCard.model.js +627 -0
- package/dist/components/MetricCard/index.d.ts +4 -0
- package/dist/components/MetricCard/index.js +4 -0
- package/dist/components/ProfileCard/ProfileCard.chart.d.ts +64 -0
- package/dist/components/ProfileCard/ProfileCard.chart.js +427 -0
- package/dist/components/ProfileCard/ProfileCard.d.ts +140 -0
- package/dist/components/ProfileCard/ProfileCard.js +735 -0
- package/dist/components/ProfileCard/ProfileCard.model.d.ts +279 -0
- package/dist/components/ProfileCard/ProfileCard.model.js +244 -0
- package/dist/components/ProfileCard/index.d.ts +4 -0
- package/dist/components/ProfileCard/index.js +4 -0
- package/dist/components/ProgressCard/ProgressCard.chart.d.ts +73 -0
- package/dist/components/ProgressCard/ProgressCard.chart.js +373 -0
- package/dist/components/ProgressCard/ProgressCard.d.ts +137 -0
- package/dist/components/ProgressCard/ProgressCard.js +908 -0
- package/dist/components/ProgressCard/ProgressCard.model.d.ts +385 -0
- package/dist/components/ProgressCard/ProgressCard.model.js +389 -0
- package/dist/components/ProgressCard/index.d.ts +4 -0
- package/dist/components/ProgressCard/index.js +4 -0
- package/dist/components/ReportCard/ReportCard.chart.d.ts +45 -0
- package/dist/components/ReportCard/ReportCard.chart.js +231 -0
- package/dist/components/ReportCard/ReportCard.d.ts +153 -0
- package/dist/components/ReportCard/ReportCard.js +1118 -0
- package/dist/components/ReportCard/ReportCard.model.d.ts +392 -0
- package/dist/components/ReportCard/ReportCard.model.js +399 -0
- package/dist/components/ReportCard/index.d.ts +4 -0
- package/dist/components/ReportCard/index.js +4 -0
- package/dist/components/SankeyCard/SankeyCard.chart.d.ts +75 -0
- package/dist/components/SankeyCard/SankeyCard.chart.js +249 -0
- package/dist/components/SankeyCard/SankeyCard.d.ts +185 -0
- package/dist/components/SankeyCard/SankeyCard.js +788 -0
- package/dist/components/SankeyCard/SankeyCard.model.d.ts +501 -0
- package/dist/components/SankeyCard/SankeyCard.model.js +789 -0
- package/dist/components/SankeyCard/index.d.ts +4 -0
- package/dist/components/SankeyCard/index.js +4 -0
- package/dist/components/StatusCard/StatusCard.chart.d.ts +60 -0
- package/dist/components/StatusCard/StatusCard.chart.js +188 -0
- package/dist/components/StatusCard/StatusCard.d.ts +176 -0
- package/dist/components/StatusCard/StatusCard.js +989 -0
- package/dist/components/StatusCard/StatusCard.model.d.ts +555 -0
- package/dist/components/StatusCard/StatusCard.model.js +776 -0
- package/dist/components/StatusCard/index.d.ts +4 -0
- package/dist/components/StatusCard/index.js +4 -0
- package/dist/components/TableCard/TableCard.chart.d.ts +53 -0
- package/dist/components/TableCard/TableCard.chart.js +192 -0
- package/dist/components/TableCard/TableCard.d.ts +140 -0
- package/dist/components/TableCard/TableCard.js +811 -0
- package/dist/components/TableCard/TableCard.model.d.ts +381 -0
- package/dist/components/TableCard/TableCard.model.js +475 -0
- package/dist/components/TableCard/index.d.ts +4 -0
- package/dist/components/TableCard/index.js +4 -0
- package/dist/components/TargetCard/TargetCard.chart.d.ts +52 -0
- package/dist/components/TargetCard/TargetCard.chart.js +273 -0
- package/dist/components/TargetCard/TargetCard.d.ts +143 -0
- package/dist/components/TargetCard/TargetCard.js +910 -0
- package/dist/components/TargetCard/TargetCard.model.d.ts +468 -0
- package/dist/components/TargetCard/TargetCard.model.js +581 -0
- package/dist/components/TargetCard/index.d.ts +4 -0
- package/dist/components/TargetCard/index.js +4 -0
- package/dist/components/TreeCard/TreeCard.chart.d.ts +80 -0
- package/dist/components/TreeCard/TreeCard.chart.js +178 -0
- package/dist/components/TreeCard/TreeCard.d.ts +182 -0
- package/dist/components/TreeCard/TreeCard.js +1187 -0
- package/dist/components/TreeCard/TreeCard.model.d.ts +330 -0
- package/dist/components/TreeCard/TreeCard.model.js +401 -0
- package/dist/components/TreeCard/index.d.ts +4 -0
- package/dist/components/TreeCard/index.js +4 -0
- package/dist/components/TrendCard/TrendCard.chart.d.ts +75 -0
- package/dist/components/TrendCard/TrendCard.chart.js +393 -0
- package/dist/components/TrendCard/TrendCard.d.ts +224 -0
- package/dist/components/TrendCard/TrendCard.js +1053 -0
- package/dist/components/TrendCard/TrendCard.model.d.ts +545 -0
- package/dist/components/TrendCard/TrendCard.model.js +623 -0
- package/dist/components/TrendCard/index.d.ts +4 -0
- package/dist/components/TrendCard/index.js +4 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +57 -0
- package/dist/lib/vega/ArcVegaChart.d.ts +24 -0
- package/dist/lib/vega/ArcVegaChart.js +42 -0
- package/dist/lib/vega/ArcVegaSurface.d.ts +13 -0
- package/dist/lib/vega/ArcVegaSurface.js +38 -0
- package/dist/lib/vega/axisTicks.js +156 -0
- package/dist/lib/vega/config.d.ts +29 -0
- package/dist/lib/vega/config.js +113 -0
- package/dist/lib/vega/embed.d.ts +29 -0
- package/dist/lib/vega/embed.js +29 -0
- package/dist/lib/vega/engine.d.ts +10 -0
- package/dist/lib/vega/engine.js +6 -0
- package/dist/lib/vega/formatters.d.ts +125 -0
- package/dist/lib/vega/formatters.js +277 -0
- package/dist/lib/vega/index.d.ts +11 -0
- package/dist/lib/vega/load.d.ts +13 -0
- package/dist/lib/vega/load.js +21 -0
- package/dist/lib/vega/time.d.ts +45 -0
- package/dist/lib/vega/time.js +105 -0
- package/dist/lib/vega/tokens.d.ts +73 -0
- package/dist/lib/vega/tokens.js +137 -0
- package/dist/lib/vega/tooltip.d.ts +45 -0
- package/dist/lib/vega/tooltip.js +124 -0
- package/dist/lib/vega/useArcVega.d.ts +43 -0
- package/dist/lib/vega/useArcVega.js +121 -0
- package/package.json +60 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ChartTokens } from "../../lib/vega/tokens.js";
|
|
2
|
+
import { ProfileMark } from "./ProfileCard.model.js";
|
|
3
|
+
import { TopLevelSpec } from "vega-lite";
|
|
4
|
+
//#region src/components/ProfileCard/ProfileCard.chart.d.ts
|
|
5
|
+
/** Long-form readings: one row per series × metric with a finite value (`ProfileCell`). */
|
|
6
|
+
declare const PROFILE_CELLS_DATASET = "cells";
|
|
7
|
+
/** One row per metric — `{ i, a, m, ml }` — the axis ring, the spokes, the band domain and the labels. */
|
|
8
|
+
declare const PROFILE_AXES_DATASET = "axes";
|
|
9
|
+
/** The web: one row per metric × ring level — `{ i, a, r }`. Empty for the non-radar marks. */
|
|
10
|
+
declare const PROFILE_RINGS_DATASET = "rings";
|
|
11
|
+
/** The hovered series' point selection. */
|
|
12
|
+
declare const PROFILE_HOVER_PARAM = "hover";
|
|
13
|
+
/** Which geometry the mark draws in. `radar` is cartesian-from-polar; the rest are ordinary plots. */
|
|
14
|
+
declare function profileNeedsRings(mark: ProfileMark): boolean;
|
|
15
|
+
interface ProfilePlotSpecOptions {
|
|
16
|
+
/** Which form the plot draws. */
|
|
17
|
+
mark: ProfileMark;
|
|
18
|
+
/** Plot height in pixels. The width rides the View API. */
|
|
19
|
+
height: number;
|
|
20
|
+
/**
|
|
21
|
+
* Which field the value channel reads. `raw` when every metric already
|
|
22
|
+
* shares one scale (`normalize="shared"`), so the axis speaks real units;
|
|
23
|
+
* `n` when each metric was scaled to its own range and only the 0–1 form
|
|
24
|
+
* is comparable. A radar and a parallel plot always read `n`.
|
|
25
|
+
*/
|
|
26
|
+
valueField: "raw" | "n";
|
|
27
|
+
/** Draw the web rings and the spokes (radar only). */
|
|
28
|
+
rings: boolean;
|
|
29
|
+
/** Fill each radar polygon faintly. Off by default — a filled area invites an area comparison the geometry cannot support. */
|
|
30
|
+
fill: boolean;
|
|
31
|
+
/** Draw a dot on each vertex, so a reading is a mark and not only a bend in a path. */
|
|
32
|
+
vertices: boolean;
|
|
33
|
+
/** Wire the hover selection and the tooltip channel. */
|
|
34
|
+
tooltip: boolean;
|
|
35
|
+
/** Dim every series but the focused one. Only meaningful while a focus exists. */
|
|
36
|
+
emphasize: boolean;
|
|
37
|
+
/** Name the metrics on the plot. `false` leaves the axis to the popover table (a tile too small for labels). */
|
|
38
|
+
axisLabels: boolean;
|
|
39
|
+
/** Tooltip titles. */
|
|
40
|
+
itemTitle: string;
|
|
41
|
+
metricTitle: string;
|
|
42
|
+
valueTitle: string;
|
|
43
|
+
/** `arcNumber` grammar for the value axis and the tooltip. */
|
|
44
|
+
valueFormat: string;
|
|
45
|
+
locale?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Builds the plot.
|
|
49
|
+
*
|
|
50
|
+
* `radar` draws a web of rings and spokes, then one closed polygon per item
|
|
51
|
+
* over the metric axes — the shape IS the reading, so the card fixes the axis
|
|
52
|
+
* order, keeps the fill off by default and never lets a rescaled axis go
|
|
53
|
+
* unannounced. `grouped` draws one bar per item inside each metric's band,
|
|
54
|
+
* which is the accurate form whenever values rather than shapes are being
|
|
55
|
+
* compared. `parallel` draws one path per item across parallel axes, which
|
|
56
|
+
* carries more items than a radar can without turning into a knot.
|
|
57
|
+
*
|
|
58
|
+
* Colour is identity here — several items on one plot — so it comes baked
|
|
59
|
+
* into every pushed row (`c`) with `scale: null`: hiding a series through the
|
|
60
|
+
* legend can then never repaint the survivors.
|
|
61
|
+
*/
|
|
62
|
+
declare function buildProfilePlotSpec(options: ProfilePlotSpecOptions, tokens: ChartTokens): TopLevelSpec;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { PROFILE_AXES_DATASET, PROFILE_CELLS_DATASET, PROFILE_HOVER_PARAM, PROFILE_RINGS_DATASET, ProfilePlotSpecOptions, buildProfilePlotSpec, profileNeedsRings };
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { arcVegaConfig } from "../../lib/vega/config.js";
|
|
2
|
+
//#region src/components/ProfileCard/ProfileCard.chart.ts
|
|
3
|
+
/** Long-form readings: one row per series × metric with a finite value (`ProfileCell`). */
|
|
4
|
+
const PROFILE_CELLS_DATASET = "cells";
|
|
5
|
+
/** One row per metric — `{ i, a, m, ml }` — the axis ring, the spokes, the band domain and the labels. */
|
|
6
|
+
const PROFILE_AXES_DATASET = "axes";
|
|
7
|
+
/** The web: one row per metric × ring level — `{ i, a, r }`. Empty for the non-radar marks. */
|
|
8
|
+
const PROFILE_RINGS_DATASET = "rings";
|
|
9
|
+
/** The hovered series' point selection. */
|
|
10
|
+
const PROFILE_HOVER_PARAM = "hover";
|
|
11
|
+
const PATH_WIDTH = 2;
|
|
12
|
+
const VERTEX_SIZE = 48;
|
|
13
|
+
const RESTING_OPACITY = .55;
|
|
14
|
+
const FILL_OPACITY = .12;
|
|
15
|
+
const LABEL_RADIUS = 1.08;
|
|
16
|
+
const RADAR_DOMAIN = 1.72;
|
|
17
|
+
const LABEL_LIMIT_EXPR = `max(24, width / 2 * ${(.6399999999999999 / RADAR_DOMAIN).toFixed(4)} - 2)`;
|
|
18
|
+
const MAX_GROUP_BAR = 18;
|
|
19
|
+
const PARALLEL_PADDING = .14;
|
|
20
|
+
/** Which geometry the mark draws in. `radar` is cartesian-from-polar; the rest are ordinary plots. */
|
|
21
|
+
function profileNeedsRings(mark) {
|
|
22
|
+
return mark === "radar";
|
|
23
|
+
}
|
|
24
|
+
/** The band domain and the axis-label lookup both read the axes dataset, so a metric nobody reported keeps its place. */
|
|
25
|
+
const AXIS_DOMAIN_EXPR = `pluck(data('${PROFILE_AXES_DATASET}'), 'm')`;
|
|
26
|
+
const AXIS_INDEX_DOMAIN_EXPR = `pluck(data('${PROFILE_AXES_DATASET}'), 'i')`;
|
|
27
|
+
const AXIS_LABEL_EXPR = `((data('${PROFILE_AXES_DATASET}')[indexof(pluck(data('${PROFILE_AXES_DATASET}'), 'm'), datum.value)] || {}).ml)`;
|
|
28
|
+
const AXIS_LABEL_BY_INDEX_EXPR = `((data('${PROFILE_AXES_DATASET}')[indexof(pluck(data('${PROFILE_AXES_DATASET}'), 'i'), datum.value)] || {}).ml)`;
|
|
29
|
+
const RADAR_X = (radius) => `sin(datum.a) * ${radius}`;
|
|
30
|
+
const RADAR_Y = (radius) => `cos(datum.a) * ${radius}`;
|
|
31
|
+
/**
|
|
32
|
+
* Builds the plot.
|
|
33
|
+
*
|
|
34
|
+
* `radar` draws a web of rings and spokes, then one closed polygon per item
|
|
35
|
+
* over the metric axes — the shape IS the reading, so the card fixes the axis
|
|
36
|
+
* order, keeps the fill off by default and never lets a rescaled axis go
|
|
37
|
+
* unannounced. `grouped` draws one bar per item inside each metric's band,
|
|
38
|
+
* which is the accurate form whenever values rather than shapes are being
|
|
39
|
+
* compared. `parallel` draws one path per item across parallel axes, which
|
|
40
|
+
* carries more items than a radar can without turning into a knot.
|
|
41
|
+
*
|
|
42
|
+
* Colour is identity here — several items on one plot — so it comes baked
|
|
43
|
+
* into every pushed row (`c`) with `scale: null`: hiding a series through the
|
|
44
|
+
* legend can then never repaint the survivors.
|
|
45
|
+
*/
|
|
46
|
+
function buildProfilePlotSpec(options, tokens) {
|
|
47
|
+
const { mark, height, valueField, rings, fill, vertices, tooltip, emphasize, axisLabels, itemTitle, metricTitle, valueTitle, valueFormat, locale } = options;
|
|
48
|
+
const colorChannel = {
|
|
49
|
+
field: "c",
|
|
50
|
+
type: "nominal",
|
|
51
|
+
scale: null,
|
|
52
|
+
legend: null
|
|
53
|
+
};
|
|
54
|
+
const seriesDetail = {
|
|
55
|
+
field: "s",
|
|
56
|
+
type: "nominal"
|
|
57
|
+
};
|
|
58
|
+
const tooltipChannel = [
|
|
59
|
+
{
|
|
60
|
+
field: "sl",
|
|
61
|
+
type: "nominal",
|
|
62
|
+
title: itemTitle
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
field: "ml",
|
|
66
|
+
type: "nominal",
|
|
67
|
+
title: metricTitle
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
field: "raw",
|
|
71
|
+
type: "quantitative",
|
|
72
|
+
title: valueTitle,
|
|
73
|
+
format: valueFormat,
|
|
74
|
+
formatType: "arcNumber"
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
const solid = { value: 1 };
|
|
78
|
+
const hovered = {
|
|
79
|
+
param: PROFILE_HOVER_PARAM,
|
|
80
|
+
...emphasize ? { empty: false } : {},
|
|
81
|
+
...solid
|
|
82
|
+
};
|
|
83
|
+
const focused = {
|
|
84
|
+
test: "datum.f",
|
|
85
|
+
...solid
|
|
86
|
+
};
|
|
87
|
+
const opacityChannel = tooltip && emphasize ? { opacity: {
|
|
88
|
+
condition: [hovered, focused],
|
|
89
|
+
value: RESTING_OPACITY
|
|
90
|
+
} } : tooltip ? { opacity: {
|
|
91
|
+
condition: hovered,
|
|
92
|
+
value: RESTING_OPACITY
|
|
93
|
+
} } : emphasize ? { opacity: {
|
|
94
|
+
condition: focused,
|
|
95
|
+
value: RESTING_OPACITY
|
|
96
|
+
} } : {};
|
|
97
|
+
const hoverParam = tooltip ? { params: [{
|
|
98
|
+
name: PROFILE_HOVER_PARAM,
|
|
99
|
+
select: {
|
|
100
|
+
type: "point",
|
|
101
|
+
on: "pointerover",
|
|
102
|
+
clear: "pointerout",
|
|
103
|
+
fields: ["s"]
|
|
104
|
+
}
|
|
105
|
+
}] } : {};
|
|
106
|
+
const layers = [];
|
|
107
|
+
if (mark === "radar") {
|
|
108
|
+
const cartesian = (radius) => [{
|
|
109
|
+
calculate: RADAR_X(radius),
|
|
110
|
+
as: "px"
|
|
111
|
+
}, {
|
|
112
|
+
calculate: RADAR_Y(radius),
|
|
113
|
+
as: "py"
|
|
114
|
+
}];
|
|
115
|
+
const px = {
|
|
116
|
+
field: "px",
|
|
117
|
+
type: "quantitative",
|
|
118
|
+
axis: null,
|
|
119
|
+
scale: {
|
|
120
|
+
domain: [-1.72, RADAR_DOMAIN],
|
|
121
|
+
nice: false
|
|
122
|
+
},
|
|
123
|
+
title: null
|
|
124
|
+
};
|
|
125
|
+
const py = {
|
|
126
|
+
field: "py",
|
|
127
|
+
type: "quantitative",
|
|
128
|
+
axis: null,
|
|
129
|
+
scale: {
|
|
130
|
+
domain: [-1.72, RADAR_DOMAIN],
|
|
131
|
+
nice: false
|
|
132
|
+
},
|
|
133
|
+
title: null
|
|
134
|
+
};
|
|
135
|
+
if (rings) {
|
|
136
|
+
layers.push({
|
|
137
|
+
data: { name: PROFILE_RINGS_DATASET },
|
|
138
|
+
transform: cartesian("datum.r"),
|
|
139
|
+
mark: {
|
|
140
|
+
type: "line",
|
|
141
|
+
interpolate: "linear-closed",
|
|
142
|
+
stroke: tokens.grid,
|
|
143
|
+
strokeWidth: 1,
|
|
144
|
+
aria: false
|
|
145
|
+
},
|
|
146
|
+
encoding: {
|
|
147
|
+
x: px,
|
|
148
|
+
y: py,
|
|
149
|
+
detail: {
|
|
150
|
+
field: "r",
|
|
151
|
+
type: "nominal"
|
|
152
|
+
},
|
|
153
|
+
order: {
|
|
154
|
+
field: "i",
|
|
155
|
+
type: "quantitative"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
layers.push({
|
|
160
|
+
data: { name: PROFILE_AXES_DATASET },
|
|
161
|
+
transform: cartesian("1"),
|
|
162
|
+
mark: {
|
|
163
|
+
type: "rule",
|
|
164
|
+
stroke: tokens.grid,
|
|
165
|
+
strokeWidth: 1,
|
|
166
|
+
aria: false
|
|
167
|
+
},
|
|
168
|
+
encoding: {
|
|
169
|
+
x: px,
|
|
170
|
+
y: py,
|
|
171
|
+
x2: { datum: 0 },
|
|
172
|
+
y2: { datum: 0 }
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
layers.push({
|
|
177
|
+
transform: cartesian("datum.n"),
|
|
178
|
+
mark: {
|
|
179
|
+
type: "line",
|
|
180
|
+
interpolate: "linear-closed",
|
|
181
|
+
strokeWidth: PATH_WIDTH,
|
|
182
|
+
strokeJoin: "round",
|
|
183
|
+
...fill ? {
|
|
184
|
+
filled: true,
|
|
185
|
+
fillOpacity: FILL_OPACITY
|
|
186
|
+
} : { fill: null },
|
|
187
|
+
aria: false
|
|
188
|
+
},
|
|
189
|
+
encoding: {
|
|
190
|
+
x: px,
|
|
191
|
+
y: py,
|
|
192
|
+
stroke: colorChannel,
|
|
193
|
+
...fill ? { fill: colorChannel } : {},
|
|
194
|
+
detail: seriesDetail,
|
|
195
|
+
order: {
|
|
196
|
+
field: "i",
|
|
197
|
+
type: "quantitative"
|
|
198
|
+
},
|
|
199
|
+
...opacityChannel
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
if (vertices) layers.push({
|
|
203
|
+
...hoverParam,
|
|
204
|
+
transform: cartesian("datum.n"),
|
|
205
|
+
mark: {
|
|
206
|
+
type: "point",
|
|
207
|
+
filled: true,
|
|
208
|
+
size: VERTEX_SIZE,
|
|
209
|
+
aria: false
|
|
210
|
+
},
|
|
211
|
+
encoding: {
|
|
212
|
+
x: px,
|
|
213
|
+
y: py,
|
|
214
|
+
fill: colorChannel,
|
|
215
|
+
...opacityChannel,
|
|
216
|
+
...tooltip ? { tooltip: tooltipChannel } : {}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
if (axisLabels) layers.push({
|
|
220
|
+
data: { name: PROFILE_AXES_DATASET },
|
|
221
|
+
transform: cartesian(String(LABEL_RADIUS)),
|
|
222
|
+
mark: {
|
|
223
|
+
type: "text",
|
|
224
|
+
fontSize: 10,
|
|
225
|
+
baseline: "middle",
|
|
226
|
+
fill: tokens.axisLabel,
|
|
227
|
+
limit: { expr: LABEL_LIMIT_EXPR },
|
|
228
|
+
align: { expr: "abs(datum.px) < 0.08 ? 'center' : (datum.px > 0 ? 'left' : 'right')" },
|
|
229
|
+
aria: false
|
|
230
|
+
},
|
|
231
|
+
encoding: {
|
|
232
|
+
x: px,
|
|
233
|
+
y: py,
|
|
234
|
+
text: {
|
|
235
|
+
field: "ml",
|
|
236
|
+
type: "nominal"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (mark === "grouped") {
|
|
242
|
+
const catAxis = (owner) => !axisLabels ? { axis: null } : owner ? { axis: {
|
|
243
|
+
title: null,
|
|
244
|
+
labelExpr: AXIS_LABEL_EXPR,
|
|
245
|
+
labelLimit: 96,
|
|
246
|
+
domain: false,
|
|
247
|
+
ticks: false,
|
|
248
|
+
grid: false,
|
|
249
|
+
aria: false
|
|
250
|
+
} } : {};
|
|
251
|
+
const band = (owner) => ({
|
|
252
|
+
field: "m",
|
|
253
|
+
type: "nominal",
|
|
254
|
+
sort: null,
|
|
255
|
+
scale: {
|
|
256
|
+
type: "band",
|
|
257
|
+
domain: { expr: AXIS_DOMAIN_EXPR },
|
|
258
|
+
paddingInner: .3,
|
|
259
|
+
paddingOuter: .15
|
|
260
|
+
},
|
|
261
|
+
...catAxis(owner)
|
|
262
|
+
});
|
|
263
|
+
layers.push({
|
|
264
|
+
data: { name: PROFILE_AXES_DATASET },
|
|
265
|
+
mark: {
|
|
266
|
+
type: "rule",
|
|
267
|
+
stroke: tokens.grid,
|
|
268
|
+
strokeWidth: 1,
|
|
269
|
+
aria: false
|
|
270
|
+
},
|
|
271
|
+
encoding: { y: band(false) }
|
|
272
|
+
});
|
|
273
|
+
layers.push({
|
|
274
|
+
...hoverParam,
|
|
275
|
+
mark: {
|
|
276
|
+
type: "bar",
|
|
277
|
+
cornerRadiusEnd: 2,
|
|
278
|
+
aria: false
|
|
279
|
+
},
|
|
280
|
+
encoding: {
|
|
281
|
+
y: band(true),
|
|
282
|
+
yOffset: {
|
|
283
|
+
field: "s",
|
|
284
|
+
type: "nominal",
|
|
285
|
+
sort: null,
|
|
286
|
+
scale: { paddingInner: .1 }
|
|
287
|
+
},
|
|
288
|
+
x: {
|
|
289
|
+
field: valueField,
|
|
290
|
+
type: "quantitative",
|
|
291
|
+
scale: {
|
|
292
|
+
zero: true,
|
|
293
|
+
nice: true
|
|
294
|
+
},
|
|
295
|
+
axis: axisLabels ? {
|
|
296
|
+
title: null,
|
|
297
|
+
tickCount: 3,
|
|
298
|
+
format: valueFormat,
|
|
299
|
+
formatType: "arcNumber",
|
|
300
|
+
domain: false,
|
|
301
|
+
ticks: false,
|
|
302
|
+
grid: true,
|
|
303
|
+
aria: false
|
|
304
|
+
} : null
|
|
305
|
+
},
|
|
306
|
+
fill: colorChannel,
|
|
307
|
+
...opacityChannel,
|
|
308
|
+
...tooltip ? { tooltip: tooltipChannel } : {}
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (mark === "parallel") {
|
|
313
|
+
const axisChannel = (owner) => ({
|
|
314
|
+
field: "i",
|
|
315
|
+
type: "ordinal",
|
|
316
|
+
sort: null,
|
|
317
|
+
scale: {
|
|
318
|
+
type: "point",
|
|
319
|
+
domain: { expr: AXIS_INDEX_DOMAIN_EXPR },
|
|
320
|
+
padding: PARALLEL_PADDING
|
|
321
|
+
},
|
|
322
|
+
...!axisLabels ? { axis: null } : owner ? { axis: {
|
|
323
|
+
title: null,
|
|
324
|
+
labelAngle: 0,
|
|
325
|
+
labelExpr: AXIS_LABEL_BY_INDEX_EXPR,
|
|
326
|
+
labelLimit: 72,
|
|
327
|
+
domain: false,
|
|
328
|
+
ticks: false,
|
|
329
|
+
grid: false,
|
|
330
|
+
aria: false
|
|
331
|
+
} } : {}
|
|
332
|
+
});
|
|
333
|
+
const valueChannel = {
|
|
334
|
+
field: "n",
|
|
335
|
+
type: "quantitative",
|
|
336
|
+
axis: null,
|
|
337
|
+
scale: {
|
|
338
|
+
domain: [0, 1],
|
|
339
|
+
nice: false
|
|
340
|
+
},
|
|
341
|
+
title: null
|
|
342
|
+
};
|
|
343
|
+
layers.push({
|
|
344
|
+
data: { name: PROFILE_AXES_DATASET },
|
|
345
|
+
mark: {
|
|
346
|
+
type: "rule",
|
|
347
|
+
stroke: tokens.grid,
|
|
348
|
+
strokeWidth: 1,
|
|
349
|
+
aria: false
|
|
350
|
+
},
|
|
351
|
+
encoding: {
|
|
352
|
+
x: axisChannel(true),
|
|
353
|
+
y: {
|
|
354
|
+
datum: 0,
|
|
355
|
+
type: "quantitative",
|
|
356
|
+
scale: {
|
|
357
|
+
domain: [0, 1],
|
|
358
|
+
nice: false
|
|
359
|
+
},
|
|
360
|
+
axis: null,
|
|
361
|
+
title: null
|
|
362
|
+
},
|
|
363
|
+
y2: { datum: 1 }
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
layers.push({
|
|
367
|
+
mark: {
|
|
368
|
+
type: "line",
|
|
369
|
+
strokeWidth: PATH_WIDTH,
|
|
370
|
+
strokeJoin: "round",
|
|
371
|
+
aria: false
|
|
372
|
+
},
|
|
373
|
+
encoding: {
|
|
374
|
+
x: axisChannel(false),
|
|
375
|
+
y: valueChannel,
|
|
376
|
+
stroke: colorChannel,
|
|
377
|
+
detail: seriesDetail,
|
|
378
|
+
order: {
|
|
379
|
+
field: "i",
|
|
380
|
+
type: "quantitative"
|
|
381
|
+
},
|
|
382
|
+
...opacityChannel
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
if (vertices) layers.push({
|
|
386
|
+
...hoverParam,
|
|
387
|
+
mark: {
|
|
388
|
+
type: "point",
|
|
389
|
+
filled: true,
|
|
390
|
+
size: VERTEX_SIZE,
|
|
391
|
+
aria: false
|
|
392
|
+
},
|
|
393
|
+
encoding: {
|
|
394
|
+
x: axisChannel(false),
|
|
395
|
+
y: valueChannel,
|
|
396
|
+
fill: colorChannel,
|
|
397
|
+
...opacityChannel,
|
|
398
|
+
...tooltip ? { tooltip: tooltipChannel } : {}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
$schema: "https://vega.github.io/schema/vega-lite/v6.json",
|
|
404
|
+
width: 280,
|
|
405
|
+
height,
|
|
406
|
+
autosize: mark === "radar" ? { type: "none" } : {
|
|
407
|
+
type: "fit",
|
|
408
|
+
contains: "padding"
|
|
409
|
+
},
|
|
410
|
+
padding: 0,
|
|
411
|
+
background: "transparent",
|
|
412
|
+
data: { name: PROFILE_CELLS_DATASET },
|
|
413
|
+
layer: layers,
|
|
414
|
+
config: (() => {
|
|
415
|
+
const base = arcVegaConfig(tokens, { locale });
|
|
416
|
+
return {
|
|
417
|
+
...base,
|
|
418
|
+
bar: {
|
|
419
|
+
...base.bar,
|
|
420
|
+
maxBandSize: MAX_GROUP_BAR
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
})()
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
export { PROFILE_AXES_DATASET, PROFILE_CELLS_DATASET, PROFILE_HOVER_PARAM, PROFILE_RINGS_DATASET, buildProfilePlotSpec, profileNeedsRings };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ArcTimeGranularity } from "../../lib/vega/formatters.js";
|
|
2
|
+
import { ProfileCardLabels, ProfileCardSize, ProfileCardStatus, ProfileItem, ProfileMark, ProfileMetric, ProfileMetricInfo, ProfileNormalize, RADAR_MAX_AXES, RADAR_MAX_SERIES } from "./ProfileCard.model.js";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
import { BoxProps } from "@arcfusionz/arc-primitive-ui";
|
|
5
|
+
import { TopLevelSpec } from "vega-lite";
|
|
6
|
+
import { Result } from "vega-embed";
|
|
7
|
+
//#region src/components/ProfileCard/ProfileCard.d.ts
|
|
8
|
+
interface ProfileCardProps extends Omit<BoxProps, "children" | "padding" | "title"> {
|
|
9
|
+
/** The comparison's name — the panel's heading. Pass `name` too when this is not plain text. */
|
|
10
|
+
label: ReactNode;
|
|
11
|
+
/** Plain-text name used in the generated descriptions and control labels. Defaults to `label` when it is a string. */
|
|
12
|
+
name?: string;
|
|
13
|
+
/** What the profile means, shown in a tooltip behind an info control beside the label and exposed to assistive technology as its description. */
|
|
14
|
+
hint?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The measures — one axis each, **in the order they should be read**. The
|
|
17
|
+
* card never sorts them, because a radar's shape depends on the axis order:
|
|
18
|
+
* pick an order the reader recognises and keep it stable across renders.
|
|
19
|
+
* `max` gives a measure its own ceiling for `normalize="max"`, `format` its
|
|
20
|
+
* own number grammar. Pass `[]` while loading.
|
|
21
|
+
*/
|
|
22
|
+
metrics: readonly ProfileMetric[];
|
|
23
|
+
/**
|
|
24
|
+
* The things being profiled. `values` is keyed by metric key; a metric an
|
|
25
|
+
* item has no reading for is a gap, not a zero — omit it or pass `null`.
|
|
26
|
+
* Pass `[]` while loading.
|
|
27
|
+
*/
|
|
28
|
+
items: readonly ProfileItem[];
|
|
29
|
+
/**
|
|
30
|
+
* The form the plot draws. `radar` (default) for the shape of a profile;
|
|
31
|
+
* `grouped` when values rather than shapes are being compared — the
|
|
32
|
+
* accurate form; `parallel` when there are more items than a radar can
|
|
33
|
+
* overlay. Stamped as `data-mark`.
|
|
34
|
+
*/
|
|
35
|
+
mark?: ProfileMark;
|
|
36
|
+
/**
|
|
37
|
+
* How readings on different measures are brought onto one axis. `shared`
|
|
38
|
+
* (default) keeps one scale across every metric — honest only when they
|
|
39
|
+
* are commensurable; `max` scales each metric to its own ceiling; `minmax`
|
|
40
|
+
* scales each to its own observed range, which is readable and the most
|
|
41
|
+
* misleading. Whichever is chosen is announced in the description and
|
|
42
|
+
* printed under the plot. Stamped as `data-normalize`.
|
|
43
|
+
*/
|
|
44
|
+
normalize?: ProfileNormalize;
|
|
45
|
+
/**
|
|
46
|
+
* Fill each radar polygon faintly. **Off by default**: a polygon's area is
|
|
47
|
+
* a sum of products of adjacent readings, so it changes when the axes are
|
|
48
|
+
* reordered and must never be read as a score. Turn it on only for a
|
|
49
|
+
* single-item profile, where there is no area to compare against.
|
|
50
|
+
*/
|
|
51
|
+
fill?: boolean;
|
|
52
|
+
/** Draw a dot at every reading, so a value is a mark and not only a bend in a path. Default `true`. */
|
|
53
|
+
vertices?: boolean;
|
|
54
|
+
/** Name the metrics on the plot. `false` on a panel too small for them — the popover table still carries every name. Default `true`. */
|
|
55
|
+
axisLabels?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Key of the subject, controlled — pair with `onFocusChange`. Its path
|
|
58
|
+
* stays solid while the others recede, and the description says where it
|
|
59
|
+
* leads and trails. `null` designates none: every item reads equally.
|
|
60
|
+
*/
|
|
61
|
+
focus?: string | null;
|
|
62
|
+
/** The subject on first render when `focus` is uncontrolled. Default: the first item. */
|
|
63
|
+
defaultFocus?: string | null;
|
|
64
|
+
/** Called with the chosen item's key when the reader picks another subject from the legend. */
|
|
65
|
+
onFocusChange?: (key: string) => void;
|
|
66
|
+
/** Keys of the items on the plot, controlled — pair with `onVisibleSeriesChange`. Omit for the uncontrolled legend. */
|
|
67
|
+
visibleSeries?: readonly string[];
|
|
68
|
+
/** The items on the plot at first render when `visibleSeries` is uncontrolled. Default: all of them. */
|
|
69
|
+
defaultVisibleSeries?: readonly string[];
|
|
70
|
+
/** Called with the new visible set when the reader toggles an item in the legend. */
|
|
71
|
+
onVisibleSeriesChange?: (keys: string[]) => void;
|
|
72
|
+
/** Show the legend. `false` only when another surface names every item. Default `true`. */
|
|
73
|
+
showLegend?: boolean;
|
|
74
|
+
/** One grammar for every reading that has no `ProfileMetric.format` of its own: `number[:digits]`, `integer`, `compact[:digits]`, `percent[:digits]`, `currency:CODE[:digits|compact]`. */
|
|
75
|
+
format?: string;
|
|
76
|
+
/** Custom formatter for readings. Takes precedence over `format` and over a metric's own `format`. */
|
|
77
|
+
formatValue?: (value: number, metric?: ProfileMetricInfo) => string;
|
|
78
|
+
/** Metrics the skeleton reserves before `metrics` arrive, so the panel is already its final height. Default 5. */
|
|
79
|
+
expectedMetrics?: number;
|
|
80
|
+
/** Items the skeleton reserves in the legend before they arrive. Default 2. */
|
|
81
|
+
expectedItems?: number;
|
|
82
|
+
/** The takeaway in a sentence, rendered beside the AI mark under the plot. Wrap values in `<strong>`. */
|
|
83
|
+
insight?: ReactNode;
|
|
84
|
+
/** The transport's status. `loading` with data already present is a refresh: the previous render stays under the veil. Default `ready`. */
|
|
85
|
+
status?: ProfileCardStatus;
|
|
86
|
+
/** The consumer's filter is active — no readings then reads as no-match rather than empty. */
|
|
87
|
+
filtered?: boolean;
|
|
88
|
+
/** The data is older than the product's freshness rule (TanStack Query's `isStale`). Shows a badge and stamps `data-stale`. */
|
|
89
|
+
stale?: boolean;
|
|
90
|
+
/** When the data was last true. Rendered as an "as of" line and included in the description. */
|
|
91
|
+
updatedAt?: Date | string | number;
|
|
92
|
+
/** How `updatedAt` is written, in the card's `locale` and `timeZone`. Default `day`. */
|
|
93
|
+
updatedAtFormat?: ArcTimeGranularity | ((date: Date) => string);
|
|
94
|
+
/** Recovery action for the error state; renders a retry button when set. */
|
|
95
|
+
onRetry?: () => void;
|
|
96
|
+
/** Recovery action for the no-match state; renders a clear-filters button when set. */
|
|
97
|
+
onClearFilters?: () => void;
|
|
98
|
+
/** Density: `sm` for a dense grid, `md` (default), `lg` for a hero panel. */
|
|
99
|
+
size?: ProfileCardSize;
|
|
100
|
+
/** Heading level of the label for the document outline. Default 3. */
|
|
101
|
+
titleLevel?: 2 | 3 | 4 | 5 | 6;
|
|
102
|
+
/** BCP 47 locale for every number and date. Thai products on the Gregorian calendar pass `th-TH-u-ca-gregory`. */
|
|
103
|
+
locale?: string;
|
|
104
|
+
/** IANA time zone for the as-of date. Defaults to the runtime's — pass it for deterministic output. */
|
|
105
|
+
timeZone?: string;
|
|
106
|
+
/** Overrides for every rendered or announced string. */
|
|
107
|
+
labels?: Partial<ProfileCardLabels>;
|
|
108
|
+
/** Replaces the generated accessible description of the whole card. */
|
|
109
|
+
description?: string;
|
|
110
|
+
/** Trailing header slot — a ghost `Button` or `Menu`. */
|
|
111
|
+
action?: ReactNode;
|
|
112
|
+
/** Open the data popover on first render. */
|
|
113
|
+
defaultTableOpen?: boolean;
|
|
114
|
+
/** Hide the header info button. Only when another surface exposes the same values to keyboard and screen-reader users. */
|
|
115
|
+
hideTableToggle?: boolean;
|
|
116
|
+
/** Called with vega-embed's result once the plot mounts — for `view` access in advanced compositions. */
|
|
117
|
+
onEmbed?: (result: Result) => void;
|
|
118
|
+
/** Escape hatch: transform the spec before it renders. Voids the token and accessibility guarantees for whatever it changes. */
|
|
119
|
+
patchSpec?: (spec: TopLevelSpec) => TopLevelSpec;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A profile panel — the ninth member of the analytics card family, and the
|
|
123
|
+
* only one that compares **several items across several measures at once**.
|
|
124
|
+
* MetricCard compares one metric with its own past; ComparisonCard compares
|
|
125
|
+
* one metric across items; TrendCard asks what shape a metric is in over
|
|
126
|
+
* time. ProfileCard answers the question none of them can: *what shape is
|
|
127
|
+
* each of these things across the same set of measures, and where does one
|
|
128
|
+
* lead or lag?*
|
|
129
|
+
*
|
|
130
|
+
* `mark="radar"` (default) draws the profile as a polygon per item — with the
|
|
131
|
+
* axis order fixed, the fill off and the scaling announced, which is what
|
|
132
|
+
* makes a radar honest. `mark="grouped"` is the accurate alternative for
|
|
133
|
+
* comparing values, and `mark="parallel"` carries more items than a radar
|
|
134
|
+
* can. Every reading is also in the popover table, the legend is real
|
|
135
|
+
* toggles, and every loading, empty, error and stale state is layout-stable
|
|
136
|
+
* inside one `Box` frame.
|
|
137
|
+
*/
|
|
138
|
+
declare const ProfileCard: import("react").ForwardRefExoticComponent<ProfileCardProps & import("react").RefAttributes<HTMLElement>>;
|
|
139
|
+
//#endregion
|
|
140
|
+
export { ProfileCard, ProfileCardProps };
|