@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,309 @@
|
|
|
1
|
+
import { ArcDeltaDirection, ArcDeltaMode } from "../../lib/vega/formatters.js";
|
|
2
|
+
//#region src/components/ComparisonCard/ComparisonCard.model.d.ts
|
|
3
|
+
/** What the consumer's transport reports. */
|
|
4
|
+
type ComparisonCardStatus = "loading" | "ready" | "error";
|
|
5
|
+
/**
|
|
6
|
+
* What the card actually shows, derived from `status`, the items and
|
|
7
|
+
* `filtered`. Stamped on the root as `data-state`.
|
|
8
|
+
*/
|
|
9
|
+
type ComparisonCardState = "loading" | "refreshing" | "ready" | "empty" | "no-match" | "error";
|
|
10
|
+
/** Which way is good for the focus item: revenue up, churn down, headcount neutral. */
|
|
11
|
+
type ComparisonGoodDirection = "up" | "down" | "none";
|
|
12
|
+
/** How the focus-vs-baseline delta reads once `goodDirection` is applied. Stamped as `data-polarity`. */
|
|
13
|
+
type ComparisonPolarity = "positive" | "negative" | "neutral";
|
|
14
|
+
type ComparisonDeltaMode = ArcDeltaMode;
|
|
15
|
+
/** Row order: the consumer's order (periods stay chronological) or ranked by value. */
|
|
16
|
+
type ComparisonSort = "none" | "asc" | "desc";
|
|
17
|
+
/**
|
|
18
|
+
* The form the plot draws. Every one answers the same question — how does
|
|
19
|
+
* this metric compare across these items — with a different channel, so the
|
|
20
|
+
* choice is about the data, never about variety (Few's pitfall #6):
|
|
21
|
+
*
|
|
22
|
+
* - `bar` (default): length from zero along a row. The safe form, and the one
|
|
23
|
+
* a reader compares fastest when the values span their own magnitude.
|
|
24
|
+
* - `column`: the same length, stood up. For two to five items with short
|
|
25
|
+
* names — periods above all, where left-to-right IS the reading order.
|
|
26
|
+
* - `dot`: position alone, free to crop. When the values cluster far from
|
|
27
|
+
* zero (NPS 40 against 47) and bars would be seven identical slabs.
|
|
28
|
+
* - `lollipop`: a dot on a stem from zero. A bar's honesty at a fraction of
|
|
29
|
+
* its ink, so a dense card carries more rows without a wall of color.
|
|
30
|
+
* - `dumbbell`: two readings per item joined by the gap between them — the
|
|
31
|
+
* change is a length, not arithmetic the reader does in their head.
|
|
32
|
+
* - `slope`: the same two readings as a path across two positions, so a
|
|
33
|
+
* crossing line says the ranking changed (Tufte's slopegraph).
|
|
34
|
+
*
|
|
35
|
+
* `dumbbell` and `slope` read `ComparisonItem.from`; the rest ignore it.
|
|
36
|
+
* Stamped as `data-mark`.
|
|
37
|
+
*/
|
|
38
|
+
type ComparisonMark = "bar" | "column" | "dot" | "lollipop" | "dumbbell" | "slope";
|
|
39
|
+
/**
|
|
40
|
+
* Whether the value scale includes zero. `zero` is the honest floor wherever
|
|
41
|
+
* LENGTH carries the comparison, so `bar`, `column` and `lollipop` take it
|
|
42
|
+
* whatever this says; `auto` lets a position mark (`dot`, `dumbbell`,
|
|
43
|
+
* `slope`) crop to its own range, which is the only way values clustered far
|
|
44
|
+
* from zero separate at tile width. Stamped as `data-scale`.
|
|
45
|
+
*/
|
|
46
|
+
type ComparisonScale = "auto" | "zero";
|
|
47
|
+
/** What the big number is: the focus item's value, the gap between focus and baseline, or nothing. Stamped as `data-headline`. */
|
|
48
|
+
type ComparisonHeadline = "focus" | "gap" | "none";
|
|
49
|
+
type ComparisonCardSize = "sm" | "md" | "lg";
|
|
50
|
+
/** A row's part in the comparison — the subject, the reference, another item, or the fold of the long tail. */
|
|
51
|
+
type ComparisonRole = "focus" | "baseline" | "item" | "other";
|
|
52
|
+
/**
|
|
53
|
+
* One thing being compared — a period, a segment, a source, a plan. `key`
|
|
54
|
+
* identifies it for `focus` and `baseline` (defaults to `label`); `value` is
|
|
55
|
+
* the reading, or `null` when this item has none (its row stays, with no bar).
|
|
56
|
+
*/
|
|
57
|
+
interface ComparisonItem {
|
|
58
|
+
key?: string;
|
|
59
|
+
label: string;
|
|
60
|
+
value: number | null;
|
|
61
|
+
/**
|
|
62
|
+
* The item's OTHER reading — where it started, what it was last period,
|
|
63
|
+
* what was planned. Read only by `mark="dumbbell"` and `mark="slope"`,
|
|
64
|
+
* which draw the pair and the gap between them; every other mark ignores
|
|
65
|
+
* it, and the popover table shows it whenever it is present. `null` when
|
|
66
|
+
* this item has no earlier reading — the pair collapses to the single
|
|
67
|
+
* `value` mark rather than drawing a gap to nowhere.
|
|
68
|
+
*/
|
|
69
|
+
from?: number | null;
|
|
70
|
+
}
|
|
71
|
+
/** An item after normalisation, in display order, with its role resolved. */
|
|
72
|
+
interface ComparisonRow {
|
|
73
|
+
key: string;
|
|
74
|
+
label: string;
|
|
75
|
+
value: number | null;
|
|
76
|
+
/** The item's earlier reading, or `null`. Only `dumbbell` and `slope` plot it. */
|
|
77
|
+
from: number | null;
|
|
78
|
+
role: ComparisonRole;
|
|
79
|
+
}
|
|
80
|
+
interface ComparisonSummary {
|
|
81
|
+
rows: ComparisonRow[];
|
|
82
|
+
/** Rows with a finite value. */
|
|
83
|
+
count: number;
|
|
84
|
+
focus: ComparisonRow | null;
|
|
85
|
+
baseline: ComparisonRow | null;
|
|
86
|
+
/** The highest and lowest finite rows — what the chart description names. */
|
|
87
|
+
max: ComparisonRow | null;
|
|
88
|
+
min: ComparisonRow | null;
|
|
89
|
+
/** Any finite value below zero — the plot then draws a zero rule. */
|
|
90
|
+
hasNegative: boolean;
|
|
91
|
+
/** At least one row carries an earlier reading, so the two-value marks and the table's `from` column have something to draw. */
|
|
92
|
+
hasFrom: boolean;
|
|
93
|
+
/** Items folded into the "Other" row; 0 when nothing was folded. */
|
|
94
|
+
folded: number;
|
|
95
|
+
}
|
|
96
|
+
interface ComparisonDelta {
|
|
97
|
+
direction: ArcDeltaDirection;
|
|
98
|
+
polarity: ComparisonPolarity;
|
|
99
|
+
/** Signed, formatted change of the focus against the baseline: `+24.9%`, `-0.3 pp`, `+฿424K`. */
|
|
100
|
+
text: string;
|
|
101
|
+
renderedMode: ArcDeltaMode;
|
|
102
|
+
/**
|
|
103
|
+
* The same change in the other form — the amount when `text` is a percent,
|
|
104
|
+
* the relative change when `text` is an amount. `null` when the other form
|
|
105
|
+
* does not exist (a zero baseline has no percent).
|
|
106
|
+
*/
|
|
107
|
+
secondaryText: string | null;
|
|
108
|
+
}
|
|
109
|
+
/** What the delta tooltip explains: the two readings behind the badge and the change between them in both forms. */
|
|
110
|
+
interface ComparisonDeltaExplanation {
|
|
111
|
+
focusLabel: string;
|
|
112
|
+
focusText: string;
|
|
113
|
+
baselineLabel: string;
|
|
114
|
+
baselineText: string;
|
|
115
|
+
changeText: string;
|
|
116
|
+
secondaryText: string | null;
|
|
117
|
+
renderedMode: ArcDeltaMode;
|
|
118
|
+
direction: ArcDeltaDirection;
|
|
119
|
+
}
|
|
120
|
+
/** Every string the card renders or announces, so a product can localise it. Sentences are functions. */
|
|
121
|
+
interface ComparisonCardLabels {
|
|
122
|
+
/** Comparison line under the delta badge when `comparisonLabel` is omitted — `vs Retail`. */
|
|
123
|
+
versus: (baseline: string) => string;
|
|
124
|
+
/** Comparison line of a `gap` headline — `Online vs Retail`. */
|
|
125
|
+
gapComparison: (focus: string, baseline: string) => string;
|
|
126
|
+
increase: string;
|
|
127
|
+
decrease: string;
|
|
128
|
+
unchanged: string;
|
|
129
|
+
/** Suffix for a percentage-point delta. */
|
|
130
|
+
percentagePoints: string;
|
|
131
|
+
noData: string;
|
|
132
|
+
noMatch: string;
|
|
133
|
+
clearFilters: string;
|
|
134
|
+
error: string;
|
|
135
|
+
retry: string;
|
|
136
|
+
/** Shown in place of the delta when the focus or the baseline has no value. */
|
|
137
|
+
insufficient: string;
|
|
138
|
+
stale: string;
|
|
139
|
+
asOf: string;
|
|
140
|
+
tableItem: string;
|
|
141
|
+
tableValue: string;
|
|
142
|
+
/** Default names of the two readings a `dumbbell` or a `slope` compares, when `fromLabel` / `toLabel` are not passed. */
|
|
143
|
+
from: string;
|
|
144
|
+
to: string;
|
|
145
|
+
/** Header of the change column the two-value marks add to the popover table. */
|
|
146
|
+
tableChange: string;
|
|
147
|
+
/** How each mark names itself at the start of the plot's `aria-label` — `Bar comparison of Revenue by channel: …`. */
|
|
148
|
+
markNames: Record<ComparisonMark, string>;
|
|
149
|
+
/** Header of the per-row variance column — `vs Retail`. */
|
|
150
|
+
tableVersus: (baseline: string) => string;
|
|
151
|
+
tableVsTarget: string;
|
|
152
|
+
/** Label for the dotted target reference line in chart details. */
|
|
153
|
+
target: string;
|
|
154
|
+
/** Label of the row the long tail folds into. */
|
|
155
|
+
other: string;
|
|
156
|
+
/** Legend key for every bar that is not the focus. */
|
|
157
|
+
otherItems: string;
|
|
158
|
+
/** Read out after the focus row's label. */
|
|
159
|
+
highlighted: string;
|
|
160
|
+
showTable: (name: string) => string;
|
|
161
|
+
hideTable: (name: string) => string;
|
|
162
|
+
about: (name: string) => string;
|
|
163
|
+
loading: (name: string) => string;
|
|
164
|
+
tableCaption: (name: string) => string;
|
|
165
|
+
/** Accessible name of the delta group beside the value. */
|
|
166
|
+
deltaComparison: (name: string) => string;
|
|
167
|
+
/** Accessible name of the selectable rows (a radio group) — `Choose the item Revenue by channel focuses on`. */
|
|
168
|
+
focusGroup: (name: string) => string;
|
|
169
|
+
/** Accessible name of the delta stack when it opens the compare-with menu — `Choose what Revenue by channel is compared with`. */
|
|
170
|
+
chooseBaseline: (name: string) => string;
|
|
171
|
+
/** Heading of the compare-with menu's options column. */
|
|
172
|
+
compareWith: string;
|
|
173
|
+
/** Column headers of the compare-with menu's changes — the relative move and the amount in the value's units. */
|
|
174
|
+
changePercent: string;
|
|
175
|
+
changeAmount: string;
|
|
176
|
+
/** Compact label/value rows explaining the delta badge — by default the subject's reading and the change in both forms; the baseline's reading is available to add. */
|
|
177
|
+
deltaExplanation: (delta: ComparisonDeltaExplanation) => readonly (readonly [label: string, value: string])[];
|
|
178
|
+
/** The comparison sentence in the card description: `word` is the direction word, `primary` the unsigned change (empty when flat), `secondary` the other form or `null`. */
|
|
179
|
+
comparisonSentence: (input: {
|
|
180
|
+
word: string;
|
|
181
|
+
primary: string;
|
|
182
|
+
secondary: string | null;
|
|
183
|
+
baseline: string;
|
|
184
|
+
baselineValue: string;
|
|
185
|
+
}) => string;
|
|
186
|
+
/** Lists every row — `list` is `Online ฿2.1M, Retail ฿1.7M, …`. */
|
|
187
|
+
itemsSummary: (count: number, list: string) => string;
|
|
188
|
+
/** Appended when items were folded into Other. */
|
|
189
|
+
foldedSummary: (count: number) => string;
|
|
190
|
+
/** The plot's own `aria-label`. */
|
|
191
|
+
chartDescription: (input: {
|
|
192
|
+
/** The mark's own name from `markNames` — the plot says what form it is, because a screen reader cannot see it. */
|
|
193
|
+
mark: string;
|
|
194
|
+
name: string;
|
|
195
|
+
count: number;
|
|
196
|
+
top: string;
|
|
197
|
+
topValue: string;
|
|
198
|
+
bottom: string;
|
|
199
|
+
bottomValue: string;
|
|
200
|
+
focus: string | null;
|
|
201
|
+
target: string | null;
|
|
202
|
+
/** The two readings a `dumbbell` or a `slope` spans; `null` for the single-value marks. */
|
|
203
|
+
span: {
|
|
204
|
+
from: string;
|
|
205
|
+
to: string;
|
|
206
|
+
} | null;
|
|
207
|
+
}) => string;
|
|
208
|
+
/** Accessible name of the AI mark beside the insight sentence. */
|
|
209
|
+
insightMark: string;
|
|
210
|
+
}
|
|
211
|
+
declare const DEFAULT_COMPARISON_LABELS: ComparisonCardLabels;
|
|
212
|
+
declare function resolveComparisonLabels(overrides?: Partial<ComparisonCardLabels>): ComparisonCardLabels;
|
|
213
|
+
interface DeriveComparisonStateInput {
|
|
214
|
+
status: ComparisonCardStatus;
|
|
215
|
+
/** At least one item has a finite value. */
|
|
216
|
+
hasValue: boolean;
|
|
217
|
+
/** Anything is on screen already — any item at all — so a reload is a refresh, not a first load. */
|
|
218
|
+
hasData: boolean;
|
|
219
|
+
filtered: boolean;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The state machine. Error wins; loading over existing items is a refresh (the
|
|
223
|
+
* previous render stays under the veil); no values is `no-match` when the
|
|
224
|
+
* consumer's filter caused it and `empty` otherwise.
|
|
225
|
+
*/
|
|
226
|
+
declare function deriveComparisonState({ status, hasValue, hasData, filtered }: DeriveComparisonStateInput): ComparisonCardState;
|
|
227
|
+
/** The key of the row the long tail folds into. Never collides with a consumer key that is a label. */
|
|
228
|
+
declare const COMPARISON_OTHER_KEY = "__other__";
|
|
229
|
+
interface NormalizeItemsOptions {
|
|
230
|
+
/** Key of the subject. `undefined` takes the first item; `null` designates none. */
|
|
231
|
+
focus?: string | null;
|
|
232
|
+
/** Key of the reference. `undefined` — or the focus itself — takes the first item that is not the focus; `null` designates none. */
|
|
233
|
+
baseline?: string | null;
|
|
234
|
+
sort?: ComparisonSort;
|
|
235
|
+
/** Fold every item past this count (after sorting) into one `Other` row; the focus and the baseline are never folded. */
|
|
236
|
+
maxItems?: number;
|
|
237
|
+
otherLabel: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Normalises `items` for the plot, the rows and the table: keys default to
|
|
241
|
+
* labels (a repeated key is suffixed so two rows never share a band), the
|
|
242
|
+
* focus and the baseline are resolved in the consumer's order before any
|
|
243
|
+
* sorting (so "first and second" mean what the consumer sees), non-finite
|
|
244
|
+
* values become `null`, the rows are sorted if asked, and the long tail past
|
|
245
|
+
* `maxItems` folds into one `Other` row whose value is the sum of the folded
|
|
246
|
+
* finite values — honest only for additive measures, which the consumer
|
|
247
|
+
* decides by passing `maxItems` at all. Returns `null` when there are no
|
|
248
|
+
* items to speak of.
|
|
249
|
+
*/
|
|
250
|
+
declare function normalizeItems(items: readonly ComparisonItem[] | undefined, { focus, baseline, sort, maxItems, otherLabel }: NormalizeItemsOptions): ComparisonSummary | null;
|
|
251
|
+
interface DeriveComparisonDeltaOptions {
|
|
252
|
+
deltaMode: ComparisonDeltaMode;
|
|
253
|
+
goodDirection: ComparisonGoodDirection;
|
|
254
|
+
format: string;
|
|
255
|
+
locale?: string;
|
|
256
|
+
percentagePoints: string;
|
|
257
|
+
}
|
|
258
|
+
/** The focus against the baseline, with polarity resolved through `goodDirection`. `null` unless both have a finite value. */
|
|
259
|
+
declare function deriveComparisonDelta(focus: ComparisonRow | null | undefined, baseline: ComparisonRow | null | undefined, { deltaMode, goodDirection, format, locale, percentagePoints }: DeriveComparisonDeltaOptions): ComparisonDelta | null;
|
|
260
|
+
interface ComparisonTextFormatters {
|
|
261
|
+
formatValue: (value: number | null | undefined) => string;
|
|
262
|
+
/** A row's signed variance against a reference value (the baseline or the target) in the card's `deltaMode`; `MISSING_VALUE` without two finite readings. */
|
|
263
|
+
formatVariance: (value: number | null | undefined, reference: number | null | undefined) => string;
|
|
264
|
+
}
|
|
265
|
+
/** The formatters every text surface shares — headline, rows, delta, table, tooltip, descriptions. */
|
|
266
|
+
declare function createComparisonFormatters({ format, locale, formatValue, deltaMode, percentagePoints }: {
|
|
267
|
+
format: string;
|
|
268
|
+
locale?: string;
|
|
269
|
+
formatValue?: (value: number) => string;
|
|
270
|
+
/** The form of every row's variance — the badge's own `deltaMode`, so the table and the badge never disagree. */
|
|
271
|
+
deltaMode?: ComparisonDeltaMode;
|
|
272
|
+
percentagePoints?: string;
|
|
273
|
+
}): ComparisonTextFormatters;
|
|
274
|
+
interface BuildComparisonDescriptionInput {
|
|
275
|
+
name: string;
|
|
276
|
+
state: ComparisonCardState;
|
|
277
|
+
headline: ComparisonHeadline;
|
|
278
|
+
summary: ComparisonSummary | null;
|
|
279
|
+
delta: ComparisonDelta | null;
|
|
280
|
+
formatters: ComparisonTextFormatters;
|
|
281
|
+
/** The formatted target, when a reference line is drawn. */
|
|
282
|
+
targetText?: string | null;
|
|
283
|
+
asOfText?: string;
|
|
284
|
+
labels: ComparisonCardLabels;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The card's text equivalent (Amy Cesal's alt-text formula: what it is, what
|
|
288
|
+
* it shows, why it is here, when it was true) — the figure's accessible
|
|
289
|
+
* description, and the summary a screen reader gets before the parts. Every
|
|
290
|
+
* row is listed, so nothing lives only in the bars.
|
|
291
|
+
*/
|
|
292
|
+
declare function buildComparisonDescription({ name, state, headline, summary, delta, formatters, targetText, asOfText, labels }: BuildComparisonDescriptionInput): string;
|
|
293
|
+
/** The plot's own `description` — the `aria-label` Vega stamps on the `graphics-document`. */
|
|
294
|
+
declare function buildComparisonChartDescription({ mark, name, summary, formatters, targetText, span, labels }: {
|
|
295
|
+
/** The form the plot draws — named in the label, because a screen reader cannot see it. */
|
|
296
|
+
mark: ComparisonMark;
|
|
297
|
+
name: string;
|
|
298
|
+
summary: ComparisonSummary;
|
|
299
|
+
formatters: ComparisonTextFormatters;
|
|
300
|
+
targetText?: string | null;
|
|
301
|
+
/** The two end names of a `dumbbell` or a `slope`; omit for the single-value marks. */
|
|
302
|
+
span?: {
|
|
303
|
+
from: string;
|
|
304
|
+
to: string;
|
|
305
|
+
} | null;
|
|
306
|
+
labels: ComparisonCardLabels;
|
|
307
|
+
}): string;
|
|
308
|
+
//#endregion
|
|
309
|
+
export { BuildComparisonDescriptionInput, COMPARISON_OTHER_KEY, ComparisonCardLabels, ComparisonCardSize, ComparisonCardState, ComparisonCardStatus, ComparisonDelta, ComparisonDeltaExplanation, ComparisonDeltaMode, ComparisonGoodDirection, ComparisonHeadline, ComparisonItem, ComparisonMark, ComparisonPolarity, ComparisonRole, ComparisonRow, ComparisonScale, ComparisonSort, ComparisonSummary, ComparisonTextFormatters, DEFAULT_COMPARISON_LABELS, DeriveComparisonDeltaOptions, DeriveComparisonStateInput, NormalizeItemsOptions, buildComparisonChartDescription, buildComparisonDescription, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels };
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { formatDelta, formatNumber } from "../../lib/vega/formatters.js";
|
|
2
|
+
//#region src/components/ComparisonCard/ComparisonCard.model.ts
|
|
3
|
+
const DEFAULT_COMPARISON_LABELS = {
|
|
4
|
+
versus: (baseline) => `vs ${baseline}`,
|
|
5
|
+
gapComparison: (focus, baseline) => `${focus} vs ${baseline}`,
|
|
6
|
+
increase: "Up",
|
|
7
|
+
decrease: "Down",
|
|
8
|
+
unchanged: "No change",
|
|
9
|
+
percentagePoints: " pp",
|
|
10
|
+
noData: "No data",
|
|
11
|
+
noMatch: "No matching data",
|
|
12
|
+
clearFilters: "Clear filters",
|
|
13
|
+
error: "Couldn't load this comparison",
|
|
14
|
+
retry: "Try again",
|
|
15
|
+
insufficient: "Not enough data to compare",
|
|
16
|
+
stale: "Stale",
|
|
17
|
+
asOf: "As of",
|
|
18
|
+
tableItem: "Item",
|
|
19
|
+
tableValue: "Value",
|
|
20
|
+
from: "Before",
|
|
21
|
+
to: "After",
|
|
22
|
+
tableChange: "Change",
|
|
23
|
+
markNames: {
|
|
24
|
+
bar: "Bar",
|
|
25
|
+
column: "Column",
|
|
26
|
+
dot: "Dot",
|
|
27
|
+
lollipop: "Lollipop",
|
|
28
|
+
dumbbell: "Dumbbell",
|
|
29
|
+
slope: "Slope"
|
|
30
|
+
},
|
|
31
|
+
tableVersus: (baseline) => `vs ${baseline}`,
|
|
32
|
+
tableVsTarget: "vs target",
|
|
33
|
+
target: "Target",
|
|
34
|
+
other: "Other",
|
|
35
|
+
otherItems: "Other items",
|
|
36
|
+
highlighted: "highlighted",
|
|
37
|
+
showTable: (name) => `Show the data behind ${name}`,
|
|
38
|
+
hideTable: (name) => `Hide the data behind ${name}`,
|
|
39
|
+
about: (name) => `About ${name}`,
|
|
40
|
+
loading: (name) => `Loading ${name}`,
|
|
41
|
+
tableCaption: (name) => `Data behind the ${name} comparison`,
|
|
42
|
+
deltaComparison: (name) => `${name} comparison`,
|
|
43
|
+
focusGroup: (name) => `Choose the item ${name} focuses on`,
|
|
44
|
+
chooseBaseline: (name) => `Choose what ${name} is compared with`,
|
|
45
|
+
compareWith: "Compare with",
|
|
46
|
+
changePercent: "Change %",
|
|
47
|
+
changeAmount: "Change",
|
|
48
|
+
deltaExplanation: ({ focusLabel, focusText, changeText, secondaryText, renderedMode, direction }) => {
|
|
49
|
+
const rows = [[focusLabel, focusText], [direction === "up" ? "Increase" : direction === "down" ? "Decrease" : "No change", changeText]];
|
|
50
|
+
if (secondaryText) rows.push([renderedMode === "percent" ? "Change" : "Change %", secondaryText]);
|
|
51
|
+
return rows;
|
|
52
|
+
},
|
|
53
|
+
comparisonSentence: ({ word, primary, secondary, baseline, baselineValue }) => primary ? `${word} ${primary}${secondary ? ` (${secondary})` : ""} vs ${baseline} (${baselineValue}).` : `${word} vs ${baseline} (${baselineValue}).`,
|
|
54
|
+
itemsSummary: (count, list) => `Comparison of ${count} ${count === 1 ? "item" : "items"}: ${list}.`,
|
|
55
|
+
foldedSummary: (count) => `${count} more folded into Other.`,
|
|
56
|
+
chartDescription: ({ mark, name, count, top, topValue, bottom, bottomValue, focus, target, span }) => {
|
|
57
|
+
return `${mark} comparison of ${name}: ${count === 1 || top === bottom ? `${count} ${count === 1 ? "item" : "items"}, ${top} ${topValue}.` : `${count} items, highest ${top} ${topValue}, lowest ${bottom} ${bottomValue}.`}` + (span ? ` Each item spans ${span.from} to ${span.to}.` : "") + (focus ? ` ${focus} highlighted.` : "") + (target ? ` Reference line at the ${target} target.` : "");
|
|
58
|
+
},
|
|
59
|
+
insightMark: "Insight"
|
|
60
|
+
};
|
|
61
|
+
function resolveComparisonLabels(overrides) {
|
|
62
|
+
return overrides ? {
|
|
63
|
+
...DEFAULT_COMPARISON_LABELS,
|
|
64
|
+
...overrides
|
|
65
|
+
} : DEFAULT_COMPARISON_LABELS;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The state machine. Error wins; loading over existing items is a refresh (the
|
|
69
|
+
* previous render stays under the veil); no values is `no-match` when the
|
|
70
|
+
* consumer's filter caused it and `empty` otherwise.
|
|
71
|
+
*/
|
|
72
|
+
function deriveComparisonState({ status, hasValue, hasData, filtered }) {
|
|
73
|
+
if (status === "error") return "error";
|
|
74
|
+
if (status === "loading") return hasData ? "refreshing" : "loading";
|
|
75
|
+
if (hasValue) return "ready";
|
|
76
|
+
return filtered ? "no-match" : "empty";
|
|
77
|
+
}
|
|
78
|
+
const isFinite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
79
|
+
/** The key of the row the long tail folds into. Never collides with a consumer key that is a label. */
|
|
80
|
+
const COMPARISON_OTHER_KEY = "__other__";
|
|
81
|
+
/**
|
|
82
|
+
* Normalises `items` for the plot, the rows and the table: keys default to
|
|
83
|
+
* labels (a repeated key is suffixed so two rows never share a band), the
|
|
84
|
+
* focus and the baseline are resolved in the consumer's order before any
|
|
85
|
+
* sorting (so "first and second" mean what the consumer sees), non-finite
|
|
86
|
+
* values become `null`, the rows are sorted if asked, and the long tail past
|
|
87
|
+
* `maxItems` folds into one `Other` row whose value is the sum of the folded
|
|
88
|
+
* finite values — honest only for additive measures, which the consumer
|
|
89
|
+
* decides by passing `maxItems` at all. Returns `null` when there are no
|
|
90
|
+
* items to speak of.
|
|
91
|
+
*/
|
|
92
|
+
function normalizeItems(items, { focus, baseline, sort = "none", maxItems, otherLabel }) {
|
|
93
|
+
if (!items) return null;
|
|
94
|
+
const seen = /* @__PURE__ */ new Map();
|
|
95
|
+
let rows = items.map((item) => {
|
|
96
|
+
const base = item.key ?? item.label;
|
|
97
|
+
const repeats = seen.get(base) ?? 0;
|
|
98
|
+
seen.set(base, repeats + 1);
|
|
99
|
+
return {
|
|
100
|
+
key: repeats === 0 ? base : `${base}·${repeats + 1}`,
|
|
101
|
+
label: item.label,
|
|
102
|
+
value: isFinite(item.value) ? item.value : null,
|
|
103
|
+
from: isFinite(item.from) ? item.from : null,
|
|
104
|
+
role: "item"
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
const has = (key) => key != null && rows.some((row) => row.key === key);
|
|
108
|
+
const focusKey = focus === null ? null : focus === void 0 ? rows[0]?.key ?? null : has(focus) ? focus : null;
|
|
109
|
+
const firstOther = focusKey == null ? null : rows.find((row) => row.key !== focusKey)?.key ?? null;
|
|
110
|
+
const baselineKey = focusKey == null || baseline === null ? null : baseline === void 0 || baseline === focusKey ? firstOther : has(baseline) ? baseline : null;
|
|
111
|
+
if (sort !== "none") {
|
|
112
|
+
const direction = sort === "asc" ? 1 : -1;
|
|
113
|
+
rows = rows.map((row, index) => ({
|
|
114
|
+
row,
|
|
115
|
+
index
|
|
116
|
+
})).sort((a, b) => {
|
|
117
|
+
const av = a.row.value;
|
|
118
|
+
const bv = b.row.value;
|
|
119
|
+
if (av == null && bv == null) return a.index - b.index;
|
|
120
|
+
if (av == null) return 1;
|
|
121
|
+
if (bv == null) return -1;
|
|
122
|
+
return av === bv ? a.index - b.index : (av - bv) * direction;
|
|
123
|
+
}).map(({ row }) => row);
|
|
124
|
+
}
|
|
125
|
+
let folded = 0;
|
|
126
|
+
if (maxItems != null && maxItems >= 1 && rows.length > maxItems) {
|
|
127
|
+
const keepCount = Math.max(0, maxItems - 1);
|
|
128
|
+
const kept = [];
|
|
129
|
+
const rest = [];
|
|
130
|
+
rows.forEach((row, index) => {
|
|
131
|
+
if (index < keepCount || row.key === focusKey || row.key === baselineKey) kept.push(row);
|
|
132
|
+
else rest.push(row);
|
|
133
|
+
});
|
|
134
|
+
if (rest.length >= 2) {
|
|
135
|
+
const finite = rest.filter((row) => row.value != null);
|
|
136
|
+
const sum = finite.length > 0 ? finite.reduce((total, row) => total + row.value, 0) : null;
|
|
137
|
+
const fromRows = rest.filter((row) => row.from != null);
|
|
138
|
+
const fromSum = rest.length > 0 && fromRows.length === rest.length ? fromRows.reduce((total, row) => total + row.from, 0) : null;
|
|
139
|
+
rows = [...kept, {
|
|
140
|
+
key: COMPARISON_OTHER_KEY,
|
|
141
|
+
label: otherLabel,
|
|
142
|
+
value: sum,
|
|
143
|
+
from: fromSum,
|
|
144
|
+
role: "other"
|
|
145
|
+
}];
|
|
146
|
+
folded = rest.length;
|
|
147
|
+
} else rows = [...kept, ...rest];
|
|
148
|
+
}
|
|
149
|
+
rows = rows.map((row) => ({
|
|
150
|
+
...row,
|
|
151
|
+
role: row.role === "other" ? "other" : row.key === focusKey ? "focus" : row.key === baselineKey ? "baseline" : "item"
|
|
152
|
+
}));
|
|
153
|
+
const finiteRows = rows.filter((row) => row.value != null);
|
|
154
|
+
let max = null;
|
|
155
|
+
let min = null;
|
|
156
|
+
for (const row of finiteRows) {
|
|
157
|
+
if (!max || row.value > max.value) max = row;
|
|
158
|
+
if (!min || row.value < min.value) min = row;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
rows,
|
|
162
|
+
count: finiteRows.length,
|
|
163
|
+
focus: rows.find((row) => row.role === "focus") ?? null,
|
|
164
|
+
baseline: rows.find((row) => row.role === "baseline") ?? null,
|
|
165
|
+
max,
|
|
166
|
+
min,
|
|
167
|
+
hasNegative: finiteRows.some((row) => row.value < 0) || rows.some((row) => row.from != null && row.from < 0),
|
|
168
|
+
hasFrom: rows.some((row) => row.from != null),
|
|
169
|
+
folded
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** The focus against the baseline, with polarity resolved through `goodDirection`. `null` unless both have a finite value. */
|
|
173
|
+
function deriveComparisonDelta(focus, baseline, { deltaMode, goodDirection, format, locale, percentagePoints }) {
|
|
174
|
+
if (!focus || !baseline || !isFinite(focus.value) || !isFinite(baseline.value)) return null;
|
|
175
|
+
const delta = formatDelta(focus.value, baseline.value, {
|
|
176
|
+
mode: deltaMode,
|
|
177
|
+
format,
|
|
178
|
+
locale,
|
|
179
|
+
pointsSuffix: percentagePoints
|
|
180
|
+
});
|
|
181
|
+
const other = formatDelta(focus.value, baseline.value, {
|
|
182
|
+
mode: delta.renderedMode === "percent" ? "absolute" : "percent",
|
|
183
|
+
format,
|
|
184
|
+
locale,
|
|
185
|
+
pointsSuffix: percentagePoints
|
|
186
|
+
});
|
|
187
|
+
let polarity = "neutral";
|
|
188
|
+
if (delta.direction !== "flat" && goodDirection !== "none") polarity = delta.direction === "up" === (goodDirection === "up") ? "positive" : "negative";
|
|
189
|
+
return {
|
|
190
|
+
direction: delta.direction,
|
|
191
|
+
polarity,
|
|
192
|
+
text: delta.text,
|
|
193
|
+
renderedMode: delta.renderedMode,
|
|
194
|
+
secondaryText: other.renderedMode === delta.renderedMode ? null : other.text
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** The formatters every text surface shares — headline, rows, delta, table, tooltip, descriptions. */
|
|
198
|
+
function createComparisonFormatters({ format, locale, formatValue, deltaMode = "percent", percentagePoints = DEFAULT_COMPARISON_LABELS.percentagePoints }) {
|
|
199
|
+
return {
|
|
200
|
+
formatValue: (value) => isFinite(value) ? formatValue ? formatValue(value) : formatNumber(value, format, locale) : "—",
|
|
201
|
+
formatVariance: (value, reference) => isFinite(value) && isFinite(reference) ? formatDelta(value, reference, {
|
|
202
|
+
mode: deltaMode,
|
|
203
|
+
format,
|
|
204
|
+
locale,
|
|
205
|
+
pointsSuffix: percentagePoints
|
|
206
|
+
}).text : "—"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const stripSign = (text) => text.replace(/^[+\-−]/, "");
|
|
210
|
+
/**
|
|
211
|
+
* The card's text equivalent (Amy Cesal's alt-text formula: what it is, what
|
|
212
|
+
* it shows, why it is here, when it was true) — the figure's accessible
|
|
213
|
+
* description, and the summary a screen reader gets before the parts. Every
|
|
214
|
+
* row is listed, so nothing lives only in the bars.
|
|
215
|
+
*/
|
|
216
|
+
function buildComparisonDescription({ name, state, headline, summary, delta, formatters, targetText, asOfText, labels }) {
|
|
217
|
+
if (state === "loading") return `${labels.loading(name)}.`;
|
|
218
|
+
if (state === "error") return `${name}: ${labels.error}.`;
|
|
219
|
+
if (state === "empty") return `${name}: ${labels.noData}.`;
|
|
220
|
+
if (state === "no-match") return `${name}: ${labels.noMatch}.`;
|
|
221
|
+
const focus = summary?.focus ?? null;
|
|
222
|
+
const baseline = summary?.baseline ?? null;
|
|
223
|
+
const sentences = [];
|
|
224
|
+
if (headline === "focus" && focus) sentences.push(`${name}: ${focus.label} ${formatters.formatValue(focus.value)}.`);
|
|
225
|
+
else if (headline === "gap" && focus && baseline) sentences.push(`${name}: ${labels.gapComparison(focus.label, baseline.label)}.`);
|
|
226
|
+
else sentences.push(`${name}.`);
|
|
227
|
+
if (delta && baseline) {
|
|
228
|
+
const word = delta.direction === "up" ? labels.increase : delta.direction === "down" ? labels.decrease : labels.unchanged;
|
|
229
|
+
const flat = delta.direction === "flat";
|
|
230
|
+
sentences.push(labels.comparisonSentence({
|
|
231
|
+
word,
|
|
232
|
+
primary: flat ? "" : stripSign(delta.text),
|
|
233
|
+
secondary: flat || !delta.secondaryText ? null : stripSign(delta.secondaryText),
|
|
234
|
+
baseline: baseline.label,
|
|
235
|
+
baselineValue: formatters.formatValue(baseline.value)
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
if (summary && summary.rows.length > 0) {
|
|
239
|
+
const list = summary.rows.map((row) => `${row.label} ${formatters.formatValue(row.value)}`).join(", ");
|
|
240
|
+
sentences.push(labels.itemsSummary(summary.rows.length, list));
|
|
241
|
+
if (summary.folded > 0) sentences.push(labels.foldedSummary(summary.folded));
|
|
242
|
+
}
|
|
243
|
+
if (targetText) sentences.push(`${labels.target} ${targetText}.`);
|
|
244
|
+
if (asOfText) sentences.push(`${labels.asOf} ${asOfText}.`);
|
|
245
|
+
return sentences.join(" ");
|
|
246
|
+
}
|
|
247
|
+
/** The plot's own `description` — the `aria-label` Vega stamps on the `graphics-document`. */
|
|
248
|
+
function buildComparisonChartDescription({ mark, name, summary, formatters, targetText, span, labels }) {
|
|
249
|
+
return labels.chartDescription({
|
|
250
|
+
mark: labels.markNames[mark],
|
|
251
|
+
name,
|
|
252
|
+
count: summary.count,
|
|
253
|
+
top: summary.max?.label ?? "—",
|
|
254
|
+
topValue: formatters.formatValue(summary.max?.value),
|
|
255
|
+
bottom: summary.min?.label ?? "—",
|
|
256
|
+
bottomValue: formatters.formatValue(summary.min?.value),
|
|
257
|
+
focus: summary.focus?.label ?? null,
|
|
258
|
+
target: targetText ?? null,
|
|
259
|
+
span: span ?? null
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
export { COMPARISON_OTHER_KEY, DEFAULT_COMPARISON_LABELS, buildComparisonChartDescription, buildComparisonDescription, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BuildComparisonDescriptionInput, COMPARISON_OTHER_KEY, ComparisonCardLabels, ComparisonCardSize, ComparisonCardState, ComparisonCardStatus, ComparisonDelta, ComparisonDeltaExplanation, ComparisonDeltaMode, ComparisonGoodDirection, ComparisonHeadline, ComparisonItem, ComparisonMark, ComparisonPolarity, ComparisonRole, ComparisonRow, ComparisonScale, ComparisonSort, ComparisonSummary, ComparisonTextFormatters, DEFAULT_COMPARISON_LABELS, DeriveComparisonDeltaOptions, DeriveComparisonStateInput, NormalizeItemsOptions, buildComparisonChartDescription, buildComparisonDescription, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels } from "./ComparisonCard.model.js";
|
|
2
|
+
import { ComparisonCard, ComparisonCardProps } from "./ComparisonCard.js";
|
|
3
|
+
import { COMPARISON_HOVER_PARAM, COMPARISON_REFERENCE_DATASET, COMPARISON_TABLE_DATASET, COMPARISON_ZERO_DATASET, ComparisonPlotSpecOptions, MAX_BAR_HEIGHT, buildComparisonPlotSpec, comparisonNeedsZero, comparisonOrientation, comparisonUsesFrom, resolveComparisonScale } from "./ComparisonCard.chart.js";
|
|
4
|
+
export { type BuildComparisonDescriptionInput, COMPARISON_HOVER_PARAM, COMPARISON_OTHER_KEY, COMPARISON_REFERENCE_DATASET, COMPARISON_TABLE_DATASET, COMPARISON_ZERO_DATASET, ComparisonCard, type ComparisonCardLabels, type ComparisonCardProps, type ComparisonCardSize, type ComparisonCardState, type ComparisonCardStatus, type ComparisonDelta, type ComparisonDeltaExplanation, type ComparisonDeltaMode, type ComparisonGoodDirection, type ComparisonHeadline, type ComparisonItem, type ComparisonMark, type ComparisonPlotSpecOptions, type ComparisonPolarity, type ComparisonRole, type ComparisonRow, type ComparisonScale, type ComparisonSort, type ComparisonSummary, type ComparisonTextFormatters, DEFAULT_COMPARISON_LABELS, type DeriveComparisonDeltaOptions, type DeriveComparisonStateInput, MAX_BAR_HEIGHT, type NormalizeItemsOptions, buildComparisonChartDescription, buildComparisonDescription, buildComparisonPlotSpec, comparisonNeedsZero, comparisonOrientation, comparisonUsesFrom, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels, resolveComparisonScale };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { COMPARISON_HOVER_PARAM, COMPARISON_REFERENCE_DATASET, COMPARISON_TABLE_DATASET, COMPARISON_ZERO_DATASET, MAX_BAR_HEIGHT, buildComparisonPlotSpec, comparisonNeedsZero, comparisonOrientation, comparisonUsesFrom, resolveComparisonScale } from "./ComparisonCard.chart.js";
|
|
2
|
+
import { COMPARISON_OTHER_KEY, DEFAULT_COMPARISON_LABELS, buildComparisonChartDescription, buildComparisonDescription, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels } from "./ComparisonCard.model.js";
|
|
3
|
+
import { ComparisonCard } from "./ComparisonCard.js";
|
|
4
|
+
export { COMPARISON_HOVER_PARAM, COMPARISON_OTHER_KEY, COMPARISON_REFERENCE_DATASET, COMPARISON_TABLE_DATASET, COMPARISON_ZERO_DATASET, ComparisonCard, DEFAULT_COMPARISON_LABELS, MAX_BAR_HEIGHT, buildComparisonChartDescription, buildComparisonDescription, buildComparisonPlotSpec, comparisonNeedsZero, comparisonOrientation, comparisonUsesFrom, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels, resolveComparisonScale };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ChartTokens } from "../../lib/vega/tokens.js";
|
|
2
|
+
import { ForecastMark, ForecastScale, ForecastXType } from "./ForecastCard.model.js";
|
|
3
|
+
import { TopLevelSpec } from "vega-lite";
|
|
4
|
+
//#region src/components/ForecastCard/ForecastCard.chart.d.ts
|
|
5
|
+
/** Every plotted period `{ x, y, lo, hi, k, c, n }`, measured then projected, with the anchor repeated. */
|
|
6
|
+
declare const FORECAST_TABLE_DATASET = "table";
|
|
7
|
+
/** The interval band `{ x, lo, hi }` — the anchor at zero width, then every projected period with bounds. */
|
|
8
|
+
declare const FORECAST_INTERVAL_DATASET = "interval";
|
|
9
|
+
/** One row per period, `{ x }` — the crosshair, filtered by the hover selection. */
|
|
10
|
+
declare const FORECAST_PERIODS_DATASET = "periods";
|
|
11
|
+
/** Zero or one row `{ start, end }` — the projected stretch: the wash, and the x axis's anchors. */
|
|
12
|
+
declare const FORECAST_REGION_DATASET = "region";
|
|
13
|
+
/** Zero or one row `{ x }` — the rule at the last measured period. */
|
|
14
|
+
declare const FORECAST_BOUNDARY_DATASET = "boundary";
|
|
15
|
+
/** Zero or one row `{ value }` — the target reference rule. */
|
|
16
|
+
declare const FORECAST_REFERENCE_DATASET = "reference";
|
|
17
|
+
/** Zero or one row `{ x, y, n, c }` — the landing dot and its direct label. */
|
|
18
|
+
declare const FORECAST_LANDING_DATASET = "landing";
|
|
19
|
+
/** The shared-x hover selection: projected onto `x`, so pointing anywhere in a column reads that period. */
|
|
20
|
+
declare const FORECAST_HOVER_PARAM = "hover";
|
|
21
|
+
/** Responsive x tick values, recomputed by Vega when the domain or the width changes. */
|
|
22
|
+
declare const FORECAST_X_TICKS_PARAM = "forecast_x_ticks";
|
|
23
|
+
/** The projected window's bounds, read from the `region` dataset — the x axis labels them. */
|
|
24
|
+
declare const FORECAST_REGION_START_PARAM = "forecast_region_start";
|
|
25
|
+
declare const FORECAST_REGION_END_PARAM = "forecast_region_end";
|
|
26
|
+
interface ForecastPlotSpecOptions {
|
|
27
|
+
/** `line` reads shape; `area` fills the MEASURED half from zero. */
|
|
28
|
+
mark: ForecastMark;
|
|
29
|
+
/** `auto` lets a line crop to its data range; `zero` includes zero. An area always includes zero. */
|
|
30
|
+
scale: ForecastScale;
|
|
31
|
+
xType: ForecastXType;
|
|
32
|
+
/** Plot height in pixels, axes included. */
|
|
33
|
+
height: number;
|
|
34
|
+
/** Draw both axes. `false` shrinks the plot toward a sparkline; the table still carries every value. */
|
|
35
|
+
axes: boolean;
|
|
36
|
+
/** Number of y ticks — the caller scales it to the plot's height. */
|
|
37
|
+
yTicks: number;
|
|
38
|
+
/** Draw the interval band (the `interval` dataset carries its rows). */
|
|
39
|
+
interval: boolean;
|
|
40
|
+
/** Wash the projected stretch and anchor the x axis on its bounds. */
|
|
41
|
+
region: boolean;
|
|
42
|
+
/** Draw the rule at the last measured period. */
|
|
43
|
+
boundary: boolean;
|
|
44
|
+
/** Draw the target reference rule. */
|
|
45
|
+
reference: boolean;
|
|
46
|
+
/** Dot the landing, and label it when `landingLabel` is true. */
|
|
47
|
+
landing: boolean;
|
|
48
|
+
/** Hang the landing's value in the right-hand gutter. */
|
|
49
|
+
landingLabel: boolean;
|
|
50
|
+
/** Wire the shared-x hover selection, the crosshair and the tooltip channel. */
|
|
51
|
+
tooltip: boolean;
|
|
52
|
+
/** Tooltip titles. */
|
|
53
|
+
xTitle: string;
|
|
54
|
+
yTitle: string;
|
|
55
|
+
kindTitle: string;
|
|
56
|
+
/** Localised title for the target reference rule's hover value. */
|
|
57
|
+
targetTitle?: string;
|
|
58
|
+
/** `arcNumber` grammar for the y axis and the tooltip. */
|
|
59
|
+
plotFormat: string;
|
|
60
|
+
/** `arcTime` grammar for temporal x in tooltips (`day@en-US@Asia/Bangkok`). */
|
|
61
|
+
timeFormat: string;
|
|
62
|
+
/** `arcTime` grammar for x-axis tick labels — the axis-length form (`day-short@en-US@Asia/Bangkok`). */
|
|
63
|
+
axisTimeFormat: string;
|
|
64
|
+
locale?: string;
|
|
65
|
+
timeZone?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Builds the plot: the measured readings as a solid path, the projection as the
|
|
69
|
+
* same path dashed, the prediction interval as a band that opens from the last
|
|
70
|
+
* reading, a wash and a rule marking where measurement stops, an optional
|
|
71
|
+
* target rule, a dotted landing, and the documented shared-x hover (a
|
|
72
|
+
* transparent `point` layer carrying a `nearest` selection projected onto `x`,
|
|
73
|
+
* with a crosshair rule read from the period dataset).
|
|
74
|
+
*
|
|
75
|
+
* Gaps stay gaps — a `null` reading breaks the path rather than dropping it to
|
|
76
|
+
* zero. An `area` keeps its zero baseline whatever `scale` says, because
|
|
77
|
+
* proportional ink is not negotiable; a line may crop, and since both axes are
|
|
78
|
+
* drawn the crop is always visible in the axis.
|
|
79
|
+
*/
|
|
80
|
+
declare function buildForecastPlotSpec(options: ForecastPlotSpecOptions, tokens: ChartTokens): TopLevelSpec;
|
|
81
|
+
//#endregion
|
|
82
|
+
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, ForecastPlotSpecOptions, buildForecastPlotSpec };
|