@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,554 @@
|
|
|
1
|
+
import { formatDate, formatDateRange, formatDelta, formatNumber } from "../../lib/vega/formatters.js";
|
|
2
|
+
//#region src/components/ForecastCard/ForecastCard.model.ts
|
|
3
|
+
const DEFAULT_FORECAST_LABELS = {
|
|
4
|
+
increase: "Up",
|
|
5
|
+
decrease: "Down",
|
|
6
|
+
unchanged: "No change",
|
|
7
|
+
percentagePoints: " pp",
|
|
8
|
+
noData: "No data",
|
|
9
|
+
noMatch: "No matching data",
|
|
10
|
+
clearFilters: "Clear filters",
|
|
11
|
+
error: "Couldn't load this forecast",
|
|
12
|
+
retry: "Try again",
|
|
13
|
+
insufficient: "Not enough data for a forecast",
|
|
14
|
+
noForecast: "No forecast for this period — measured readings only",
|
|
15
|
+
stale: "Stale",
|
|
16
|
+
asOf: "As of",
|
|
17
|
+
actual: "Actual",
|
|
18
|
+
forecast: "Forecast",
|
|
19
|
+
interval: "Interval",
|
|
20
|
+
target: "Target",
|
|
21
|
+
boundary: "Forecast starts",
|
|
22
|
+
tablePeriod: "Period",
|
|
23
|
+
tableValue: "Value",
|
|
24
|
+
tableKind: "Kind",
|
|
25
|
+
tableInterval: "Interval",
|
|
26
|
+
headlineSentence: ({ name, value, caption }) => `${name}: ${value} (${caption}).`,
|
|
27
|
+
landingCaption: (period) => `Projected for ${period}`,
|
|
28
|
+
latestCaption: (period) => `Actual, ${period}`,
|
|
29
|
+
intervalCaption: (range, level) => level ? `${range} (${level})` : range,
|
|
30
|
+
intervalKey: (level) => level ? `${level} interval` : "Interval",
|
|
31
|
+
levelText: (level) => `${Math.round(level * 100)}%`,
|
|
32
|
+
outlook: {
|
|
33
|
+
likely: "Likely to hit target",
|
|
34
|
+
uncertain: "Too close to call",
|
|
35
|
+
unlikely: "Unlikely to hit target"
|
|
36
|
+
},
|
|
37
|
+
outlookExplanation: ({ outlook, target, landing, range, horizon }) => {
|
|
38
|
+
const head = `Projected ${landing} by ${horizon} against a ${target} target`;
|
|
39
|
+
if (outlook === "uncertain") return `${head}. The target falls inside the ${range} interval, so the forecast does not decide it.`;
|
|
40
|
+
if (outlook === "likely") return range ? `${head}. The whole ${range} interval reaches it.` : `${head}.`;
|
|
41
|
+
return range ? `${head}. No part of the ${range} interval reaches it.` : `${head}.`;
|
|
42
|
+
},
|
|
43
|
+
crossesAt: (period) => `Reaches the target in ${period}`,
|
|
44
|
+
noCrossing: "Does not reach the target inside the horizon",
|
|
45
|
+
showTable: (name) => `Show the data behind ${name}`,
|
|
46
|
+
hideTable: (name) => `Hide the data behind ${name}`,
|
|
47
|
+
about: (name) => `About ${name}`,
|
|
48
|
+
loading: (name) => `Loading ${name}`,
|
|
49
|
+
tableCaption: (name) => `Data behind the ${name} forecast`,
|
|
50
|
+
changeGroup: (name) => `${name} projected change`,
|
|
51
|
+
deltaExplanation: ({ fromLabel, fromText, toLabel, toText, changeText, secondaryText, renderedMode, direction, rangeText }) => {
|
|
52
|
+
const rows = [
|
|
53
|
+
[fromLabel, fromText],
|
|
54
|
+
[toLabel, toText],
|
|
55
|
+
[direction === "up" ? "Increase" : direction === "down" ? "Decrease" : "No change", changeText]
|
|
56
|
+
];
|
|
57
|
+
if (secondaryText) rows.push([renderedMode === "percent" ? "Change" : "Change %", secondaryText]);
|
|
58
|
+
if (rangeText) rows.push(["Interval", rangeText]);
|
|
59
|
+
return rows;
|
|
60
|
+
},
|
|
61
|
+
projectionSentence: ({ word, primary, secondary, from, to, horizon }) => primary ? `Projected ${word.toLowerCase()} ${primary}${secondary ? ` (${secondary})` : ""} from ${from} to ${to} by ${horizon}.` : `Projected to hold at ${from} through ${horizon}.`,
|
|
62
|
+
intervalSentence: ({ low, high, level, horizon }) => `Between ${low} and ${high} by ${horizon}${level ? `, ${level} interval` : ""}.`,
|
|
63
|
+
historySentence: ({ count, range, latest }) => `Based on ${count} measured periods, ${range}, latest ${latest}.`,
|
|
64
|
+
partialIntervalSentence: ({ with: withInterval, total }) => `${withInterval} of ${total} projected periods carry an interval.`,
|
|
65
|
+
markNames: {
|
|
66
|
+
line: "Line",
|
|
67
|
+
area: "Area"
|
|
68
|
+
},
|
|
69
|
+
chartDescription: ({ mark, name, actualCount, forecastCount, range, min, max, boundary, interval, target }) => `${mark} chart of ${name}: ${actualCount} measured periods followed by ${forecastCount} projected, ${range}, ranging ${min} to ${max}.` + (boundary ? ` The forecast starts after ${boundary}.` : "") + (interval ? ` ${interval}.` : "") + (target ? ` Reference line at the ${target} target.` : ""),
|
|
70
|
+
insightMark: "Insight",
|
|
71
|
+
insight: ({ name, direction, from, to, change, horizon, low, high, level, outlook, target, crossesAt }) => {
|
|
72
|
+
const segments = direction === "flat" ? [
|
|
73
|
+
`${name} is projected to hold at `,
|
|
74
|
+
{ value: to },
|
|
75
|
+
` through ${horizon}.`
|
|
76
|
+
] : [
|
|
77
|
+
`${name} is projected to ${direction === "up" ? "reach" : "fall to"} `,
|
|
78
|
+
{ value: to },
|
|
79
|
+
` by ${horizon}, ${direction === "up" ? "up" : "down"} `,
|
|
80
|
+
{ value: change },
|
|
81
|
+
` from `,
|
|
82
|
+
{ value: from },
|
|
83
|
+
"."
|
|
84
|
+
];
|
|
85
|
+
if (low && high) segments.push(` The ${level ? `${level} ` : ""}interval runs `, { value: low }, " to ", { value: high }, ".");
|
|
86
|
+
if (target && outlook !== "none") {
|
|
87
|
+
if (outlook === "uncertain") segments.push(` The ${target} target sits inside that range, so it is too close to call.`);
|
|
88
|
+
else if (crossesAt) segments.push(` It reaches the ${target} target in ${crossesAt}.`);
|
|
89
|
+
else if (outlook === "likely") segments.push(` That clears the ${target} target.`);
|
|
90
|
+
else segments.push(` That misses the ${target} target.`);
|
|
91
|
+
}
|
|
92
|
+
return segments;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
function resolveForecastLabels(overrides) {
|
|
96
|
+
return overrides ? {
|
|
97
|
+
...DEFAULT_FORECAST_LABELS,
|
|
98
|
+
...overrides
|
|
99
|
+
} : DEFAULT_FORECAST_LABELS;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The state machine. Error wins; loading over existing readings is a refresh
|
|
103
|
+
* (the previous render stays under the veil); no readings is `no-match` when
|
|
104
|
+
* the consumer's filter caused it and `empty` otherwise.
|
|
105
|
+
*/
|
|
106
|
+
function deriveForecastState({ status, hasValue, hasData, filtered }) {
|
|
107
|
+
if (status === "error") return "error";
|
|
108
|
+
if (status === "loading") return hasData ? "refreshing" : "loading";
|
|
109
|
+
if (hasValue) return "ready";
|
|
110
|
+
return filtered ? "no-match" : "empty";
|
|
111
|
+
}
|
|
112
|
+
const isFinite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
113
|
+
const DAY_MS = 864e5;
|
|
114
|
+
const ISO_DATE = /^\d{4}-\d{2}(-\d{2})?([T ].*)?$/;
|
|
115
|
+
function inferXType(points) {
|
|
116
|
+
const sample = points.find((point) => point.x != null);
|
|
117
|
+
if (!sample) return "ordinal";
|
|
118
|
+
const { x } = sample;
|
|
119
|
+
if (x instanceof Date) return "temporal";
|
|
120
|
+
if (typeof x === "number") return "quantitative";
|
|
121
|
+
return ISO_DATE.test(x) ? "temporal" : "ordinal";
|
|
122
|
+
}
|
|
123
|
+
function inferGranularity(periods) {
|
|
124
|
+
const xs = periods.filter((x) => typeof x === "number");
|
|
125
|
+
if (xs.length < 2) return "day";
|
|
126
|
+
const gaps = [];
|
|
127
|
+
for (let index = 1; index < xs.length; index += 1) gaps.push(Math.abs(xs[index] - xs[index - 1]));
|
|
128
|
+
gaps.sort((a, b) => a - b);
|
|
129
|
+
const median = gaps[Math.floor(gaps.length / 2)];
|
|
130
|
+
if (median < DAY_MS) return "time";
|
|
131
|
+
if (median >= 360 * DAY_MS) return "year";
|
|
132
|
+
if (median >= 27 * DAY_MS) return "month";
|
|
133
|
+
return "day";
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The granularity an x axis labels its ticks at: the tooltip and the table keep
|
|
137
|
+
* the full form (`Sep 8, 2026`); an axis has room for `Sep 8`. Duplicated per
|
|
138
|
+
* component rather than shared, so a card's text grammar is readable in one
|
|
139
|
+
* file — the same reason each card owns its own state machine.
|
|
140
|
+
*/
|
|
141
|
+
function forecastAxisGranularity(granularity) {
|
|
142
|
+
switch (granularity) {
|
|
143
|
+
case "day": return "day-short";
|
|
144
|
+
case "month": return "month-short";
|
|
145
|
+
case "datetime": return "time";
|
|
146
|
+
default: return granularity;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function normalizeX(x, xType) {
|
|
150
|
+
if (xType === "temporal") {
|
|
151
|
+
const time = x instanceof Date ? x.getTime() : new Date(x).getTime();
|
|
152
|
+
return Number.isNaN(time) ? null : time;
|
|
153
|
+
}
|
|
154
|
+
if (xType === "quantitative") return isFinite(x) ? x : null;
|
|
155
|
+
return String(x);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Lays the measured and the projected readings onto one period domain.
|
|
159
|
+
*
|
|
160
|
+
* The two halves stay distinguishable at every level: each period carries its
|
|
161
|
+
* `kind`, the projected half is the only one with interval bounds, and the plot
|
|
162
|
+
* rows repeat the last measured reading as the projected path's ANCHOR so the
|
|
163
|
+
* solid and dashed paths meet — the interval opens from that anchor at zero
|
|
164
|
+
* width, which is what makes a fan read as growing uncertainty rather than as a
|
|
165
|
+
* band that appears from nowhere. `readings` carries the anchor once, so the
|
|
166
|
+
* table and the description never double-count it.
|
|
167
|
+
*
|
|
168
|
+
* A projected period that repeats a measured one is dropped: a forecast cannot
|
|
169
|
+
* overwrite a reading. Returns `null` when there is nothing at all to show.
|
|
170
|
+
*/
|
|
171
|
+
function normalizeForecast(actual, forecast, { granularity, name, color }) {
|
|
172
|
+
const actualPoints = actual ?? [];
|
|
173
|
+
const forecastPoints = forecast ?? [];
|
|
174
|
+
if (actualPoints.length === 0 && forecastPoints.length === 0) return null;
|
|
175
|
+
const xType = inferXType([...actualPoints, ...forecastPoints]);
|
|
176
|
+
const measured = /* @__PURE__ */ new Map();
|
|
177
|
+
for (const point of actualPoints) {
|
|
178
|
+
const x = normalizeX(point.x, xType);
|
|
179
|
+
if (x == null) continue;
|
|
180
|
+
measured.set(String(x), {
|
|
181
|
+
x,
|
|
182
|
+
y: isFinite(point.y) ? point.y : null,
|
|
183
|
+
lower: null,
|
|
184
|
+
upper: null,
|
|
185
|
+
kind: "actual"
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const projected = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const point of forecastPoints) {
|
|
190
|
+
const x = normalizeX(point.x, xType);
|
|
191
|
+
if (x == null) continue;
|
|
192
|
+
if (measured.has(String(x))) continue;
|
|
193
|
+
const lower = isFinite(point.lower) ? point.lower : null;
|
|
194
|
+
const upper = isFinite(point.upper) ? point.upper : null;
|
|
195
|
+
const hasInterval = lower != null && upper != null;
|
|
196
|
+
projected.set(String(x), {
|
|
197
|
+
x,
|
|
198
|
+
y: isFinite(point.y) ? point.y : null,
|
|
199
|
+
lower: hasInterval ? Math.min(lower, upper) : null,
|
|
200
|
+
upper: hasInterval ? Math.max(lower, upper) : null,
|
|
201
|
+
kind: "forecast"
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const sortReadings = (entries) => xType === "ordinal" ? entries : [...entries].sort((a, b) => a.x - b.x);
|
|
205
|
+
const measuredReadings = sortReadings([...measured.values()]);
|
|
206
|
+
const projectedReadings = sortReadings([...projected.values()]);
|
|
207
|
+
const readings = [...measuredReadings, ...projectedReadings];
|
|
208
|
+
const periods = readings.map((reading) => reading.x);
|
|
209
|
+
const finiteMeasured = measuredReadings.filter((reading) => reading.y != null);
|
|
210
|
+
const finiteProjected = projectedReadings.filter((reading) => reading.y != null);
|
|
211
|
+
const lastMeasured = finiteMeasured[finiteMeasured.length - 1] ?? null;
|
|
212
|
+
const lastProjected = finiteProjected[finiteProjected.length - 1] ?? null;
|
|
213
|
+
const rows = readings.map((reading) => ({
|
|
214
|
+
x: reading.x,
|
|
215
|
+
y: reading.y,
|
|
216
|
+
lo: reading.lower,
|
|
217
|
+
hi: reading.upper,
|
|
218
|
+
k: reading.kind,
|
|
219
|
+
c: color,
|
|
220
|
+
n: name
|
|
221
|
+
}));
|
|
222
|
+
const anchor = lastMeasured && finiteProjected.length > 0 ? lastMeasured : null;
|
|
223
|
+
if (anchor) {
|
|
224
|
+
const at = rows.findIndex((row) => String(row.x) === String(anchor.x) && row.k === "actual");
|
|
225
|
+
const anchorRow = {
|
|
226
|
+
x: anchor.x,
|
|
227
|
+
y: anchor.y,
|
|
228
|
+
lo: anchor.y,
|
|
229
|
+
hi: anchor.y,
|
|
230
|
+
k: "forecast",
|
|
231
|
+
c: color,
|
|
232
|
+
n: name
|
|
233
|
+
};
|
|
234
|
+
rows.splice(at + 1, 0, anchorRow);
|
|
235
|
+
}
|
|
236
|
+
const interval = [];
|
|
237
|
+
if (anchor) interval.push({
|
|
238
|
+
x: anchor.x,
|
|
239
|
+
lo: anchor.y,
|
|
240
|
+
hi: anchor.y
|
|
241
|
+
});
|
|
242
|
+
for (const reading of projectedReadings) {
|
|
243
|
+
if (reading.lower == null || reading.upper == null) continue;
|
|
244
|
+
interval.push({
|
|
245
|
+
x: reading.x,
|
|
246
|
+
lo: reading.lower,
|
|
247
|
+
hi: reading.upper
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const extent = [];
|
|
251
|
+
for (const reading of readings) {
|
|
252
|
+
if (reading.y != null) extent.push(reading.y);
|
|
253
|
+
if (reading.lower != null) extent.push(reading.lower);
|
|
254
|
+
if (reading.upper != null) extent.push(reading.upper);
|
|
255
|
+
}
|
|
256
|
+
const moment = (reading) => reading && reading.y != null ? {
|
|
257
|
+
x: reading.x,
|
|
258
|
+
y: reading.y,
|
|
259
|
+
lower: reading.lower,
|
|
260
|
+
upper: reading.upper
|
|
261
|
+
} : null;
|
|
262
|
+
return {
|
|
263
|
+
xType,
|
|
264
|
+
granularity: granularity ?? (xType === "temporal" ? inferGranularity(periods) : "day"),
|
|
265
|
+
periods,
|
|
266
|
+
readings,
|
|
267
|
+
rows,
|
|
268
|
+
interval,
|
|
269
|
+
boundary: lastMeasured?.x ?? null,
|
|
270
|
+
latest: moment(lastMeasured),
|
|
271
|
+
landing: moment(lastProjected),
|
|
272
|
+
actualCount: finiteMeasured.length,
|
|
273
|
+
forecastCount: finiteProjected.length,
|
|
274
|
+
intervalCount: projectedReadings.filter((reading) => reading.lower != null && reading.upper != null).length,
|
|
275
|
+
min: extent.length > 0 ? Math.min(...extent) : null,
|
|
276
|
+
max: extent.length > 0 ? Math.max(...extent) : null,
|
|
277
|
+
hasNegative: extent.some((value) => value < 0)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** The periods the crosshair reads — one row each, so it lands on the period rather than on a mark. */
|
|
281
|
+
function buildForecastPeriodRows(summary) {
|
|
282
|
+
return summary.periods.map((x) => ({ x }));
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The projected stretch as one washed rect, from the last measured period to
|
|
286
|
+
* the horizon — the ground under everything the card did not measure. Empty
|
|
287
|
+
* when nothing is projected, so a card with history alone draws no wash.
|
|
288
|
+
*/
|
|
289
|
+
function buildForecastRegionRows(summary) {
|
|
290
|
+
const projected = summary.readings.filter((reading) => reading.kind === "forecast");
|
|
291
|
+
if (projected.length === 0) return [];
|
|
292
|
+
return [{
|
|
293
|
+
start: summary.boundary ?? projected[0].x,
|
|
294
|
+
end: projected[projected.length - 1].x
|
|
295
|
+
}];
|
|
296
|
+
}
|
|
297
|
+
/** Zero or one row — the rule at the last measured period. */
|
|
298
|
+
function buildForecastBoundaryRows(summary) {
|
|
299
|
+
const hasProjection = summary.readings.some((reading) => reading.kind === "forecast");
|
|
300
|
+
return summary.boundary != null && hasProjection ? [{ x: summary.boundary }] : [];
|
|
301
|
+
}
|
|
302
|
+
/** Zero or one row — where the landing label sits. */
|
|
303
|
+
function buildForecastLandingRows(summary, text) {
|
|
304
|
+
if (!summary.landing) return [];
|
|
305
|
+
return [{
|
|
306
|
+
x: summary.landing.x,
|
|
307
|
+
y: summary.landing.y,
|
|
308
|
+
n: text,
|
|
309
|
+
c: summary.rows[0]?.c ?? ""
|
|
310
|
+
}];
|
|
311
|
+
}
|
|
312
|
+
/** Whether a value reaches the target, given which direction is good. `null` when reaching is undefined. */
|
|
313
|
+
function reachesTarget(value, target, goodDirection) {
|
|
314
|
+
if (goodDirection === "none") return null;
|
|
315
|
+
return goodDirection === "down" ? value <= target : value >= target;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* The verdict the interval supports, and nothing more: `likely` when the WHOLE
|
|
319
|
+
* interval reaches the target, `unlikely` when none of it does, `uncertain`
|
|
320
|
+
* when the target falls inside it — the honest answer a point estimate cannot
|
|
321
|
+
* give. Without an interval the point estimate decides between `likely` and
|
|
322
|
+
* `unlikely` (the card says so in its explanation); without a target, a landing
|
|
323
|
+
* or a direction, there is no verdict at all.
|
|
324
|
+
*/
|
|
325
|
+
function deriveForecastOutlook({ landing, target, goodDirection }) {
|
|
326
|
+
if (!landing || target == null || !isFinite(target)) return "none";
|
|
327
|
+
const point = reachesTarget(landing.y, target, goodDirection);
|
|
328
|
+
if (point == null) return "none";
|
|
329
|
+
if (landing.lower == null || landing.upper == null) return point ? "likely" : "unlikely";
|
|
330
|
+
const lowReaches = reachesTarget(landing.lower, target, goodDirection);
|
|
331
|
+
if (lowReaches === reachesTarget(landing.upper, target, goodDirection)) return lowReaches ? "likely" : "unlikely";
|
|
332
|
+
return "uncertain";
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* The first projected period whose reading reaches the target — the "when" a
|
|
336
|
+
* forecast is really asked for. `null` when the last measured reading already
|
|
337
|
+
* reaches it (there is nothing to wait for), when the path never gets there, or
|
|
338
|
+
* when reaching is undefined.
|
|
339
|
+
*/
|
|
340
|
+
function deriveForecastCrossing(summary, target, goodDirection) {
|
|
341
|
+
if (!summary || target == null || !isFinite(target) || goodDirection === "none") return null;
|
|
342
|
+
if (summary.latest && reachesTarget(summary.latest.y, target, goodDirection)) return null;
|
|
343
|
+
for (const reading of summary.readings) {
|
|
344
|
+
if (reading.kind !== "forecast" || reading.y == null) continue;
|
|
345
|
+
if (reachesTarget(reading.y, target, goodDirection)) return {
|
|
346
|
+
x: reading.x,
|
|
347
|
+
y: reading.y,
|
|
348
|
+
lower: reading.lower,
|
|
349
|
+
upper: reading.upper
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
/** A change between two readings, with polarity resolved through `goodDirection`. */
|
|
355
|
+
function deriveForecastDelta(value, previous, { deltaMode, goodDirection, format, locale, percentagePoints }) {
|
|
356
|
+
if (!isFinite(value) || !isFinite(previous)) return null;
|
|
357
|
+
const delta = formatDelta(value, previous, {
|
|
358
|
+
mode: deltaMode,
|
|
359
|
+
format,
|
|
360
|
+
locale,
|
|
361
|
+
pointsSuffix: percentagePoints
|
|
362
|
+
});
|
|
363
|
+
const other = formatDelta(value, previous, {
|
|
364
|
+
mode: delta.renderedMode === "percent" ? "absolute" : "percent",
|
|
365
|
+
format,
|
|
366
|
+
locale,
|
|
367
|
+
pointsSuffix: percentagePoints
|
|
368
|
+
});
|
|
369
|
+
let polarity = "neutral";
|
|
370
|
+
if (delta.direction !== "flat" && goodDirection !== "none") polarity = delta.direction === "up" === (goodDirection === "up") ? "positive" : "negative";
|
|
371
|
+
return {
|
|
372
|
+
direction: delta.direction,
|
|
373
|
+
polarity,
|
|
374
|
+
text: delta.text,
|
|
375
|
+
renderedMode: delta.renderedMode,
|
|
376
|
+
secondaryText: other.renderedMode === delta.renderedMode ? null : other.text
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The move from the last measured reading to the landing. `null` unless the
|
|
381
|
+
* card has both — a forecast with no history to leave from, or a history with
|
|
382
|
+
* nothing projected, has no projected change to state.
|
|
383
|
+
*/
|
|
384
|
+
function deriveForecastProjection(summary, { formatters, ...deltaOptions }) {
|
|
385
|
+
if (!summary?.latest || !summary.landing) return null;
|
|
386
|
+
const delta = deriveForecastDelta(summary.landing.y, summary.latest.y, deltaOptions);
|
|
387
|
+
if (!delta) return null;
|
|
388
|
+
return {
|
|
389
|
+
from: summary.latest,
|
|
390
|
+
to: summary.landing,
|
|
391
|
+
rangeText: formatters.formatXRange(summary.latest.x, summary.landing.x),
|
|
392
|
+
delta
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** The formatters every text surface shares — headline, badge, table, tooltip, descriptions. */
|
|
396
|
+
function createForecastFormatters({ format, locale, timeZone, formatValue, xType, granularity }) {
|
|
397
|
+
const formatX = (x) => {
|
|
398
|
+
if (xType === "temporal" && typeof x === "number") return formatDate(x, {
|
|
399
|
+
granularity,
|
|
400
|
+
locale,
|
|
401
|
+
timeZone
|
|
402
|
+
});
|
|
403
|
+
if (xType === "quantitative" && typeof x === "number") return formatNumber(x, "number", locale);
|
|
404
|
+
return String(x);
|
|
405
|
+
};
|
|
406
|
+
const value = (input) => isFinite(input) ? formatValue ? formatValue(input) : formatNumber(input, format, locale) : "—";
|
|
407
|
+
return {
|
|
408
|
+
formatValue: value,
|
|
409
|
+
formatX,
|
|
410
|
+
formatXRange: (start, end) => xType === "temporal" && typeof start === "number" && typeof end === "number" ? formatDateRange(start, end, {
|
|
411
|
+
granularity,
|
|
412
|
+
locale,
|
|
413
|
+
timeZone
|
|
414
|
+
}) : `${formatX(start)} – ${formatX(end)}`,
|
|
415
|
+
formatInterval: (lower, upper) => isFinite(lower) && isFinite(upper) ? `${value(lower)} – ${value(upper)}` : null
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const stripSign = (text) => text.replace(/^[+\-−]/, "");
|
|
419
|
+
/**
|
|
420
|
+
* The hover chip's contents for one period — pure, so the reading is
|
|
421
|
+
* unit-tested rather than only reachable through a pointer event (Vega's own
|
|
422
|
+
* hit-testing needs a visible document, which a hidden frame does not give).
|
|
423
|
+
* Returns `null` for a period the card does not have.
|
|
424
|
+
*/
|
|
425
|
+
function buildForecastTooltip({ summary, x, target, formatters, labels }) {
|
|
426
|
+
if (x == null || !summary) return null;
|
|
427
|
+
const reading = summary.readings.find((entry) => String(entry.x) === String(x));
|
|
428
|
+
if (!reading) return null;
|
|
429
|
+
return {
|
|
430
|
+
periodText: formatters.formatX(reading.x),
|
|
431
|
+
kind: reading.kind,
|
|
432
|
+
kindText: reading.kind === "actual" ? labels.actual : labels.forecast,
|
|
433
|
+
valueText: formatters.formatValue(reading.y),
|
|
434
|
+
intervalText: formatters.formatInterval(reading.lower, reading.upper),
|
|
435
|
+
targetText: target != null ? formatters.formatValue(target) : null
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* The generated insight — what the card shows beside the AI mark when the
|
|
440
|
+
* consumer passes no `insight`: where the measure lands, the move that takes it
|
|
441
|
+
* there, the interval around it, and what that means for the target. Values
|
|
442
|
+
* come back as `{ value }` segments so the card can set them in bold.
|
|
443
|
+
*/
|
|
444
|
+
function buildForecastInsight({ name, summary, projection, outlook, crossing, target, level, formatters, labels }) {
|
|
445
|
+
const landing = summary.landing;
|
|
446
|
+
if (!landing || !projection) return null;
|
|
447
|
+
return labels.insight({
|
|
448
|
+
name,
|
|
449
|
+
direction: projection.delta.direction,
|
|
450
|
+
from: formatters.formatValue(projection.from.y),
|
|
451
|
+
to: formatters.formatValue(landing.y),
|
|
452
|
+
change: stripSign(projection.delta.text),
|
|
453
|
+
horizon: formatters.formatX(landing.x),
|
|
454
|
+
low: landing.lower != null ? formatters.formatValue(landing.lower) : null,
|
|
455
|
+
high: landing.upper != null ? formatters.formatValue(landing.upper) : null,
|
|
456
|
+
level: level != null ? labels.levelText(level) : null,
|
|
457
|
+
outlook,
|
|
458
|
+
target: target != null ? formatters.formatValue(target) : null,
|
|
459
|
+
crossesAt: crossing ? formatters.formatX(crossing.x) : null
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* The card's text equivalent (Amy Cesal's alt-text formula: what it is, what it
|
|
464
|
+
* shows, why it is here, when it was true) — the figure's accessible
|
|
465
|
+
* description, and the summary a screen reader gets before the parts. The
|
|
466
|
+
* landing, its interval, the measured span behind it and the verdict are all
|
|
467
|
+
* here, so nothing the plot says lives only in the marks.
|
|
468
|
+
*/
|
|
469
|
+
function buildForecastDescription({ name, state, headline, summary, projection, outlook, crossing, target, level, formatters, asOfText, labels }) {
|
|
470
|
+
if (state === "loading") return `${labels.loading(name)}.`;
|
|
471
|
+
if (state === "error") return `${name}: ${labels.error}.`;
|
|
472
|
+
if (state === "empty") return `${name}: ${labels.noData}.`;
|
|
473
|
+
if (state === "no-match") return `${name}: ${labels.noMatch}.`;
|
|
474
|
+
const sentences = [];
|
|
475
|
+
const landing = summary?.landing ?? null;
|
|
476
|
+
const latest = summary?.latest ?? null;
|
|
477
|
+
const levelText = level != null ? labels.levelText(level) : null;
|
|
478
|
+
if (headline === "landing" && landing) sentences.push(labels.headlineSentence({
|
|
479
|
+
name,
|
|
480
|
+
value: formatters.formatValue(landing.y),
|
|
481
|
+
caption: labels.landingCaption(formatters.formatX(landing.x))
|
|
482
|
+
}));
|
|
483
|
+
else if (headline === "latest" && latest) sentences.push(labels.headlineSentence({
|
|
484
|
+
name,
|
|
485
|
+
value: formatters.formatValue(latest.y),
|
|
486
|
+
caption: labels.latestCaption(formatters.formatX(latest.x))
|
|
487
|
+
}));
|
|
488
|
+
else if (headline === "change" && projection) sentences.push(`${name}: ${projection.delta.text} projected over ${projection.rangeText}.`);
|
|
489
|
+
else sentences.push(`${name}.`);
|
|
490
|
+
if (projection) {
|
|
491
|
+
const flat = projection.delta.direction === "flat";
|
|
492
|
+
const word = projection.delta.direction === "up" ? labels.increase : projection.delta.direction === "down" ? labels.decrease : labels.unchanged;
|
|
493
|
+
sentences.push(labels.projectionSentence({
|
|
494
|
+
word,
|
|
495
|
+
primary: flat ? "" : stripSign(projection.delta.text),
|
|
496
|
+
secondary: flat || !projection.delta.secondaryText ? null : stripSign(projection.delta.secondaryText),
|
|
497
|
+
from: formatters.formatValue(projection.from.y),
|
|
498
|
+
to: formatters.formatValue(projection.to.y),
|
|
499
|
+
horizon: formatters.formatX(projection.to.x)
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
if (landing?.lower != null && landing.upper != null) sentences.push(labels.intervalSentence({
|
|
503
|
+
low: formatters.formatValue(landing.lower),
|
|
504
|
+
high: formatters.formatValue(landing.upper),
|
|
505
|
+
level: levelText,
|
|
506
|
+
horizon: formatters.formatX(landing.x)
|
|
507
|
+
}));
|
|
508
|
+
if (summary && summary.actualCount > 0) {
|
|
509
|
+
const measured = summary.readings.filter((reading) => reading.kind === "actual");
|
|
510
|
+
const first = measured[0];
|
|
511
|
+
const last = measured[measured.length - 1];
|
|
512
|
+
sentences.push(labels.historySentence({
|
|
513
|
+
count: summary.actualCount,
|
|
514
|
+
range: first && last ? formatters.formatXRange(first.x, last.x) : "—",
|
|
515
|
+
latest: formatters.formatValue(latest?.y)
|
|
516
|
+
}));
|
|
517
|
+
}
|
|
518
|
+
if (summary && summary.forecastCount > 0 && summary.intervalCount > 0 && summary.intervalCount < summary.forecastCount) sentences.push(labels.partialIntervalSentence({
|
|
519
|
+
with: summary.intervalCount,
|
|
520
|
+
total: summary.forecastCount
|
|
521
|
+
}));
|
|
522
|
+
if (target != null) {
|
|
523
|
+
sentences.push(`${labels.target} ${formatters.formatValue(target)}.`);
|
|
524
|
+
if (outlook !== "none") sentences.push(`${labels.outlook[outlook]}.` + (crossing ? ` ${labels.crossesAt(formatters.formatX(crossing.x))}.` : outlook === "unlikely" ? ` ${labels.noCrossing}.` : ""));
|
|
525
|
+
}
|
|
526
|
+
if (asOfText) sentences.push(`${labels.asOf} ${asOfText}.`);
|
|
527
|
+
return sentences.join(" ");
|
|
528
|
+
}
|
|
529
|
+
/** The plot's own `description` — the `aria-label` Vega stamps on the `graphics-document`. */
|
|
530
|
+
function buildForecastChartDescription({ name, mark, summary, level, formatters, targetText, labels }) {
|
|
531
|
+
const first = summary.periods[0];
|
|
532
|
+
const last = summary.periods[summary.periods.length - 1];
|
|
533
|
+
const landing = summary.landing;
|
|
534
|
+
const intervalText = summary.intervalCount > 0 && landing?.lower != null && landing.upper != null ? labels.intervalSentence({
|
|
535
|
+
low: formatters.formatValue(landing.lower),
|
|
536
|
+
high: formatters.formatValue(landing.upper),
|
|
537
|
+
level: level != null ? labels.levelText(level) : null,
|
|
538
|
+
horizon: formatters.formatX(landing.x)
|
|
539
|
+
}).replace(/\.$/, "") : null;
|
|
540
|
+
return labels.chartDescription({
|
|
541
|
+
mark: labels.markNames[mark],
|
|
542
|
+
name,
|
|
543
|
+
actualCount: summary.actualCount,
|
|
544
|
+
forecastCount: summary.forecastCount,
|
|
545
|
+
range: first != null && last != null ? formatters.formatXRange(first, last) : "—",
|
|
546
|
+
min: formatters.formatValue(summary.min),
|
|
547
|
+
max: formatters.formatValue(summary.max),
|
|
548
|
+
boundary: summary.boundary != null && summary.forecastCount > 0 ? formatters.formatX(summary.boundary) : null,
|
|
549
|
+
interval: intervalText,
|
|
550
|
+
target: targetText ?? null
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
//#endregion
|
|
554
|
+
export { DEFAULT_FORECAST_LABELS, buildForecastBoundaryRows, buildForecastChartDescription, buildForecastDescription, buildForecastInsight, buildForecastLandingRows, buildForecastPeriodRows, buildForecastRegionRows, buildForecastTooltip, createForecastFormatters, deriveForecastCrossing, deriveForecastDelta, deriveForecastOutlook, deriveForecastProjection, deriveForecastState, forecastAxisGranularity, normalizeForecast, reachesTarget, resolveForecastLabels };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BuildForecastDescriptionInput, BuildForecastInsightInput, DEFAULT_FORECAST_LABELS, DeriveForecastDeltaOptions, DeriveForecastOutlookInput, DeriveForecastProjectionOptions, DeriveForecastStateInput, ForecastCardLabels, ForecastCardSize, ForecastCardState, ForecastCardStatus, ForecastDelta, ForecastDeltaExplanation, ForecastDeltaMode, ForecastGoodDirection, ForecastHeadline, ForecastInsightSegment, ForecastKind, ForecastMark, ForecastMoment, ForecastOutlook, ForecastPeriodRow, ForecastPoint, ForecastPolarity, ForecastProjection, ForecastReading, ForecastRegionRow, ForecastRow, ForecastScale, ForecastSummary, ForecastTextFormatters, ForecastTooltipModel, ForecastXType, NormalizeForecastOptions, buildForecastBoundaryRows, buildForecastChartDescription, buildForecastDescription, buildForecastInsight, buildForecastLandingRows, buildForecastPeriodRows, buildForecastRegionRows, buildForecastTooltip, createForecastFormatters, deriveForecastCrossing, deriveForecastDelta, deriveForecastOutlook, deriveForecastProjection, deriveForecastState, forecastAxisGranularity, normalizeForecast, reachesTarget, resolveForecastLabels } from "./ForecastCard.model.js";
|
|
2
|
+
import { ForecastCard, ForecastCardProps } from "./ForecastCard.js";
|
|
3
|
+
import { 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, ForecastPlotSpecOptions, buildForecastPlotSpec } from "./ForecastCard.chart.js";
|
|
4
|
+
export { type BuildForecastDescriptionInput, type BuildForecastInsightInput, DEFAULT_FORECAST_LABELS, type DeriveForecastDeltaOptions, type DeriveForecastOutlookInput, type DeriveForecastProjectionOptions, type DeriveForecastStateInput, 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, ForecastCard, type ForecastCardLabels, type ForecastCardProps, type ForecastCardSize, type ForecastCardState, type ForecastCardStatus, type ForecastDelta, type ForecastDeltaExplanation, type ForecastDeltaMode, type ForecastGoodDirection, type ForecastHeadline, type ForecastInsightSegment, type ForecastKind, type ForecastMark, type ForecastMoment, type ForecastOutlook, type ForecastPeriodRow, type ForecastPlotSpecOptions, type ForecastPoint, type ForecastPolarity, type ForecastProjection, type ForecastReading, type ForecastRegionRow, type ForecastRow, type ForecastScale, type ForecastSummary, type ForecastTextFormatters, type ForecastTooltipModel, type ForecastXType, type NormalizeForecastOptions, buildForecastBoundaryRows, buildForecastChartDescription, buildForecastDescription, buildForecastInsight, buildForecastLandingRows, buildForecastPeriodRows, buildForecastPlotSpec, buildForecastRegionRows, buildForecastTooltip, createForecastFormatters, deriveForecastCrossing, deriveForecastDelta, deriveForecastOutlook, deriveForecastProjection, deriveForecastState, forecastAxisGranularity, normalizeForecast, reachesTarget, resolveForecastLabels };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { 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 } from "./ForecastCard.chart.js";
|
|
2
|
+
import { DEFAULT_FORECAST_LABELS, buildForecastBoundaryRows, buildForecastChartDescription, buildForecastDescription, buildForecastInsight, buildForecastLandingRows, buildForecastPeriodRows, buildForecastRegionRows, buildForecastTooltip, createForecastFormatters, deriveForecastCrossing, deriveForecastDelta, deriveForecastOutlook, deriveForecastProjection, deriveForecastState, forecastAxisGranularity, normalizeForecast, reachesTarget, resolveForecastLabels } from "./ForecastCard.model.js";
|
|
3
|
+
import { ForecastCard } from "./ForecastCard.js";
|
|
4
|
+
export { DEFAULT_FORECAST_LABELS, 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, ForecastCard, buildForecastBoundaryRows, buildForecastChartDescription, buildForecastDescription, buildForecastInsight, buildForecastLandingRows, buildForecastPeriodRows, buildForecastPlotSpec, buildForecastRegionRows, buildForecastTooltip, createForecastFormatters, deriveForecastCrossing, deriveForecastDelta, deriveForecastOutlook, deriveForecastProjection, deriveForecastState, forecastAxisGranularity, normalizeForecast, reachesTarget, resolveForecastLabels };
|