@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.
Files changed (156) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +13 -0
  2. package/dist/components/AnomalyCard/AnomalyCard.chart.d.ts +64 -0
  3. package/dist/components/AnomalyCard/AnomalyCard.chart.js +341 -0
  4. package/dist/components/AnomalyCard/AnomalyCard.d.ts +169 -0
  5. package/dist/components/AnomalyCard/AnomalyCard.js +1019 -0
  6. package/dist/components/AnomalyCard/AnomalyCard.model.d.ts +508 -0
  7. package/dist/components/AnomalyCard/AnomalyCard.model.js +553 -0
  8. package/dist/components/AnomalyCard/index.d.ts +4 -0
  9. package/dist/components/AnomalyCard/index.js +4 -0
  10. package/dist/components/ChartCard/ChartCard.chart.d.ts +72 -0
  11. package/dist/components/ChartCard/ChartCard.chart.js +1333 -0
  12. package/dist/components/ChartCard/ChartCard.d.ts +206 -0
  13. package/dist/components/ChartCard/ChartCard.js +718 -0
  14. package/dist/components/ChartCard/ChartCard.model.d.ts +452 -0
  15. package/dist/components/ChartCard/ChartCard.model.js +1223 -0
  16. package/dist/components/ChartCard/index.d.ts +4 -0
  17. package/dist/components/ChartCard/index.js +4 -0
  18. package/dist/components/ComparisonCard/ComparisonCard.chart.d.ts +80 -0
  19. package/dist/components/ComparisonCard/ComparisonCard.chart.js +337 -0
  20. package/dist/components/ComparisonCard/ComparisonCard.d.ts +200 -0
  21. package/dist/components/ComparisonCard/ComparisonCard.js +1325 -0
  22. package/dist/components/ComparisonCard/ComparisonCard.model.d.ts +309 -0
  23. package/dist/components/ComparisonCard/ComparisonCard.model.js +263 -0
  24. package/dist/components/ComparisonCard/index.d.ts +4 -0
  25. package/dist/components/ComparisonCard/index.js +4 -0
  26. package/dist/components/ForecastCard/ForecastCard.chart.d.ts +82 -0
  27. package/dist/components/ForecastCard/ForecastCard.chart.js +416 -0
  28. package/dist/components/ForecastCard/ForecastCard.d.ts +148 -0
  29. package/dist/components/ForecastCard/ForecastCard.js +1029 -0
  30. package/dist/components/ForecastCard/ForecastCard.model.d.ts +497 -0
  31. package/dist/components/ForecastCard/ForecastCard.model.js +554 -0
  32. package/dist/components/ForecastCard/index.d.ts +4 -0
  33. package/dist/components/ForecastCard/index.js +4 -0
  34. package/dist/components/HighlightCard/HighlightCard.chart.d.ts +122 -0
  35. package/dist/components/HighlightCard/HighlightCard.chart.js +660 -0
  36. package/dist/components/HighlightCard/HighlightCard.d.ts +202 -0
  37. package/dist/components/HighlightCard/HighlightCard.js +1286 -0
  38. package/dist/components/HighlightCard/HighlightCard.model.d.ts +692 -0
  39. package/dist/components/HighlightCard/HighlightCard.model.js +875 -0
  40. package/dist/components/HighlightCard/index.d.ts +4 -0
  41. package/dist/components/HighlightCard/index.js +4 -0
  42. package/dist/components/InsightCard/InsightCard.chart.d.ts +91 -0
  43. package/dist/components/InsightCard/InsightCard.chart.js +471 -0
  44. package/dist/components/InsightCard/InsightCard.d.ts +161 -0
  45. package/dist/components/InsightCard/InsightCard.js +961 -0
  46. package/dist/components/InsightCard/InsightCard.model.d.ts +405 -0
  47. package/dist/components/InsightCard/InsightCard.model.js +421 -0
  48. package/dist/components/InsightCard/index.d.ts +4 -0
  49. package/dist/components/InsightCard/index.js +4 -0
  50. package/dist/components/MetricCard/MetricCard.chart.d.ts +79 -0
  51. package/dist/components/MetricCard/MetricCard.chart.js +435 -0
  52. package/dist/components/MetricCard/MetricCard.d.ts +227 -0
  53. package/dist/components/MetricCard/MetricCard.js +1293 -0
  54. package/dist/components/MetricCard/MetricCard.model.d.ts +521 -0
  55. package/dist/components/MetricCard/MetricCard.model.js +627 -0
  56. package/dist/components/MetricCard/index.d.ts +4 -0
  57. package/dist/components/MetricCard/index.js +4 -0
  58. package/dist/components/ProfileCard/ProfileCard.chart.d.ts +64 -0
  59. package/dist/components/ProfileCard/ProfileCard.chart.js +427 -0
  60. package/dist/components/ProfileCard/ProfileCard.d.ts +140 -0
  61. package/dist/components/ProfileCard/ProfileCard.js +735 -0
  62. package/dist/components/ProfileCard/ProfileCard.model.d.ts +279 -0
  63. package/dist/components/ProfileCard/ProfileCard.model.js +244 -0
  64. package/dist/components/ProfileCard/index.d.ts +4 -0
  65. package/dist/components/ProfileCard/index.js +4 -0
  66. package/dist/components/ProgressCard/ProgressCard.chart.d.ts +73 -0
  67. package/dist/components/ProgressCard/ProgressCard.chart.js +373 -0
  68. package/dist/components/ProgressCard/ProgressCard.d.ts +137 -0
  69. package/dist/components/ProgressCard/ProgressCard.js +908 -0
  70. package/dist/components/ProgressCard/ProgressCard.model.d.ts +385 -0
  71. package/dist/components/ProgressCard/ProgressCard.model.js +389 -0
  72. package/dist/components/ProgressCard/index.d.ts +4 -0
  73. package/dist/components/ProgressCard/index.js +4 -0
  74. package/dist/components/ReportCard/ReportCard.chart.d.ts +45 -0
  75. package/dist/components/ReportCard/ReportCard.chart.js +231 -0
  76. package/dist/components/ReportCard/ReportCard.d.ts +153 -0
  77. package/dist/components/ReportCard/ReportCard.js +1118 -0
  78. package/dist/components/ReportCard/ReportCard.model.d.ts +392 -0
  79. package/dist/components/ReportCard/ReportCard.model.js +399 -0
  80. package/dist/components/ReportCard/index.d.ts +4 -0
  81. package/dist/components/ReportCard/index.js +4 -0
  82. package/dist/components/SankeyCard/SankeyCard.chart.d.ts +75 -0
  83. package/dist/components/SankeyCard/SankeyCard.chart.js +249 -0
  84. package/dist/components/SankeyCard/SankeyCard.d.ts +185 -0
  85. package/dist/components/SankeyCard/SankeyCard.js +788 -0
  86. package/dist/components/SankeyCard/SankeyCard.model.d.ts +501 -0
  87. package/dist/components/SankeyCard/SankeyCard.model.js +789 -0
  88. package/dist/components/SankeyCard/index.d.ts +4 -0
  89. package/dist/components/SankeyCard/index.js +4 -0
  90. package/dist/components/StatusCard/StatusCard.chart.d.ts +60 -0
  91. package/dist/components/StatusCard/StatusCard.chart.js +188 -0
  92. package/dist/components/StatusCard/StatusCard.d.ts +176 -0
  93. package/dist/components/StatusCard/StatusCard.js +989 -0
  94. package/dist/components/StatusCard/StatusCard.model.d.ts +555 -0
  95. package/dist/components/StatusCard/StatusCard.model.js +776 -0
  96. package/dist/components/StatusCard/index.d.ts +4 -0
  97. package/dist/components/StatusCard/index.js +4 -0
  98. package/dist/components/TableCard/TableCard.chart.d.ts +53 -0
  99. package/dist/components/TableCard/TableCard.chart.js +192 -0
  100. package/dist/components/TableCard/TableCard.d.ts +140 -0
  101. package/dist/components/TableCard/TableCard.js +811 -0
  102. package/dist/components/TableCard/TableCard.model.d.ts +381 -0
  103. package/dist/components/TableCard/TableCard.model.js +475 -0
  104. package/dist/components/TableCard/index.d.ts +4 -0
  105. package/dist/components/TableCard/index.js +4 -0
  106. package/dist/components/TargetCard/TargetCard.chart.d.ts +52 -0
  107. package/dist/components/TargetCard/TargetCard.chart.js +273 -0
  108. package/dist/components/TargetCard/TargetCard.d.ts +143 -0
  109. package/dist/components/TargetCard/TargetCard.js +910 -0
  110. package/dist/components/TargetCard/TargetCard.model.d.ts +468 -0
  111. package/dist/components/TargetCard/TargetCard.model.js +581 -0
  112. package/dist/components/TargetCard/index.d.ts +4 -0
  113. package/dist/components/TargetCard/index.js +4 -0
  114. package/dist/components/TreeCard/TreeCard.chart.d.ts +80 -0
  115. package/dist/components/TreeCard/TreeCard.chart.js +178 -0
  116. package/dist/components/TreeCard/TreeCard.d.ts +182 -0
  117. package/dist/components/TreeCard/TreeCard.js +1187 -0
  118. package/dist/components/TreeCard/TreeCard.model.d.ts +330 -0
  119. package/dist/components/TreeCard/TreeCard.model.js +401 -0
  120. package/dist/components/TreeCard/index.d.ts +4 -0
  121. package/dist/components/TreeCard/index.js +4 -0
  122. package/dist/components/TrendCard/TrendCard.chart.d.ts +75 -0
  123. package/dist/components/TrendCard/TrendCard.chart.js +393 -0
  124. package/dist/components/TrendCard/TrendCard.d.ts +224 -0
  125. package/dist/components/TrendCard/TrendCard.js +1053 -0
  126. package/dist/components/TrendCard/TrendCard.model.d.ts +545 -0
  127. package/dist/components/TrendCard/TrendCard.model.js +623 -0
  128. package/dist/components/TrendCard/index.d.ts +4 -0
  129. package/dist/components/TrendCard/index.js +4 -0
  130. package/dist/index.d.ts +76 -0
  131. package/dist/index.js +57 -0
  132. package/dist/lib/vega/ArcVegaChart.d.ts +24 -0
  133. package/dist/lib/vega/ArcVegaChart.js +42 -0
  134. package/dist/lib/vega/ArcVegaSurface.d.ts +13 -0
  135. package/dist/lib/vega/ArcVegaSurface.js +38 -0
  136. package/dist/lib/vega/axisTicks.js +156 -0
  137. package/dist/lib/vega/config.d.ts +29 -0
  138. package/dist/lib/vega/config.js +113 -0
  139. package/dist/lib/vega/embed.d.ts +29 -0
  140. package/dist/lib/vega/embed.js +29 -0
  141. package/dist/lib/vega/engine.d.ts +10 -0
  142. package/dist/lib/vega/engine.js +6 -0
  143. package/dist/lib/vega/formatters.d.ts +125 -0
  144. package/dist/lib/vega/formatters.js +277 -0
  145. package/dist/lib/vega/index.d.ts +11 -0
  146. package/dist/lib/vega/load.d.ts +13 -0
  147. package/dist/lib/vega/load.js +21 -0
  148. package/dist/lib/vega/time.d.ts +45 -0
  149. package/dist/lib/vega/time.js +105 -0
  150. package/dist/lib/vega/tokens.d.ts +73 -0
  151. package/dist/lib/vega/tokens.js +137 -0
  152. package/dist/lib/vega/tooltip.d.ts +45 -0
  153. package/dist/lib/vega/tooltip.js +124 -0
  154. package/dist/lib/vega/useArcVega.d.ts +43 -0
  155. package/dist/lib/vega/useArcVega.js +121 -0
  156. package/package.json +60 -0
