@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,416 @@
|
|
|
1
|
+
import { AXIS_LABEL_LIMIT_EXPR } from "../../lib/vega/axisTicks.js";
|
|
2
|
+
import { arcVegaConfig } from "../../lib/vega/config.js";
|
|
3
|
+
//#region src/components/ForecastCard/ForecastCard.chart.ts
|
|
4
|
+
/** Every plotted period `{ x, y, lo, hi, k, c, n }`, measured then projected, with the anchor repeated. */
|
|
5
|
+
const FORECAST_TABLE_DATASET = "table";
|
|
6
|
+
/** The interval band `{ x, lo, hi }` — the anchor at zero width, then every projected period with bounds. */
|
|
7
|
+
const FORECAST_INTERVAL_DATASET = "interval";
|
|
8
|
+
/** One row per period, `{ x }` — the crosshair, filtered by the hover selection. */
|
|
9
|
+
const FORECAST_PERIODS_DATASET = "periods";
|
|
10
|
+
/** Zero or one row `{ start, end }` — the projected stretch: the wash, and the x axis's anchors. */
|
|
11
|
+
const FORECAST_REGION_DATASET = "region";
|
|
12
|
+
/** Zero or one row `{ x }` — the rule at the last measured period. */
|
|
13
|
+
const FORECAST_BOUNDARY_DATASET = "boundary";
|
|
14
|
+
/** Zero or one row `{ value }` — the target reference rule. */
|
|
15
|
+
const FORECAST_REFERENCE_DATASET = "reference";
|
|
16
|
+
/** Zero or one row `{ x, y, n, c }` — the landing dot and its direct label. */
|
|
17
|
+
const FORECAST_LANDING_DATASET = "landing";
|
|
18
|
+
/** The shared-x hover selection: projected onto `x`, so pointing anywhere in a column reads that period. */
|
|
19
|
+
const FORECAST_HOVER_PARAM = "hover";
|
|
20
|
+
/** Responsive x tick values, recomputed by Vega when the domain or the width changes. */
|
|
21
|
+
const FORECAST_X_TICKS_PARAM = "forecast_x_ticks";
|
|
22
|
+
/** The projected window's bounds, read from the `region` dataset — the x axis labels them. */
|
|
23
|
+
const FORECAST_REGION_START_PARAM = "forecast_region_start";
|
|
24
|
+
const FORECAST_REGION_END_PARAM = "forecast_region_end";
|
|
25
|
+
const POINT_SIZE = 64;
|
|
26
|
+
const PLOT_PADDING = 6;
|
|
27
|
+
const LABEL_LIMIT_PX = 72;
|
|
28
|
+
const LABEL_DODGE_PX = 14;
|
|
29
|
+
const LABEL_DODGE_ROOM_PX = 20;
|
|
30
|
+
const LABEL_INSET_PX = 4;
|
|
31
|
+
const INTERVAL_OPACITY = .16;
|
|
32
|
+
const REGION_OPACITY = .55;
|
|
33
|
+
const ACTUAL_DASH = [1, 0];
|
|
34
|
+
const FORECAST_DASH = [6, 4];
|
|
35
|
+
const AREA_FILL_OPACITY = .12;
|
|
36
|
+
const DISCRETE_DOMAIN_EXPR = `pluck(data('${FORECAST_PERIODS_DATASET}'), 'x')`;
|
|
37
|
+
/**
|
|
38
|
+
* Builds the plot: the measured readings as a solid path, the projection as the
|
|
39
|
+
* same path dashed, the prediction interval as a band that opens from the last
|
|
40
|
+
* reading, a wash and a rule marking where measurement stops, an optional
|
|
41
|
+
* target rule, a dotted landing, and the documented shared-x hover (a
|
|
42
|
+
* transparent `point` layer carrying a `nearest` selection projected onto `x`,
|
|
43
|
+
* with a crosshair rule read from the period dataset).
|
|
44
|
+
*
|
|
45
|
+
* Gaps stay gaps — a `null` reading breaks the path rather than dropping it to
|
|
46
|
+
* zero. An `area` keeps its zero baseline whatever `scale` says, because
|
|
47
|
+
* proportional ink is not negotiable; a line may crop, and since both axes are
|
|
48
|
+
* drawn the crop is always visible in the axis.
|
|
49
|
+
*/
|
|
50
|
+
function buildForecastPlotSpec(options, tokens) {
|
|
51
|
+
const { mark, scale, xType, height, axes, yTicks, interval, region, boundary, reference, landing, landingLabel, tooltip, xTitle, yTitle, kindTitle, plotFormat, timeFormat, axisTimeFormat } = options;
|
|
52
|
+
const zero = mark === "area" || scale === "zero";
|
|
53
|
+
const discreteX = xType === "ordinal";
|
|
54
|
+
const xTickFormat = xType === "temporal" ? axisTimeFormat : "compact";
|
|
55
|
+
const xTickKind = xType === "temporal" ? "time" : xType === "quantitative" ? "number" : "text";
|
|
56
|
+
const axisArgs = `domain('x'), width, ${discreteX}, ${JSON.stringify(xTickFormat)}, ${JSON.stringify(xTickKind)}`;
|
|
57
|
+
const anchorsArg = region ? `, [${FORECAST_REGION_START_PARAM}, ${FORECAST_REGION_END_PARAM}]` : "";
|
|
58
|
+
const xAxisLabels = xType === "temporal" ? {
|
|
59
|
+
format: axisTimeFormat,
|
|
60
|
+
formatType: "arcTime"
|
|
61
|
+
} : xType === "quantitative" ? {
|
|
62
|
+
format: "compact",
|
|
63
|
+
formatType: "arcNumber"
|
|
64
|
+
} : {};
|
|
65
|
+
const xAxis = axes ? {
|
|
66
|
+
title: null,
|
|
67
|
+
orient: "bottom",
|
|
68
|
+
domain: true,
|
|
69
|
+
ticks: false,
|
|
70
|
+
grid: false,
|
|
71
|
+
values: { expr: FORECAST_X_TICKS_PARAM },
|
|
72
|
+
labelAngle: 0,
|
|
73
|
+
labelAlign: { expr: `arcAxisLabelAlign(datum.value, ${axisArgs}${anchorsArg})` },
|
|
74
|
+
labelFlush: false,
|
|
75
|
+
labelOverlap: false,
|
|
76
|
+
labelLimit: { expr: AXIS_LABEL_LIMIT_EXPR },
|
|
77
|
+
labelPadding: 4,
|
|
78
|
+
...xAxisLabels
|
|
79
|
+
} : null;
|
|
80
|
+
const yAxis = axes ? {
|
|
81
|
+
title: null,
|
|
82
|
+
orient: "left",
|
|
83
|
+
domain: false,
|
|
84
|
+
ticks: false,
|
|
85
|
+
grid: true,
|
|
86
|
+
tickCount: yTicks,
|
|
87
|
+
labelOverlap: true,
|
|
88
|
+
labelPadding: 6,
|
|
89
|
+
format: plotFormat,
|
|
90
|
+
formatType: "arcNumber"
|
|
91
|
+
} : null;
|
|
92
|
+
const xField = discreteX ? {
|
|
93
|
+
field: "x",
|
|
94
|
+
type: "ordinal",
|
|
95
|
+
sort: null,
|
|
96
|
+
axis: xAxis,
|
|
97
|
+
...discreteX ? { scale: {
|
|
98
|
+
domain: { expr: DISCRETE_DOMAIN_EXPR },
|
|
99
|
+
paddingOuter: .05
|
|
100
|
+
} } : {}
|
|
101
|
+
} : {
|
|
102
|
+
field: "x",
|
|
103
|
+
type: xType,
|
|
104
|
+
axis: xAxis,
|
|
105
|
+
scale: {
|
|
106
|
+
nice: false,
|
|
107
|
+
...xType === "quantitative" ? { zero: false } : {}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const yField = {
|
|
111
|
+
field: "y",
|
|
112
|
+
type: "quantitative",
|
|
113
|
+
axis: yAxis,
|
|
114
|
+
scale: {
|
|
115
|
+
zero,
|
|
116
|
+
nice: axes
|
|
117
|
+
},
|
|
118
|
+
stack: null
|
|
119
|
+
};
|
|
120
|
+
const colorField = {
|
|
121
|
+
field: "c",
|
|
122
|
+
type: "nominal",
|
|
123
|
+
scale: null,
|
|
124
|
+
legend: null
|
|
125
|
+
};
|
|
126
|
+
const dashField = {
|
|
127
|
+
field: "k",
|
|
128
|
+
type: "nominal",
|
|
129
|
+
scale: {
|
|
130
|
+
domain: ["actual", "forecast"],
|
|
131
|
+
range: [ACTUAL_DASH, FORECAST_DASH]
|
|
132
|
+
},
|
|
133
|
+
legend: null
|
|
134
|
+
};
|
|
135
|
+
const tooltipChannel = [
|
|
136
|
+
xType === "temporal" ? {
|
|
137
|
+
field: "x",
|
|
138
|
+
type: "temporal",
|
|
139
|
+
title: xTitle,
|
|
140
|
+
format: timeFormat,
|
|
141
|
+
formatType: "arcTime"
|
|
142
|
+
} : xType === "quantitative" ? {
|
|
143
|
+
field: "x",
|
|
144
|
+
type: "quantitative",
|
|
145
|
+
title: xTitle,
|
|
146
|
+
format: "number",
|
|
147
|
+
formatType: "arcNumber"
|
|
148
|
+
} : {
|
|
149
|
+
field: "x",
|
|
150
|
+
type: "nominal",
|
|
151
|
+
title: xTitle
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
field: "k",
|
|
155
|
+
type: "nominal",
|
|
156
|
+
title: kindTitle
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
field: "y",
|
|
160
|
+
type: "quantitative",
|
|
161
|
+
title: yTitle,
|
|
162
|
+
format: plotFormat,
|
|
163
|
+
formatType: "arcNumber"
|
|
164
|
+
}
|
|
165
|
+
];
|
|
166
|
+
const layers = [];
|
|
167
|
+
if (region) layers.push({
|
|
168
|
+
data: { name: FORECAST_REGION_DATASET },
|
|
169
|
+
mark: {
|
|
170
|
+
type: "rect",
|
|
171
|
+
fill: tokens.muted,
|
|
172
|
+
opacity: REGION_OPACITY,
|
|
173
|
+
aria: false
|
|
174
|
+
},
|
|
175
|
+
encoding: {
|
|
176
|
+
x: {
|
|
177
|
+
...xField,
|
|
178
|
+
field: "start",
|
|
179
|
+
axis: xAxis,
|
|
180
|
+
...discreteX ? { bandPosition: 0 } : {}
|
|
181
|
+
},
|
|
182
|
+
x2: {
|
|
183
|
+
field: "end",
|
|
184
|
+
...discreteX ? { bandPosition: 1 } : {}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (interval) layers.push({
|
|
189
|
+
data: { name: FORECAST_INTERVAL_DATASET },
|
|
190
|
+
mark: {
|
|
191
|
+
type: "area",
|
|
192
|
+
line: false,
|
|
193
|
+
interpolate: "linear",
|
|
194
|
+
fillOpacity: INTERVAL_OPACITY,
|
|
195
|
+
aria: false
|
|
196
|
+
},
|
|
197
|
+
encoding: {
|
|
198
|
+
x: xField,
|
|
199
|
+
y: {
|
|
200
|
+
...yField,
|
|
201
|
+
field: "lo"
|
|
202
|
+
},
|
|
203
|
+
y2: { field: "hi" },
|
|
204
|
+
fill: { value: tokens.emphasis }
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
if (mark === "area") layers.push({
|
|
208
|
+
mark: {
|
|
209
|
+
type: "area",
|
|
210
|
+
line: false,
|
|
211
|
+
interpolate: "linear",
|
|
212
|
+
invalid: "break-paths-show-domains",
|
|
213
|
+
aria: false
|
|
214
|
+
},
|
|
215
|
+
encoding: {
|
|
216
|
+
x: xField,
|
|
217
|
+
y: yField,
|
|
218
|
+
fill: colorField,
|
|
219
|
+
fillOpacity: {
|
|
220
|
+
field: "k",
|
|
221
|
+
type: "nominal",
|
|
222
|
+
sort: ["actual", "forecast"],
|
|
223
|
+
scale: {
|
|
224
|
+
domain: ["actual", "forecast"],
|
|
225
|
+
range: [AREA_FILL_OPACITY, 0]
|
|
226
|
+
},
|
|
227
|
+
legend: null
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
layers.push({
|
|
232
|
+
mark: {
|
|
233
|
+
type: "line",
|
|
234
|
+
strokeWidth: 2,
|
|
235
|
+
strokeCap: "round",
|
|
236
|
+
strokeJoin: "round",
|
|
237
|
+
interpolate: "linear",
|
|
238
|
+
invalid: "break-paths-show-domains"
|
|
239
|
+
},
|
|
240
|
+
encoding: {
|
|
241
|
+
x: xField,
|
|
242
|
+
y: yField,
|
|
243
|
+
stroke: colorField,
|
|
244
|
+
strokeDash: dashField
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
if (boundary) layers.push({
|
|
248
|
+
data: { name: FORECAST_BOUNDARY_DATASET },
|
|
249
|
+
mark: {
|
|
250
|
+
type: "rule",
|
|
251
|
+
stroke: tokens.axis,
|
|
252
|
+
strokeWidth: 1,
|
|
253
|
+
strokeDash: [4, 3],
|
|
254
|
+
opacity: .9,
|
|
255
|
+
aria: false
|
|
256
|
+
},
|
|
257
|
+
encoding: { x: {
|
|
258
|
+
...xField,
|
|
259
|
+
axis: xAxis
|
|
260
|
+
} }
|
|
261
|
+
});
|
|
262
|
+
if (reference) layers.push({
|
|
263
|
+
data: { name: FORECAST_REFERENCE_DATASET },
|
|
264
|
+
mark: {
|
|
265
|
+
type: "rule",
|
|
266
|
+
stroke: tokens.reference,
|
|
267
|
+
strokeWidth: 1.5,
|
|
268
|
+
strokeDash: [0, 4],
|
|
269
|
+
strokeCap: "round",
|
|
270
|
+
opacity: .9,
|
|
271
|
+
aria: false
|
|
272
|
+
},
|
|
273
|
+
encoding: {
|
|
274
|
+
y: {
|
|
275
|
+
...yField,
|
|
276
|
+
field: "value"
|
|
277
|
+
},
|
|
278
|
+
...tooltip ? { tooltip: [{
|
|
279
|
+
field: "value",
|
|
280
|
+
type: "quantitative",
|
|
281
|
+
title: options.targetTitle ?? "Target",
|
|
282
|
+
format: plotFormat,
|
|
283
|
+
formatType: "arcNumber"
|
|
284
|
+
}] } : {}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
if (landing) {
|
|
288
|
+
layers.push({
|
|
289
|
+
data: { name: FORECAST_LANDING_DATASET },
|
|
290
|
+
mark: {
|
|
291
|
+
type: "point",
|
|
292
|
+
filled: false,
|
|
293
|
+
size: POINT_SIZE,
|
|
294
|
+
strokeWidth: 2,
|
|
295
|
+
fill: tokens.background,
|
|
296
|
+
aria: false
|
|
297
|
+
},
|
|
298
|
+
encoding: {
|
|
299
|
+
x: xField,
|
|
300
|
+
y: yField,
|
|
301
|
+
stroke: colorField
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
if (landingLabel) layers.push({
|
|
305
|
+
data: { name: FORECAST_LANDING_DATASET },
|
|
306
|
+
mark: {
|
|
307
|
+
type: "text",
|
|
308
|
+
align: "right",
|
|
309
|
+
baseline: "middle",
|
|
310
|
+
dx: LABEL_INSET_PX,
|
|
311
|
+
dy: { expr: `scale('y', datum.y) < height / 2 ? (scale('y', datum.y) >= ${LABEL_DODGE_ROOM_PX} ? -${LABEL_DODGE_PX} : ${LABEL_DODGE_PX}) : (height - scale('y', datum.y) >= ${LABEL_DODGE_ROOM_PX} ? ${LABEL_DODGE_PX} : -${LABEL_DODGE_PX})` },
|
|
312
|
+
fontSize: 11,
|
|
313
|
+
fontWeight: 500,
|
|
314
|
+
limit: { expr: `max(${LABEL_LIMIT_PX}, width / 3)` },
|
|
315
|
+
aria: false
|
|
316
|
+
},
|
|
317
|
+
encoding: {
|
|
318
|
+
x: xField,
|
|
319
|
+
y: yField,
|
|
320
|
+
text: {
|
|
321
|
+
field: "n",
|
|
322
|
+
type: "nominal"
|
|
323
|
+
},
|
|
324
|
+
color: colorField
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (tooltip) {
|
|
329
|
+
layers.push({
|
|
330
|
+
params: [{
|
|
331
|
+
name: FORECAST_HOVER_PARAM,
|
|
332
|
+
select: {
|
|
333
|
+
type: "point",
|
|
334
|
+
on: "pointermove",
|
|
335
|
+
nearest: true,
|
|
336
|
+
clear: "pointerout",
|
|
337
|
+
encodings: ["x"]
|
|
338
|
+
}
|
|
339
|
+
}],
|
|
340
|
+
mark: {
|
|
341
|
+
type: "point",
|
|
342
|
+
filled: true,
|
|
343
|
+
size: POINT_SIZE,
|
|
344
|
+
stroke: tokens.background,
|
|
345
|
+
strokeWidth: 1.5,
|
|
346
|
+
aria: false
|
|
347
|
+
},
|
|
348
|
+
encoding: {
|
|
349
|
+
x: xField,
|
|
350
|
+
y: yField,
|
|
351
|
+
fill: colorField,
|
|
352
|
+
opacity: {
|
|
353
|
+
condition: {
|
|
354
|
+
param: FORECAST_HOVER_PARAM,
|
|
355
|
+
empty: false,
|
|
356
|
+
value: 1
|
|
357
|
+
},
|
|
358
|
+
value: 0
|
|
359
|
+
},
|
|
360
|
+
tooltip: tooltipChannel
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
layers.push({
|
|
364
|
+
data: { name: FORECAST_PERIODS_DATASET },
|
|
365
|
+
mark: {
|
|
366
|
+
type: "rule",
|
|
367
|
+
stroke: tokens.axis,
|
|
368
|
+
strokeWidth: 1,
|
|
369
|
+
aria: false
|
|
370
|
+
},
|
|
371
|
+
encoding: {
|
|
372
|
+
x: xField,
|
|
373
|
+
opacity: {
|
|
374
|
+
condition: {
|
|
375
|
+
param: FORECAST_HOVER_PARAM,
|
|
376
|
+
empty: false,
|
|
377
|
+
value: .6
|
|
378
|
+
},
|
|
379
|
+
value: 0
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const params = [];
|
|
385
|
+
if (region) params.push({
|
|
386
|
+
name: FORECAST_REGION_START_PARAM,
|
|
387
|
+
expr: `length(data('${FORECAST_REGION_DATASET}')) ? data('${FORECAST_REGION_DATASET}')[0].start : null`
|
|
388
|
+
}, {
|
|
389
|
+
name: FORECAST_REGION_END_PARAM,
|
|
390
|
+
expr: `length(data('${FORECAST_REGION_DATASET}')) ? data('${FORECAST_REGION_DATASET}')[0].end : null`
|
|
391
|
+
});
|
|
392
|
+
if (axes) params.push({
|
|
393
|
+
name: FORECAST_X_TICKS_PARAM,
|
|
394
|
+
expr: `arcAxisTicks(${axisArgs}${anchorsArg})`
|
|
395
|
+
});
|
|
396
|
+
return {
|
|
397
|
+
$schema: "https://vega.github.io/schema/vega-lite/v6.json",
|
|
398
|
+
width: 320,
|
|
399
|
+
height,
|
|
400
|
+
autosize: {
|
|
401
|
+
type: "fit",
|
|
402
|
+
contains: "padding"
|
|
403
|
+
},
|
|
404
|
+
padding: PLOT_PADDING,
|
|
405
|
+
background: "transparent",
|
|
406
|
+
data: { name: FORECAST_TABLE_DATASET },
|
|
407
|
+
...params.length > 0 ? { params } : {},
|
|
408
|
+
layer: layers,
|
|
409
|
+
config: arcVegaConfig(tokens, {
|
|
410
|
+
locale: options.locale,
|
|
411
|
+
timeZone: options.timeZone
|
|
412
|
+
})
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
416
|
+
export { FORECAST_BOUNDARY_DATASET, FORECAST_HOVER_PARAM, FORECAST_INTERVAL_DATASET, FORECAST_LANDING_DATASET, FORECAST_PERIODS_DATASET, FORECAST_REFERENCE_DATASET, FORECAST_REGION_DATASET, FORECAST_REGION_END_PARAM, FORECAST_REGION_START_PARAM, FORECAST_TABLE_DATASET, FORECAST_X_TICKS_PARAM, buildForecastPlotSpec };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ArcTimeGranularity } from "../../lib/vega/formatters.js";
|
|
2
|
+
import { ForecastCardLabels, ForecastCardSize, ForecastCardStatus, ForecastDeltaMode, ForecastGoodDirection, ForecastHeadline, ForecastMark, ForecastPoint, ForecastScale } from "./ForecastCard.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/ForecastCard/ForecastCard.d.ts
|
|
8
|
+
interface ForecastCardProps extends Omit<BoxProps, "children" | "padding" | "title"> {
|
|
9
|
+
/** The measure'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
|
+
/** Definition of the measure, shown in a tooltip behind an info control beside the label and exposed to assistive technology as the control's description. */
|
|
14
|
+
hint?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The measured readings, oldest first: `{ x, y }` — `x` a `Date`, ISO string,
|
|
17
|
+
* number or period label; `y` the value or `null` for a gap (drawn as a gap,
|
|
18
|
+
* never as zero). Pass `[]` while loading to reserve the plot's height.
|
|
19
|
+
*/
|
|
20
|
+
actual: readonly ForecastPoint[];
|
|
21
|
+
/**
|
|
22
|
+
* The projection, in the same `x` type and continuing past the last measured
|
|
23
|
+
* period: `{ x, y, lower?, upper? }`. `lower`/`upper` are the prediction
|
|
24
|
+
* interval at that period — pass both or neither; the band opens from the
|
|
25
|
+
* last reading, so the fan starts where the data stops. A projected period
|
|
26
|
+
* that repeats a measured one is dropped: a forecast cannot overwrite a
|
|
27
|
+
* reading. Omit for a card that has history but nothing projected yet — it
|
|
28
|
+
* plots the history and says so.
|
|
29
|
+
*/
|
|
30
|
+
forecast?: readonly ForecastPoint[];
|
|
31
|
+
/**
|
|
32
|
+
* The interval's confidence level as a ratio (`0.9` → `90% interval`), named
|
|
33
|
+
* in the headline caption, the key row, the insight and the description. A
|
|
34
|
+
* band with no level is not a claim a reader can act on, so pass it whenever
|
|
35
|
+
* the transport knows it.
|
|
36
|
+
*/
|
|
37
|
+
level?: number | null;
|
|
38
|
+
/** A goal for the measure. Draws a dotted reference rule and turns on the verdict chip. */
|
|
39
|
+
target?: number | null;
|
|
40
|
+
/**
|
|
41
|
+
* The big number: `landing` (default) is where the forecast ends, with its
|
|
42
|
+
* interval beneath and the projected change in a polarity badge; `latest` is
|
|
43
|
+
* the last measured reading; `change` makes the projected move itself the
|
|
44
|
+
* headline; `none` leaves the plot to carry the panel. Stamped as
|
|
45
|
+
* `data-headline`.
|
|
46
|
+
*/
|
|
47
|
+
headline?: ForecastHeadline;
|
|
48
|
+
/** `percent` (default) is relative change; `absolute` is the difference in the value's units — percentage points for a `percent` format. */
|
|
49
|
+
deltaMode?: ForecastDeltaMode;
|
|
50
|
+
/**
|
|
51
|
+
* Which direction is good. Drives the change badge's polarity AND what
|
|
52
|
+
* "reaching the target" means: `up` (revenue), `down` (churn, cost, latency)
|
|
53
|
+
* or `none` (headcount — the change stays neutral and the card gives no
|
|
54
|
+
* verdict, because reaching is undefined without a direction). Default `up`.
|
|
55
|
+
*/
|
|
56
|
+
goodDirection?: ForecastGoodDirection;
|
|
57
|
+
/** `line` (default, may crop) or `area`, which fills the MEASURED half from zero — the projected half is never filled, because its magnitude is not measured. */
|
|
58
|
+
mark?: ForecastMark;
|
|
59
|
+
/** Y-scale baseline for a `line` plot: `auto` (default) fits the data range and reads for shape; `zero` includes zero. An `area` always includes zero. */
|
|
60
|
+
scale?: ForecastScale;
|
|
61
|
+
/**
|
|
62
|
+
* Number format for the headline, the interval, the axis, the key row, the
|
|
63
|
+
* table and the tooltip — one grammar so nothing rounds two ways:
|
|
64
|
+
* `number[:digits]`, `integer`, `compact[:digits]`, `percent[:digits]`,
|
|
65
|
+
* `currency:CODE[:digits|compact]`. Default `compact`.
|
|
66
|
+
*/
|
|
67
|
+
format?: string;
|
|
68
|
+
/** Custom formatter for values. Takes precedence over `format` everywhere a reading is rendered. */
|
|
69
|
+
formatValue?: (value: number) => string;
|
|
70
|
+
/** Unit shown after the headline in the muted role (`ms`, `req/s`). Prefer `format="currency:…"` for money. */
|
|
71
|
+
unit?: ReactNode;
|
|
72
|
+
/** Plot height in pixels, axes included. Defaults per `size` (120 / 160 / 200), plus 24 when `axes` draws the x labels. */
|
|
73
|
+
plotHeight?: number;
|
|
74
|
+
/** Draw both axes with grid lines and labels. Default `true`; `false` shrinks the plot toward a sparkline — the table still carries every value. */
|
|
75
|
+
axes?: boolean;
|
|
76
|
+
/** Draw the key row under the plot — actual, forecast, interval, target. Default `true`; the tooltip, table and description name every mark regardless. */
|
|
77
|
+
keys?: boolean;
|
|
78
|
+
/** Wash the projected stretch of the axis and anchor the x labels on its bounds. Default `true`. */
|
|
79
|
+
region?: boolean;
|
|
80
|
+
/** Hang the landing's value beside its dot, in a right-hand gutter, so the answer needs no round trip to the axis. Default `false`. */
|
|
81
|
+
labelLanding?: boolean;
|
|
82
|
+
/** Period granularity of temporal `x` values for the axis, tooltips and the table. Inferred from the spacing when omitted. */
|
|
83
|
+
granularity?: ArcTimeGranularity;
|
|
84
|
+
/**
|
|
85
|
+
* The takeaway in a sentence, rendered beside the AI mark under the plot —
|
|
86
|
+
* the insight slot. Wrap values in `<strong>` to set them in bold. Omitted,
|
|
87
|
+
* the card generates one from the projection, its interval and the target;
|
|
88
|
+
* pass `null` or `false` to render none.
|
|
89
|
+
*/
|
|
90
|
+
insight?: ReactNode;
|
|
91
|
+
/** The transport's status. `loading` with readings already present is a refresh: the previous render stays under the veil. Default `ready`. */
|
|
92
|
+
status?: ForecastCardStatus;
|
|
93
|
+
/** The consumer's filter is active — no readings then reads as no-match rather than empty, with a clear-filters action when `onClearFilters` is set. */
|
|
94
|
+
filtered?: boolean;
|
|
95
|
+
/** The data is older than the product's freshness rule (TanStack Query's `isStale`). Shows a badge and stamps `data-stale`. */
|
|
96
|
+
stale?: boolean;
|
|
97
|
+
/** When the data was last true. Rendered as an "as of" line and included in the description. */
|
|
98
|
+
updatedAt?: Date | string | number;
|
|
99
|
+
/**
|
|
100
|
+
* How `updatedAt` is written, as a time-grammar granularity — `day`
|
|
101
|
+
* (default, `Sep 8, 2026`), `datetime`, `time`, `month`, `year` — or a
|
|
102
|
+
* formatter for a custom form. Applies to the as-of line and the description
|
|
103
|
+
* alike, in the card's `locale` and `timeZone`.
|
|
104
|
+
*/
|
|
105
|
+
updatedAtFormat?: ArcTimeGranularity | ((date: Date) => string);
|
|
106
|
+
/** Recovery action for the error state; renders a retry button when set. */
|
|
107
|
+
onRetry?: () => void;
|
|
108
|
+
/** Recovery action for the no-match state; renders a clear-filters button when set. */
|
|
109
|
+
onClearFilters?: () => void;
|
|
110
|
+
/** Density: `sm` for dense grids, `md` (default), `lg` for a hero panel. Scales type, padding and the default plot height. */
|
|
111
|
+
size?: ForecastCardSize;
|
|
112
|
+
/** Heading level of the label for the document outline. Default 3 — panels usually sit under a page's h2. */
|
|
113
|
+
titleLevel?: 2 | 3 | 4 | 5 | 6;
|
|
114
|
+
/** BCP 47 locale for every number and date. Defaults to the runtime's. Thai products on the Gregorian calendar pass `th-TH-u-ca-gregory`. */
|
|
115
|
+
locale?: string;
|
|
116
|
+
/** IANA time zone for dates. Defaults to the runtime's — pass it for deterministic output and single-business-zone reporting. */
|
|
117
|
+
timeZone?: string;
|
|
118
|
+
/** Overrides for every rendered or announced string. */
|
|
119
|
+
labels?: Partial<ForecastCardLabels>;
|
|
120
|
+
/** Replaces the generated accessible description of the whole card (the figure's `aria-describedby` text). */
|
|
121
|
+
description?: string;
|
|
122
|
+
/** Trailing header slot — a ghost `Button` or `Menu` for drill-through, export, or settings. */
|
|
123
|
+
action?: ReactNode;
|
|
124
|
+
/** Open the data popover on first render, anchored to the card. */
|
|
125
|
+
defaultTableOpen?: boolean;
|
|
126
|
+
/** Hide the header info button for the data popover. Only when another surface exposes the same values to keyboard and screen-reader users. */
|
|
127
|
+
hideTableToggle?: boolean;
|
|
128
|
+
/** Called with vega-embed's result once the plot mounts — for `view` access in advanced compositions. */
|
|
129
|
+
onEmbed?: (result: Result) => void;
|
|
130
|
+
/** Escape hatch: transform the plot spec before it renders. Voids the token and accessibility guarantees for whatever it changes. */
|
|
131
|
+
patchSpec?: (spec: TopLevelSpec) => TopLevelSpec;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A forecast panel for dashboards: the measured readings as a solid path, the
|
|
135
|
+
* projection as the same path dashed, its prediction interval as a band opening
|
|
136
|
+
* from the last reading, the boundary between measurement and projection marked
|
|
137
|
+
* three ways, an optional target with the verdict only uncertainty can give
|
|
138
|
+
* (likely / too close to call / unlikely), the landing as the headline, and
|
|
139
|
+
* every loading, empty, error and stale state — layout-stable, with a generated
|
|
140
|
+
* text equivalent and a period-by-period table in an info popover.
|
|
141
|
+
*
|
|
142
|
+
* Where `TrendCard` answers "what shape is this metric in", ForecastCard
|
|
143
|
+
* answers "where is it heading, how sure are we, and does that clear the
|
|
144
|
+
* target".
|
|
145
|
+
*/
|
|
146
|
+
declare const ForecastCard: import("react").ForwardRefExoticComponent<ForecastCardProps & import("react").RefAttributes<HTMLElement>>;
|
|
147
|
+
//#endregion
|
|
148
|
+
export { ForecastCard, ForecastCardProps };
|