@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,1223 @@
1
+ import { formatDate, formatNumber } from "../../lib/vega/formatters.js";
2
+ //#region src/components/ChartCard/ChartCard.model.ts
3
+ const NO_FIELDS = {
4
+ x: "none",
5
+ y: "none",
6
+ series: "none",
7
+ value: false,
8
+ size: false,
9
+ x2: false
10
+ };
11
+ const fields = (overrides) => ({
12
+ ...NO_FIELDS,
13
+ ...overrides
14
+ });
15
+ const XY = fields({
16
+ x: "required",
17
+ y: "required",
18
+ series: "optional"
19
+ });
20
+ /** Raw observations: a value, optionally grouped — Vega bins or aggregates them. */
21
+ const OBSERVATIONS = fields({
22
+ x: "optional",
23
+ y: "required",
24
+ series: "optional"
25
+ });
26
+ /**
27
+ * The registry every other part of the card reads: field-type inference, the
28
+ * legend, the description, the table twin and the spec builder all branch on
29
+ * this rather than on `type` directly, so adding a form is one entry plus one
30
+ * encoding.
31
+ */
32
+ const CHART_TYPES = {
33
+ bar: {
34
+ intent: "magnitude",
35
+ mark: "bar",
36
+ fields: XY,
37
+ polar: false,
38
+ square: false,
39
+ horizontal: false,
40
+ stack: false,
41
+ color: "single",
42
+ legend: "series",
43
+ aggregating: false,
44
+ minRows: 1,
45
+ name: "bar chart"
46
+ },
47
+ "bar-horizontal": {
48
+ intent: "ranking",
49
+ mark: "bar",
50
+ fields: XY,
51
+ polar: false,
52
+ square: false,
53
+ horizontal: true,
54
+ stack: false,
55
+ color: "single",
56
+ legend: "series",
57
+ aggregating: false,
58
+ minRows: 1,
59
+ name: "horizontal bar chart"
60
+ },
61
+ "bar-grouped": {
62
+ intent: "magnitude",
63
+ mark: "bar",
64
+ fields: fields({
65
+ x: "required",
66
+ y: "required",
67
+ series: "required"
68
+ }),
69
+ polar: false,
70
+ square: false,
71
+ horizontal: false,
72
+ stack: false,
73
+ color: "identity",
74
+ legend: "series",
75
+ aggregating: false,
76
+ minRows: 2,
77
+ name: "grouped bar chart"
78
+ },
79
+ "bar-stacked": {
80
+ intent: "part-to-whole",
81
+ mark: "bar",
82
+ fields: fields({
83
+ x: "required",
84
+ y: "required",
85
+ series: "required"
86
+ }),
87
+ polar: false,
88
+ square: false,
89
+ horizontal: false,
90
+ stack: "zero",
91
+ color: "identity",
92
+ legend: "series",
93
+ aggregating: false,
94
+ minRows: 2,
95
+ name: "stacked bar chart"
96
+ },
97
+ "bar-normalized": {
98
+ intent: "part-to-whole",
99
+ mark: "bar",
100
+ fields: fields({
101
+ x: "required",
102
+ y: "required",
103
+ series: "required"
104
+ }),
105
+ polar: false,
106
+ square: false,
107
+ horizontal: false,
108
+ stack: "normalize",
109
+ color: "identity",
110
+ legend: "series",
111
+ aggregating: false,
112
+ minRows: 2,
113
+ name: "100% stacked bar chart"
114
+ },
115
+ "bar-diverging": {
116
+ intent: "deviation",
117
+ mark: "bar",
118
+ fields: XY,
119
+ polar: false,
120
+ square: false,
121
+ horizontal: true,
122
+ stack: false,
123
+ color: "single",
124
+ legend: "none",
125
+ aggregating: false,
126
+ minRows: 1,
127
+ name: "diverging bar chart"
128
+ },
129
+ "bar-range": {
130
+ intent: "flow",
131
+ mark: "bar",
132
+ fields: fields({
133
+ x: "required",
134
+ y: "optional",
135
+ series: "required",
136
+ x2: true
137
+ }),
138
+ polar: false,
139
+ square: false,
140
+ horizontal: true,
141
+ stack: false,
142
+ color: "identity",
143
+ legend: "series",
144
+ aggregating: false,
145
+ minRows: 1,
146
+ name: "range bar chart"
147
+ },
148
+ lollipop: {
149
+ intent: "ranking",
150
+ mark: "rule",
151
+ fields: XY,
152
+ polar: false,
153
+ square: false,
154
+ horizontal: true,
155
+ stack: false,
156
+ color: "single",
157
+ legend: "none",
158
+ aggregating: false,
159
+ minRows: 1,
160
+ name: "lollipop chart"
161
+ },
162
+ waterfall: {
163
+ intent: "flow",
164
+ mark: "bar",
165
+ fields: fields({
166
+ x: "required",
167
+ y: "required"
168
+ }),
169
+ polar: false,
170
+ square: false,
171
+ horizontal: false,
172
+ stack: false,
173
+ color: "single",
174
+ legend: "none",
175
+ aggregating: false,
176
+ minRows: 2,
177
+ name: "waterfall chart"
178
+ },
179
+ line: {
180
+ intent: "change-over-time",
181
+ mark: "line",
182
+ fields: XY,
183
+ polar: false,
184
+ square: false,
185
+ horizontal: false,
186
+ stack: false,
187
+ color: "identity",
188
+ legend: "series",
189
+ aggregating: false,
190
+ minRows: 2,
191
+ name: "line chart"
192
+ },
193
+ "line-points": {
194
+ intent: "change-over-time",
195
+ mark: "line",
196
+ fields: XY,
197
+ polar: false,
198
+ square: false,
199
+ horizontal: false,
200
+ stack: false,
201
+ color: "identity",
202
+ legend: "series",
203
+ aggregating: false,
204
+ minRows: 2,
205
+ name: "line chart with point markers"
206
+ },
207
+ step: {
208
+ intent: "change-over-time",
209
+ mark: "line",
210
+ fields: XY,
211
+ polar: false,
212
+ square: false,
213
+ horizontal: false,
214
+ stack: false,
215
+ color: "identity",
216
+ legend: "series",
217
+ aggregating: false,
218
+ minRows: 2,
219
+ name: "step chart"
220
+ },
221
+ trail: {
222
+ intent: "change-over-time",
223
+ mark: "line",
224
+ fields: fields({
225
+ x: "required",
226
+ y: "required",
227
+ series: "optional",
228
+ size: true
229
+ }),
230
+ polar: false,
231
+ square: false,
232
+ horizontal: false,
233
+ stack: false,
234
+ color: "identity",
235
+ legend: "series",
236
+ aggregating: false,
237
+ minRows: 2,
238
+ name: "trail chart"
239
+ },
240
+ area: {
241
+ intent: "change-over-time",
242
+ mark: "area",
243
+ fields: XY,
244
+ polar: false,
245
+ square: false,
246
+ horizontal: false,
247
+ stack: false,
248
+ color: "identity",
249
+ legend: "series",
250
+ aggregating: false,
251
+ minRows: 2,
252
+ name: "area chart"
253
+ },
254
+ "area-stacked": {
255
+ intent: "part-to-whole",
256
+ mark: "area",
257
+ fields: fields({
258
+ x: "required",
259
+ y: "required",
260
+ series: "required"
261
+ }),
262
+ polar: false,
263
+ square: false,
264
+ horizontal: false,
265
+ stack: "zero",
266
+ color: "identity",
267
+ legend: "series",
268
+ aggregating: false,
269
+ minRows: 2,
270
+ name: "stacked area chart"
271
+ },
272
+ "area-normalized": {
273
+ intent: "part-to-whole",
274
+ mark: "area",
275
+ fields: fields({
276
+ x: "required",
277
+ y: "required",
278
+ series: "required"
279
+ }),
280
+ polar: false,
281
+ square: false,
282
+ horizontal: false,
283
+ stack: "normalize",
284
+ color: "identity",
285
+ legend: "series",
286
+ aggregating: false,
287
+ minRows: 2,
288
+ name: "100% stacked area chart"
289
+ },
290
+ streamgraph: {
291
+ intent: "change-over-time",
292
+ mark: "area",
293
+ fields: fields({
294
+ x: "required",
295
+ y: "required",
296
+ series: "required"
297
+ }),
298
+ polar: false,
299
+ square: false,
300
+ horizontal: false,
301
+ stack: "center",
302
+ color: "identity",
303
+ legend: "series",
304
+ aggregating: false,
305
+ minRows: 2,
306
+ name: "streamgraph"
307
+ },
308
+ slope: {
309
+ intent: "change-over-time",
310
+ mark: "line",
311
+ fields: fields({
312
+ x: "required",
313
+ y: "required",
314
+ series: "required"
315
+ }),
316
+ polar: false,
317
+ square: false,
318
+ horizontal: false,
319
+ stack: false,
320
+ color: "identity",
321
+ legend: "series",
322
+ aggregating: false,
323
+ minRows: 2,
324
+ name: "slope graph"
325
+ },
326
+ pie: {
327
+ intent: "part-to-whole",
328
+ mark: "arc",
329
+ fields: fields({
330
+ series: "required",
331
+ value: true
332
+ }),
333
+ polar: true,
334
+ square: true,
335
+ horizontal: false,
336
+ stack: "zero",
337
+ color: "identity",
338
+ legend: "series",
339
+ aggregating: false,
340
+ minRows: 2,
341
+ name: "pie chart"
342
+ },
343
+ donut: {
344
+ intent: "part-to-whole",
345
+ mark: "arc",
346
+ fields: fields({
347
+ series: "required",
348
+ value: true
349
+ }),
350
+ polar: true,
351
+ square: true,
352
+ horizontal: false,
353
+ stack: "zero",
354
+ color: "identity",
355
+ legend: "series",
356
+ aggregating: false,
357
+ minRows: 2,
358
+ name: "donut chart"
359
+ },
360
+ radial: {
361
+ intent: "part-to-whole",
362
+ mark: "arc",
363
+ fields: fields({
364
+ series: "required",
365
+ value: true
366
+ }),
367
+ polar: true,
368
+ square: true,
369
+ horizontal: false,
370
+ stack: "zero",
371
+ color: "identity",
372
+ legend: "series",
373
+ aggregating: false,
374
+ minRows: 2,
375
+ name: "radial plot"
376
+ },
377
+ scatter: {
378
+ intent: "correlation",
379
+ mark: "point",
380
+ fields: XY,
381
+ polar: false,
382
+ square: false,
383
+ horizontal: false,
384
+ stack: false,
385
+ color: "identity",
386
+ legend: "series",
387
+ aggregating: false,
388
+ minRows: 2,
389
+ name: "scatter plot"
390
+ },
391
+ bubble: {
392
+ intent: "correlation",
393
+ mark: "point",
394
+ fields: fields({
395
+ x: "required",
396
+ y: "required",
397
+ series: "optional",
398
+ size: true
399
+ }),
400
+ polar: false,
401
+ square: false,
402
+ horizontal: false,
403
+ stack: false,
404
+ color: "identity",
405
+ legend: "series",
406
+ aggregating: false,
407
+ minRows: 2,
408
+ name: "bubble plot"
409
+ },
410
+ "connected-scatter": {
411
+ intent: "correlation",
412
+ mark: "line",
413
+ fields: XY,
414
+ polar: false,
415
+ square: false,
416
+ horizontal: false,
417
+ stack: false,
418
+ color: "identity",
419
+ legend: "series",
420
+ aggregating: false,
421
+ minRows: 2,
422
+ name: "connected scatter plot"
423
+ },
424
+ "heatmap-binned": {
425
+ intent: "correlation",
426
+ mark: "rect",
427
+ fields: fields({
428
+ x: "required",
429
+ y: "required"
430
+ }),
431
+ polar: false,
432
+ square: false,
433
+ horizontal: false,
434
+ stack: false,
435
+ color: "magnitude",
436
+ legend: "ramp",
437
+ aggregating: true,
438
+ minRows: 2,
439
+ name: "2D histogram heatmap"
440
+ },
441
+ histogram: {
442
+ intent: "distribution",
443
+ mark: "bar",
444
+ fields: fields({
445
+ y: "required",
446
+ series: "optional"
447
+ }),
448
+ polar: false,
449
+ square: false,
450
+ horizontal: false,
451
+ stack: "zero",
452
+ color: "identity",
453
+ legend: "series",
454
+ aggregating: true,
455
+ minRows: 2,
456
+ name: "histogram"
457
+ },
458
+ density: {
459
+ intent: "distribution",
460
+ mark: "area",
461
+ fields: fields({
462
+ y: "required",
463
+ series: "optional"
464
+ }),
465
+ polar: false,
466
+ square: false,
467
+ horizontal: false,
468
+ stack: false,
469
+ color: "identity",
470
+ legend: "series",
471
+ aggregating: true,
472
+ minRows: 3,
473
+ name: "density plot"
474
+ },
475
+ boxplot: {
476
+ intent: "distribution",
477
+ mark: "boxplot",
478
+ fields: OBSERVATIONS,
479
+ polar: false,
480
+ square: false,
481
+ horizontal: false,
482
+ stack: false,
483
+ color: "identity",
484
+ legend: "series",
485
+ aggregating: true,
486
+ minRows: 2,
487
+ name: "box plot"
488
+ },
489
+ violin: {
490
+ intent: "distribution",
491
+ mark: "area",
492
+ fields: fields({
493
+ x: "required",
494
+ y: "required",
495
+ series: "optional"
496
+ }),
497
+ polar: false,
498
+ square: false,
499
+ horizontal: false,
500
+ stack: "center",
501
+ color: "identity",
502
+ legend: "series",
503
+ aggregating: true,
504
+ minRows: 3,
505
+ name: "violin plot"
506
+ },
507
+ strip: {
508
+ intent: "distribution",
509
+ mark: "tick",
510
+ fields: OBSERVATIONS,
511
+ polar: false,
512
+ square: false,
513
+ horizontal: false,
514
+ stack: false,
515
+ color: "identity",
516
+ legend: "series",
517
+ aggregating: false,
518
+ minRows: 1,
519
+ name: "strip plot"
520
+ },
521
+ dot: {
522
+ intent: "distribution",
523
+ mark: "point",
524
+ fields: fields({
525
+ x: "required",
526
+ y: "required",
527
+ series: "optional"
528
+ }),
529
+ polar: false,
530
+ square: false,
531
+ horizontal: false,
532
+ stack: false,
533
+ color: "identity",
534
+ legend: "series",
535
+ aggregating: false,
536
+ minRows: 1,
537
+ name: "dot plot"
538
+ },
539
+ errorbar: {
540
+ intent: "distribution",
541
+ mark: "rule",
542
+ fields: OBSERVATIONS,
543
+ polar: false,
544
+ square: false,
545
+ horizontal: false,
546
+ stack: false,
547
+ color: "identity",
548
+ legend: "series",
549
+ aggregating: true,
550
+ minRows: 2,
551
+ name: "error bar chart"
552
+ },
553
+ errorband: {
554
+ intent: "distribution",
555
+ mark: "band",
556
+ fields: OBSERVATIONS,
557
+ polar: false,
558
+ square: false,
559
+ horizontal: false,
560
+ stack: false,
561
+ color: "identity",
562
+ legend: "series",
563
+ aggregating: true,
564
+ minRows: 2,
565
+ name: "line chart with a confidence band"
566
+ },
567
+ heatmap: {
568
+ intent: "magnitude",
569
+ mark: "rect",
570
+ fields: fields({
571
+ x: "required",
572
+ y: "required",
573
+ value: true
574
+ }),
575
+ polar: false,
576
+ square: false,
577
+ horizontal: false,
578
+ stack: false,
579
+ color: "magnitude",
580
+ legend: "ramp",
581
+ aggregating: false,
582
+ minRows: 1,
583
+ name: "heatmap"
584
+ },
585
+ punchcard: {
586
+ intent: "magnitude",
587
+ mark: "point",
588
+ fields: fields({
589
+ x: "required",
590
+ y: "required",
591
+ value: true
592
+ }),
593
+ polar: false,
594
+ square: false,
595
+ horizontal: false,
596
+ stack: false,
597
+ color: "magnitude",
598
+ legend: "ramp",
599
+ aggregating: false,
600
+ minRows: 1,
601
+ name: "punch card"
602
+ },
603
+ radar: {
604
+ intent: "correlation",
605
+ mark: "line",
606
+ fields: fields({
607
+ x: "required",
608
+ y: "required",
609
+ series: "optional"
610
+ }),
611
+ polar: true,
612
+ square: false,
613
+ horizontal: false,
614
+ stack: false,
615
+ color: "identity",
616
+ legend: "series",
617
+ aggregating: false,
618
+ minRows: 3,
619
+ name: "radar chart"
620
+ }
621
+ };
622
+ /** Every form, in the docs' own order — the docs table and the type control read this. */
623
+ const CHART_TYPE_ORDER = Object.keys(CHART_TYPES);
624
+ const DEFAULT_CHART_LABELS = {
625
+ noData: "No data",
626
+ noMatch: "No data matches the current filters",
627
+ clearFilters: "Clear filters",
628
+ error: "Could not load this chart",
629
+ retry: "Retry",
630
+ insufficient: "Not enough data to draw this chart",
631
+ stale: "Stale",
632
+ legendGroup: (name) => `Series shown in ${name}`,
633
+ showSeries: (label) => `Show ${label}`,
634
+ hideSeries: (label) => `Hide ${label}`,
635
+ other: "Other",
636
+ showTable: (name) => `Show the data behind ${name}`,
637
+ hideTable: (name) => `Hide the data behind ${name}`,
638
+ tableCaption: (name) => `Every value plotted in ${name}`,
639
+ tableSeries: "Series",
640
+ tableValue: "Value",
641
+ tableShare: "Share",
642
+ tableTruncated: (shown, total) => `First ${shown} of ${total} rows.`,
643
+ chartOf: (form, measure, dimension) => `${form} of ${measure} by ${dimension}`,
644
+ describeSeries: (count, names) => `${count} series: ${names}.`,
645
+ describeRange: (from, to) => `From ${from} to ${to}.`,
646
+ describeExtremes: (maxLabel, maxValue, minLabel, minValue) => `Highest ${maxLabel} at ${maxValue}; lowest ${minLabel} at ${minValue}.`,
647
+ describeTotal: (total) => `Total ${total}.`,
648
+ describeRamp: (low, high) => `Colour runs from ${low} to ${high}.`,
649
+ describeAggregate: (op, measure) => `${measure} aggregated by ${op}.`,
650
+ describeFolded: (count, label) => `${count} smaller series folded into ${label}.`,
651
+ describeHidden: (count) => `${count} series hidden.`,
652
+ describeStale: "This data is out of date.",
653
+ describeLoading: (name) => `${name} is loading.`,
654
+ describeEmpty: (name) => `${name} has no data.`,
655
+ describeError: (name) => `${name} could not be loaded.`
656
+ };
657
+ function resolveChartLabels(overrides) {
658
+ return overrides ? {
659
+ ...DEFAULT_CHART_LABELS,
660
+ ...overrides
661
+ } : DEFAULT_CHART_LABELS;
662
+ }
663
+ /**
664
+ * The state machine every card in this package shares, plus `insufficient`:
665
+ * rows arrived but the form needs more of them than it got.
666
+ */
667
+ function deriveChartState({ status, hasChart, hasData, filtered }) {
668
+ if (status === "error") return "error";
669
+ if (status === "loading") return hasData ? "refreshing" : "loading";
670
+ if (hasChart) return "ready";
671
+ if (hasData) return "insufficient";
672
+ return filtered ? "no-match" : "empty";
673
+ }
674
+ /**
675
+ * Whether the form has enough to say something. Beyond the row floor in the
676
+ * registry, the forms Vega aggregates need several observations INSIDE a
677
+ * group: a box plot of one reading per category has no interquartile range to
678
+ * draw, and Vega then logs an empty-extent warning rather than a chart.
679
+ */
680
+ function hasEnoughData(type, summary) {
681
+ if (!summary) return false;
682
+ const info = CHART_TYPES[type];
683
+ if ((summary.hasMeasure ? summary.count : summary.rows.length) < info.minRows) return false;
684
+ if (!info.aggregating) return true;
685
+ if (summary.count < 4) return false;
686
+ const perGroup = /* @__PURE__ */ new Map();
687
+ for (const row of summary.rows) {
688
+ if (row.v == null) continue;
689
+ const key = JSON.stringify([row.s ?? "", row.x ?? ""]);
690
+ perGroup.set(key, (perGroup.get(key) ?? 0) + 1);
691
+ }
692
+ for (const count of perGroup.values()) if (count >= 2) return true;
693
+ return false;
694
+ }
695
+ /**
696
+ * The field names a form needs but did not get. A card missing one of these
697
+ * cannot draw at all, so it shows the insufficient state and says which field
698
+ * is absent rather than rendering an empty frame.
699
+ */
700
+ function missingChartFields(type, provided) {
701
+ const { fields } = CHART_TYPES[type];
702
+ const missing = [];
703
+ if (fields.x === "required" && !provided.x) missing.push("x");
704
+ if (fields.y === "required" && !provided.y) missing.push("y");
705
+ if (fields.series === "required" && !provided.series) missing.push("series");
706
+ if (fields.value && !provided.value && !provided.y) missing.push("value");
707
+ if (fields.x2 && !provided.x2) missing.push("x2");
708
+ return missing;
709
+ }
710
+ const ISO_DATE = /^\d{4}-\d{2}(-\d{2})?([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
711
+ function looksTemporal(value) {
712
+ if (value instanceof Date) return !Number.isNaN(value.getTime());
713
+ return typeof value === "string" && ISO_DATE.test(value);
714
+ }
715
+ /**
716
+ * Infers a field's Vega-Lite type from the first rows that actually carry it:
717
+ * `Date` objects and ISO-8601 strings are temporal, numbers quantitative,
718
+ * everything else nominal. Returns `undefined` for a field no row defines, so
719
+ * a caller can tell "not present" from "nominal".
720
+ */
721
+ function inferFieldType(rows, field) {
722
+ if (!rows || !field) return void 0;
723
+ let seen = 0;
724
+ let temporal = 0;
725
+ let numeric = 0;
726
+ for (const row of rows) {
727
+ const value = row[field];
728
+ if (value == null) continue;
729
+ seen += 1;
730
+ if (looksTemporal(value)) temporal += 1;
731
+ else if (typeof value === "number") numeric += 1;
732
+ if (seen >= 50) break;
733
+ }
734
+ if (seen === 0) return void 0;
735
+ if (temporal === seen) return "temporal";
736
+ if (numeric === seen) return "quantitative";
737
+ return "nominal";
738
+ }
739
+ /** The key the folded tail takes. Never collides with a consumer key, which is a field value. */
740
+ const CHART_OTHER_KEY = "__other__";
741
+ function toNumber(value) {
742
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
743
+ if (typeof value === "string" && value.trim() !== "") {
744
+ const parsed = Number(value);
745
+ return Number.isFinite(parsed) ? parsed : null;
746
+ }
747
+ return null;
748
+ }
749
+ /**
750
+ * Coerces a position to what Vega can plot. A temporal field becomes epoch
751
+ * MILLISECONDS, because a spec's `format.parse` runs only for the data the
752
+ * spec itself carries — rows pushed through `view.data()` after the parse
753
+ * arrive verbatim, so an ISO string would reach a temporal scale as text and
754
+ * the marks would silently not draw. The sibling cards coerce the same way.
755
+ */
756
+ function normalizePosition(value, type) {
757
+ if (value == null) return value;
758
+ if (type !== "temporal") return value;
759
+ const time = value instanceof Date ? value.getTime() : new Date(String(value)).getTime();
760
+ return Number.isNaN(time) ? null : time;
761
+ }
762
+ function xKey(value) {
763
+ if (value instanceof Date) return `d:${value.getTime()}`;
764
+ return `${typeof value}:${String(value)}`;
765
+ }
766
+ /**
767
+ * Picks the paint for series `index` under the resolved colour job. Identity
768
+ * slots are assigned by the series' place in the consumer's order — never by
769
+ * rank — so hiding or re-sorting can never repaint the survivors. Past the
770
+ * theme's identity budget the ramp takes over: repeating a slot would make two
771
+ * series look like one, and a ramp at least stays ordered and separable.
772
+ */
773
+ function chartSeriesColorAt(mode, index, total, tokens) {
774
+ if (mode === "single") return tokens.emphasis;
775
+ if (mode === "emphasis") return index === 0 ? tokens.emphasis : tokens.context;
776
+ if (mode === "categorical") {
777
+ const slots = tokens.category.length;
778
+ return tokens.category[index % slots];
779
+ }
780
+ const steps = tokens.sequential.length;
781
+ if (total <= 1) return tokens.emphasis;
782
+ const first = total >= steps ? 0 : Math.max(0, steps - total);
783
+ const span = steps - 1 - first;
784
+ const at = first + Math.round(index / (total - 1) * span);
785
+ return tokens.sequential[Math.min(steps - 1, Math.max(0, at))];
786
+ }
787
+ /**
788
+ * Resolves the colour job from the prop, the series count and the theme's own
789
+ * identity budget (`chart-1…N`, three hues on the stock theme). A form whose
790
+ * colour encodes a MEASURE — a heatmap, a punch card — always resolves to
791
+ * `magnitude`, whatever the prop says: colour has one job per chart, and there
792
+ * it is already spoken for.
793
+ */
794
+ function resolveChartColorMode(requested, type, seriesCount, slots) {
795
+ if (CHART_TYPES[type].color === "magnitude") return "magnitude";
796
+ if (seriesCount <= 1) return "single";
797
+ if (requested === "categorical") return seriesCount <= slots ? "categorical" : "sequential";
798
+ if (requested === "sequential") return "sequential";
799
+ if (requested === "emphasis") return "emphasis";
800
+ return seriesCount <= slots ? "categorical" : "sequential";
801
+ }
802
+ const DAY_MS = 864e5;
803
+ /** Infers the period of a temporal axis from the closest spacing its values show. */
804
+ function inferGranularity(values) {
805
+ const times = values.map((value) => value instanceof Date ? value.getTime() : typeof value === "number" ? value : Date.parse(String(value))).filter((time) => Number.isFinite(time)).sort((a, b) => a - b);
806
+ if (times.length < 2) return "day";
807
+ let smallest = Infinity;
808
+ for (let at = 1; at < times.length; at += 1) {
809
+ const gap = times[at] - times[at - 1];
810
+ if (gap > 0 && gap < smallest) smallest = gap;
811
+ }
812
+ if (!Number.isFinite(smallest)) return "day";
813
+ if (smallest < DAY_MS) return "datetime";
814
+ if (smallest >= DAY_MS * 300) return "year";
815
+ if (smallest >= DAY_MS * 27) return "month";
816
+ return "day";
817
+ }
818
+ /**
819
+ * Turns the consumer's records into the long-form rows the plot draws: series
820
+ * keys and labels resolved (a series is one distinct value of the `series`
821
+ * field, in first-seen order), the measure coerced to a finite number or
822
+ * `null`, a paint baked into every row, the tail folded, the x categories
823
+ * optionally ranked, and the waterfall's running total computed here rather
824
+ * than in Vega so the table twin and the tooltip can show the same feet and
825
+ * heads the bars stand on.
826
+ *
827
+ * Returns `null` when there is nothing to normalise.
828
+ */
829
+ function normalizeChartData(data, options) {
830
+ if (!data) return null;
831
+ const info = CHART_TYPES[options.type];
832
+ const { x: xField, y: yField, series: seriesField, value: valueField, size: sizeField, x2: x2Field, visible, maxSeries, otherLabel, sort = "none", colorMode, tokens } = options;
833
+ const measureField = info.fields.value ? valueField ?? yField : yField;
834
+ const xType = options.xType ?? inferFieldType(data, xField) ?? "nominal";
835
+ const yType = options.yType ?? inferFieldType(data, yField) ?? "quantitative";
836
+ const order = [];
837
+ const seen = /* @__PURE__ */ new Set();
838
+ if (seriesField) for (const record of data) {
839
+ const raw = record[seriesField];
840
+ if (raw == null) continue;
841
+ const key = String(raw);
842
+ if (seen.has(key)) continue;
843
+ seen.add(key);
844
+ order.push(key);
845
+ }
846
+ const singleSeries = order.length === 0;
847
+ const keys = singleSeries ? [""] : order;
848
+ const totals = new Map(keys.map((key) => [key, 0]));
849
+ const counts = new Map(keys.map((key) => [key, 0]));
850
+ for (const record of data) {
851
+ const key = singleSeries ? "" : String(record[seriesField] ?? "");
852
+ if (!totals.has(key)) continue;
853
+ const measure = toNumber(measureField ? record[measureField] : null);
854
+ if (measure == null) continue;
855
+ totals.set(key, (totals.get(key) ?? 0) + measure);
856
+ counts.set(key, (counts.get(key) ?? 0) + 1);
857
+ }
858
+ let kept = keys;
859
+ let foldedKeys = /* @__PURE__ */ new Set();
860
+ if (maxSeries != null && maxSeries > 0 && keys.length > maxSeries) {
861
+ const ranked = [...keys].sort((a, b) => (totals.get(b) ?? 0) - (totals.get(a) ?? 0));
862
+ const survivors = new Set(ranked.slice(0, Math.max(1, maxSeries - 1)));
863
+ foldedKeys = new Set(keys.filter((key) => !survivors.has(key)));
864
+ kept = keys.filter((key) => survivors.has(key));
865
+ if (foldedKeys.size > 0) kept = [...kept, CHART_OTHER_KEY];
866
+ }
867
+ const labelFor = (key) => key === "__other__" ? otherLabel : key === "" ? "" : key;
868
+ const infos = kept.map((key, index) => ({
869
+ key,
870
+ label: labelFor(key),
871
+ color: chartSeriesColorAt(colorMode, index, kept.length, tokens),
872
+ index,
873
+ total: key === "__other__" ? [...foldedKeys].reduce((sum, folded) => sum + (totals.get(folded) ?? 0), 0) : totals.get(key) ?? 0,
874
+ count: key === "__other__" ? [...foldedKeys].reduce((sum, folded) => sum + (counts.get(folded) ?? 0), 0) : counts.get(key) ?? 0
875
+ }));
876
+ const byKey = new Map(infos.map((entry) => [entry.key, entry]));
877
+ const known = new Set(infos.map((entry) => entry.key));
878
+ const requested = visible?.filter((key) => known.has(key));
879
+ const shown = requested && requested.length > 0 ? new Set(requested) : known;
880
+ const rows = [];
881
+ const xSeen = /* @__PURE__ */ new Map();
882
+ let total = 0;
883
+ let count = 0;
884
+ let min = null;
885
+ let max = null;
886
+ for (const record of data) {
887
+ const rawKey = singleSeries ? "" : String(record[seriesField] ?? "");
888
+ const key = foldedKeys.has(rawKey) ? CHART_OTHER_KEY : rawKey;
889
+ const entry = byKey.get(key);
890
+ if (!entry) continue;
891
+ const xValue = xField ? normalizePosition(record[xField], xType) : void 0;
892
+ if (xField && xValue != null) {
893
+ const seenKey = xKey(xValue);
894
+ if (!xSeen.has(seenKey)) xSeen.set(seenKey, xValue);
895
+ }
896
+ let measure = toNumber(measureField ? record[measureField] : null);
897
+ if (measure == null && info.fields.x2 && x2Field && xField) {
898
+ const from = toNumber(xValue);
899
+ const to = toNumber(normalizePosition(record[x2Field], xType));
900
+ if (from != null && to != null) measure = to - from;
901
+ }
902
+ const yRaw = yField ? record[yField] : void 0;
903
+ const yValue = yType === "quantitative" ? toNumber(yRaw) : normalizePosition(yRaw, yType);
904
+ if (!shown.has(key)) continue;
905
+ const row = {
906
+ ...xField ? { x: xValue } : {},
907
+ ...x2Field && info.fields.x2 ? { x2: normalizePosition(record[x2Field], xType) } : {},
908
+ ...yField ? { y: yValue ?? null } : {},
909
+ v: measure,
910
+ ...sizeField && info.fields.size ? { z: toNumber(record[sizeField]) } : {},
911
+ s: key,
912
+ n: entry.label,
913
+ c: entry.color,
914
+ i: entry.index,
915
+ o: rows.length
916
+ };
917
+ rows.push(row);
918
+ if (measure != null) {
919
+ total += measure;
920
+ count += 1;
921
+ const label = [entry.label, xField ? String(xValue ?? "") : ""].filter(Boolean).join(" ") || String(xValue ?? "");
922
+ if (!max || measure > max.value) max = {
923
+ label,
924
+ value: measure
925
+ };
926
+ if (!min || measure < min.value) min = {
927
+ label,
928
+ value: measure
929
+ };
930
+ }
931
+ }
932
+ if (rows.length === 0 && infos.every((entry) => entry.count === 0) && data.length === 0) return null;
933
+ let xValues = [...xSeen.values()];
934
+ if (sort !== "none" && xField && xType !== "quantitative") {
935
+ const byX = /* @__PURE__ */ new Map();
936
+ for (const row of rows) {
937
+ if (row.v == null) continue;
938
+ const seenKey = xKey(row.x);
939
+ byX.set(seenKey, (byX.get(seenKey) ?? 0) + row.v);
940
+ }
941
+ xValues = xValues.sort((a, b) => {
942
+ const left = byX.get(xKey(a)) ?? 0;
943
+ const right = byX.get(xKey(b)) ?? 0;
944
+ return sort === "desc" ? right - left : left - right;
945
+ });
946
+ }
947
+ if (options.type === "waterfall") {
948
+ let running = 0;
949
+ for (const row of rows) {
950
+ const step = row.v ?? 0;
951
+ row.lo = running;
952
+ running += step;
953
+ row.hi = running;
954
+ row.d = step > 0 ? "up" : step < 0 ? "down" : "total";
955
+ row.c = step < 0 ? tokens.divergingNegative : tokens.divergingPositive;
956
+ }
957
+ }
958
+ if (options.type === "radar") {
959
+ const at = new Map(xValues.map((value, index) => [xKey(value), index]));
960
+ const outer = max && max.value > 0 ? max.value : 1;
961
+ for (const row of rows) {
962
+ const index = at.get(xKey(row.x)) ?? 0;
963
+ const angle = 2 * Math.PI * index / Math.max(1, xValues.length);
964
+ const unit = Math.max(0, row.v ?? 0) / outer;
965
+ row.px = Math.sin(angle) * unit;
966
+ row.py = Math.cos(angle) * unit;
967
+ row.ai = index;
968
+ }
969
+ }
970
+ const domain = min && max ? [min.value, max.value] : null;
971
+ return {
972
+ rows,
973
+ series: infos,
974
+ count,
975
+ hasMeasure: measureField != null || info.fields.x2 === true && x2Field != null,
976
+ xValues,
977
+ xType,
978
+ yType,
979
+ domain,
980
+ total,
981
+ extremes: min && max ? {
982
+ maxLabel: max.label,
983
+ maxValue: max.value,
984
+ minLabel: min.label,
985
+ minValue: min.value
986
+ } : null,
987
+ folded: foldedKeys.size,
988
+ colorMode,
989
+ granularity: xType === "temporal" ? inferGranularity(xValues) : "day"
990
+ };
991
+ }
992
+ /** Fractions of the outer ring a radar's web is drawn at. */
993
+ const RADAR_RING_FRACTIONS = [
994
+ .25,
995
+ .5,
996
+ .75,
997
+ 1
998
+ ];
999
+ /** Where a radar's axis labels sit, as a fraction of the outer ring. */
1000
+ const RADAR_LABEL_RADIUS = 1.13;
1001
+ /**
1002
+ * The radar's grid: one row per axis per ring, on the same unit plane the
1003
+ * polygons are drawn on. Built here rather than in the spec because the spec
1004
+ * must stay structural — and because a ring then stands for a real value the
1005
+ * description can name, not just a fraction of the frame.
1006
+ */
1007
+ function buildRadarWebRows(summary) {
1008
+ if (!summary || summary.xValues.length === 0) return [];
1009
+ const outer = summary.extremes && summary.extremes.maxValue > 0 ? summary.extremes.maxValue : 1;
1010
+ const count = summary.xValues.length;
1011
+ const web = [];
1012
+ summary.xValues.forEach((x, index) => {
1013
+ const angle = 2 * Math.PI * index / count;
1014
+ const sin = Math.sin(angle);
1015
+ const cos = Math.cos(angle);
1016
+ for (const k of [0, ...RADAR_RING_FRACTIONS]) web.push({
1017
+ x,
1018
+ ai: index,
1019
+ k,
1020
+ px: sin * k,
1021
+ py: cos * k,
1022
+ lx: sin * RADAR_LABEL_RADIUS,
1023
+ ly: cos * RADAR_LABEL_RADIUS,
1024
+ r: outer * k
1025
+ });
1026
+ });
1027
+ return web;
1028
+ }
1029
+ /**
1030
+ * Buckets a magnitude ramp for the legend row. Vega owns the continuous scale
1031
+ * on the plot; this is the reader's key to it, and — like the reference
1032
+ * heatmap's HIGH / MODERATE / LOW row — it is the only way a colour-encoded
1033
+ * measure is legible without hovering.
1034
+ */
1035
+ function buildRampBuckets(domain, tokens, format, steps = 4) {
1036
+ if (!domain) return [];
1037
+ const [low, high] = domain;
1038
+ const ramp = tokens.sequential;
1039
+ const count = Math.max(2, Math.min(steps, ramp.length));
1040
+ if (!(high > low)) return [{
1041
+ color: ramp[ramp.length - 1],
1042
+ from: low,
1043
+ to: high,
1044
+ label: format(low)
1045
+ }];
1046
+ const width = (high - low) / count;
1047
+ return Array.from({ length: count }, (_, at) => {
1048
+ const from = low + width * at;
1049
+ const to = at === count - 1 ? high : low + width * (at + 1);
1050
+ return {
1051
+ color: ramp[Math.round(at / (count - 1) * (ramp.length - 1))],
1052
+ from,
1053
+ to,
1054
+ label: `${format(from)}–${format(to)}`
1055
+ };
1056
+ });
1057
+ }
1058
+ /**
1059
+ * One set of formatters for the axis, the tooltip, the legend, the table and
1060
+ * the description, so the same value never rounds two ways (Few's precision
1061
+ * pitfall).
1062
+ */
1063
+ function createChartFormatters({ format = "compact", locale, timeZone, formatValue, xType, yType, granularity }) {
1064
+ const value = (input) => {
1065
+ if (input == null || !Number.isFinite(input)) return "—";
1066
+ return formatValue ? formatValue(input) : formatNumber(input, format, locale);
1067
+ };
1068
+ const position = (type) => (input) => {
1069
+ if (input == null) return "—";
1070
+ if (type === "temporal") {
1071
+ const date = input instanceof Date ? input : new Date(typeof input === "number" ? input : String(input));
1072
+ return Number.isNaN(date.getTime()) ? "—" : formatDate(date, {
1073
+ granularity,
1074
+ locale,
1075
+ timeZone
1076
+ });
1077
+ }
1078
+ if (type === "quantitative") {
1079
+ const numeric = toNumber(input);
1080
+ return numeric == null ? "—" : formatNumber(numeric, "compact", locale);
1081
+ }
1082
+ return String(input);
1083
+ };
1084
+ return {
1085
+ formatValue: value,
1086
+ formatX: position(xType),
1087
+ formatY: position(yType),
1088
+ formatShare: (input) => input == null || !Number.isFinite(input) ? "—" : formatNumber(input, "percent:1", locale)
1089
+ };
1090
+ }
1091
+ /**
1092
+ * The card's own accessible description (the figure's `aria-describedby`
1093
+ * text): what the chart is, what it covers, where its extremes are, and what
1094
+ * has been folded, hidden or aggregated away. Amy Cesal's alt-text formula —
1095
+ * chart type, what data, why it is here — generated from the props, because a
1096
+ * card whose face carries no text has nothing else for a screen reader to read.
1097
+ */
1098
+ function buildChartDescription({ name, type, state, summary, visibleKeys, formatters, labels, measureName, dimensionName, aggregate, stale }) {
1099
+ if (state === "loading") return labels.describeLoading(name);
1100
+ if (state === "error") return labels.describeError(name);
1101
+ if (state === "empty" || state === "no-match") return labels.describeEmpty(name);
1102
+ const info = CHART_TYPES[type];
1103
+ const parts = [`${name}: ${labels.chartOf(info.name, measureName, dimensionName)}.`];
1104
+ if (state === "insufficient") {
1105
+ parts.push(labels.insufficient + ".");
1106
+ return parts.join(" ");
1107
+ }
1108
+ if (!summary) return parts.join(" ");
1109
+ if (aggregate) parts.push(labels.describeAggregate(aggregate, measureName));
1110
+ const named = summary.series.filter((entry) => entry.label !== "");
1111
+ if (named.length > 1) parts.push(labels.describeSeries(named.length, named.map((entry) => entry.label).join(", ")));
1112
+ if (summary.xValues.length > 1) parts.push(labels.describeRange(formatters.formatX(summary.xValues[0]), formatters.formatX(summary.xValues[summary.xValues.length - 1])));
1113
+ if (info.color === "magnitude" && summary.domain) parts.push(labels.describeRamp(formatters.formatValue(summary.domain[0]), formatters.formatValue(summary.domain[1])));
1114
+ if (summary.extremes) parts.push(labels.describeExtremes(summary.extremes.maxLabel, formatters.formatValue(summary.extremes.maxValue), summary.extremes.minLabel, formatters.formatValue(summary.extremes.minValue)));
1115
+ if (info.intent === "part-to-whole" && summary.count > 0) parts.push(labels.describeTotal(formatters.formatValue(summary.total)));
1116
+ if (summary.folded > 0) parts.push(labels.describeFolded(summary.folded, labels.other));
1117
+ const hidden = summary.series.length - visibleKeys.length;
1118
+ if (hidden > 0) parts.push(labels.describeHidden(hidden));
1119
+ if (stale) parts.push(labels.describeStale);
1120
+ return parts.join(" ");
1121
+ }
1122
+ /**
1123
+ * The plot's own `aria-label` — set live through `view.description`, so Vega
1124
+ * stamps it on the `graphics-document` node. Deliberately shorter than the
1125
+ * card description: the figure's description already carries the detail, and
1126
+ * repeating it makes a screen reader say everything twice.
1127
+ */
1128
+ function buildChartPlotDescription({ type, measureName, dimensionName, labels }) {
1129
+ return labels.chartOf(CHART_TYPES[type].name, measureName, dimensionName);
1130
+ }
1131
+ /** How many rows the table twin renders before it says "first N of M". */
1132
+ const CHART_TABLE_ROW_CAP = 200;
1133
+ /**
1134
+ * Every value the plot draws, as text — the keyboard path to the data, since
1135
+ * Vega gives marks none. Columns follow the form: a part-to-whole chart gets a
1136
+ * share column, a range bar gets both bounds, a bubble gets its size, a
1137
+ * waterfall gets the running total the bars stand on.
1138
+ */
1139
+ function buildChartTableModel({ type, summary, formatters, labels, measureName, dimensionName, seriesName, sizeName, cap = 200 }) {
1140
+ if (!summary || summary.rows.length === 0) return null;
1141
+ const info = CHART_TYPES[type];
1142
+ const multiSeries = summary.series.some((entry) => entry.label !== "");
1143
+ const columns = [];
1144
+ if (info.fields.x !== "none") columns.push({
1145
+ key: "x",
1146
+ label: dimensionName,
1147
+ numeric: false
1148
+ });
1149
+ if (info.fields.x2) columns.push({
1150
+ key: "x2",
1151
+ label: `${dimensionName} end`,
1152
+ numeric: false
1153
+ });
1154
+ if (multiSeries) columns.push({
1155
+ key: "s",
1156
+ label: seriesName,
1157
+ numeric: false
1158
+ });
1159
+ if (info.fields.value && info.fields.y !== "none") columns.push({
1160
+ key: "y",
1161
+ label: "Row",
1162
+ numeric: false
1163
+ });
1164
+ columns.push({
1165
+ key: "v",
1166
+ label: measureName,
1167
+ numeric: true
1168
+ });
1169
+ if (info.fields.size && sizeName) columns.push({
1170
+ key: "z",
1171
+ label: sizeName,
1172
+ numeric: true
1173
+ });
1174
+ if (type === "waterfall") columns.push({
1175
+ key: "hi",
1176
+ label: "Running total",
1177
+ numeric: true
1178
+ });
1179
+ if (info.intent === "part-to-whole" && summary.total !== 0) columns.push({
1180
+ key: "share",
1181
+ label: labels.tableShare,
1182
+ numeric: true
1183
+ });
1184
+ const body = summary.rows.slice(0, cap).map((row) => {
1185
+ const cells = [];
1186
+ for (const column of columns) switch (column.key) {
1187
+ case "x":
1188
+ cells.push(formatters.formatX(row.x));
1189
+ break;
1190
+ case "x2":
1191
+ cells.push(formatters.formatX(row.x2));
1192
+ break;
1193
+ case "s":
1194
+ cells.push(row.n ?? "");
1195
+ break;
1196
+ case "y":
1197
+ cells.push(formatters.formatY(row.y));
1198
+ break;
1199
+ case "v":
1200
+ cells.push(formatters.formatValue(row.v));
1201
+ break;
1202
+ case "z":
1203
+ cells.push(formatters.formatValue(row.z));
1204
+ break;
1205
+ case "hi":
1206
+ cells.push(formatters.formatValue(row.hi));
1207
+ break;
1208
+ case "share":
1209
+ cells.push(row.v == null ? "—" : formatters.formatShare(row.v / summary.total));
1210
+ break;
1211
+ default: cells.push("");
1212
+ }
1213
+ return cells;
1214
+ });
1215
+ return {
1216
+ columns,
1217
+ rows: body,
1218
+ shown: body.length,
1219
+ total: summary.rows.length
1220
+ };
1221
+ }
1222
+ //#endregion
1223
+ export { CHART_OTHER_KEY, CHART_TABLE_ROW_CAP, CHART_TYPES, CHART_TYPE_ORDER, DEFAULT_CHART_LABELS, RADAR_RING_FRACTIONS, buildChartDescription, buildChartPlotDescription, buildChartTableModel, buildRadarWebRows, buildRampBuckets, chartSeriesColorAt, createChartFormatters, deriveChartState, hasEnoughData, inferFieldType, inferGranularity, missingChartFields, normalizeChartData, resolveChartColorMode, resolveChartLabels };