@@ -0,0 +1,389 @@
1
+ import { formatDate, formatNumber } from "../../lib/vega/formatters.js";
2
+ import { resolveInstant } from "../../lib/vega/time.js";
3
+ //#region src/components/ProgressCard/ProgressCard.model.ts
4
+ const DAY_MS = 864e5;
5
+ const DEFAULT_PROGRESS_LABELS = {
6
+ complete: "complete",
7
+ countLine: (value, total) => `${value} of ${total}`,
8
+ percentLine: (percent, labels) => `${percent} ${labels.complete}`,
9
+ remainingHeadline: (value) => `${value} left`,
10
+ finishes: (date) => `Finishes ${date}`,
11
+ daysLate: (days) => `${days} ${days === 1 ? "day" : "days"} late`,
12
+ daysEarly: (days) => `${days} ${days === 1 ? "day" : "days"} early`,
13
+ onDueDate: "on the due date",
14
+ verdicts: {
15
+ done: "Complete",
16
+ "on-time": "On time",
17
+ "at-risk": "At risk",
18
+ late: "Late",
19
+ stalled: "Stalled"
20
+ },
21
+ perDay: (value) => `${value}/day`,
22
+ needsPerDay: (value) => `needs ${value}/day`,
23
+ markNames: {
24
+ completed: "Completed",
25
+ scope: "Scope",
26
+ ideal: "Required pace",
27
+ projection: "Projected",
28
+ due: "Due"
29
+ },
30
+ active: "In progress",
31
+ blocked: "Blocked",
32
+ remaining: "Not started",
33
+ total: "Total",
34
+ scope: "Scope",
35
+ scopeMoved: (from, to) => `Scope moved from ${from} to ${to}`,
36
+ tableItem: "Stage",
37
+ tableValue: "Count",
38
+ tableShare: "Share",
39
+ tableDate: "Date",
40
+ tableCompleted: "Completed",
41
+ needsHistory: "Not enough history for a trend yet",
42
+ noData: "No data",
43
+ noMatch: "No matching data",
44
+ clearFilters: "Clear filters",
45
+ error: "Could not load",
46
+ retry: "Retry",
47
+ stale: "Stale",
48
+ asOf: "as of",
49
+ showTable: (name) => `Show ${name} details`,
50
+ hideTable: (name) => `Hide ${name} details`,
51
+ about: (name) => `About ${name}`,
52
+ loading: (name) => `Loading ${name}`,
53
+ tableCaption: (name) => `${name} readings`,
54
+ progressGroup: (name) => `${name} progress`,
55
+ stageBar: (name, parts) => `${name} by stage: ${parts}`,
56
+ insightMark: "AI insight",
57
+ explanation: (run, labels) => {
58
+ const rows = [
59
+ [labels.markNames.completed, `${run.completedText} (${run.percentText})`],
60
+ [labels.remaining, run.remainingText],
61
+ [labels.scope, run.scopeText]
62
+ ];
63
+ if (run.rate) {
64
+ rows.push([labels.markNames.ideal, labels.perDay(run.rate.text)]);
65
+ if (run.rate.requiredText) rows.push([labels.markNames.due, labels.needsPerDay(run.rate.requiredText)]);
66
+ }
67
+ if (run.projection) rows.push([labels.markNames.projection, run.projection.text]);
68
+ return rows;
69
+ },
70
+ chartDescription: (name, run, labels) => {
71
+ const sentences = [`Burn-up chart: ${name}, ${run.completedText} of ${run.scopeText} ${labels.complete} (${run.percentText}) as of ${run.asOfText}.`];
72
+ if (run.ideal) sentences.push(`The required pace reaches ${run.scopeText} on ${run.dueText}.`);
73
+ if (run.scopeChanged) sentences.push(`${labels.scopeMoved(run.openingScopeText, run.scopeText)}.`);
74
+ if (run.projection) sentences.push(`At ${labels.perDay(run.rate?.text ?? "")} it finishes ${run.projection.dateText}${run.due ? ` — ${run.projection.text}` : ""}.`);
75
+ else if (run.verdict === "stalled") sentences.push(`${labels.verdicts.stalled}.`);
76
+ return sentences.join(" ");
77
+ },
78
+ description: ({ name, state, run, labels }) => {
79
+ if (state === "loading") return labels.loading(name);
80
+ if (state === "error") return `${name}: ${labels.error}.`;
81
+ if (state === "no-match") return `${name}: ${labels.noMatch}.`;
82
+ if (state === "empty" || !run) return `${name}: ${labels.noData}.`;
83
+ const sentences = [`${name}: ${run.percentText} ${labels.complete} — ${labels.countLine(run.completedText, run.scopeText)}, ${labels.asOf} ${run.asOfText}.`];
84
+ if (state === "insufficient") sentences.push(`${labels.needsHistory}.`);
85
+ if (run.scopeChanged) sentences.push(`${labels.scopeMoved(run.openingScopeText, run.scopeText)}.`);
86
+ if (run.rate) sentences.push(`${labels.perDay(run.rate.text)}${run.rate.requiredText ? `, ${labels.needsPerDay(run.rate.requiredText)}` : ""}.`);
87
+ if (run.projection) sentences.push(`${labels.finishes(run.projection.dateText)}${run.due ? ` — ${run.projection.text}` : ""}.`);
88
+ if (run.verdict) sentences.push(`${labels.verdicts[run.verdict]}.`);
89
+ return sentences.join(" ");
90
+ }
91
+ };
92
+ /** Merges consumer overrides over the English defaults. */
93
+ function resolveProgressLabels(overrides) {
94
+ return overrides ? {
95
+ ...DEFAULT_PROGRESS_LABELS,
96
+ ...overrides
97
+ } : DEFAULT_PROGRESS_LABELS;
98
+ }
99
+ function createProgressFormatters({ format = "integer", locale, timeZone, formatValue, percentDigits = 0, dateGranularity = "day", formatDateValue } = {}) {
100
+ const shareFormat = `percent:${percentDigits}`;
101
+ const rateFormat = format === "integer" ? "number:1" : format;
102
+ return {
103
+ formatValue: (value) => value == null || !Number.isFinite(value) ? "—" : formatValue ? formatValue(value) : formatNumber(value, format, locale),
104
+ formatShare: (ratio) => formatNumber(Number.isFinite(ratio) ? ratio : 0, shareFormat, locale),
105
+ formatRate: (value) => Number.isFinite(value) ? formatNumber(value, rateFormat, locale) : "—",
106
+ formatDay: (ms) => !Number.isFinite(ms) ? "—" : formatDateValue ? formatDateValue(new Date(ms)) : formatDate(new Date(ms), {
107
+ granularity: dateGranularity,
108
+ locale,
109
+ timeZone
110
+ })
111
+ };
112
+ }
113
+ const dayKeyCache = /* @__PURE__ */ new Map();
114
+ function dayKeyFormat(timeZone) {
115
+ const key = timeZone ?? "";
116
+ let cached = dayKeyCache.get(key);
117
+ if (!cached) {
118
+ cached = new Intl.DateTimeFormat("en-CA", {
119
+ ...timeZone ? { timeZone } : {},
120
+ year: "numeric",
121
+ month: "2-digit",
122
+ day: "2-digit"
123
+ });
124
+ dayKeyCache.set(key, cached);
125
+ }
126
+ return cached;
127
+ }
128
+ /** The calendar day an instant falls on in `timeZone`, as a day number. */
129
+ function zonedDayNumber(ms, timeZone) {
130
+ const [year, month, day] = dayKeyFormat(timeZone).format(new Date(ms)).split("-").map(Number);
131
+ return Math.floor(Date.UTC(year ?? 1970, (month ?? 1) - 1, day ?? 1) / DAY_MS);
132
+ }
133
+ /**
134
+ * Whole calendar days from `from` to `to` in `timeZone` — counted between
135
+ * calendar dates, so a daylight-saving change never shifts the count and
136
+ * "2 days late" means two dates on the wall calendar.
137
+ */
138
+ function dayDiff(from, to, timeZone) {
139
+ return zonedDayNumber(to, timeZone) - zonedDayNumber(from, timeZone);
140
+ }
141
+ /**
142
+ * The state machine: the transport's `status` first, then what the data says.
143
+ * A refresh over existing readings keeps the previous render under the veil
144
+ * rather than swapping to a skeleton, so a dashboard never jumps. One reading
145
+ * is `insufficient`, not `ready`: a completion with no history.
146
+ */
147
+ function deriveProgressState({ status, hasValues, hasData, hasTrend, filtered }) {
148
+ if (status === "error") return "error";
149
+ if (status === "loading") return hasData ? "refreshing" : "loading";
150
+ if (!hasData || !hasValues) return filtered ? "no-match" : "empty";
151
+ return hasTrend ? "ready" : "insufficient";
152
+ }
153
+ /** Places the optional breakdown stages over the run's scope. */
154
+ function deriveProgressStages(input, scope, formatters, labels) {
155
+ if (input.length === 0 || scope <= 0) return [];
156
+ const counted = input.map((stage) => {
157
+ const value = stage.value;
158
+ return {
159
+ stage,
160
+ value: value != null && Number.isFinite(value) && value >= 0 ? value : null
161
+ };
162
+ });
163
+ const accounted = counted.reduce((sum, entry) => sum + (entry.value ?? 0), 0);
164
+ const total = Math.max(scope, accounted);
165
+ const share = (value) => value / total;
166
+ const stages = counted.map(({ stage, value }) => ({
167
+ key: stage.key ?? stage.label,
168
+ label: stage.label,
169
+ value: value ?? 0,
170
+ missing: value == null,
171
+ tone: stage.tone ?? "active",
172
+ share: share(value ?? 0),
173
+ width: `${(share(value ?? 0) * 100).toFixed(4)}%`,
174
+ valueText: value == null ? "—" : formatters.formatValue(value),
175
+ shareText: formatters.formatShare(share(value ?? 0)),
176
+ implicit: false
177
+ }));
178
+ const unaccounted = total - accounted;
179
+ if (unaccounted > 0) stages.push({
180
+ key: "__remaining__",
181
+ label: labels.remaining,
182
+ value: unaccounted,
183
+ missing: false,
184
+ tone: "remaining",
185
+ share: share(unaccounted),
186
+ width: `${(share(unaccounted) * 100).toFixed(4)}%`,
187
+ valueText: formatters.formatValue(unaccounted),
188
+ shareText: formatters.formatShare(share(unaccounted)),
189
+ implicit: true
190
+ });
191
+ return stages;
192
+ }
193
+ /**
194
+ * Normalises the snapshots into a burn-up run: readings in order, the scope as
195
+ * it stood at each of them, the rate the work is actually being finished at,
196
+ * where that rate crosses the scope, and the verdict against the deadline.
197
+ * Returns `null` when there is nothing to divide — no reading carries a count
198
+ * and no `total` was declared.
199
+ */
200
+ function deriveProgressRun({ snapshots, total: declaredTotal, due: dueInput, rateWindow, projected, tolerance = 2, stages: stageInput = [], formatters, labels, timeZone }) {
201
+ const declared = declaredTotal != null && Number.isFinite(declaredTotal) && declaredTotal > 0 ? declaredTotal : null;
202
+ const points = snapshots.map((snapshot) => {
203
+ return {
204
+ t: resolveInstant(snapshot.date, timeZone),
205
+ completed: snapshot.completed != null && Number.isFinite(snapshot.completed) && snapshot.completed >= 0 ? snapshot.completed : null,
206
+ total: (snapshot.total != null && Number.isFinite(snapshot.total) && snapshot.total > 0 ? snapshot.total : null) ?? declared ?? 0
207
+ };
208
+ }).filter((point) => Number.isFinite(point.t)).sort((a, b) => a.t - b.t);
209
+ const observed = points.filter((point) => point.completed != null);
210
+ const latest = observed.at(-1) ?? null;
211
+ const scope = Math.max(latest?.total ?? declared ?? 0, latest?.completed ?? 0);
212
+ if (scope <= 0 || !latest) return null;
213
+ const completed = latest.completed;
214
+ const remaining = Math.max(0, scope - completed);
215
+ const completeRatio = completed / scope;
216
+ const done = completed >= scope;
217
+ const start = points[0]?.t ?? latest.t;
218
+ const openingScope = points[0]?.total ?? scope;
219
+ const due = dueInput == null ? null : resolveInstant(dueInput, timeZone);
220
+ const hasDue = due != null && Number.isFinite(due);
221
+ const windowStart = rateWindow != null && rateWindow > 0 ? latest.t - rateWindow * DAY_MS : start;
222
+ const inWindow = observed.filter((point) => point.t >= windowStart);
223
+ const first = inWindow.length >= 2 ? inWindow[0] : observed[0];
224
+ const insufficient = observed.length < 2;
225
+ let rate = null;
226
+ if (first && first !== latest) {
227
+ const days = (latest.t - first.t) / DAY_MS;
228
+ const perDay = days > 0 ? (completed - first.completed) / days : 0;
229
+ const daysLeft = hasDue ? (due - latest.t) / DAY_MS : null;
230
+ const requiredPerDay = daysLeft != null && daysLeft > 0 && remaining > 0 ? remaining / daysLeft : null;
231
+ rate = {
232
+ perDay,
233
+ days,
234
+ requiredPerDay,
235
+ text: formatters.formatRate(perDay),
236
+ requiredText: requiredPerDay == null ? null : formatters.formatRate(Math.ceil(requiredPerDay))
237
+ };
238
+ }
239
+ const previous = observed.at(-2) ?? null;
240
+ const stalled = !done && previous != null && completed - previous.completed <= 0;
241
+ let projection = null;
242
+ const explicit = projected == null ? null : resolveInstant(projected, timeZone);
243
+ const finishAt = done ? latest.t : explicit != null && Number.isFinite(explicit) ? explicit : rate && rate.perDay > 0 && !stalled ? Math.round(latest.t + remaining / rate.perDay * DAY_MS) : null;
244
+ if (finishAt != null) {
245
+ const daysLate = hasDue ? dayDiff(due, finishAt, timeZone) : 0;
246
+ const dateText = formatters.formatDay(finishAt);
247
+ const text = !hasDue ? dateText : daysLate > 0 ? labels.daysLate(daysLate) : daysLate < 0 ? labels.daysEarly(-daysLate) : labels.onDueDate;
248
+ projection = {
249
+ date: finishAt,
250
+ daysLate,
251
+ late: hasDue && daysLate > 0,
252
+ dateText,
253
+ text,
254
+ source: explicit != null && Number.isFinite(explicit) ? "explicit" : "observed"
255
+ };
256
+ }
257
+ let verdict = null;
258
+ if (done) verdict = "done";
259
+ else if (stalled) verdict = "stalled";
260
+ else if (projection && hasDue) verdict = projection.daysLate <= 0 ? "on-time" : projection.daysLate <= tolerance ? "at-risk" : "late";
261
+ const polarity = verdict === "done" || verdict === "on-time" ? "positive" : verdict === "late" || verdict === "stalled" ? "negative" : verdict === "at-risk" ? "negative" : "neutral";
262
+ const ideal = hasDue ? {
263
+ from: {
264
+ t: start,
265
+ y: 0
266
+ },
267
+ to: {
268
+ t: due,
269
+ y: scope
270
+ }
271
+ } : null;
272
+ const candidates = [
273
+ start,
274
+ latest.t,
275
+ ...hasDue ? [due] : [],
276
+ ...projection ? [projection.date] : []
277
+ ];
278
+ const bounds = {
279
+ min: Math.min(...candidates),
280
+ max: Math.max(...candidates)
281
+ };
282
+ return {
283
+ points,
284
+ observed,
285
+ start,
286
+ asOf: latest.t,
287
+ due: hasDue ? due : null,
288
+ scope,
289
+ openingScope,
290
+ openingScopeText: formatters.formatValue(openingScope),
291
+ scopeChanged: openingScope !== scope,
292
+ completed,
293
+ remaining,
294
+ completeRatio,
295
+ rate,
296
+ projection,
297
+ verdict,
298
+ polarity,
299
+ ideal,
300
+ bounds,
301
+ scopeText: formatters.formatValue(scope),
302
+ completedText: formatters.formatValue(completed),
303
+ remainingText: formatters.formatValue(remaining),
304
+ percentText: formatters.formatShare(completeRatio),
305
+ dueText: hasDue ? formatters.formatDay(due) : null,
306
+ asOfText: formatters.formatDay(latest.t),
307
+ done,
308
+ insufficient,
309
+ stalled,
310
+ stages: deriveProgressStages(stageInput, scope, formatters, labels)
311
+ };
312
+ }
313
+ /** The headline's big text and the line beneath it, per `headline`. */
314
+ function buildProgressHeadline(run, headline, labels) {
315
+ switch (headline) {
316
+ case "date": return {
317
+ big: run.projection ? run.projection.dateText : "—",
318
+ sub: labels.percentLine(run.percentText, labels)
319
+ };
320
+ case "remaining": return {
321
+ big: labels.remainingHeadline(run.remainingText),
322
+ sub: labels.percentLine(run.percentText, labels)
323
+ };
324
+ default: return {
325
+ big: run.percentText,
326
+ sub: labels.countLine(run.completedText, run.scopeText)
327
+ };
328
+ }
329
+ }
330
+ /**
331
+ * The table twin: every reading in order, labelled by its own date, with the
332
+ * count finished by then and the share it made of the scope as it stood — so
333
+ * a reader who cannot use the plot reads the same trajectory — then the run's
334
+ * own figures beneath.
335
+ */
336
+ function buildProgressReadingRows(run, formatters, labels) {
337
+ const rows = run.points.map((point, index) => ({
338
+ key: `reading-${index}`,
339
+ label: formatters.formatDay(point.t),
340
+ value: point.completed == null ? "—" : formatters.formatValue(point.completed),
341
+ share: point.completed == null || point.total <= 0 ? "—" : formatters.formatShare(point.completed / point.total)
342
+ }));
343
+ rows.push({
344
+ key: "__scope__",
345
+ label: labels.scope,
346
+ value: run.scopeText,
347
+ share: run.percentText,
348
+ summary: true
349
+ });
350
+ return rows;
351
+ }
352
+ /** The stage rows for the details table, when the consumer passed a breakdown. */
353
+ function buildProgressStageRows(run, labels) {
354
+ if (run.stages.length === 0) return [];
355
+ const rows = run.stages.map((stage) => ({
356
+ key: stage.key,
357
+ label: stage.label,
358
+ value: stage.valueText,
359
+ share: stage.missing ? "—" : stage.shareText,
360
+ stage
361
+ }));
362
+ rows.push({
363
+ key: "__total__",
364
+ label: labels.total,
365
+ value: run.scopeText,
366
+ share: run.percentText,
367
+ summary: true
368
+ });
369
+ return rows;
370
+ }
371
+ /** The headline group's explanation rows. */
372
+ function buildProgressExplanation(run, labels) {
373
+ return labels.explanation(run, labels);
374
+ }
375
+ /** The plot's own accessible label. */
376
+ function buildProgressChartDescription(name, run, labels) {
377
+ return labels.chartDescription(name, run, labels);
378
+ }
379
+ /** The whole card's accessible description. */
380
+ function buildProgressDescription(input) {
381
+ return input.labels.description(input);
382
+ }
383
+ /** The composed breakdown bar is one image, so its parts are named in one string. */
384
+ function buildStageBarLabel(name, run, labels) {
385
+ const parts = run.stages.map((stage) => `${stage.label} ${stage.shareText}`).join(", ");
386
+ return labels.stageBar(name, parts);
387
+ }
388
+ //#endregion
389
+ export { DEFAULT_PROGRESS_LABELS, buildProgressChartDescription, buildProgressDescription, buildProgressExplanation, buildProgressHeadline, buildProgressReadingRows, buildProgressStageRows, buildStageBarLabel, createProgressFormatters, dayDiff, deriveProgressRun, deriveProgressStages, deriveProgressState, resolveProgressLabels, zonedDayNumber };
@@ -0,0 +1,4 @@
1
+ import { CreateProgressFormattersInput, DEFAULT_PROGRESS_LABELS, DeriveProgressRunInput, DeriveProgressStateInput, ProgressCardLabels, ProgressCardSize, ProgressCardState, ProgressCardStatus, ProgressDescriptionInput, ProgressDetailRow, ProgressHeadline, ProgressPoint, ProgressPolarity, ProgressProjection, ProgressRate, ProgressRun, ProgressSnapshot, ProgressStage, ProgressStageInput, ProgressStageTone, ProgressTextFormatters, ProgressVerdict, buildProgressChartDescription, buildProgressDescription, buildProgressExplanation, buildProgressHeadline, buildProgressReadingRows, buildProgressStageRows, buildStageBarLabel, createProgressFormatters, dayDiff, deriveProgressRun, deriveProgressStages, deriveProgressState, resolveProgressLabels } from "./ProgressCard.model.js";
2
+ import { ProgressCard, ProgressCardProps } from "./ProgressCard.js";
3
+ import { PROGRESS_COMPLETED_DATASET, PROGRESS_DUE_DATASET, PROGRESS_FINISH_DATASET, PROGRESS_GAP_DATASET, PROGRESS_HOVER_PARAM, PROGRESS_IDEAL_DATASET, PROGRESS_LATEST_DATASET, PROGRESS_PROJECTION_DATASET, PROGRESS_SCOPE_DATASET, PROGRESS_X_TICKS_PARAM, ProgressBurnUpSpecOptions, buildProgressBurnUpSpec } from "./ProgressCard.chart.js";
4
+ export { type CreateProgressFormattersInput, DEFAULT_PROGRESS_LABELS, type DeriveProgressRunInput, type DeriveProgressStateInput, PROGRESS_COMPLETED_DATASET, PROGRESS_DUE_DATASET, PROGRESS_FINISH_DATASET, PROGRESS_GAP_DATASET, PROGRESS_HOVER_PARAM, PROGRESS_IDEAL_DATASET, PROGRESS_LATEST_DATASET, PROGRESS_PROJECTION_DATASET, PROGRESS_SCOPE_DATASET, PROGRESS_X_TICKS_PARAM, type ProgressBurnUpSpecOptions, ProgressCard, type ProgressCardLabels, type ProgressCardProps, type ProgressCardSize, type ProgressCardState, type ProgressCardStatus, type ProgressDescriptionInput, type ProgressDetailRow, type ProgressHeadline, type ProgressPoint, type ProgressPolarity, type ProgressProjection, type ProgressRate, type ProgressRun, type ProgressSnapshot, type ProgressStage, type ProgressStageInput, type ProgressStageTone, type ProgressTextFormatters, type ProgressVerdict, buildProgressBurnUpSpec, buildProgressChartDescription, buildProgressDescription, buildProgressExplanation, buildProgressHeadline, buildProgressReadingRows, buildProgressStageRows, buildStageBarLabel, createProgressFormatters, dayDiff, deriveProgressRun, deriveProgressStages, deriveProgressState, resolveProgressLabels };
@@ -0,0 +1,4 @@
1
+ import { PROGRESS_COMPLETED_DATASET, PROGRESS_DUE_DATASET, PROGRESS_FINISH_DATASET, PROGRESS_GAP_DATASET, PROGRESS_HOVER_PARAM, PROGRESS_IDEAL_DATASET, PROGRESS_LATEST_DATASET, PROGRESS_PROJECTION_DATASET, PROGRESS_SCOPE_DATASET, PROGRESS_X_TICKS_PARAM, buildProgressBurnUpSpec } from "./ProgressCard.chart.js";
2
+ import { DEFAULT_PROGRESS_LABELS, buildProgressChartDescription, buildProgressDescription, buildProgressExplanation, buildProgressHeadline, buildProgressReadingRows, buildProgressStageRows, buildStageBarLabel, createProgressFormatters, dayDiff, deriveProgressRun, deriveProgressStages, deriveProgressState, resolveProgressLabels } from "./ProgressCard.model.js";
3
+ import { ProgressCard } from "./ProgressCard.js";
4
+ export { DEFAULT_PROGRESS_LABELS, PROGRESS_COMPLETED_DATASET, PROGRESS_DUE_DATASET, PROGRESS_FINISH_DATASET, PROGRESS_GAP_DATASET, PROGRESS_HOVER_PARAM, PROGRESS_IDEAL_DATASET, PROGRESS_LATEST_DATASET, PROGRESS_PROJECTION_DATASET, PROGRESS_SCOPE_DATASET, PROGRESS_X_TICKS_PARAM, ProgressCard, buildProgressBurnUpSpec, buildProgressChartDescription, buildProgressDescription, buildProgressExplanation, buildProgressHeadline, buildProgressReadingRows, buildProgressStageRows, buildStageBarLabel, createProgressFormatters, dayDiff, deriveProgressRun, deriveProgressStages, deriveProgressState, resolveProgressLabels };
@@ -0,0 +1,45 @@
1
+ import { ChartTokens } from "../../lib/vega/tokens.js";
2
+ import { TopLevelSpec } from "vega-lite";
3
+ //#region src/components/ReportCard/ReportCard.chart.d.ts
4
+ /** The rows: `{ key, label, plotted, targetScore, tone, … }` per `ReportRow`, in display order. */
5
+ declare const REPORT_TABLE_DATASET = "table";
6
+ /** The grade ladder: `{ key, label, from, to, tone }` per `ReportBandSpan`, low to high. */
7
+ declare const REPORT_BANDS_DATASET = "bands";
8
+ /** One row `{ min, max }` — the plotted score domain, widened to hold every score. */
9
+ declare const REPORT_BOUNDS_DATASET = "bounds";
10
+ /** The plotted domain's ends, read from the `bounds` dataset. */
11
+ declare const REPORT_MIN_PARAM = "report_x_min";
12
+ declare const REPORT_MAX_PARAM = "report_x_max";
13
+ declare const REPORT_MAX_BAR_HEIGHT = 24;
14
+ /** How the grade bands behind the bars are painted. */
15
+ type ReportBandPaint = "neutral" | "status";
16
+ interface ReportBulletSpecOptions {
17
+ /** Bar thickness in pixels, capped at 24. Centered in each row's band. */
18
+ barHeight: number;
19
+ /** Initial plot height — rows × step. The live value rides the View API. */
20
+ height: number;
21
+ /** `neutral` (default) paints the ladder in one gray at four intensities; `status` paints each band in its own tone. */
22
+ bandPaint: ReportBandPaint;
23
+ /** Draw the per-row comparative tick where a criterion is expected to score. */
24
+ ticks: boolean;
25
+ /** Wire the tooltip channel on a transparent full-row target — a 12px bar is no pointer target. */
26
+ tooltip: boolean;
27
+ /** Tooltip titles. */
28
+ criterionTitle: string;
29
+ scoreTitle: string;
30
+ /** `arcNumber` grammar for scores (`number:0@en-US`). */
31
+ scoreFormat: string;
32
+ locale?: string;
33
+ }
34
+ /**
35
+ * Builds the bullet rows: the grade ladder as quiet full-height stripes, one
36
+ * bar per criterion from the scale's floor to its score (length on a shared
37
+ * position scale — Cleveland & McGill's channel for magnitude, Wilke's
38
+ * proportional ink), the bar painted with its grade's tone, a comparative
39
+ * tick where the criterion is expected to score, and a transparent full-row
40
+ * target carrying the tooltip. Every band has no padding, so the bar centers
41
+ * line up with the DOM rows that carry the names, scores and grades.
42
+ */
43
+ declare function buildReportBulletSpec(options: ReportBulletSpecOptions, tokens: ChartTokens): TopLevelSpec;
44
+ //#endregion
45
+ export { REPORT_BANDS_DATASET, REPORT_BOUNDS_DATASET, REPORT_MAX_BAR_HEIGHT, REPORT_MAX_PARAM, REPORT_MIN_PARAM, REPORT_TABLE_DATASET, ReportBandPaint, ReportBulletSpecOptions, buildReportBulletSpec };
@@ -0,0 +1,231 @@
1
+ import { arcVegaConfig } from "../../lib/vega/config.js";
2
+ //#region src/components/ReportCard/ReportCard.chart.ts
3
+ /** The rows: `{ key, label, plotted, targetScore, tone, … }` per `ReportRow`, in display order. */
4
+ const REPORT_TABLE_DATASET = "table";
5
+ /** The grade ladder: `{ key, label, from, to, tone }` per `ReportBandSpan`, low to high. */
6
+ const REPORT_BANDS_DATASET = "bands";
7
+ /** One row `{ min, max }` — the plotted score domain, widened to hold every score. */
8
+ const REPORT_BOUNDS_DATASET = "bounds";
9
+ /** The plotted domain's ends, read from the `bounds` dataset. */
10
+ const REPORT_MIN_PARAM = "report_x_min";
11
+ const REPORT_MAX_PARAM = "report_x_max";
12
+ const REPORT_MAX_BAR_HEIGHT = 24;
13
+ const TICK_OVERSHOOT_PX = 8;
14
+ const NEUTRAL_BAND_OPACITY = {
15
+ positive: .05,
16
+ neutral: .07,
17
+ warning: .12,
18
+ negative: .18
19
+ };
20
+ const STATUS_BAND_OPACITY = .14;
21
+ const ROW_DOMAIN_EXPR = `pluck(data('${REPORT_TABLE_DATASET}'), 'key')`;
22
+ /**
23
+ * A fill that follows the row's grade through its `tone`: the diverging ends
24
+ * for a passing and a failing grade, the degraded status hue for the band
25
+ * between them, the emphasis hue for a neutral grade and for anything
26
+ * ungraded — four token values in the spec, so the spec stays structural
27
+ * while the data decides. This is the one place status hues belong on a mark:
28
+ * the encoded value *is* the judgement.
29
+ */
30
+ function toneColor(tokens) {
31
+ return {
32
+ condition: [
33
+ {
34
+ test: "datum.tone === 'positive'",
35
+ value: tokens.divergingPositive
36
+ },
37
+ {
38
+ test: "datum.tone === 'warning'",
39
+ value: tokens.status.degraded
40
+ },
41
+ {
42
+ test: "datum.tone === 'negative'",
43
+ value: tokens.divergingNegative
44
+ }
45
+ ],
46
+ value: tokens.emphasis
47
+ };
48
+ }
49
+ /** The neutral ladder's intensity, worst band darkest. */
50
+ function neutralOpacity() {
51
+ return {
52
+ condition: [
53
+ {
54
+ test: "datum.tone === 'negative'",
55
+ value: NEUTRAL_BAND_OPACITY.negative
56
+ },
57
+ {
58
+ test: "datum.tone === 'warning'",
59
+ value: NEUTRAL_BAND_OPACITY.warning
60
+ },
61
+ {
62
+ test: "datum.tone === 'positive'",
63
+ value: NEUTRAL_BAND_OPACITY.positive
64
+ }
65
+ ],
66
+ value: NEUTRAL_BAND_OPACITY.neutral
67
+ };
68
+ }
69
+ /**
70
+ * Builds the bullet rows: the grade ladder as quiet full-height stripes, one
71
+ * bar per criterion from the scale's floor to its score (length on a shared
72
+ * position scale — Cleveland & McGill's channel for magnitude, Wilke's
73
+ * proportional ink), the bar painted with its grade's tone, a comparative
74
+ * tick where the criterion is expected to score, and a transparent full-row
75
+ * target carrying the tooltip. Every band has no padding, so the bar centers
76
+ * line up with the DOM rows that carry the names, scores and grades.
77
+ */
78
+ function buildReportBulletSpec(options, tokens) {
79
+ const { barHeight, height, bandPaint, ticks, tooltip, criterionTitle, scoreTitle, scoreFormat, locale } = options;
80
+ const thickness = Math.min(24, barHeight);
81
+ const xScale = {
82
+ domain: { expr: `[${REPORT_MIN_PARAM}, ${REPORT_MAX_PARAM}]` },
83
+ nice: false
84
+ };
85
+ const yField = {
86
+ field: "key",
87
+ type: "nominal",
88
+ sort: null,
89
+ axis: null,
90
+ scale: {
91
+ domain: { expr: ROW_DOMAIN_EXPR },
92
+ paddingInner: 0,
93
+ paddingOuter: 0
94
+ }
95
+ };
96
+ const scoreTooltip = { tooltip: [{
97
+ field: "label",
98
+ type: "nominal",
99
+ title: criterionTitle
100
+ }, {
101
+ field: "score",
102
+ type: "quantitative",
103
+ title: scoreTitle,
104
+ format: scoreFormat,
105
+ formatType: "arcNumber"
106
+ }] };
107
+ const layers = [
108
+ {
109
+ data: { name: REPORT_BOUNDS_DATASET },
110
+ mark: {
111
+ type: "rect",
112
+ fill: tokens.muted,
113
+ aria: false
114
+ },
115
+ encoding: {
116
+ x: {
117
+ field: "min",
118
+ type: "quantitative",
119
+ axis: null,
120
+ scale: xScale
121
+ },
122
+ x2: { field: "max" },
123
+ y: { value: 0 },
124
+ y2: { value: "height" }
125
+ }
126
+ },
127
+ {
128
+ data: { name: REPORT_BANDS_DATASET },
129
+ mark: {
130
+ type: "rect",
131
+ ...bandPaint === "status" ? { fillOpacity: STATUS_BAND_OPACITY } : { fill: tokens.context },
132
+ aria: false
133
+ },
134
+ encoding: {
135
+ x: {
136
+ field: "from",
137
+ type: "quantitative",
138
+ axis: null,
139
+ scale: xScale
140
+ },
141
+ x2: { field: "to" },
142
+ y: { value: 0 },
143
+ y2: { value: "height" },
144
+ ...bandPaint === "status" ? { fill: toneColor(tokens) } : { fillOpacity: neutralOpacity() }
145
+ }
146
+ },
147
+ {
148
+ data: { name: REPORT_TABLE_DATASET },
149
+ mark: {
150
+ type: "bar",
151
+ height: thickness,
152
+ cornerRadiusEnd: 0,
153
+ cornerRadiusTopRight: 2,
154
+ cornerRadiusBottomRight: 2,
155
+ aria: false
156
+ },
157
+ encoding: {
158
+ y: yField,
159
+ x: {
160
+ field: "plotted",
161
+ type: "quantitative",
162
+ axis: null,
163
+ scale: xScale
164
+ },
165
+ x2: { datum: { expr: REPORT_MIN_PARAM } },
166
+ fill: toneColor(tokens)
167
+ }
168
+ }
169
+ ];
170
+ if (ticks) layers.push({
171
+ data: { name: REPORT_TABLE_DATASET },
172
+ transform: [{ filter: "isValid(datum.targetScore)" }],
173
+ mark: {
174
+ type: "tick",
175
+ thickness: 2,
176
+ size: thickness + TICK_OVERSHOOT_PX,
177
+ color: tokens.foreground,
178
+ opacity: .75,
179
+ aria: false
180
+ },
181
+ encoding: {
182
+ y: yField,
183
+ x: {
184
+ field: "targetScore",
185
+ type: "quantitative",
186
+ axis: null,
187
+ scale: xScale
188
+ }
189
+ }
190
+ });
191
+ if (tooltip) layers.push({
192
+ data: { name: REPORT_TABLE_DATASET },
193
+ mark: {
194
+ type: "rect",
195
+ fill: "transparent",
196
+ aria: false
197
+ },
198
+ encoding: {
199
+ y: yField,
200
+ x: {
201
+ datum: { expr: REPORT_MIN_PARAM },
202
+ type: "quantitative",
203
+ axis: null,
204
+ scale: xScale
205
+ },
206
+ x2: { datum: { expr: REPORT_MAX_PARAM } },
207
+ ...scoreTooltip
208
+ }
209
+ });
210
+ const bounds = (field, fallback) => `length(data('${REPORT_BOUNDS_DATASET}')) ? data('${REPORT_BOUNDS_DATASET}')[0].${field} : ${fallback}`;
211
+ return {
212
+ $schema: "https://vega.github.io/schema/vega-lite/v6.json",
213
+ width: 240,
214
+ height,
215
+ autosize: { type: "none" },
216
+ padding: 0,
217
+ background: "transparent",
218
+ params: [{
219
+ name: REPORT_MIN_PARAM,
220
+ expr: bounds("min", 0)
221
+ }, {
222
+ name: REPORT_MAX_PARAM,
223
+ expr: bounds("max", 100)
224
+ }],
225
+ data: { name: REPORT_TABLE_DATASET },
226
+ layer: layers,
227
+ config: arcVegaConfig(tokens, { locale })
228
+ };
229
+ }
230
+ //#endregion
231
+ export { REPORT_BANDS_DATASET, REPORT_BOUNDS_DATASET, REPORT_MAX_BAR_HEIGHT, REPORT_MAX_PARAM, REPORT_MIN_PARAM, REPORT_TABLE_DATASET, buildReportBulletSpec };