@fluentui/react-charts 9.3.12 → 9.3.14

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 (55) hide show
  1. package/CHANGELOG.md +25 -2
  2. package/dist/index.d.ts +859 -0
  3. package/lib/VegaDeclarativeChart.js +1 -0
  4. package/lib/VegaDeclarativeChart.js.map +1 -0
  5. package/lib/components/LineChart/LineChart.js +47 -7
  6. package/lib/components/LineChart/LineChart.js.map +1 -1
  7. package/lib/components/ScatterChart/ScatterChart.js +12 -12
  8. package/lib/components/ScatterChart/ScatterChart.js.map +1 -1
  9. package/lib/components/VegaDeclarativeChart/VegaDeclarativeChart.js +405 -0
  10. package/lib/components/VegaDeclarativeChart/VegaDeclarativeChart.js.map +1 -0
  11. package/lib/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js +20 -0
  12. package/lib/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js.map +1 -0
  13. package/lib/components/VegaDeclarativeChart/VegaLiteColorAdapter.js +415 -0
  14. package/lib/components/VegaDeclarativeChart/VegaLiteColorAdapter.js.map +1 -0
  15. package/lib/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js +537 -0
  16. package/lib/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js.map +1 -0
  17. package/lib/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js +3279 -0
  18. package/lib/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js.map +1 -0
  19. package/lib/components/VegaDeclarativeChart/VegaLiteTypes.js +28 -0
  20. package/lib/components/VegaDeclarativeChart/VegaLiteTypes.js.map +1 -0
  21. package/lib/components/VegaDeclarativeChart/index.js +1 -0
  22. package/lib/components/VegaDeclarativeChart/index.js.map +1 -0
  23. package/lib/components/VerticalStackedBarChart/VerticalStackedBarChart.js +5 -2
  24. package/lib/components/VerticalStackedBarChart/VerticalStackedBarChart.js.map +1 -1
  25. package/lib/index.js +1 -0
  26. package/lib/index.js.map +1 -1
  27. package/lib/utilities/utilities.js +41 -0
  28. package/lib/utilities/utilities.js.map +1 -1
  29. package/lib-commonjs/VegaDeclarativeChart.js +6 -0
  30. package/lib-commonjs/VegaDeclarativeChart.js.map +1 -0
  31. package/lib-commonjs/components/LineChart/LineChart.js +46 -6
  32. package/lib-commonjs/components/LineChart/LineChart.js.map +1 -1
  33. package/lib-commonjs/components/ScatterChart/ScatterChart.js +11 -11
  34. package/lib-commonjs/components/ScatterChart/ScatterChart.js.map +1 -1
  35. package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChart.js +274 -0
  36. package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChart.js.map +1 -0
  37. package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js +35 -0
  38. package/lib-commonjs/components/VegaDeclarativeChart/VegaDeclarativeChartHooks.js.map +1 -0
  39. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteColorAdapter.js +412 -0
  40. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteColorAdapter.js.map +1 -0
  41. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js +533 -0
  42. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteExpressionEvaluator.js.map +1 -0
  43. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js +3214 -0
  44. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteSchemaAdapter.js.map +1 -0
  45. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteTypes.js +31 -0
  46. package/lib-commonjs/components/VegaDeclarativeChart/VegaLiteTypes.js.map +1 -0
  47. package/lib-commonjs/components/VegaDeclarativeChart/index.js +6 -0
  48. package/lib-commonjs/components/VegaDeclarativeChart/index.js.map +1 -0
  49. package/lib-commonjs/components/VerticalStackedBarChart/VerticalStackedBarChart.js +4 -1
  50. package/lib-commonjs/components/VerticalStackedBarChart/VerticalStackedBarChart.js.map +1 -1
  51. package/lib-commonjs/index.js +1 -0
  52. package/lib-commonjs/index.js.map +1 -1
  53. package/lib-commonjs/utilities/utilities.js +33 -0
  54. package/lib-commonjs/utilities/utilities.js.map +1 -1
  55. package/package.json +3 -3
@@ -0,0 +1,3279 @@
1
+ // Using custom VegaLiteTypes for internal adapter logic
2
+ // For public API, VegaDeclarativeChart accepts vega-lite's TopLevelSpec
3
+ import { getVegaColorFromMap, getVegaColor, getSequentialSchemeColors } from './VegaLiteColorAdapter';
4
+ import { safeEvaluateExpression } from './VegaLiteExpressionEvaluator';
5
+ import { bin as d3Bin, extent as d3Extent, sum as d3Sum, min as d3Min, max as d3Max, mean as d3Mean } from 'd3-array';
6
+ import { format as d3Format } from 'd3-format';
7
+ import { isInvalidValue } from '@fluentui/chart-utilities';
8
+ /**
9
+ * Vega-Lite to Fluent Charts adapter for line/point charts.
10
+ *
11
+ * Transforms Vega-Lite JSON specifications into Fluent LineChart props.
12
+ * Supports basic line charts with temporal/quantitative axes and color-encoded series.
13
+ *
14
+ * TODO: Future enhancements
15
+ * - Multi-view layouts (facet, concat, repeat)
16
+ * - Selection interactions
17
+ * - Remote data loading (url)
18
+ * - Transform pipeline (filter, aggregate, calculate)
19
+ * - Conditional encodings
20
+ * - Additional mark types (area, bar, etc.)
21
+ * - Tooltip customization
22
+ */ /**
23
+ * Default configuration values for VegaLite charts
24
+ */ const DEFAULT_CHART_HEIGHT = 350;
25
+ const DEFAULT_MAX_BAR_WIDTH = 50;
26
+ const DEFAULT_TRUNCATE_CHARS = 20;
27
+ /**
28
+ * Determines if a spec is a layered specification
29
+ */ function isLayerSpec(spec) {
30
+ return Array.isArray(spec.layer) && spec.layer.length > 0;
31
+ }
32
+ /**
33
+ * Determines if a spec is a single unit specification
34
+ */ function isUnitSpec(spec) {
35
+ return spec.mark !== undefined && spec.encoding !== undefined;
36
+ }
37
+ /**
38
+ * Extracts the mark type string from a VegaLiteMarkDef (string or object with type property)
39
+ */ export function getMarkType(mark) {
40
+ if (!mark) {
41
+ return undefined;
42
+ }
43
+ return typeof mark === 'string' ? mark : mark.type;
44
+ }
45
+ /**
46
+ * Resolves the color for a legend label using the priority chain:
47
+ * 1. Explicit color value from encoding
48
+ * 2. Mark-level color
49
+ * 3. Cached color from the shared color map
50
+ * 4. New color via local index for deterministic per-chart assignment
51
+ */ function resolveColor(legend, index, colorValue, markColor, colorMap, colorScheme, colorRange, isDarkTheme) {
52
+ var _colorMap_current, _colorMap_current1;
53
+ if (colorValue) {
54
+ return colorValue;
55
+ }
56
+ if (markColor) {
57
+ return markColor;
58
+ }
59
+ // Check colorMap cache first for cross-chart consistency
60
+ if ((_colorMap_current = colorMap.current) === null || _colorMap_current === void 0 ? void 0 : _colorMap_current.has(legend)) {
61
+ return colorMap.current.get(legend);
62
+ }
63
+ // Use local index (not colorMap.size) for deterministic per-chart color assignment
64
+ const color = getVegaColor(index, colorScheme, colorRange, isDarkTheme);
65
+ (_colorMap_current1 = colorMap.current) === null || _colorMap_current1 === void 0 ? void 0 : _colorMap_current1.set(legend, color);
66
+ return color;
67
+ }
68
+ /**
69
+ * Extracts inline data values from a Vega-Lite data specification
70
+ * TODO: Add support for URL-based data loading
71
+ * TODO: Add support for named dataset resolution
72
+ * TODO: Add support for data format parsing (csv, tsv)
73
+ */ function extractDataValues(data) {
74
+ if (!data) {
75
+ return [];
76
+ }
77
+ if (data.values && Array.isArray(data.values)) {
78
+ return data.values;
79
+ }
80
+ // TODO: Handle data.url - load remote data
81
+ if (data.url) {
82
+ // Remote data URLs are not yet supported
83
+ return [];
84
+ }
85
+ // TODO: Handle data.name - resolve named datasets
86
+ if (data.name) {
87
+ // Named datasets are not yet supported
88
+ return [];
89
+ }
90
+ return [];
91
+ }
92
+ /**
93
+ * Applies a fold transform to convert wide-format data to long-format
94
+ * The fold transform unpivots specified fields into key-value pairs
95
+ *
96
+ * @param data - Array of data records in wide format
97
+ * @param foldFields - Array of field names to fold
98
+ * @param asFields - [keyName, valueName] for the new columns (defaults to ['key', 'value'])
99
+ * @returns Array of data records in long format
100
+ */ function applyFoldTransform(data, foldFields, asFields = [
101
+ 'key',
102
+ 'value'
103
+ ]) {
104
+ const [keyField, valueField] = asFields;
105
+ const result = [];
106
+ for (const row of data){
107
+ // Create a base row without the fields being folded
108
+ const baseRow = {};
109
+ for (const [key, value] of Object.entries(row)){
110
+ if (!foldFields.includes(key)) {
111
+ baseRow[key] = value;
112
+ }
113
+ }
114
+ // Create a new row for each folded field
115
+ for (const field of foldFields){
116
+ if (field in row) {
117
+ result.push({
118
+ ...baseRow,
119
+ [keyField]: field,
120
+ [valueField]: row[field]
121
+ });
122
+ }
123
+ }
124
+ }
125
+ return result;
126
+ }
127
+ /**
128
+ * Applies transforms from a Vega-Lite spec to data
129
+ * Currently supports: fold transform
130
+ *
131
+ * @param data - Array of data records
132
+ * @param transforms - Array of Vega-Lite transform specifications
133
+ * @returns Transformed data array
134
+ */ function applyTransforms(data, transforms) {
135
+ if (!transforms || transforms.length === 0) {
136
+ return data;
137
+ }
138
+ let result = data;
139
+ for (const transform of transforms){
140
+ // Handle fold transform
141
+ if ('fold' in transform && Array.isArray(transform.fold)) {
142
+ const foldFields = transform.fold;
143
+ const asFields = transform.as || [
144
+ 'key',
145
+ 'value'
146
+ ];
147
+ result = applyFoldTransform(result, foldFields, asFields);
148
+ }
149
+ // Handle filter transform
150
+ if ('filter' in transform) {
151
+ const filterExpr = transform.filter;
152
+ if (typeof filterExpr === 'string') {
153
+ result = result.filter((row)=>{
154
+ try {
155
+ return safeEvaluateExpression(filterExpr, row);
156
+ } catch {
157
+ return true;
158
+ }
159
+ });
160
+ }
161
+ }
162
+ // Handle calculate transform
163
+ if ('calculate' in transform && 'as' in transform) {
164
+ const expr = transform.calculate;
165
+ const asField = transform.as;
166
+ result = result.map((row)=>{
167
+ try {
168
+ const value = safeEvaluateExpression(expr, row);
169
+ return {
170
+ ...row,
171
+ [asField]: value
172
+ };
173
+ } catch {
174
+ return row;
175
+ }
176
+ });
177
+ }
178
+ // Handle aggregate transform
179
+ if ('aggregate' in transform && Array.isArray(transform.aggregate)) {
180
+ const aggSpecs = transform.aggregate;
181
+ const groupby = transform.groupby || [];
182
+ const groups = new Map();
183
+ result.forEach((row)=>{
184
+ const key = groupby.map((g)=>String(row[g])).join('|');
185
+ if (!groups.has(key)) {
186
+ groups.set(key, []);
187
+ }
188
+ groups.get(key).push(row);
189
+ });
190
+ result = Array.from(groups.entries()).map(([key, rows])=>{
191
+ const baseRow = {};
192
+ groupby.forEach((g, i)=>{
193
+ baseRow[g] = rows[0][g];
194
+ });
195
+ aggSpecs.forEach((spec)=>{
196
+ const values = spec.field ? rows.map((r)=>Number(r[spec.field])).filter((v)=>!isNaN(v)) : [];
197
+ switch(spec.op){
198
+ case 'count':
199
+ baseRow[spec.as] = rows.length;
200
+ break;
201
+ case 'sum':
202
+ baseRow[spec.as] = d3Sum(values);
203
+ break;
204
+ case 'mean':
205
+ case 'average':
206
+ var _d3Mean;
207
+ baseRow[spec.as] = (_d3Mean = d3Mean(values)) !== null && _d3Mean !== void 0 ? _d3Mean : 0;
208
+ break;
209
+ case 'min':
210
+ var _d3Min;
211
+ baseRow[spec.as] = (_d3Min = d3Min(values)) !== null && _d3Min !== void 0 ? _d3Min : 0;
212
+ break;
213
+ case 'max':
214
+ var _d3Max;
215
+ baseRow[spec.as] = (_d3Max = d3Max(values)) !== null && _d3Max !== void 0 ? _d3Max : 0;
216
+ break;
217
+ default:
218
+ baseRow[spec.as] = rows.length;
219
+ }
220
+ });
221
+ return baseRow;
222
+ });
223
+ }
224
+ // Handle window transform
225
+ if ('window' in transform && Array.isArray(transform.window)) {
226
+ const windowOps = transform.window;
227
+ const sortFields = transform.sort || [];
228
+ const groupby = transform.groupby || [];
229
+ // Group data
230
+ const groups = new Map();
231
+ result.forEach((row)=>{
232
+ const key = groupby.length > 0 ? groupby.map((g)=>String(row[g])).join('|') : '__all__';
233
+ if (!groups.has(key)) {
234
+ groups.set(key, []);
235
+ }
236
+ groups.get(key).push(row);
237
+ });
238
+ const newResult = [];
239
+ groups.forEach((rows)=>{
240
+ // Sort within group
241
+ if (sortFields.length > 0) {
242
+ rows.sort((a, b)=>{
243
+ for (const sf of sortFields){
244
+ const va = Number(a[sf.field]) || 0;
245
+ const vb = Number(b[sf.field]) || 0;
246
+ const cmp = sf.order === 'descending' ? vb - va : va - vb;
247
+ if (cmp !== 0) {
248
+ return cmp;
249
+ }
250
+ }
251
+ return 0;
252
+ });
253
+ }
254
+ let runningSum = 0;
255
+ rows.forEach((row, idx)=>{
256
+ const newRow = {
257
+ ...row
258
+ };
259
+ windowOps.forEach((op)=>{
260
+ switch(op.op){
261
+ case 'sum':
262
+ runningSum += Number(row[op.field]) || 0;
263
+ newRow[op.as] = runningSum;
264
+ break;
265
+ case 'rank':
266
+ newRow[op.as] = idx + 1;
267
+ break;
268
+ case 'row_number':
269
+ newRow[op.as] = idx + 1;
270
+ break;
271
+ case 'count':
272
+ newRow[op.as] = idx + 1;
273
+ break;
274
+ default:
275
+ newRow[op.as] = idx + 1;
276
+ }
277
+ });
278
+ newResult.push(newRow);
279
+ });
280
+ });
281
+ result = newResult;
282
+ }
283
+ // Handle joinaggregate transform
284
+ if ('joinaggregate' in transform && Array.isArray(transform.joinaggregate)) {
285
+ const aggSpecs = transform.joinaggregate;
286
+ const groupby = transform.groupby || [];
287
+ // Compute aggregates
288
+ const groups = new Map();
289
+ result.forEach((row)=>{
290
+ const key = groupby.length > 0 ? groupby.map((g)=>String(row[g])).join('|') : '__all__';
291
+ if (!groups.has(key)) {
292
+ groups.set(key, []);
293
+ }
294
+ groups.get(key).push(row);
295
+ });
296
+ const aggResults = new Map();
297
+ groups.forEach((rows, key)=>{
298
+ const aggs = {};
299
+ aggSpecs.forEach((spec)=>{
300
+ const values = spec.field ? rows.map((r)=>Number(r[spec.field])).filter((v)=>!isNaN(v)) : [];
301
+ switch(spec.op){
302
+ case 'mean':
303
+ case 'average':
304
+ var _d3Mean;
305
+ aggs[spec.as] = (_d3Mean = d3Mean(values)) !== null && _d3Mean !== void 0 ? _d3Mean : 0;
306
+ break;
307
+ case 'sum':
308
+ aggs[spec.as] = d3Sum(values);
309
+ break;
310
+ case 'count':
311
+ aggs[spec.as] = rows.length;
312
+ break;
313
+ case 'min':
314
+ var _d3Min;
315
+ aggs[spec.as] = (_d3Min = d3Min(values)) !== null && _d3Min !== void 0 ? _d3Min : 0;
316
+ break;
317
+ case 'max':
318
+ var _d3Max;
319
+ aggs[spec.as] = (_d3Max = d3Max(values)) !== null && _d3Max !== void 0 ? _d3Max : 0;
320
+ break;
321
+ default:
322
+ aggs[spec.as] = rows.length;
323
+ }
324
+ });
325
+ aggResults.set(key, aggs);
326
+ });
327
+ // Join back: add aggregate values to each row
328
+ result = result.map((row)=>{
329
+ const key = groupby.length > 0 ? groupby.map((g)=>String(row[g])).join('|') : '__all__';
330
+ return {
331
+ ...row,
332
+ ...aggResults.get(key) || {}
333
+ };
334
+ });
335
+ }
336
+ // Handle regression transform (simple linear regression)
337
+ if ('regression' in transform && 'on' in transform) {
338
+ const yField = transform.regression;
339
+ const xField = transform.on;
340
+ const points = result.map((r)=>({
341
+ x: Number(r[xField]),
342
+ y: Number(r[yField])
343
+ })).filter((p)=>!isNaN(p.x) && !isNaN(p.y));
344
+ if (points.length >= 2) {
345
+ const n = points.length;
346
+ const sumX = d3Sum(points.map((p)=>p.x));
347
+ const sumY = d3Sum(points.map((p)=>p.y));
348
+ const sumXY = d3Sum(points.map((p)=>p.x * p.y));
349
+ const sumX2 = d3Sum(points.map((p)=>p.x * p.x));
350
+ const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
351
+ const intercept = (sumY - slope * sumX) / n;
352
+ var _d3Min;
353
+ const xMin = (_d3Min = d3Min(points.map((p)=>p.x))) !== null && _d3Min !== void 0 ? _d3Min : 0;
354
+ var _d3Max;
355
+ const xMax = (_d3Max = d3Max(points.map((p)=>p.x))) !== null && _d3Max !== void 0 ? _d3Max : 0;
356
+ result = [
357
+ {
358
+ [xField]: xMin,
359
+ [yField]: slope * xMin + intercept
360
+ },
361
+ {
362
+ [xField]: xMax,
363
+ [yField]: slope * xMax + intercept
364
+ }
365
+ ];
366
+ }
367
+ }
368
+ // Handle loess transform (simplified: moving average approximation)
369
+ if ('loess' in transform && 'on' in transform) {
370
+ const yField = transform.loess;
371
+ const xField = transform.on;
372
+ const sorted = [
373
+ ...result
374
+ ].filter((r)=>!isNaN(Number(r[xField])) && !isNaN(Number(r[yField]))).sort((a, b)=>Number(a[xField]) - Number(b[xField]));
375
+ const windowSize = Math.max(3, Math.floor(sorted.length / 4));
376
+ result = sorted.map((row, i)=>{
377
+ const start = Math.max(0, i - Math.floor(windowSize / 2));
378
+ const end = Math.min(sorted.length, start + windowSize);
379
+ const windowSlice = sorted.slice(start, end);
380
+ var _d3Mean;
381
+ const avgY = (_d3Mean = d3Mean(windowSlice.map((r)=>Number(r[yField])))) !== null && _d3Mean !== void 0 ? _d3Mean : Number(row[yField]);
382
+ return {
383
+ [xField]: row[xField],
384
+ [yField]: avgY
385
+ };
386
+ });
387
+ }
388
+ // Handle density transform (simplified: histogram-based density estimation)
389
+ if ('density' in transform) {
390
+ const field = transform.density;
391
+ const groupby = transform.groupby || [];
392
+ const groups = new Map();
393
+ result.forEach((row)=>{
394
+ const key = groupby.length > 0 ? groupby.map((g)=>String(row[g])).join('|') : '__all__';
395
+ if (!groups.has(key)) {
396
+ groups.set(key, []);
397
+ }
398
+ groups.get(key).push(Number(row[field]));
399
+ });
400
+ const densityResult = [];
401
+ groups.forEach((values, key)=>{
402
+ var _d3Min;
403
+ const min = (_d3Min = d3Min(values)) !== null && _d3Min !== void 0 ? _d3Min : 0;
404
+ var _d3Max;
405
+ const max = (_d3Max = d3Max(values)) !== null && _d3Max !== void 0 ? _d3Max : 0;
406
+ const range = max - min || 1;
407
+ const bins = 20;
408
+ const bandwidth = range / bins;
409
+ const groupFields = {};
410
+ if (groupby.length > 0) {
411
+ const sampleRow = result.find((r)=>groupby.map((g)=>String(r[g])).join('|') === key);
412
+ groupby.forEach((g)=>{
413
+ groupFields[g] = sampleRow === null || sampleRow === void 0 ? void 0 : sampleRow[g];
414
+ });
415
+ }
416
+ for(let i = 0; i <= bins; i++){
417
+ const x = min + i / bins * range;
418
+ const count = values.filter((v)=>Math.abs(v - x) < bandwidth).length;
419
+ const density = count / (values.length * bandwidth);
420
+ densityResult.push({
421
+ value: x,
422
+ density,
423
+ ...groupFields
424
+ });
425
+ }
426
+ });
427
+ result = densityResult;
428
+ }
429
+ // Handle quantile transform
430
+ if ('quantile' in transform) {
431
+ const field = transform.quantile;
432
+ const probs = transform.probs || [
433
+ 0.25,
434
+ 0.5,
435
+ 0.75
436
+ ];
437
+ const values = result.map((r)=>Number(r[field])).filter((v)=>!isNaN(v)).sort((a, b)=>a - b);
438
+ if (values.length > 0) {
439
+ result = probs.map((p)=>{
440
+ const idx = Math.min(Math.floor(p * values.length), values.length - 1);
441
+ return {
442
+ prob: String(p),
443
+ value: values[idx]
444
+ };
445
+ });
446
+ }
447
+ }
448
+ // Handle impute transform (fill missing values)
449
+ if ('impute' in transform && 'key' in transform) {
450
+ const field = transform.impute;
451
+ const keyField = transform.key;
452
+ const method = transform.method || 'value';
453
+ var _transform_value;
454
+ const fillValue = (_transform_value = transform.value) !== null && _transform_value !== void 0 ? _transform_value : 0;
455
+ const existingKeys = new Set(result.map((r)=>r[keyField]));
456
+ const allKeyValues = result.map((r)=>Number(r[keyField])).filter((v)=>!isNaN(v));
457
+ if (allKeyValues.length > 0) {
458
+ var _d3Min1;
459
+ const minKey = (_d3Min1 = d3Min(allKeyValues)) !== null && _d3Min1 !== void 0 ? _d3Min1 : 0;
460
+ var _d3Max1;
461
+ const maxKey = (_d3Max1 = d3Max(allKeyValues)) !== null && _d3Max1 !== void 0 ? _d3Max1 : 0;
462
+ for(let k = minKey; k <= maxKey; k++){
463
+ if (!existingKeys.has(k)) {
464
+ const imputed = {
465
+ [keyField]: k
466
+ };
467
+ imputed[field] = method === 'value' ? fillValue : 0;
468
+ result.push(imputed);
469
+ }
470
+ }
471
+ result.sort((a, b)=>Number(a[keyField]) - Number(b[keyField]));
472
+ }
473
+ }
474
+ // Handle lookup transform (join with secondary dataset)
475
+ if ('lookup' in transform && 'from' in transform) {
476
+ var _fromSpec_data;
477
+ const lookupField = transform.lookup;
478
+ const fromSpec = transform.from;
479
+ if (((_fromSpec_data = fromSpec.data) === null || _fromSpec_data === void 0 ? void 0 : _fromSpec_data.values) && fromSpec.key && fromSpec.fields) {
480
+ const lookupMap = new Map();
481
+ fromSpec.data.values.forEach((row)=>{
482
+ lookupMap.set(String(row[fromSpec.key]), row);
483
+ });
484
+ result = result.map((row)=>{
485
+ const lookupRow = lookupMap.get(String(row[lookupField]));
486
+ if (lookupRow) {
487
+ const extra = {};
488
+ fromSpec.fields.forEach((f)=>{
489
+ extra[f] = lookupRow[f];
490
+ });
491
+ return {
492
+ ...row,
493
+ ...extra
494
+ };
495
+ }
496
+ return row;
497
+ });
498
+ }
499
+ }
500
+ }
501
+ return result;
502
+ }
503
+ /**
504
+ * Normalizes a Vega-Lite spec into an array of unit specs with resolved data and encoding
505
+ * Handles both single-view and layered specifications
506
+ */ function normalizeSpec(spec) {
507
+ if (isLayerSpec(spec)) {
508
+ // Layered spec: merge shared data and encoding with each layer
509
+ const sharedData = spec.data;
510
+ const sharedEncoding = spec.encoding;
511
+ return spec.layer.map((layer)=>({
512
+ ...layer,
513
+ data: layer.data || sharedData,
514
+ encoding: {
515
+ ...sharedEncoding,
516
+ ...layer.encoding
517
+ }
518
+ }));
519
+ }
520
+ if (isUnitSpec(spec)) {
521
+ // Single unit spec
522
+ return [
523
+ {
524
+ mark: spec.mark,
525
+ encoding: spec.encoding,
526
+ data: spec.data
527
+ }
528
+ ];
529
+ }
530
+ // Unsupported spec structure
531
+ return [];
532
+ }
533
+ /**
534
+ * Parses a value to a Date if it's temporal, otherwise returns as number or string
535
+ */ function parseValue(value, isTemporalType) {
536
+ if (value === null || value === undefined) {
537
+ return '';
538
+ }
539
+ if (isTemporalType) {
540
+ // Try parsing as date
541
+ const dateValue = new Date(value);
542
+ if (!isNaN(dateValue.getTime())) {
543
+ return dateValue;
544
+ }
545
+ }
546
+ if (typeof value === 'number') {
547
+ return value;
548
+ }
549
+ return String(value);
550
+ }
551
+ /**
552
+ * Maps Vega-Lite interpolate to Fluent curve options
553
+ * Note: Only maps to curve types supported by LineChartLineOptions
554
+ */ function mapInterpolateToCurve(interpolate) {
555
+ if (!interpolate) {
556
+ return undefined;
557
+ }
558
+ switch(interpolate){
559
+ case 'linear':
560
+ case 'linear-closed':
561
+ return 'linear';
562
+ case 'step':
563
+ return 'step';
564
+ case 'step-before':
565
+ return 'stepBefore';
566
+ case 'step-after':
567
+ return 'stepAfter';
568
+ case 'natural':
569
+ return 'natural';
570
+ case 'monotone':
571
+ return 'linear';
572
+ // Note: basis, cardinal, catmull-rom are not supported by LineChartLineOptions
573
+ default:
574
+ return 'linear';
575
+ }
576
+ }
577
+ /**
578
+ * Extracts mark properties from VegaLiteMarkDef
579
+ */ function getMarkProperties(mark) {
580
+ if (typeof mark === 'string') {
581
+ return {};
582
+ }
583
+ return {
584
+ color: mark.color,
585
+ interpolate: mark.interpolate,
586
+ strokeWidth: mark.strokeWidth,
587
+ strokeDash: mark.strokeDash,
588
+ point: mark.point
589
+ };
590
+ }
591
+ /**
592
+ * Extracts annotations from Vega-Lite layers with text or rule marks
593
+ * Text marks become text annotations, rule marks become reference lines
594
+ */ function extractAnnotations(spec) {
595
+ const annotations = [];
596
+ if (!spec.layer || !Array.isArray(spec.layer)) {
597
+ return annotations;
598
+ }
599
+ spec.layer.forEach((layer, index)=>{
600
+ const mark = getMarkType(layer.mark);
601
+ const encoding = layer.encoding || {};
602
+ // Text marks become annotations
603
+ if (mark === 'text' && encoding.x && encoding.y) {
604
+ var _encoding_text, _encoding_text1, _encoding_text2;
605
+ const textValue = ((_encoding_text = encoding.text) === null || _encoding_text === void 0 ? void 0 : _encoding_text.datum) || ((_encoding_text1 = encoding.text) === null || _encoding_text1 === void 0 ? void 0 : _encoding_text1.value) || ((_encoding_text2 = encoding.text) === null || _encoding_text2 === void 0 ? void 0 : _encoding_text2.field) || '';
606
+ const xValue = encoding.x.datum || encoding.x.value || encoding.x.field;
607
+ const yValue = encoding.y.datum || encoding.y.value || encoding.y.field;
608
+ if (textValue && (xValue !== undefined || encoding.x.datum !== undefined) && (yValue !== undefined || encoding.y.datum !== undefined)) {
609
+ annotations.push({
610
+ id: `text-annotation-${index}`,
611
+ text: String(textValue),
612
+ coordinates: {
613
+ type: 'data',
614
+ x: encoding.x.datum || xValue || 0,
615
+ y: encoding.y.datum || yValue || 0
616
+ },
617
+ style: {
618
+ textColor: typeof layer.mark === 'object' ? layer.mark.color : undefined
619
+ }
620
+ });
621
+ }
622
+ }
623
+ // Rule marks can become reference lines (horizontal or vertical)
624
+ if (mark === 'rule') {
625
+ const markColor = typeof layer.mark === 'object' ? layer.mark.color : '#000';
626
+ const markStrokeWidth = typeof layer.mark === 'object' ? layer.mark.strokeWidth || 1 : 1;
627
+ const markStrokeDash = typeof layer.mark === 'object' ? layer.mark.strokeDash : undefined;
628
+ // Horizontal rule (y value constant)
629
+ if (encoding.y && (encoding.y.value !== undefined || encoding.y.datum !== undefined)) {
630
+ var _spec_layer, _companionText_encoding_text, _companionText_encoding, _companionText_encoding_text1, _companionText_encoding1;
631
+ var _encoding_y_value;
632
+ const yValue = (_encoding_y_value = encoding.y.value) !== null && _encoding_y_value !== void 0 ? _encoding_y_value : encoding.y.datum;
633
+ // Look for a companion text annotation at the same y-value
634
+ const companionText = (_spec_layer = spec.layer) === null || _spec_layer === void 0 ? void 0 : _spec_layer.find((l, i)=>{
635
+ var _l_encoding;
636
+ if (i === index) {
637
+ return false;
638
+ }
639
+ const m = getMarkType(l.mark);
640
+ var _l_encoding_y_datum;
641
+ return m === 'text' && ((_l_encoding = l.encoding) === null || _l_encoding === void 0 ? void 0 : _l_encoding.y) && ((_l_encoding_y_datum = l.encoding.y.datum) !== null && _l_encoding_y_datum !== void 0 ? _l_encoding_y_datum : l.encoding.y.value) === yValue;
642
+ });
643
+ const ruleText = companionText ? String(((_companionText_encoding = companionText.encoding) === null || _companionText_encoding === void 0 ? void 0 : (_companionText_encoding_text = _companionText_encoding.text) === null || _companionText_encoding_text === void 0 ? void 0 : _companionText_encoding_text.datum) || ((_companionText_encoding1 = companionText.encoding) === null || _companionText_encoding1 === void 0 ? void 0 : (_companionText_encoding_text1 = _companionText_encoding1.text) === null || _companionText_encoding_text1 === void 0 ? void 0 : _companionText_encoding_text1.value) || yValue) : String(yValue);
644
+ annotations.push({
645
+ id: `rule-h-${index}`,
646
+ text: ruleText,
647
+ coordinates: {
648
+ type: 'data',
649
+ x: 0,
650
+ y: yValue
651
+ },
652
+ style: {
653
+ textColor: markColor,
654
+ borderColor: markColor,
655
+ borderWidth: markStrokeWidth,
656
+ ...markStrokeDash && Array.isArray(markStrokeDash) ? {
657
+ borderRadius: 0
658
+ } // Indicate dashed style
659
+ : {}
660
+ }
661
+ });
662
+ } else if (encoding.x && (encoding.x.value !== undefined || encoding.x.datum !== undefined)) {
663
+ var _encoding_x_value;
664
+ const xValue = (_encoding_x_value = encoding.x.value) !== null && _encoding_x_value !== void 0 ? _encoding_x_value : encoding.x.datum;
665
+ annotations.push({
666
+ id: `rule-v-${index}`,
667
+ text: String(xValue),
668
+ coordinates: {
669
+ type: 'data',
670
+ x: xValue,
671
+ y: 0
672
+ },
673
+ style: {
674
+ textColor: markColor,
675
+ borderColor: markColor,
676
+ borderWidth: markStrokeWidth
677
+ }
678
+ });
679
+ }
680
+ }
681
+ });
682
+ return annotations;
683
+ }
684
+ /**
685
+ * Extracts color fill bars (background regions) from rect marks with x/x2 or y/y2 encodings
686
+ */ function extractColorFillBars(spec, colorMap, isDarkTheme) {
687
+ const colorFillBars = [];
688
+ if (!spec.layer || !Array.isArray(spec.layer)) {
689
+ return colorFillBars;
690
+ }
691
+ // Detect if x-axis is temporal by checking the primary data layer (non-rect layer)
692
+ const isXTemporal = spec.layer.some((layer)=>{
693
+ var _layer_encoding_x, _layer_encoding;
694
+ const layerMark = getMarkType(layer.mark);
695
+ // Skip rect layers, look at line/point/area layers
696
+ if (layerMark === 'rect') {
697
+ return false;
698
+ }
699
+ return ((_layer_encoding = layer.encoding) === null || _layer_encoding === void 0 ? void 0 : (_layer_encoding_x = _layer_encoding.x) === null || _layer_encoding_x === void 0 ? void 0 : _layer_encoding_x.type) === 'temporal';
700
+ });
701
+ spec.layer.forEach((layer, index)=>{
702
+ const mark = getMarkType(layer.mark);
703
+ const encoding = layer.encoding || {};
704
+ // Rect marks with x and x2 become color fill bars (vertical regions)
705
+ if (mark === 'rect' && encoding.x && encoding.x2) {
706
+ const legend = `region-${index}`;
707
+ const color = typeof layer.mark === 'object' && layer.mark.color ? layer.mark.color : getVegaColorFromMap(legend, colorMap, undefined, undefined, isDarkTheme);
708
+ // Extract start and end x values
709
+ const rawStartX = encoding.x.datum || encoding.x.value;
710
+ const rawEndX = encoding.x2.datum || encoding.x2.value;
711
+ if (rawStartX !== undefined && rawEndX !== undefined) {
712
+ // Convert to Date if x-axis is temporal and values are date-like strings
713
+ let startX = rawStartX;
714
+ let endX = rawEndX;
715
+ if (isXTemporal) {
716
+ const parsedStart = new Date(rawStartX);
717
+ const parsedEnd = new Date(rawEndX);
718
+ if (!isNaN(parsedStart.getTime())) {
719
+ startX = parsedStart;
720
+ }
721
+ if (!isNaN(parsedEnd.getTime())) {
722
+ endX = parsedEnd;
723
+ }
724
+ }
725
+ colorFillBars.push({
726
+ legend,
727
+ color,
728
+ data: [
729
+ {
730
+ startX,
731
+ endX
732
+ }
733
+ ],
734
+ applyPattern: false
735
+ });
736
+ }
737
+ }
738
+ });
739
+ return colorFillBars;
740
+ }
741
+ /**
742
+ * Extracts tick configuration from axis properties
743
+ */ function extractTickConfig(spec) {
744
+ var _encoding_x, _encoding_y;
745
+ const config = {};
746
+ const encoding = spec.encoding || {};
747
+ if ((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.axis) {
748
+ if (encoding.x.axis.values) {
749
+ config.tickValues = encoding.x.axis.values;
750
+ }
751
+ if (encoding.x.axis.tickCount) {
752
+ config.xAxisTickCount = encoding.x.axis.tickCount;
753
+ }
754
+ }
755
+ if ((_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.axis) {
756
+ if (encoding.y.axis.tickCount) {
757
+ config.yAxisTickCount = encoding.y.axis.tickCount;
758
+ }
759
+ }
760
+ return config;
761
+ }
762
+ /**
763
+ * Validates that data array is not empty and contains valid values for the specified field
764
+ * @param data - Array of data objects
765
+ * @param field - Field name to validate
766
+ * @param chartType - Chart type for error message context
767
+ * @throws Error if data is empty or field has no valid values
768
+ */ function validateDataArray(data, field, chartType) {
769
+ if (!data || data.length === 0) {
770
+ throw new Error(`VegaLiteSchemaAdapter: Empty data array for ${chartType}`);
771
+ }
772
+ const hasValidValues = data.some((row)=>row[field] !== undefined && row[field] !== null);
773
+ if (!hasValidValues) {
774
+ throw new Error(`VegaLiteSchemaAdapter: No valid values found for field '${field}' in ${chartType}`);
775
+ }
776
+ }
777
+ /**
778
+ * Validates that nested arrays are not present in the data field (unsupported)
779
+ * @param data - Array of data objects
780
+ * @param field - Field name to validate
781
+ * @throws Error if nested arrays are detected
782
+ */ function validateNoNestedArrays(data, field) {
783
+ const hasNestedArrays = data.some((row)=>Array.isArray(row[field]));
784
+ if (hasNestedArrays) {
785
+ throw new Error(`VegaLiteSchemaAdapter: Nested arrays not supported for field '${field}'. ` + `Use flat data structures only.`);
786
+ }
787
+ }
788
+ /**
789
+ * Validates data type compatibility with encoding type
790
+ * @param data - Array of data objects
791
+ * @param field - Field name to validate
792
+ * @param expectedType - Expected Vega-Lite type (quantitative, temporal, nominal, ordinal)
793
+ * @throws Error if data type doesn't match encoding type
794
+ */ /**
795
+ * Validates and potentially auto-corrects encoding types based on actual data
796
+ * Returns the corrected type if auto-correction was applied
797
+ *
798
+ * @param data - Array of data values
799
+ * @param field - Field name to validate
800
+ * @param expectedType - Expected Vega-Lite type from schema
801
+ * @param encoding - Encoding object to potentially modify
802
+ * @param channelName - Name of encoding channel (x, y, etc.) for auto-correction
803
+ * @returns Corrected type if auto-correction was applied, otherwise undefined
804
+ */ function validateEncodingType(data, field, expectedType, encoding, channelName) {
805
+ if (!expectedType || expectedType === 'nominal' || expectedType === 'ordinal' || expectedType === 'geojson') {
806
+ return; // Nominal, ordinal, and geojson accept any type
807
+ }
808
+ // Find first non-null value to check type
809
+ const sampleValue = data.map((row)=>row[field]).find((v)=>v !== null && v !== undefined);
810
+ if (!sampleValue) {
811
+ return; // No values to validate
812
+ }
813
+ if (expectedType === 'quantitative') {
814
+ if (typeof sampleValue !== 'number' && !isFinite(Number(sampleValue))) {
815
+ // Type mismatch: quantitative declared but data is not numeric
816
+ const actualType = typeof sampleValue;
817
+ if (actualType === 'string') {
818
+ // Auto-correct: treat as nominal for categorical string data
819
+ // This matches Plotly behavior - render as categorical chart
820
+ // Modify encoding to use nominal type
821
+ if (encoding && channelName && encoding[channelName]) {
822
+ encoding[channelName].type = 'nominal';
823
+ }
824
+ return 'nominal';
825
+ }
826
+ // For non-string types, still throw error (truly invalid)
827
+ throw new Error(`VegaLiteSchemaAdapter: Field '${field}' marked as quantitative but contains non-numeric values (${actualType}).`);
828
+ }
829
+ } else if (expectedType === 'temporal') {
830
+ const isValidDate = sampleValue instanceof Date || typeof sampleValue === 'string' && !isNaN(Date.parse(sampleValue));
831
+ if (!isValidDate) {
832
+ let suggestion = '';
833
+ if (typeof sampleValue === 'number') {
834
+ suggestion = ' The data contains numbers. Change the type to "quantitative" instead.';
835
+ } else if (typeof sampleValue === 'string') {
836
+ suggestion = ` The data contains strings that are not valid dates (e.g., "${sampleValue}"). Ensure dates are in ISO format (YYYY-MM-DD) or valid date strings.`;
837
+ }
838
+ throw new Error(`VegaLiteSchemaAdapter: Field '${field}' marked as temporal but contains invalid date values.${suggestion}`);
839
+ }
840
+ }
841
+ return undefined;
842
+ }
843
+ /**
844
+ * Validates X and Y encodings for charts requiring both axes
845
+ * Performs comprehensive validation including data array, nested arrays, and encoding types
846
+ * Can auto-correct type mismatches (e.g., quantitative with string data → nominal)
847
+ *
848
+ * @param data - Array of data objects
849
+ * @param xField - X field name
850
+ * @param yField - Y field name
851
+ * @param xType - Expected X encoding type
852
+ * @param yType - Expected Y encoding type
853
+ * @param chartType - Chart type for error message context
854
+ * @param encoding - Encoding object (optional, for auto-correction)
855
+ * @throws Error if validation fails
856
+ */ function validateXYEncodings(data, xField, yField, xType, yType, chartType, encoding) {
857
+ validateDataArray(data, xField, chartType);
858
+ validateDataArray(data, yField, chartType);
859
+ validateNoNestedArrays(data, xField);
860
+ validateNoNestedArrays(data, yField);
861
+ // Validate types with auto-correction support
862
+ validateEncodingType(data, xField, xType, encoding, 'x');
863
+ validateEncodingType(data, yField, yType, encoding, 'y');
864
+ }
865
+ /**
866
+ * Extracts Y-axis scale type from encoding
867
+ * Returns 'log' if logarithmic scale is specified, undefined otherwise
868
+ */ function extractYAxisType(encoding) {
869
+ var _encoding_y;
870
+ const yScale = encoding === null || encoding === void 0 ? void 0 : (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.scale;
871
+ return (yScale === null || yScale === void 0 ? void 0 : yScale.type) === 'log' ? 'log' : undefined;
872
+ }
873
+ /**
874
+ * Extracts y-axis min/max considering both scale.domain and scale.zero.
875
+ * When scale.zero is false and no explicit domain, yMinValue is computed from data.
876
+ */ function extractYMinMax(encoding, dataValues) {
877
+ var _encoding_y;
878
+ const yScale = encoding === null || encoding === void 0 ? void 0 : (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.scale;
879
+ const domain = yScale === null || yScale === void 0 ? void 0 : yScale.domain;
880
+ // Explicit domain takes priority
881
+ if (Array.isArray(domain)) {
882
+ return {
883
+ yMinValue: domain[0],
884
+ yMaxValue: domain[1]
885
+ };
886
+ }
887
+ // When zero is explicitly false, compute min from data so y-axis doesn't start at 0
888
+ if ((yScale === null || yScale === void 0 ? void 0 : yScale.zero) === false) {
889
+ var _encoding_y1;
890
+ const yField = encoding === null || encoding === void 0 ? void 0 : (_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.field;
891
+ if (yField) {
892
+ const yValues = dataValues.map((row)=>Number(row[yField])).filter((v)=>!isNaN(v));
893
+ if (yValues.length > 0) {
894
+ var _d3Min;
895
+ const dataMin = (_d3Min = d3Min(yValues)) !== null && _d3Min !== void 0 ? _d3Min : 0;
896
+ var _d3Max;
897
+ const dataMax = (_d3Max = d3Max(yValues)) !== null && _d3Max !== void 0 ? _d3Max : 0;
898
+ const padding = (dataMax - dataMin) * 0.05;
899
+ return {
900
+ yMinValue: dataMin - padding
901
+ };
902
+ }
903
+ }
904
+ }
905
+ return {};
906
+ }
907
+ /**
908
+ * Creates a value formatter from a d3-format specifier string.
909
+ * Returns undefined if no format is specified or if the format is invalid.
910
+ */ function createValueFormatter(formatSpec) {
911
+ if (!formatSpec) {
912
+ return undefined;
913
+ }
914
+ try {
915
+ const formatter = d3Format(formatSpec);
916
+ return formatter;
917
+ } catch {
918
+ return undefined;
919
+ }
920
+ }
921
+ /**
922
+ * Converts Vega-Lite sort specification to Fluent Charts AxisCategoryOrder
923
+ * Supports: 'ascending', 'descending', null, array, or object with op/order
924
+ * @param sort - Vega-Lite sort specification
925
+ * @returns AxisCategoryOrder compatible value
926
+ */ function convertVegaSortToAxisCategoryOrder(sort) {
927
+ if (!sort) {
928
+ return undefined;
929
+ }
930
+ // Handle string sorts: 'ascending' | 'descending'
931
+ if (typeof sort === 'string') {
932
+ if (sort === 'ascending') {
933
+ return 'category ascending';
934
+ }
935
+ if (sort === 'descending') {
936
+ return 'category descending';
937
+ }
938
+ return undefined;
939
+ }
940
+ // Handle array sort (explicit ordering)
941
+ if (Array.isArray(sort)) {
942
+ return sort;
943
+ }
944
+ // Handle object sort with op and order
945
+ if (typeof sort === 'object' && sort.op && sort.order) {
946
+ const op = sort.op === 'average' ? 'mean' : sort.op; // Map 'average' to 'mean'
947
+ const order = sort.order === 'ascending' ? 'ascending' : 'descending';
948
+ return `${op} ${order}`;
949
+ }
950
+ return undefined;
951
+ }
952
+ /**
953
+ * Extracts axis category ordering from Vega-Lite encoding
954
+ * Returns props for xAxisCategoryOrder and yAxisCategoryOrder
955
+ */ function extractAxisCategoryOrderProps(encoding) {
956
+ var _encoding_x, _encoding_y;
957
+ const result = {};
958
+ if (encoding === null || encoding === void 0 ? void 0 : (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.sort) {
959
+ const xOrder = convertVegaSortToAxisCategoryOrder(encoding.x.sort);
960
+ if (xOrder) {
961
+ result.xAxisCategoryOrder = xOrder;
962
+ }
963
+ }
964
+ if (encoding === null || encoding === void 0 ? void 0 : (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.sort) {
965
+ const yOrder = convertVegaSortToAxisCategoryOrder(encoding.y.sort);
966
+ if (yOrder) {
967
+ result.yAxisCategoryOrder = yOrder;
968
+ }
969
+ }
970
+ return result;
971
+ }
972
+ /**
973
+ * Initializes the transformation context by normalizing spec and extracting common data
974
+ * This reduces boilerplate across all transformer functions
975
+ *
976
+ * @param spec - Vega-Lite specification
977
+ * @returns Normalized context with unit specs, data values, encoding, and mark properties
978
+ */ function initializeTransformContext(spec) {
979
+ var _colorEnc_condition, _encoding_x, _encoding_x1;
980
+ const unitSpecs = normalizeSpec(spec);
981
+ if (unitSpecs.length === 0) {
982
+ throw new Error('VegaLiteSchemaAdapter: No valid unit specs found in specification');
983
+ }
984
+ const primarySpec = unitSpecs[0];
985
+ const rawDataValues = extractDataValues(primarySpec.data);
986
+ // Apply any transforms from both top-level spec and primary unit spec
987
+ let dataValues = applyTransforms(rawDataValues, spec.transform);
988
+ dataValues = applyTransforms(dataValues, primarySpec.transform);
989
+ const encoding = primarySpec.encoding || {};
990
+ // Handle conditional color encoding — evaluate test expressions and materialize color values
991
+ const colorEnc = encoding.color;
992
+ if ((colorEnc === null || colorEnc === void 0 ? void 0 : colorEnc.condition) && typeof ((_colorEnc_condition = colorEnc.condition) === null || _colorEnc_condition === void 0 ? void 0 : _colorEnc_condition.test) === 'string') {
993
+ const condition = colorEnc.condition;
994
+ const elseValue = colorEnc.value || '#999';
995
+ const colorField = '__conditional_color__';
996
+ dataValues.forEach((row)=>{
997
+ try {
998
+ const testResult = safeEvaluateExpression(condition.test, row);
999
+ row[colorField] = testResult ? condition.value : elseValue;
1000
+ } catch {
1001
+ row[colorField] = elseValue;
1002
+ }
1003
+ });
1004
+ // Replace conditional color with a field-based color encoding using the materialized values
1005
+ encoding.color = {
1006
+ field: colorField,
1007
+ type: 'nominal',
1008
+ scale: {
1009
+ domain: [
1010
+ condition.value,
1011
+ elseValue
1012
+ ],
1013
+ range: [
1014
+ condition.value,
1015
+ elseValue
1016
+ ]
1017
+ },
1018
+ legend: null
1019
+ };
1020
+ }
1021
+ // Handle timeUnit on x/y encodings — aggregate data by time unit
1022
+ if (((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.timeUnit) && ((_encoding_x1 = encoding.x) === null || _encoding_x1 === void 0 ? void 0 : _encoding_x1.field)) {
1023
+ var _encoding_y, _encoding_y1, _encoding_y2;
1024
+ const field = encoding.x.field;
1025
+ const unit = encoding.x.timeUnit;
1026
+ const yField = (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.field;
1027
+ const yAgg = ((_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.aggregate) || (yField ? 'mean' : 'count');
1028
+ const groups = new Map();
1029
+ dataValues.forEach((row)=>{
1030
+ const dateVal = new Date(row[field]);
1031
+ if (isNaN(dateVal.getTime())) {
1032
+ return;
1033
+ }
1034
+ let key;
1035
+ switch(unit){
1036
+ case 'year':
1037
+ key = String(dateVal.getFullYear());
1038
+ break;
1039
+ case 'quarter':
1040
+ key = `Q${Math.floor(dateVal.getMonth() / 3) + 1}`;
1041
+ break;
1042
+ case 'month':
1043
+ key = dateVal.toLocaleString('en', {
1044
+ month: 'short'
1045
+ });
1046
+ break;
1047
+ case 'day':
1048
+ key = String(dateVal.getDate());
1049
+ break;
1050
+ case 'hours':
1051
+ key = String(dateVal.getHours());
1052
+ break;
1053
+ default:
1054
+ key = String(dateVal);
1055
+ }
1056
+ if (!groups.has(key)) {
1057
+ groups.set(key, []);
1058
+ }
1059
+ groups.get(key).push(row);
1060
+ });
1061
+ dataValues = Array.from(groups.entries()).map(([key, rows])=>{
1062
+ const result = {
1063
+ [field]: key
1064
+ };
1065
+ if (yField && yAgg !== 'count') {
1066
+ const vals = rows.map((r)=>Number(r[yField])).filter((v)=>!isNaN(v));
1067
+ var _d3Mean;
1068
+ result[yField] = yAgg === 'sum' ? d3Sum(vals) : (_d3Mean = d3Mean(vals)) !== null && _d3Mean !== void 0 ? _d3Mean : 0;
1069
+ } else {
1070
+ result[yField || '__count'] = rows.length;
1071
+ }
1072
+ return result;
1073
+ });
1074
+ // Switch x from temporal to ordinal since we've aggregated
1075
+ encoding.x.type = 'ordinal';
1076
+ delete encoding.x.timeUnit;
1077
+ if ((_encoding_y2 = encoding.y) === null || _encoding_y2 === void 0 ? void 0 : _encoding_y2.aggregate) {
1078
+ delete encoding.y.aggregate;
1079
+ }
1080
+ }
1081
+ const markProps = getMarkProperties(primarySpec.mark);
1082
+ return {
1083
+ unitSpecs,
1084
+ primarySpec,
1085
+ dataValues,
1086
+ encoding,
1087
+ markProps
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Extracts common encoding fields and aggregates from Vega-Lite encoding
1092
+ *
1093
+ * @param encoding - Vega-Lite encoding specification
1094
+ * @returns Object containing extracted field names and aggregates
1095
+ */ function extractEncodingFields(encoding) {
1096
+ var _encoding_x, _encoding_y, _encoding_x2, _encoding_color, _encoding_size, _encoding_theta, _encoding_radius, _encoding_x1, _encoding_y1;
1097
+ return {
1098
+ xField: (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.field,
1099
+ yField: (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.field,
1100
+ x2Field: (_encoding_x2 = encoding.x2) === null || _encoding_x2 === void 0 ? void 0 : _encoding_x2.field,
1101
+ colorField: (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.field,
1102
+ sizeField: (_encoding_size = encoding.size) === null || _encoding_size === void 0 ? void 0 : _encoding_size.field,
1103
+ thetaField: (_encoding_theta = encoding.theta) === null || _encoding_theta === void 0 ? void 0 : _encoding_theta.field,
1104
+ radiusField: (_encoding_radius = encoding.radius) === null || _encoding_radius === void 0 ? void 0 : _encoding_radius.field,
1105
+ xAggregate: (_encoding_x1 = encoding.x) === null || _encoding_x1 === void 0 ? void 0 : _encoding_x1.aggregate,
1106
+ yAggregate: (_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.aggregate
1107
+ };
1108
+ }
1109
+ /**
1110
+ * Computes aggregate values for bar charts
1111
+ * Supports count, sum, mean, min, max aggregations
1112
+ *
1113
+ * @param data - Array of data values
1114
+ * @param groupField - Field to group by (x-axis field)
1115
+ * @param valueField - Field to aggregate (y-axis field, optional for count)
1116
+ * @param aggregate - Aggregate function (count, sum, mean, min, max)
1117
+ * @returns Array of {category, value} objects
1118
+ */ function computeAggregateData(data, groupField, valueField, aggregate) {
1119
+ // Group data by category
1120
+ const groups = new Map();
1121
+ data.forEach((row)=>{
1122
+ const category = String(row[groupField]);
1123
+ if (aggregate === 'count') {
1124
+ // For count, just track the count
1125
+ if (!groups.has(category)) {
1126
+ groups.set(category, []);
1127
+ }
1128
+ groups.get(category).push(1);
1129
+ } else if (valueField && row[valueField] !== undefined && row[valueField] !== null) {
1130
+ // For other aggregates, collect values
1131
+ const value = Number(row[valueField]);
1132
+ if (!isNaN(value)) {
1133
+ if (!groups.has(category)) {
1134
+ groups.set(category, []);
1135
+ }
1136
+ groups.get(category).push(value);
1137
+ }
1138
+ }
1139
+ });
1140
+ // Compute aggregate for each group
1141
+ const result = [];
1142
+ groups.forEach((values, category)=>{
1143
+ let aggregatedValue;
1144
+ switch(aggregate){
1145
+ case 'count':
1146
+ aggregatedValue = values.length;
1147
+ break;
1148
+ case 'sum':
1149
+ aggregatedValue = values.reduce((a, b)=>a + b, 0);
1150
+ break;
1151
+ case 'mean':
1152
+ case 'average':
1153
+ aggregatedValue = values.reduce((a, b)=>a + b, 0) / values.length;
1154
+ break;
1155
+ case 'min':
1156
+ var _d3Min;
1157
+ aggregatedValue = (_d3Min = d3Min(values)) !== null && _d3Min !== void 0 ? _d3Min : 0;
1158
+ break;
1159
+ case 'max':
1160
+ var _d3Max;
1161
+ aggregatedValue = (_d3Max = d3Max(values)) !== null && _d3Max !== void 0 ? _d3Max : 0;
1162
+ break;
1163
+ default:
1164
+ aggregatedValue = values.length; // Default to count
1165
+ }
1166
+ result.push({
1167
+ category,
1168
+ value: aggregatedValue
1169
+ });
1170
+ });
1171
+ return result;
1172
+ }
1173
+ /**
1174
+ * Counts rows per x-category, optionally grouped by a secondary (color) field.
1175
+ * Returns a Map<xKey, Map<legend, count>>.
1176
+ */ function countByCategory(dataValues, xField, colorField, defaultLegend) {
1177
+ const countMap = new Map();
1178
+ dataValues.forEach((row)=>{
1179
+ const xValue = row[xField];
1180
+ if (xValue === undefined) {
1181
+ return;
1182
+ }
1183
+ const xKey = String(xValue);
1184
+ const legend = colorField && row[colorField] !== undefined ? String(row[colorField]) : defaultLegend;
1185
+ if (!countMap.has(xKey)) {
1186
+ countMap.set(xKey, new Map());
1187
+ }
1188
+ const legendMap = countMap.get(xKey);
1189
+ legendMap.set(legend, (legendMap.get(legend) || 0) + 1);
1190
+ });
1191
+ return countMap;
1192
+ }
1193
+ /**
1194
+ * Extracts color configuration from Vega-Lite encoding
1195
+ *
1196
+ * @param encoding - Vega-Lite encoding specification
1197
+ * @returns Color scheme and range configuration
1198
+ */ function extractColorConfig(encoding) {
1199
+ var _encoding_color_scale, _encoding_color, _encoding_color_scale1, _encoding_color1;
1200
+ return {
1201
+ colorScheme: (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : (_encoding_color_scale = _encoding_color.scale) === null || _encoding_color_scale === void 0 ? void 0 : _encoding_color_scale.scheme,
1202
+ colorRange: (_encoding_color1 = encoding.color) === null || _encoding_color1 === void 0 ? void 0 : (_encoding_color_scale1 = _encoding_color1.scale) === null || _encoding_color_scale1 === void 0 ? void 0 : _encoding_color_scale1.range
1203
+ };
1204
+ }
1205
+ /**
1206
+ * Groups data rows into series based on color encoding field
1207
+ * Returns a map of series name to data points and ordinal mapping for categorical x-axis
1208
+ */ function groupDataBySeries(dataValues, xField, yField, colorField, isXTemporal, isYTemporal, xType, sizeField, yType) {
1209
+ const seriesMap = new Map();
1210
+ if (!xField || !yField) {
1211
+ return {
1212
+ seriesMap
1213
+ };
1214
+ }
1215
+ // Check if x-axis is ordinal/nominal (categorical)
1216
+ const isXOrdinal = xType === 'ordinal' || xType === 'nominal';
1217
+ const ordinalMapping = isXOrdinal ? new Map() : undefined;
1218
+ const ordinalLabels = [];
1219
+ // Check if y-axis is ordinal/nominal (categorical)
1220
+ const isYOrdinal = yType === 'ordinal' || yType === 'nominal';
1221
+ const yOrdinalMapping = isYOrdinal ? new Map() : undefined;
1222
+ const yOrdinalLabels = [];
1223
+ dataValues.forEach((row)=>{
1224
+ const xValue = parseValue(row[xField], isXTemporal);
1225
+ const yValue = parseValue(row[yField], isYTemporal);
1226
+ // Skip invalid values using chart-utilities validation
1227
+ if (isInvalidValue(xValue) || isInvalidValue(yValue)) {
1228
+ return;
1229
+ }
1230
+ // Skip if x or y is empty string (from null/undefined) or y is not a valid number/string
1231
+ if (xValue === '' || yValue === '' || typeof yValue !== 'number' && typeof yValue !== 'string') {
1232
+ return;
1233
+ }
1234
+ const seriesName = colorField && row[colorField] !== undefined ? String(row[colorField]) : 'default';
1235
+ if (!seriesMap.has(seriesName)) {
1236
+ seriesMap.set(seriesName, []);
1237
+ }
1238
+ // Handle x-value based on type
1239
+ let numericX;
1240
+ if (isXOrdinal && typeof xValue === 'string') {
1241
+ // For ordinal data, map each unique string to a sequential index
1242
+ if (!ordinalMapping.has(xValue)) {
1243
+ ordinalMapping.set(xValue, ordinalMapping.size);
1244
+ ordinalLabels.push(xValue);
1245
+ }
1246
+ numericX = ordinalMapping.get(xValue);
1247
+ } else if (typeof xValue === 'string') {
1248
+ // For non-ordinal strings, try to parse as float (fallback to 0)
1249
+ const parsed = parseFloat(xValue);
1250
+ if (isNaN(parsed)) {
1251
+ return;
1252
+ }
1253
+ numericX = parsed;
1254
+ } else {
1255
+ numericX = xValue;
1256
+ }
1257
+ const markerSize = sizeField && row[sizeField] !== undefined ? Number(row[sizeField]) : undefined;
1258
+ // Handle y-value: numeric or ordinal mapping
1259
+ let numericY;
1260
+ if (isYOrdinal && typeof yValue === 'string') {
1261
+ if (!yOrdinalMapping.has(yValue)) {
1262
+ yOrdinalMapping.set(yValue, yOrdinalMapping.size);
1263
+ yOrdinalLabels.push(yValue);
1264
+ }
1265
+ numericY = yOrdinalMapping.get(yValue);
1266
+ } else {
1267
+ numericY = typeof yValue === 'number' ? yValue : 0;
1268
+ }
1269
+ seriesMap.get(seriesName).push({
1270
+ x: numericX,
1271
+ y: numericY,
1272
+ ...markerSize !== undefined && !isNaN(markerSize) && {
1273
+ markerSize
1274
+ }
1275
+ });
1276
+ });
1277
+ return {
1278
+ seriesMap,
1279
+ ordinalMapping,
1280
+ ordinalLabels: ordinalLabels.length > 0 ? ordinalLabels : undefined,
1281
+ yOrdinalLabels: yOrdinalLabels.length > 0 ? yOrdinalLabels : undefined
1282
+ };
1283
+ }
1284
+ /**
1285
+ * Finds the primary data layer from unit specs for line/area charts
1286
+ * Skips rect layers (used for color fill bars) and finds the actual line/point/area layer
1287
+ *
1288
+ * @param unitSpecs - Array of normalized unit specs
1289
+ * @returns The primary spec containing the actual chart data, or undefined if not found
1290
+ */ function findPrimaryLineSpec(unitSpecs) {
1291
+ // First, try to find a line, point, or area layer
1292
+ const lineSpec = unitSpecs.find((spec)=>{
1293
+ const markType = getMarkType(spec.mark);
1294
+ return markType === 'line' || markType === 'point' || markType === 'area';
1295
+ });
1296
+ if (lineSpec) {
1297
+ return lineSpec;
1298
+ }
1299
+ // If no line/point/area layer, find first layer with actual field encodings (not just datum)
1300
+ const dataSpec = unitSpecs.find((spec)=>{
1301
+ var _encoding_x, _encoding_y;
1302
+ const encoding = spec.encoding || {};
1303
+ return ((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.field) || ((_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.field);
1304
+ });
1305
+ return dataSpec || unitSpecs[0];
1306
+ }
1307
+ /**
1308
+ * Transforms Vega-Lite specification to Fluent LineChart props
1309
+ *
1310
+ * @param spec - Vega-Lite specification
1311
+ * @param colorMap - Color mapping ref for consistent coloring
1312
+ * @param isDarkTheme - Whether dark theme is active
1313
+ * @returns LineChartProps for rendering with Fluent LineChart component
1314
+ */ /**
1315
+ * Auto-corrects encoding types in a Vega-Lite spec based on actual data values.
1316
+ * Call this before chart type detection so routing decisions use corrected types.
1317
+ *
1318
+ * Corrections applied:
1319
+ * - quantitative + string data → nominal (render as categorical)
1320
+ *
1321
+ * This mutates the spec encoding in place.
1322
+ */ export function autoCorrectEncodingTypes(spec) {
1323
+ var _encoding_x, _encoding_y;
1324
+ const unitSpec = spec.layer ? spec.layer[0] : spec;
1325
+ if (!unitSpec) {
1326
+ return;
1327
+ }
1328
+ const encoding = unitSpec.encoding;
1329
+ var _unitSpec_data;
1330
+ const data = extractDataValues((_unitSpec_data = unitSpec.data) !== null && _unitSpec_data !== void 0 ? _unitSpec_data : spec.data);
1331
+ if (!encoding || data.length === 0) {
1332
+ return;
1333
+ }
1334
+ // Check x encoding
1335
+ if ((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.field) {
1336
+ const sample = data.map((row)=>row[encoding.x.field]).find((v)=>v !== null && v !== undefined);
1337
+ if (sample !== undefined) {
1338
+ if (encoding.x.type === 'quantitative') {
1339
+ if (typeof sample === 'string' && !isFinite(Number(sample))) {
1340
+ encoding.x.type = 'nominal';
1341
+ } else if (typeof sample === 'object') {
1342
+ encoding.x.type = 'nominal';
1343
+ }
1344
+ } else if (encoding.x.type === 'temporal') {
1345
+ const isValidDate = sample instanceof Date || typeof sample === 'string' && !isNaN(Date.parse(sample));
1346
+ if (!isValidDate) {
1347
+ encoding.x.type = typeof sample === 'number' ? 'quantitative' : 'nominal';
1348
+ }
1349
+ }
1350
+ }
1351
+ }
1352
+ // Check y encoding
1353
+ if ((_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.field) {
1354
+ const sample = data.map((row)=>row[encoding.y.field]).find((v)=>v !== null && v !== undefined);
1355
+ if (sample !== undefined) {
1356
+ if (encoding.y.type === 'quantitative') {
1357
+ if (typeof sample === 'string' && !isFinite(Number(sample))) {
1358
+ encoding.y.type = 'nominal';
1359
+ } else if (typeof sample === 'object' && sample !== null && !Array.isArray(sample) && !(sample instanceof Date)) {
1360
+ // Try to extract a numeric value from the object
1361
+ const numericKeys = Object.keys(sample).filter((k)=>typeof sample[k] === 'number');
1362
+ if (numericKeys.length === 1) {
1363
+ // Object has exactly one numeric property - use it as the value
1364
+ const numericKey = numericKeys[0];
1365
+ const yField = encoding.y.field;
1366
+ data.forEach((row)=>{
1367
+ const obj = row[yField];
1368
+ if (typeof obj === 'object' && obj !== null) {
1369
+ row[yField] = obj[numericKey];
1370
+ }
1371
+ });
1372
+ // Keep type as quantitative since we extracted numeric values
1373
+ } else {
1374
+ encoding.y.type = 'nominal';
1375
+ }
1376
+ } else if (typeof sample === 'object') {
1377
+ encoding.y.type = 'nominal';
1378
+ }
1379
+ } else if (encoding.y.type === 'temporal') {
1380
+ const isValidDate = sample instanceof Date || typeof sample === 'string' && !isNaN(Date.parse(sample));
1381
+ if (!isValidDate) {
1382
+ encoding.y.type = typeof sample === 'number' ? 'quantitative' : 'nominal';
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Determines the chart type based on Vega-Lite spec
1390
+ */ export function getChartType(spec) {
1391
+ var _spec_layer_, _spec_layer_1, _encoding_color, _encoding_x, _encoding_y, _encoding_color1;
1392
+ // Auto-correct encoding types based on actual data BEFORE chart type detection
1393
+ autoCorrectEncodingTypes(spec);
1394
+ // Handle layered specs - check if it's a bar+line combo for stacked bar with lines
1395
+ if (spec.layer && spec.layer.length > 1) {
1396
+ const marks = spec.layer.map((layer)=>getMarkType(layer.mark));
1397
+ const hasBar = marks.includes('bar');
1398
+ const hasLine = marks.includes('line') || marks.includes('point');
1399
+ // Bar + line combo should use stacked bar chart (which supports line overlays)
1400
+ if (hasBar && hasLine) {
1401
+ var _barLayer_encoding_color, _barLayer_encoding;
1402
+ const barLayer = spec.layer.find((layer)=>getMarkType(layer.mark) === 'bar');
1403
+ if (barLayer === null || barLayer === void 0 ? void 0 : (_barLayer_encoding = barLayer.encoding) === null || _barLayer_encoding === void 0 ? void 0 : (_barLayer_encoding_color = _barLayer_encoding.color) === null || _barLayer_encoding_color === void 0 ? void 0 : _barLayer_encoding_color.field) {
1404
+ return {
1405
+ type: 'stacked-bar',
1406
+ mark: 'bar'
1407
+ };
1408
+ }
1409
+ return {
1410
+ type: 'stacked-bar',
1411
+ mark: 'bar'
1412
+ };
1413
+ }
1414
+ }
1415
+ // Handle layered specs - use first layer's mark for other cases
1416
+ const mark = spec.layer ? (_spec_layer_ = spec.layer[0]) === null || _spec_layer_ === void 0 ? void 0 : _spec_layer_.mark : spec.mark;
1417
+ const markType = getMarkType(mark);
1418
+ const encoding = spec.layer ? (_spec_layer_1 = spec.layer[0]) === null || _spec_layer_1 === void 0 ? void 0 : _spec_layer_1.encoding : spec.encoding;
1419
+ const hasColorEncoding = !!(encoding === null || encoding === void 0 ? void 0 : (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.field);
1420
+ // Polar charts with arc marks: theta AND radius encodings
1421
+ if (markType === 'arc' && (encoding === null || encoding === void 0 ? void 0 : encoding.theta) && (encoding === null || encoding === void 0 ? void 0 : encoding.radius)) {
1422
+ return {
1423
+ type: 'polar',
1424
+ mark: markType
1425
+ };
1426
+ }
1427
+ // Arc marks for pie/donut charts (theta only, no radius)
1428
+ if (markType === 'arc' && (encoding === null || encoding === void 0 ? void 0 : encoding.theta)) {
1429
+ return {
1430
+ type: 'donut',
1431
+ mark: markType
1432
+ };
1433
+ }
1434
+ // Polar charts: non-arc marks with theta and radius encodings
1435
+ if ((encoding === null || encoding === void 0 ? void 0 : encoding.theta) && (encoding === null || encoding === void 0 ? void 0 : encoding.radius)) {
1436
+ return {
1437
+ type: 'polar',
1438
+ mark: markType
1439
+ };
1440
+ }
1441
+ // Rect marks for heatmaps (quantitative or nominal color)
1442
+ if (markType === 'rect' && (encoding === null || encoding === void 0 ? void 0 : (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.field) && (encoding === null || encoding === void 0 ? void 0 : (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.field) && (encoding === null || encoding === void 0 ? void 0 : (_encoding_color1 = encoding.color) === null || _encoding_color1 === void 0 ? void 0 : _encoding_color1.field)) {
1443
+ return {
1444
+ type: 'heatmap',
1445
+ mark: markType
1446
+ };
1447
+ }
1448
+ // Bar charts
1449
+ if (markType === 'bar') {
1450
+ var _encoding_x1, _encoding_x2, _encoding_x3, _encoding_y1, _encoding_y2, _encoding_x4, _spec_data;
1451
+ if (encoding === null || encoding === void 0 ? void 0 : (_encoding_x1 = encoding.x) === null || _encoding_x1 === void 0 ? void 0 : _encoding_x1.bin) {
1452
+ return {
1453
+ type: 'histogram',
1454
+ mark: markType
1455
+ };
1456
+ }
1457
+ const isXNominal = (encoding === null || encoding === void 0 ? void 0 : (_encoding_x2 = encoding.x) === null || _encoding_x2 === void 0 ? void 0 : _encoding_x2.type) === 'nominal' || (encoding === null || encoding === void 0 ? void 0 : (_encoding_x3 = encoding.x) === null || _encoding_x3 === void 0 ? void 0 : _encoding_x3.type) === 'ordinal';
1458
+ const isYNominal = (encoding === null || encoding === void 0 ? void 0 : (_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.type) === 'nominal' || (encoding === null || encoding === void 0 ? void 0 : (_encoding_y2 = encoding.y) === null || _encoding_y2 === void 0 ? void 0 : _encoding_y2.type) === 'ordinal';
1459
+ if (isYNominal && !isXNominal) {
1460
+ return {
1461
+ type: 'horizontal-bar',
1462
+ mark: markType
1463
+ };
1464
+ }
1465
+ if (hasColorEncoding) {
1466
+ const hasXOffset = !!(encoding === null || encoding === void 0 ? void 0 : encoding.xOffset);
1467
+ if (hasXOffset) {
1468
+ return {
1469
+ type: 'grouped-bar',
1470
+ mark: markType
1471
+ };
1472
+ }
1473
+ return {
1474
+ type: 'stacked-bar',
1475
+ mark: markType
1476
+ };
1477
+ }
1478
+ const xField = encoding === null || encoding === void 0 ? void 0 : (_encoding_x4 = encoding.x) === null || _encoding_x4 === void 0 ? void 0 : _encoding_x4.field;
1479
+ const dataValues = (_spec_data = spec.data) === null || _spec_data === void 0 ? void 0 : _spec_data.values;
1480
+ if (xField && Array.isArray(dataValues) && dataValues.length > 0) {
1481
+ const xValues = dataValues.map((row)=>row[xField]);
1482
+ const uniqueXValues = new Set(xValues);
1483
+ if (uniqueXValues.size < xValues.length) {
1484
+ return {
1485
+ type: 'stacked-bar',
1486
+ mark: markType
1487
+ };
1488
+ }
1489
+ }
1490
+ return {
1491
+ type: 'bar',
1492
+ mark: markType
1493
+ };
1494
+ }
1495
+ if (markType === 'area') {
1496
+ return {
1497
+ type: 'area',
1498
+ mark: markType
1499
+ };
1500
+ }
1501
+ if (markType === 'point' || markType === 'circle' || markType === 'square' || markType === 'tick') {
1502
+ return {
1503
+ type: 'scatter',
1504
+ mark: markType
1505
+ };
1506
+ }
1507
+ // Trail marks rendered as line charts (size encoding as markerSize)
1508
+ if (markType === 'trail') {
1509
+ return {
1510
+ type: 'line',
1511
+ mark: 'line'
1512
+ };
1513
+ }
1514
+ // Error bar/band marks rendered as line charts
1515
+ if (markType === 'errorbar' || markType === 'errorband') {
1516
+ return {
1517
+ type: 'line',
1518
+ mark: 'line'
1519
+ };
1520
+ }
1521
+ return {
1522
+ type: 'line',
1523
+ mark: markType || 'line'
1524
+ };
1525
+ }
1526
+ export function transformVegaLiteToLineChartProps(spec, colorMap, isDarkTheme) {
1527
+ var _encoding_x, _encoding_y, _encoding_x1, _encoding_y1, _encoding_x2, _encoding_y2, _spec_title, _encoding_x_axis, _encoding_x3, _encoding_y_axis, _encoding_y3, _encoding_x_axis1, _encoding_x4, _encoding_y_axis1, _encoding_y4, _encoding_x_axis2, _encoding_x5, _encoding_y_axis2, _encoding_y5, _encoding_color_legend, _encoding_color;
1528
+ // Initialize transformation context, but find the primary line/point layer for layered specs
1529
+ const unitSpecs = normalizeSpec(spec);
1530
+ if (unitSpecs.length === 0) {
1531
+ throw new Error('VegaLiteSchemaAdapter: No valid unit specs found in specification');
1532
+ }
1533
+ // For layered specs, find the actual line/point layer (not rect layers for color fill bars)
1534
+ const primarySpec = findPrimaryLineSpec(unitSpecs);
1535
+ if (!primarySpec) {
1536
+ throw new Error('VegaLiteSchemaAdapter: No valid line/point layer found in specification');
1537
+ }
1538
+ // Check if there's a point layer in addition to line layer (for line+point combo charts)
1539
+ const hasPointLayer = unitSpecs.some((unitSpec)=>getMarkType(unitSpec.mark) === 'point');
1540
+ const hasLineLayer = unitSpecs.some((unitSpec)=>getMarkType(unitSpec.mark) === 'line');
1541
+ const shouldShowPoints = hasPointLayer && hasLineLayer;
1542
+ const rawDataValues = extractDataValues(primarySpec.data);
1543
+ // Apply any transforms (fold, etc.) from the spec
1544
+ const dataValues = applyTransforms(rawDataValues, spec.transform);
1545
+ const encoding = primarySpec.encoding || {};
1546
+ const markProps = getMarkProperties(primarySpec.mark);
1547
+ // Extract field names
1548
+ const { xField, yField, colorField } = extractEncodingFields(encoding);
1549
+ // Check for size encoding from any layer (e.g., point layer with size in line+point combo)
1550
+ let sizeField;
1551
+ if (unitSpecs.length > 1) {
1552
+ for (const unitSpec of unitSpecs){
1553
+ var _unitEncoding_size;
1554
+ const unitEncoding = unitSpec.encoding || {};
1555
+ if ((_unitEncoding_size = unitEncoding.size) === null || _unitEncoding_size === void 0 ? void 0 : _unitEncoding_size.field) {
1556
+ sizeField = unitEncoding.size.field;
1557
+ break;
1558
+ }
1559
+ }
1560
+ } else {
1561
+ var _encoding_size;
1562
+ sizeField = (_encoding_size = encoding.size) === null || _encoding_size === void 0 ? void 0 : _encoding_size.field;
1563
+ }
1564
+ // Validate data and encodings
1565
+ if (!xField || !yField) {
1566
+ throw new Error('VegaLiteSchemaAdapter: Line chart requires both x and y encodings with field names');
1567
+ }
1568
+ validateXYEncodings(dataValues, xField, yField, (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.type, (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.type, 'LineChart', encoding);
1569
+ const isXTemporal = ((_encoding_x1 = encoding.x) === null || _encoding_x1 === void 0 ? void 0 : _encoding_x1.type) === 'temporal';
1570
+ const isYTemporal = ((_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.type) === 'temporal';
1571
+ // Group data into series
1572
+ const { seriesMap, ordinalLabels, yOrdinalLabels } = groupDataBySeries(dataValues, xField, yField, colorField, isXTemporal, isYTemporal, (_encoding_x2 = encoding.x) === null || _encoding_x2 === void 0 ? void 0 : _encoding_x2.type, sizeField, (_encoding_y2 = encoding.y) === null || _encoding_y2 === void 0 ? void 0 : _encoding_y2.type);
1573
+ // Extract color configuration
1574
+ const { colorScheme, colorRange } = extractColorConfig(encoding);
1575
+ // Convert series map to LineChartPoints array
1576
+ const lineChartData = [];
1577
+ const colorIndex = new Map();
1578
+ let currentColorIndex = 0;
1579
+ seriesMap.forEach((dataPoints, seriesName)=>{
1580
+ if (!colorIndex.has(seriesName)) {
1581
+ colorIndex.set(seriesName, currentColorIndex++);
1582
+ }
1583
+ const color = resolveColor(seriesName, colorIndex.get(seriesName), undefined, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
1584
+ const curveOption = mapInterpolateToCurve(markProps.interpolate);
1585
+ // Build line options with curve, strokeDash, and strokeWidth
1586
+ const lineOptions = {};
1587
+ if (curveOption) {
1588
+ lineOptions.curve = curveOption;
1589
+ }
1590
+ if (markProps.strokeDash) {
1591
+ lineOptions.strokeDasharray = markProps.strokeDash.join(' ');
1592
+ }
1593
+ if (markProps.strokeWidth) {
1594
+ lineOptions.strokeWidth = markProps.strokeWidth;
1595
+ }
1596
+ lineChartData.push({
1597
+ legend: seriesName,
1598
+ data: dataPoints,
1599
+ color,
1600
+ hideNonActiveDots: !shouldShowPoints,
1601
+ ...Object.keys(lineOptions).length > 0 && {
1602
+ lineOptions
1603
+ }
1604
+ });
1605
+ });
1606
+ // Extract chart title
1607
+ const chartTitle = typeof spec.title === 'string' ? spec.title : (_spec_title = spec.title) === null || _spec_title === void 0 ? void 0 : _spec_title.text;
1608
+ var _encoding_x_axis_title;
1609
+ // Extract axis titles and formats
1610
+ const xAxisTitle = (_encoding_x_axis_title = (_encoding_x3 = encoding.x) === null || _encoding_x3 === void 0 ? void 0 : (_encoding_x_axis = _encoding_x3.axis) === null || _encoding_x_axis === void 0 ? void 0 : _encoding_x_axis.title) !== null && _encoding_x_axis_title !== void 0 ? _encoding_x_axis_title : undefined;
1611
+ var _encoding_y_axis_title;
1612
+ const yAxisTitle = (_encoding_y_axis_title = (_encoding_y3 = encoding.y) === null || _encoding_y3 === void 0 ? void 0 : (_encoding_y_axis = _encoding_y3.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.title) !== null && _encoding_y_axis_title !== void 0 ? _encoding_y_axis_title : undefined;
1613
+ const tickFormat = (_encoding_x4 = encoding.x) === null || _encoding_x4 === void 0 ? void 0 : (_encoding_x_axis1 = _encoding_x4.axis) === null || _encoding_x_axis1 === void 0 ? void 0 : _encoding_x_axis1.format;
1614
+ const yAxisTickFormat = (_encoding_y4 = encoding.y) === null || _encoding_y4 === void 0 ? void 0 : (_encoding_y_axis1 = _encoding_y4.axis) === null || _encoding_y_axis1 === void 0 ? void 0 : _encoding_y_axis1.format;
1615
+ // Extract tick values and counts
1616
+ // Use ordinalLabels for ordinal x-axis, otherwise use explicit values from spec
1617
+ const tickValues = ordinalLabels || ((_encoding_x5 = encoding.x) === null || _encoding_x5 === void 0 ? void 0 : (_encoding_x_axis2 = _encoding_x5.axis) === null || _encoding_x_axis2 === void 0 ? void 0 : _encoding_x_axis2.values);
1618
+ const yAxisTickCount = (_encoding_y5 = encoding.y) === null || _encoding_y5 === void 0 ? void 0 : (_encoding_y_axis2 = _encoding_y5.axis) === null || _encoding_y_axis2 === void 0 ? void 0 : _encoding_y_axis2.tickCount;
1619
+ // Extract domain/range for min/max values
1620
+ const { yMinValue, yMaxValue } = extractYMinMax(encoding, dataValues);
1621
+ // Extract annotations and color fill bars from layers
1622
+ const annotations = extractAnnotations(spec);
1623
+ const colorFillBars = extractColorFillBars(spec, colorMap, isDarkTheme);
1624
+ // Convert rule marks in layered specs to reference line series
1625
+ // Each horizontal rule becomes a 2-point line at constant y spanning the data x-range
1626
+ if (spec.layer && Array.isArray(spec.layer) && lineChartData.length > 0) {
1627
+ const allXValues = lineChartData.flatMap((series)=>series.data.map((p)=>p.x));
1628
+ const xMin = allXValues.length > 0 ? allXValues.reduce((a, b)=>a < b ? a : b) : 0;
1629
+ const xMax = allXValues.length > 0 ? allXValues.reduce((a, b)=>a > b ? a : b) : 0;
1630
+ spec.layer.forEach((layer, layerIndex)=>{
1631
+ var _ruleEncoding_y, _ruleEncoding_y1, _textLayer_encoding_text, _textLayer_encoding, _textLayer_encoding_text1, _textLayer_encoding1;
1632
+ const layerMark = getMarkType(layer.mark);
1633
+ if (layerMark !== 'rule') {
1634
+ return;
1635
+ }
1636
+ const ruleEncoding = layer.encoding || {};
1637
+ var _ruleEncoding_y_datum;
1638
+ const yDatum = (_ruleEncoding_y_datum = (_ruleEncoding_y = ruleEncoding.y) === null || _ruleEncoding_y === void 0 ? void 0 : _ruleEncoding_y.datum) !== null && _ruleEncoding_y_datum !== void 0 ? _ruleEncoding_y_datum : (_ruleEncoding_y1 = ruleEncoding.y) === null || _ruleEncoding_y1 === void 0 ? void 0 : _ruleEncoding_y1.value;
1639
+ if (yDatum === undefined) {
1640
+ return;
1641
+ }
1642
+ const ruleMarkProps = getMarkProperties(layer.mark);
1643
+ const ruleColor = ruleMarkProps.color || '#d62728';
1644
+ // Find companion text annotation for legend name
1645
+ const textLayer = spec.layer.find((l)=>{
1646
+ var _l_encoding;
1647
+ const m = getMarkType(l.mark);
1648
+ var _l_encoding_y_datum;
1649
+ return m === 'text' && ((_l_encoding = l.encoding) === null || _l_encoding === void 0 ? void 0 : _l_encoding.y) && ((_l_encoding_y_datum = l.encoding.y.datum) !== null && _l_encoding_y_datum !== void 0 ? _l_encoding_y_datum : l.encoding.y.value) === yDatum;
1650
+ });
1651
+ const ruleLegend = textLayer ? String(((_textLayer_encoding = textLayer.encoding) === null || _textLayer_encoding === void 0 ? void 0 : (_textLayer_encoding_text = _textLayer_encoding.text) === null || _textLayer_encoding_text === void 0 ? void 0 : _textLayer_encoding_text.datum) || ((_textLayer_encoding1 = textLayer.encoding) === null || _textLayer_encoding1 === void 0 ? void 0 : (_textLayer_encoding_text1 = _textLayer_encoding1.text) === null || _textLayer_encoding_text1 === void 0 ? void 0 : _textLayer_encoding_text1.value) || `y=${yDatum}`) : `y=${yDatum}`;
1652
+ const ruleLineOptions = {};
1653
+ if (ruleMarkProps.strokeDash) {
1654
+ ruleLineOptions.strokeDasharray = ruleMarkProps.strokeDash.join(' ');
1655
+ }
1656
+ if (ruleMarkProps.strokeWidth) {
1657
+ ruleLineOptions.strokeWidth = ruleMarkProps.strokeWidth;
1658
+ }
1659
+ lineChartData.push({
1660
+ legend: ruleLegend,
1661
+ data: [
1662
+ {
1663
+ x: xMin,
1664
+ y: yDatum
1665
+ },
1666
+ {
1667
+ x: xMax,
1668
+ y: yDatum
1669
+ }
1670
+ ],
1671
+ color: ruleColor,
1672
+ hideNonActiveDots: true,
1673
+ ...Object.keys(ruleLineOptions).length > 0 && {
1674
+ lineOptions: ruleLineOptions
1675
+ }
1676
+ });
1677
+ });
1678
+ }
1679
+ // Check for log scale on Y-axis
1680
+ const yAxisType = extractYAxisType(encoding);
1681
+ // Extract axis category ordering
1682
+ const categoryOrderProps = extractAxisCategoryOrderProps(encoding);
1683
+ // Build LineChartProps
1684
+ const chartProps = {
1685
+ lineChartData,
1686
+ ...chartTitle && {
1687
+ chartTitle
1688
+ }
1689
+ };
1690
+ var _encoding_color_legend_disable;
1691
+ return {
1692
+ data: chartProps,
1693
+ width: typeof spec.width === 'number' ? spec.width : undefined,
1694
+ height: typeof spec.height === 'number' ? spec.height : undefined,
1695
+ ...xAxisTitle && {
1696
+ chartTitle: xAxisTitle
1697
+ },
1698
+ ...yAxisTitle && {
1699
+ yAxisTitle
1700
+ },
1701
+ ...tickFormat && {
1702
+ tickFormat
1703
+ },
1704
+ ...yAxisTickFormat && {
1705
+ yAxisTickFormat
1706
+ },
1707
+ ...tickValues && {
1708
+ tickValues
1709
+ },
1710
+ ...yAxisTickCount && {
1711
+ yAxisTickCount
1712
+ },
1713
+ ...yMinValue !== undefined && {
1714
+ yMinValue
1715
+ },
1716
+ ...yMaxValue !== undefined && {
1717
+ yMaxValue
1718
+ },
1719
+ ...annotations.length > 0 && {
1720
+ annotations
1721
+ },
1722
+ ...colorFillBars.length > 0 && {
1723
+ colorFillBars
1724
+ },
1725
+ ...yAxisType && {
1726
+ yScaleType: yAxisType
1727
+ },
1728
+ // For nominal y-axis, provide tick values and labels
1729
+ ...yOrdinalLabels && yOrdinalLabels.length > 0 && {
1730
+ yAxisTickValues: Array.from({
1731
+ length: yOrdinalLabels.length
1732
+ }, (_, i)=>i),
1733
+ yAxisTickFormat: (val)=>{
1734
+ var _yOrdinalLabels_val;
1735
+ return (_yOrdinalLabels_val = yOrdinalLabels[val]) !== null && _yOrdinalLabels_val !== void 0 ? _yOrdinalLabels_val : String(val);
1736
+ },
1737
+ yMinValue: -0.5,
1738
+ yMaxValue: yOrdinalLabels.length - 0.5
1739
+ },
1740
+ ...categoryOrderProps,
1741
+ hideLegend: (_encoding_color_legend_disable = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : (_encoding_color_legend = _encoding_color.legend) === null || _encoding_color_legend === void 0 ? void 0 : _encoding_color_legend.disable) !== null && _encoding_color_legend_disable !== void 0 ? _encoding_color_legend_disable : false
1742
+ };
1743
+ }
1744
+ /**
1745
+ * Generates legend props from Vega-Lite specification
1746
+ * Used for multi-plot scenarios where legends are rendered separately
1747
+ *
1748
+ * @param spec - Vega-Lite specification
1749
+ * @param colorMap - Color mapping ref for consistent coloring
1750
+ * @param isDarkTheme - Whether dark theme is active
1751
+ * @returns LegendsProps for rendering legends
1752
+ */ export function getVegaLiteLegendsProps(spec, colorMap, isDarkTheme) {
1753
+ var _encoding_color;
1754
+ const unitSpecs = normalizeSpec(spec);
1755
+ const legends = [];
1756
+ if (unitSpecs.length === 0) {
1757
+ return {
1758
+ legends,
1759
+ centerLegends: true,
1760
+ enabledWrapLines: true,
1761
+ canSelectMultipleLegends: true
1762
+ };
1763
+ }
1764
+ const primarySpec = unitSpecs[0];
1765
+ const dataValues = extractDataValues(primarySpec.data);
1766
+ const encoding = primarySpec.encoding || {};
1767
+ const colorField = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.field;
1768
+ if (!colorField) {
1769
+ return {
1770
+ legends,
1771
+ centerLegends: true,
1772
+ enabledWrapLines: true,
1773
+ canSelectMultipleLegends: true
1774
+ };
1775
+ }
1776
+ // Extract unique series names
1777
+ const seriesNames = new Set();
1778
+ dataValues.forEach((row)=>{
1779
+ if (row[colorField] !== undefined) {
1780
+ seriesNames.add(String(row[colorField]));
1781
+ }
1782
+ });
1783
+ // Generate legends
1784
+ seriesNames.forEach((seriesName)=>{
1785
+ const color = getVegaColorFromMap(seriesName, colorMap, undefined, undefined, isDarkTheme);
1786
+ legends.push({
1787
+ title: seriesName,
1788
+ color
1789
+ });
1790
+ });
1791
+ return {
1792
+ legends,
1793
+ centerLegends: true,
1794
+ enabledWrapLines: true,
1795
+ canSelectMultipleLegends: true
1796
+ };
1797
+ }
1798
+ /**
1799
+ * Extracts chart titles and title styles from Vega-Lite specification
1800
+ */ export function getVegaLiteTitles(spec) {
1801
+ var _spec_title, _encoding_x, _encoding_x_axis, _encoding_x1, _encoding_y, _encoding_y_axis, _encoding_y1;
1802
+ const unitSpecs = normalizeSpec(spec);
1803
+ if (unitSpecs.length === 0) {
1804
+ return {};
1805
+ }
1806
+ const primarySpec = unitSpecs[0];
1807
+ const encoding = primarySpec.encoding || {};
1808
+ // Extract chart title
1809
+ const chartTitle = typeof spec.title === 'string' ? spec.title : (_spec_title = spec.title) === null || _spec_title === void 0 ? void 0 : _spec_title.text;
1810
+ // Extract title styles if title is an object
1811
+ let titleStyles;
1812
+ if (typeof spec.title === 'object' && spec.title !== null) {
1813
+ const titleObj = spec.title;
1814
+ // Build titleFont object if any font properties are present
1815
+ const titleFont = {};
1816
+ if (titleObj.font) {
1817
+ titleFont.family = titleObj.font;
1818
+ }
1819
+ if (titleObj.fontSize) {
1820
+ titleFont.size = titleObj.fontSize;
1821
+ }
1822
+ if (titleObj.fontWeight) {
1823
+ // Convert string weights to numbers (Font interface expects number)
1824
+ const weight = titleObj.fontWeight;
1825
+ if (typeof weight === 'string') {
1826
+ const weightMap = {
1827
+ normal: 400,
1828
+ bold: 700,
1829
+ lighter: 300,
1830
+ bolder: 600
1831
+ };
1832
+ titleFont.weight = weightMap[weight.toLowerCase()] || 400;
1833
+ } else {
1834
+ titleFont.weight = weight;
1835
+ }
1836
+ }
1837
+ if (titleObj.color) {
1838
+ titleFont.color = titleObj.color;
1839
+ }
1840
+ // Map Vega-Lite anchor values to TitleStyles anchor values
1841
+ const anchorMap = {
1842
+ start: 'left',
1843
+ middle: 'center',
1844
+ end: 'right'
1845
+ };
1846
+ titleStyles = {
1847
+ ...Object.keys(titleFont).length > 0 ? {
1848
+ titleFont
1849
+ } : {},
1850
+ ...titleObj.anchor && anchorMap[titleObj.anchor] ? {
1851
+ titleXAnchor: anchorMap[titleObj.anchor]
1852
+ } : {},
1853
+ ...titleObj.offset !== undefined || titleObj.subtitlePadding !== undefined ? {
1854
+ titlePad: {
1855
+ t: titleObj.offset,
1856
+ b: titleObj.subtitlePadding
1857
+ }
1858
+ } : {}
1859
+ };
1860
+ // Only include titleStyles if it has properties
1861
+ if (Object.keys(titleStyles).length === 0) {
1862
+ titleStyles = undefined;
1863
+ }
1864
+ }
1865
+ var _encoding_x_title, _ref, _encoding_y_title, _ref1;
1866
+ return {
1867
+ chartTitle,
1868
+ xAxisTitle: (_ref = (_encoding_x_title = (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.title) !== null && _encoding_x_title !== void 0 ? _encoding_x_title : (_encoding_x1 = encoding.x) === null || _encoding_x1 === void 0 ? void 0 : (_encoding_x_axis = _encoding_x1.axis) === null || _encoding_x_axis === void 0 ? void 0 : _encoding_x_axis.title) !== null && _ref !== void 0 ? _ref : undefined,
1869
+ yAxisTitle: (_ref1 = (_encoding_y_title = (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.title) !== null && _encoding_y_title !== void 0 ? _encoding_y_title : (_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : (_encoding_y_axis = _encoding_y1.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.title) !== null && _ref1 !== void 0 ? _ref1 : undefined,
1870
+ ...titleStyles ? {
1871
+ titleStyles
1872
+ } : {}
1873
+ };
1874
+ }
1875
+ /**
1876
+ * Transforms Vega-Lite specification to Fluent VerticalBarChart props
1877
+ *
1878
+ * Supports bar mark with quantitative y-axis and nominal/ordinal x-axis
1879
+ *
1880
+ * @param spec - Vega-Lite specification
1881
+ * @param colorMap - Color mapping ref for consistent coloring
1882
+ * @param isDarkTheme - Whether dark theme is active
1883
+ * @returns VerticalBarChartProps for rendering
1884
+ */ export function transformVegaLiteToVerticalBarChartProps(spec, colorMap, isDarkTheme) {
1885
+ var _encoding_color, _encoding_y_axis, _encoding_y, _barData_, _encoding_color_legend, _encoding_color1;
1886
+ // Initialize transformation context
1887
+ const { dataValues, encoding, markProps } = initializeTransformContext(spec);
1888
+ // Extract field names and aggregates
1889
+ const { xField, yField, colorField, yAggregate } = extractEncodingFields(encoding);
1890
+ // Check if this is an aggregate bar chart
1891
+ // Aggregate can be: count (no field needed) or sum/mean/etc (with field)
1892
+ const isAggregate = !!yAggregate;
1893
+ if (!xField && !isAggregate) {
1894
+ throw new Error('VegaLiteSchemaAdapter: x encoding is required for bar charts');
1895
+ }
1896
+ // For aggregate charts, compute aggregated data
1897
+ let aggregatedData;
1898
+ if (isAggregate && xField) {
1899
+ aggregatedData = computeAggregateData(dataValues, xField, yField, yAggregate);
1900
+ }
1901
+ // Validate data and encodings (skip for aggregate charts)
1902
+ if (!isAggregate && xField && yField) {
1903
+ var _encoding_x, _encoding_y1;
1904
+ validateXYEncodings(dataValues, xField, yField, (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.type, (_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.type, 'VerticalBarChart', encoding);
1905
+ }
1906
+ // Extract color configuration
1907
+ const { colorScheme, colorRange } = extractColorConfig(encoding);
1908
+ const colorValue = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.value;
1909
+ const barData = [];
1910
+ const colorIndex = new Map();
1911
+ let currentColorIndex = 0;
1912
+ // When there's no color field, all bars share a single legend
1913
+ const useSingleLegendForAggregate = !colorField;
1914
+ if (aggregatedData) {
1915
+ // Use aggregated data
1916
+ aggregatedData.forEach(({ category, value })=>{
1917
+ const legend = useSingleLegendForAggregate ? 'Bar' : String(category);
1918
+ if (!colorIndex.has(legend)) {
1919
+ colorIndex.set(legend, currentColorIndex++);
1920
+ }
1921
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
1922
+ barData.push({
1923
+ x: category,
1924
+ y: value,
1925
+ legend,
1926
+ color
1927
+ });
1928
+ });
1929
+ } else if (xField && yField) {
1930
+ var _dataValues_find;
1931
+ // Check if y values are numeric; if not, fall back to count aggregation
1932
+ const firstYValue = (_dataValues_find = dataValues.find((r)=>r[yField] !== undefined)) === null || _dataValues_find === void 0 ? void 0 : _dataValues_find[yField];
1933
+ const yIsNumeric = typeof firstYValue === 'number';
1934
+ if (!yIsNumeric) {
1935
+ // y values are non-numeric: compute count per x category
1936
+ const counts = countByCategory(dataValues, xField, undefined, '');
1937
+ counts.forEach((legendMap, xKey)=>{
1938
+ // No color grouping - each xKey gets one bar; use xKey as legend
1939
+ const totalCount = Array.from(legendMap.values()).reduce((a, b)=>a + b, 0);
1940
+ const legend = xKey;
1941
+ if (!colorIndex.has(legend)) {
1942
+ colorIndex.set(legend, currentColorIndex++);
1943
+ }
1944
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
1945
+ barData.push({
1946
+ x: xKey,
1947
+ y: totalCount,
1948
+ legend,
1949
+ color
1950
+ });
1951
+ });
1952
+ } else {
1953
+ var _encoding_y_axis1, _encoding_y2;
1954
+ // When there's no color field encoding, use a single legend name for all bars
1955
+ // This ensures: uniform bar color, single legend entry, no tooltip duplication
1956
+ const useSingleLegend = !colorField;
1957
+ // Create value formatter for bar data labels
1958
+ const yFormatter = createValueFormatter((_encoding_y2 = encoding.y) === null || _encoding_y2 === void 0 ? void 0 : (_encoding_y_axis1 = _encoding_y2.axis) === null || _encoding_y_axis1 === void 0 ? void 0 : _encoding_y_axis1.format);
1959
+ // Use raw data (normal numeric y values)
1960
+ dataValues.forEach((row)=>{
1961
+ const xValue = row[xField];
1962
+ const yValue = row[yField];
1963
+ // Use chart-utilities validation
1964
+ if (isInvalidValue(xValue) || isInvalidValue(yValue) || typeof yValue !== 'number') {
1965
+ return;
1966
+ }
1967
+ const legend = colorField && row[colorField] !== undefined ? String(row[colorField]) : useSingleLegend ? 'Bar' : String(xValue);
1968
+ if (!colorIndex.has(legend)) {
1969
+ colorIndex.set(legend, currentColorIndex++);
1970
+ }
1971
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
1972
+ // For bar charts, x-axis values are treated as categories (even if numeric)
1973
+ // Convert to string to ensure consistent categorical positioning
1974
+ const xCategory = typeof xValue === 'number' ? String(xValue) : xValue;
1975
+ barData.push({
1976
+ x: xCategory,
1977
+ y: yValue,
1978
+ legend,
1979
+ color,
1980
+ ...yFormatter && {
1981
+ yAxisCalloutData: yFormatter(yValue),
1982
+ barLabel: yFormatter(yValue)
1983
+ }
1984
+ });
1985
+ });
1986
+ }
1987
+ }
1988
+ const titles = getVegaLiteTitles(spec);
1989
+ // Extract axis category ordering
1990
+ const categoryOrderProps = extractAxisCategoryOrderProps(encoding);
1991
+ // Extract tick configuration
1992
+ const tickConfig = extractTickConfig(spec);
1993
+ // Extract y-axis formatting and scale props
1994
+ const yAxisTickFormat = (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : (_encoding_y_axis = _encoding_y.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.format;
1995
+ const { yMinValue, yMaxValue } = extractYMinMax(encoding, dataValues);
1996
+ const yAxisType = extractYAxisType(encoding);
1997
+ // Compute truncation based on number of unique x-axis categories
1998
+ const uniqueXCount = new Set(barData.map((d)=>String(d.x))).size;
1999
+ const barTruncateChars = uniqueXCount > 20 ? 6 : uniqueXCount > 10 ? 10 : DEFAULT_TRUNCATE_CHARS;
2000
+ var _encoding_color_legend_disable;
2001
+ const result = {
2002
+ data: barData,
2003
+ chartTitle: titles.chartTitle,
2004
+ xAxisTitle: titles.xAxisTitle,
2005
+ yAxisTitle: titles.yAxisTitle,
2006
+ ...titles.titleStyles ? titles.titleStyles : {},
2007
+ roundCorners: true,
2008
+ wrapXAxisLables: typeof ((_barData_ = barData[0]) === null || _barData_ === void 0 ? void 0 : _barData_.x) === 'string',
2009
+ hideTickOverlap: true,
2010
+ noOfCharsToTruncate: barTruncateChars,
2011
+ xAxis: {
2012
+ tickLayout: 'auto'
2013
+ },
2014
+ ...yAxisTickFormat && {
2015
+ yAxisTickFormat
2016
+ },
2017
+ ...yMinValue !== undefined && {
2018
+ yMinValue
2019
+ },
2020
+ ...yMaxValue !== undefined && {
2021
+ yMaxValue
2022
+ },
2023
+ ...yAxisType && {
2024
+ yScaleType: yAxisType
2025
+ },
2026
+ ...categoryOrderProps,
2027
+ // Hide legend for single-series bar charts (no color encoding) to avoid showing "Bar" legend
2028
+ hideLegend: !colorField ? true : (_encoding_color_legend_disable = (_encoding_color1 = encoding.color) === null || _encoding_color1 === void 0 ? void 0 : (_encoding_color_legend = _encoding_color1.legend) === null || _encoding_color_legend === void 0 ? void 0 : _encoding_color_legend.disable) !== null && _encoding_color_legend_disable !== void 0 ? _encoding_color_legend_disable : false
2029
+ };
2030
+ if (tickConfig.tickValues) {
2031
+ result.tickValues = tickConfig.tickValues;
2032
+ }
2033
+ if (tickConfig.xAxisTickCount) {
2034
+ result.xAxisTickCount = tickConfig.xAxisTickCount;
2035
+ }
2036
+ return result;
2037
+ }
2038
+ /**
2039
+ * Transforms Vega-Lite specification to Fluent VerticalStackedBarChart props
2040
+ *
2041
+ * Supports stacked bar charts with color encoding for stacking
2042
+ *
2043
+ * @param spec - Vega-Lite specification
2044
+ * @param colorMap - Color mapping ref for consistent coloring
2045
+ * @param isDarkTheme - Whether dark theme is active
2046
+ * @returns VerticalStackedBarChartProps for rendering
2047
+ */ export function transformVegaLiteToVerticalStackedBarChartProps(spec, colorMap, isDarkTheme) {
2048
+ var _encoding_color, _encoding_y_axis, _encoding_y, _encoding_color_legend, _encoding_color1, _chartData_;
2049
+ // Initialize transformation context (skip warnings as we handle layered spec differently)
2050
+ const { unitSpecs } = initializeTransformContext(spec);
2051
+ // Separate bar, line, and rule specs from layered specifications
2052
+ const barSpecs = unitSpecs.filter((s)=>getMarkType(s.mark) === 'bar');
2053
+ const lineSpecs = unitSpecs.filter((s)=>{
2054
+ const mark = getMarkType(s.mark);
2055
+ return mark === 'line' || mark === 'point';
2056
+ });
2057
+ const ruleSpecs = unitSpecs.filter((s)=>getMarkType(s.mark) === 'rule');
2058
+ // Use bar specs if available, otherwise fall back to first unit spec
2059
+ const primarySpec = barSpecs.length > 0 ? barSpecs[0] : unitSpecs[0];
2060
+ const rawDataValues = extractDataValues(primarySpec.data);
2061
+ // Apply transforms from both top-level spec and primary spec
2062
+ let dataValues = applyTransforms(rawDataValues, spec.transform);
2063
+ dataValues = applyTransforms(dataValues, primarySpec.transform);
2064
+ const encoding = primarySpec.encoding || {};
2065
+ const markProps = getMarkProperties(primarySpec.mark);
2066
+ // Extract field names and aggregates
2067
+ const { xField, yField, colorField, yAggregate } = extractEncodingFields(encoding);
2068
+ const colorValue = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.value; // Static color value
2069
+ // Support aggregate encodings (e.g., count, sum)
2070
+ const isAggregate = !!yAggregate;
2071
+ if (!xField) {
2072
+ throw new Error('VegaLiteSchemaAdapter: x encoding is required for stacked bar charts');
2073
+ }
2074
+ // For aggregate charts, compute aggregated data
2075
+ let aggregatedData;
2076
+ if (isAggregate) {
2077
+ aggregatedData = computeAggregateData(dataValues, xField, yField, yAggregate);
2078
+ } else if (!yField) {
2079
+ throw new Error('VegaLiteSchemaAdapter: y encoding is required for stacked bar charts');
2080
+ }
2081
+ // Extract color configuration
2082
+ const { colorScheme, colorRange } = extractColorConfig(encoding);
2083
+ // Group data by x value, then by color (stack)
2084
+ const mapXToDataPoints = {};
2085
+ const colorIndex = new Map();
2086
+ let currentColorIndex = 0;
2087
+ if (aggregatedData) {
2088
+ // Use aggregated data
2089
+ aggregatedData.forEach(({ category, value })=>{
2090
+ const xKey = String(category);
2091
+ const legend = 'Bar';
2092
+ if (!mapXToDataPoints[xKey]) {
2093
+ mapXToDataPoints[xKey] = {
2094
+ xAxisPoint: category,
2095
+ chartData: [],
2096
+ lineData: []
2097
+ };
2098
+ }
2099
+ if (!colorIndex.has(legend)) {
2100
+ colorIndex.set(legend, currentColorIndex++);
2101
+ }
2102
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
2103
+ mapXToDataPoints[xKey].chartData.push({
2104
+ legend,
2105
+ data: value,
2106
+ color
2107
+ });
2108
+ });
2109
+ } else {
2110
+ var _dataValues_find;
2111
+ // Check if y values are actually numeric; if not, fall back to count aggregation
2112
+ const firstYValue = (_dataValues_find = dataValues.find((r)=>r[yField] !== undefined)) === null || _dataValues_find === void 0 ? void 0 : _dataValues_find[yField];
2113
+ const yIsNumeric = typeof firstYValue === 'number';
2114
+ if (!yIsNumeric && yField) {
2115
+ // y values are non-numeric (e.g., strings after auto-correction from quantitative to nominal)
2116
+ // Fall back to count aggregation: count rows per x category and color
2117
+ const counts = countByCategory(dataValues, xField, colorField, 'Bar');
2118
+ counts.forEach((legendMap, xKey)=>{
2119
+ mapXToDataPoints[xKey] = {
2120
+ xAxisPoint: xKey,
2121
+ chartData: [],
2122
+ lineData: []
2123
+ };
2124
+ legendMap.forEach((count, legend)=>{
2125
+ if (!colorIndex.has(legend)) {
2126
+ colorIndex.set(legend, currentColorIndex++);
2127
+ }
2128
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
2129
+ mapXToDataPoints[xKey].chartData.push({
2130
+ legend,
2131
+ data: count,
2132
+ color
2133
+ });
2134
+ });
2135
+ });
2136
+ } else {
2137
+ // Process bar data (normal numeric y values)
2138
+ dataValues.forEach((row)=>{
2139
+ var _encoding_y_axis, _encoding_y;
2140
+ const xValue = row[xField];
2141
+ const yValue = row[yField];
2142
+ const stackValue = colorField ? row[colorField] : 'Bar'; // Default legend if no color field
2143
+ if (isInvalidValue(xValue) || isInvalidValue(yValue) || typeof yValue !== 'number') {
2144
+ return;
2145
+ }
2146
+ const xKey = String(xValue);
2147
+ const legend = stackValue !== undefined ? String(stackValue) : 'Bar';
2148
+ if (!mapXToDataPoints[xKey]) {
2149
+ // For bar charts, x-axis values are treated as categories (even if numeric)
2150
+ const xCategory = typeof xValue === 'number' ? String(xValue) : xValue;
2151
+ mapXToDataPoints[xKey] = {
2152
+ xAxisPoint: xCategory,
2153
+ chartData: [],
2154
+ lineData: []
2155
+ };
2156
+ }
2157
+ if (!colorIndex.has(legend)) {
2158
+ colorIndex.set(legend, currentColorIndex++);
2159
+ }
2160
+ // Use static color if provided, otherwise use color scheme/scale
2161
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
2162
+ const stackYFormatter = createValueFormatter((_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : (_encoding_y_axis = _encoding_y.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.format);
2163
+ mapXToDataPoints[xKey].chartData.push({
2164
+ legend,
2165
+ data: yValue,
2166
+ color,
2167
+ ...stackYFormatter && {
2168
+ yAxisCalloutData: stackYFormatter(yValue),
2169
+ barLabel: stackYFormatter(yValue)
2170
+ }
2171
+ });
2172
+ });
2173
+ }
2174
+ } // end else (non-aggregate)
2175
+ // Process line data from additional layers (if any)
2176
+ lineSpecs.forEach((lineSpec, lineIndex)=>{
2177
+ var _lineEncoding_x, _lineEncoding_y, _lineEncoding_color;
2178
+ let lineDataValues = extractDataValues(lineSpec.data);
2179
+ // Apply transforms from both top-level spec and line spec
2180
+ lineDataValues = applyTransforms(lineDataValues, spec.transform);
2181
+ lineDataValues = applyTransforms(lineDataValues, lineSpec.transform);
2182
+ const lineEncoding = lineSpec.encoding || {};
2183
+ const lineMarkProps = getMarkProperties(lineSpec.mark);
2184
+ const lineXField = (_lineEncoding_x = lineEncoding.x) === null || _lineEncoding_x === void 0 ? void 0 : _lineEncoding_x.field;
2185
+ const lineYField = (_lineEncoding_y = lineEncoding.y) === null || _lineEncoding_y === void 0 ? void 0 : _lineEncoding_y.field;
2186
+ const lineColorField = (_lineEncoding_color = lineEncoding.color) === null || _lineEncoding_color === void 0 ? void 0 : _lineEncoding_color.field;
2187
+ if (!lineXField || !lineYField) {
2188
+ return; // Skip if required fields are missing
2189
+ }
2190
+ const lineLegendBase = lineColorField ? 'Line' : `Line ${lineIndex + 1}`;
2191
+ lineDataValues.forEach((row)=>{
2192
+ var _spec_resolve_scale, _spec_resolve;
2193
+ const xValue = row[lineXField];
2194
+ const yValue = row[lineYField];
2195
+ if (isInvalidValue(xValue) || isInvalidValue(yValue)) {
2196
+ return;
2197
+ }
2198
+ const xKey = String(xValue);
2199
+ const lineLegend = lineColorField && row[lineColorField] !== undefined ? String(row[lineColorField]) : lineLegendBase;
2200
+ // Ensure x-axis point exists
2201
+ if (!mapXToDataPoints[xKey]) {
2202
+ mapXToDataPoints[xKey] = {
2203
+ xAxisPoint: xValue,
2204
+ chartData: [],
2205
+ lineData: []
2206
+ };
2207
+ }
2208
+ // Determine line color
2209
+ if (!colorIndex.has(lineLegend)) {
2210
+ colorIndex.set(lineLegend, currentColorIndex++);
2211
+ }
2212
+ let lineColor;
2213
+ if (lineMarkProps.color) {
2214
+ lineColor = lineMarkProps.color;
2215
+ } else {
2216
+ // Use lineLegend for consistent color assignment
2217
+ lineColor = resolveColor(lineLegend, colorIndex.get(lineLegend), undefined, undefined, colorMap, undefined, undefined, isDarkTheme);
2218
+ }
2219
+ // Determine if this line should use secondary Y-axis
2220
+ // Check if spec has independent Y scales AND line uses different Y field than bars
2221
+ const hasIndependentYScales = ((_spec_resolve = spec.resolve) === null || _spec_resolve === void 0 ? void 0 : (_spec_resolve_scale = _spec_resolve.scale) === null || _spec_resolve_scale === void 0 ? void 0 : _spec_resolve_scale.y) === 'independent';
2222
+ const useSecondaryYScale = hasIndependentYScales && lineYField !== yField;
2223
+ const lineData = {
2224
+ y: yValue,
2225
+ color: lineColor,
2226
+ legend: lineLegend,
2227
+ legendShape: 'triangle',
2228
+ data: typeof yValue === 'number' ? yValue : undefined,
2229
+ useSecondaryYScale
2230
+ };
2231
+ // Add line options if available
2232
+ if (lineMarkProps.strokeWidth || lineMarkProps.strokeDash) {
2233
+ lineData.lineOptions = {
2234
+ ...lineMarkProps.strokeWidth && {
2235
+ strokeWidth: lineMarkProps.strokeWidth
2236
+ },
2237
+ ...lineMarkProps.strokeDash && {
2238
+ strokeDasharray: lineMarkProps.strokeDash.join(' ')
2239
+ }
2240
+ };
2241
+ }
2242
+ mapXToDataPoints[xKey].lineData.push(lineData);
2243
+ });
2244
+ });
2245
+ // Process rule specs as horizontal reference lines
2246
+ // Each rule with a constant y-value becomes a flat line across all x-axis points
2247
+ ruleSpecs.forEach((ruleSpec, ruleIndex)=>{
2248
+ var _ruleEncoding_y, _ruleEncoding_y1;
2249
+ const ruleEncoding = ruleSpec.encoding || {};
2250
+ const ruleMarkProps = getMarkProperties(ruleSpec.mark);
2251
+ var _ruleEncoding_y_datum;
2252
+ const yDatum = (_ruleEncoding_y_datum = (_ruleEncoding_y = ruleEncoding.y) === null || _ruleEncoding_y === void 0 ? void 0 : _ruleEncoding_y.datum) !== null && _ruleEncoding_y_datum !== void 0 ? _ruleEncoding_y_datum : (_ruleEncoding_y1 = ruleEncoding.y) === null || _ruleEncoding_y1 === void 0 ? void 0 : _ruleEncoding_y1.value;
2253
+ if (yDatum !== undefined) {
2254
+ var _textSpec_encoding_text, _textSpec_encoding, _textSpec_encoding_text1, _textSpec_encoding1;
2255
+ const ruleLegend = `Reference_${ruleIndex}`;
2256
+ const ruleColor = ruleMarkProps.color || '#d62728';
2257
+ if (!colorIndex.has(ruleLegend)) {
2258
+ colorIndex.set(ruleLegend, currentColorIndex++);
2259
+ }
2260
+ const lineOptions = {};
2261
+ if (ruleMarkProps.strokeDash) {
2262
+ lineOptions.strokeDasharray = ruleMarkProps.strokeDash.join(' ');
2263
+ }
2264
+ if (ruleMarkProps.strokeWidth) {
2265
+ lineOptions.strokeWidth = ruleMarkProps.strokeWidth;
2266
+ }
2267
+ // Look for companion text annotation at the same y-value
2268
+ const textSpec = unitSpecs.find((s, i)=>{
2269
+ var _s_encoding;
2270
+ var _s_encoding_y_datum;
2271
+ return getMarkType(s.mark) === 'text' && ((_s_encoding = s.encoding) === null || _s_encoding === void 0 ? void 0 : _s_encoding.y) && ((_s_encoding_y_datum = s.encoding.y.datum) !== null && _s_encoding_y_datum !== void 0 ? _s_encoding_y_datum : s.encoding.y.value) === yDatum;
2272
+ });
2273
+ const ruleText = textSpec ? String(((_textSpec_encoding = textSpec.encoding) === null || _textSpec_encoding === void 0 ? void 0 : (_textSpec_encoding_text = _textSpec_encoding.text) === null || _textSpec_encoding_text === void 0 ? void 0 : _textSpec_encoding_text.datum) || ((_textSpec_encoding1 = textSpec.encoding) === null || _textSpec_encoding1 === void 0 ? void 0 : (_textSpec_encoding_text1 = _textSpec_encoding1.text) === null || _textSpec_encoding_text1 === void 0 ? void 0 : _textSpec_encoding_text1.value) || yDatum) : String(yDatum);
2274
+ // Add the constant y-value line to every x-axis point
2275
+ Object.keys(mapXToDataPoints).forEach((xKey)=>{
2276
+ mapXToDataPoints[xKey].lineData.push({
2277
+ y: yDatum,
2278
+ legend: ruleText,
2279
+ color: ruleColor,
2280
+ ...Object.keys(lineOptions).length > 0 && {
2281
+ lineOptions
2282
+ },
2283
+ useSecondaryYScale: false
2284
+ });
2285
+ });
2286
+ }
2287
+ });
2288
+ const chartData = Object.values(mapXToDataPoints);
2289
+ const titles = getVegaLiteTitles(spec);
2290
+ // Check if we have secondary Y-axis data
2291
+ const hasSecondaryYAxis = chartData.some((point)=>{
2292
+ var _point_lineData;
2293
+ return (_point_lineData = point.lineData) === null || _point_lineData === void 0 ? void 0 : _point_lineData.some((line)=>line.useSecondaryYScale);
2294
+ });
2295
+ // Extract secondary Y-axis properties from line layers
2296
+ let secondaryYAxisProps = {};
2297
+ if (hasSecondaryYAxis && lineSpecs.length > 0) {
2298
+ var _lineEncoding_y;
2299
+ const lineSpec = lineSpecs[0];
2300
+ const lineEncoding = lineSpec.encoding || {};
2301
+ const lineYAxis = (_lineEncoding_y = lineEncoding.y) === null || _lineEncoding_y === void 0 ? void 0 : _lineEncoding_y.axis;
2302
+ if (lineYAxis === null || lineYAxis === void 0 ? void 0 : lineYAxis.title) {
2303
+ secondaryYAxisProps.secondaryYAxistitle = lineYAxis.title;
2304
+ }
2305
+ // Compute secondary Y scale domain from line data values
2306
+ const allLineYValues = [];
2307
+ chartData.forEach((point)=>{
2308
+ var _point_lineData;
2309
+ (_point_lineData = point.lineData) === null || _point_lineData === void 0 ? void 0 : _point_lineData.forEach((line)=>{
2310
+ if (line.useSecondaryYScale && typeof line.y === 'number') {
2311
+ allLineYValues.push(line.y);
2312
+ }
2313
+ });
2314
+ });
2315
+ if (allLineYValues.length > 0) {
2316
+ var _lineEncoding_y_scale, _lineEncoding_y1;
2317
+ // Use explicit domain from line encoding if available, otherwise compute from data
2318
+ const lineDomain = (_lineEncoding_y1 = lineEncoding.y) === null || _lineEncoding_y1 === void 0 ? void 0 : (_lineEncoding_y_scale = _lineEncoding_y1.scale) === null || _lineEncoding_y_scale === void 0 ? void 0 : _lineEncoding_y_scale.domain;
2319
+ var _d3Min;
2320
+ const secYMin = Array.isArray(lineDomain) ? lineDomain[0] : (_d3Min = d3Min(allLineYValues)) !== null && _d3Min !== void 0 ? _d3Min : 0;
2321
+ var _d3Max;
2322
+ const secYMax = Array.isArray(lineDomain) ? lineDomain[1] : (_d3Max = d3Max(allLineYValues)) !== null && _d3Max !== void 0 ? _d3Max : 0;
2323
+ secondaryYAxisProps.secondaryYScaleOptions = {
2324
+ yMinValue: secYMin,
2325
+ yMaxValue: secYMax
2326
+ };
2327
+ }
2328
+ }
2329
+ // Check for log scale on primary Y-axis
2330
+ const yAxisType = extractYAxisType(encoding);
2331
+ // Extract y-axis formatting and domain props
2332
+ const yAxisTickFormat = (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : (_encoding_y_axis = _encoding_y.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.format;
2333
+ const { yMinValue, yMaxValue } = extractYMinMax(encoding, dataValues);
2334
+ // Extract axis category ordering
2335
+ const categoryOrderProps = extractAxisCategoryOrderProps(encoding);
2336
+ var _spec_height, _encoding_color_legend_disable;
2337
+ return {
2338
+ data: chartData,
2339
+ chartTitle: titles.chartTitle,
2340
+ xAxisTitle: titles.xAxisTitle,
2341
+ yAxisTitle: titles.yAxisTitle,
2342
+ ...titles.titleStyles ? titles.titleStyles : {},
2343
+ width: spec.width,
2344
+ height: (_spec_height = spec.height) !== null && _spec_height !== void 0 ? _spec_height : DEFAULT_CHART_HEIGHT,
2345
+ hideLegend: (_encoding_color_legend_disable = (_encoding_color1 = encoding.color) === null || _encoding_color1 === void 0 ? void 0 : (_encoding_color_legend = _encoding_color1.legend) === null || _encoding_color_legend === void 0 ? void 0 : _encoding_color_legend.disable) !== null && _encoding_color_legend_disable !== void 0 ? _encoding_color_legend_disable : false,
2346
+ showYAxisLables: true,
2347
+ roundCorners: true,
2348
+ hideTickOverlap: true,
2349
+ barGapMax: 2,
2350
+ noOfCharsToTruncate: DEFAULT_TRUNCATE_CHARS,
2351
+ showYAxisLablesTooltip: true,
2352
+ wrapXAxisLables: typeof ((_chartData_ = chartData[0]) === null || _chartData_ === void 0 ? void 0 : _chartData_.xAxisPoint) === 'string',
2353
+ xAxis: {
2354
+ tickLayout: 'auto'
2355
+ },
2356
+ ...yAxisTickFormat && {
2357
+ yAxisTickFormat
2358
+ },
2359
+ ...yMinValue !== undefined && {
2360
+ yMinValue
2361
+ },
2362
+ ...yMaxValue !== undefined && {
2363
+ yMaxValue
2364
+ },
2365
+ ...yAxisType && {
2366
+ yScaleType: yAxisType
2367
+ },
2368
+ ...secondaryYAxisProps,
2369
+ ...categoryOrderProps
2370
+ };
2371
+ }
2372
+ /**
2373
+ * Transforms Vega-Lite specification to Fluent GroupedVerticalBarChart props
2374
+ *
2375
+ * Supports grouped bar charts with color encoding for grouping
2376
+ *
2377
+ * @param spec - Vega-Lite specification
2378
+ * @param colorMap - Color mapping ref for consistent coloring
2379
+ * @param isDarkTheme - Whether dark theme is active
2380
+ * @returns GroupedVerticalBarChartProps for rendering
2381
+ */ export function transformVegaLiteToGroupedVerticalBarChartProps(spec, colorMap, isDarkTheme) {
2382
+ var _encoding_y_axis, _encoding_y;
2383
+ // Initialize transformation context
2384
+ const { dataValues, encoding } = initializeTransformContext(spec);
2385
+ // Extract field names
2386
+ const { xField, yField, colorField } = extractEncodingFields(encoding);
2387
+ if (!xField || !yField || !colorField) {
2388
+ throw new Error('VegaLiteSchemaAdapter: x, y, and color encodings are required for grouped bar charts');
2389
+ }
2390
+ // Extract color configuration
2391
+ const { colorScheme, colorRange } = extractColorConfig(encoding);
2392
+ // Group data by x value (name), then by color (series)
2393
+ const groupedData = {};
2394
+ const colorIndex = new Map();
2395
+ let currentColorIndex = 0;
2396
+ dataValues.forEach((row)=>{
2397
+ const xValue = row[xField];
2398
+ const yValue = row[yField];
2399
+ const groupValue = row[colorField];
2400
+ if (isInvalidValue(xValue) || isInvalidValue(yValue) || typeof yValue !== 'number' || isInvalidValue(groupValue)) {
2401
+ return;
2402
+ }
2403
+ const xKey = String(xValue);
2404
+ const legend = String(groupValue);
2405
+ if (!groupedData[xKey]) {
2406
+ groupedData[xKey] = {};
2407
+ }
2408
+ groupedData[xKey][legend] = yValue;
2409
+ if (!colorIndex.has(legend)) {
2410
+ colorIndex.set(legend, currentColorIndex++);
2411
+ }
2412
+ });
2413
+ // Convert to GroupedVerticalBarChartData format
2414
+ const chartData = Object.keys(groupedData).map((name)=>{
2415
+ const series = Object.keys(groupedData[name]).map((legend)=>({
2416
+ key: legend,
2417
+ data: groupedData[name][legend],
2418
+ legend,
2419
+ color: resolveColor(legend, colorIndex.get(legend), undefined, undefined, colorMap, colorScheme, colorRange, isDarkTheme)
2420
+ }));
2421
+ return {
2422
+ name,
2423
+ series
2424
+ };
2425
+ });
2426
+ const titles = getVegaLiteTitles(spec);
2427
+ // Extract y-axis formatting and scale props
2428
+ const yAxisTickFormat = (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : (_encoding_y_axis = _encoding_y.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.format;
2429
+ const { yMinValue, yMaxValue } = extractYMinMax(encoding, dataValues);
2430
+ const yAxisType = extractYAxisType(encoding);
2431
+ return {
2432
+ data: chartData,
2433
+ chartTitle: titles.chartTitle,
2434
+ xAxisTitle: titles.xAxisTitle,
2435
+ yAxisTitle: titles.yAxisTitle,
2436
+ ...titles.titleStyles ? titles.titleStyles : {},
2437
+ ...yAxisTickFormat && {
2438
+ yAxisTickFormat
2439
+ },
2440
+ ...yMinValue !== undefined && {
2441
+ yMinValue
2442
+ },
2443
+ ...yMaxValue !== undefined && {
2444
+ yMaxValue
2445
+ },
2446
+ ...yAxisType && {
2447
+ yScaleType: yAxisType
2448
+ }
2449
+ };
2450
+ }
2451
+ /**
2452
+ * Transforms Vega-Lite specification to Fluent HorizontalBarChartWithAxis props
2453
+ *
2454
+ * Supports horizontal bar charts with quantitative x-axis and nominal/ordinal y-axis
2455
+ *
2456
+ * @param spec - Vega-Lite specification
2457
+ * @param colorMap - Color mapping ref for consistent coloring
2458
+ * @param isDarkTheme - Whether dark theme is active
2459
+ * @returns HorizontalBarChartWithAxisProps for rendering
2460
+ */ export function transformVegaLiteToHorizontalBarChartProps(spec, colorMap, isDarkTheme) {
2461
+ var _encoding_color, _encoding_color_legend, _encoding_color1;
2462
+ // Initialize transformation context
2463
+ const { dataValues, encoding, markProps } = initializeTransformContext(spec);
2464
+ // Extract field names and aggregates
2465
+ const { xField, yField, colorField, xAggregate, x2Field } = extractEncodingFields(encoding);
2466
+ // Check if this is an aggregate bar chart
2467
+ // Aggregate can be: count (no field needed) or sum/mean/etc (with field)
2468
+ const isAggregate = !!xAggregate;
2469
+ if (!yField && !isAggregate) {
2470
+ throw new Error('VegaLiteSchemaAdapter: y encoding is required for horizontal bar charts');
2471
+ }
2472
+ // For aggregate charts, compute aggregated data
2473
+ let aggregatedData;
2474
+ if (isAggregate && yField) {
2475
+ aggregatedData = computeAggregateData(dataValues, yField, xField, xAggregate);
2476
+ }
2477
+ const colorValue = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.value;
2478
+ const barData = [];
2479
+ const colorIndex = new Map();
2480
+ let currentColorIndex = 0;
2481
+ if (aggregatedData) {
2482
+ // Use aggregated data
2483
+ aggregatedData.forEach(({ category, value })=>{
2484
+ const legend = String(category);
2485
+ if (!colorIndex.has(legend)) {
2486
+ colorIndex.set(legend, currentColorIndex++);
2487
+ }
2488
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, undefined, undefined, isDarkTheme);
2489
+ barData.push({
2490
+ x: value,
2491
+ y: category,
2492
+ legend,
2493
+ color
2494
+ });
2495
+ });
2496
+ } else if (x2Field && xField && yField) {
2497
+ var _encoding_x;
2498
+ // Gantt chart: bar mark with x/x2 temporal range encoding
2499
+ const isXTemporal = ((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.type) === 'temporal';
2500
+ dataValues.forEach((row)=>{
2501
+ const startVal = row[xField];
2502
+ const endVal = row[x2Field];
2503
+ const yValue = row[yField];
2504
+ if (startVal === undefined || endVal === undefined || yValue === undefined) {
2505
+ return;
2506
+ }
2507
+ let xNumeric;
2508
+ if (isXTemporal) {
2509
+ const startDate = new Date(startVal);
2510
+ const endDate = new Date(endVal);
2511
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
2512
+ return;
2513
+ }
2514
+ xNumeric = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
2515
+ } else {
2516
+ xNumeric = Number(endVal) - Number(startVal);
2517
+ if (isNaN(xNumeric)) {
2518
+ return;
2519
+ }
2520
+ }
2521
+ const legend = colorField && row[colorField] !== undefined ? String(row[colorField]) : String(yValue);
2522
+ if (!colorIndex.has(legend)) {
2523
+ colorIndex.set(legend, currentColorIndex++);
2524
+ }
2525
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, undefined, undefined, isDarkTheme);
2526
+ barData.push({
2527
+ x: xNumeric,
2528
+ y: yValue,
2529
+ legend,
2530
+ color
2531
+ });
2532
+ });
2533
+ } else if (xField && yField) {
2534
+ // Use raw data
2535
+ dataValues.forEach((row)=>{
2536
+ const xValue = row[xField];
2537
+ const yValue = row[yField];
2538
+ if (isInvalidValue(xValue) || isInvalidValue(yValue) || typeof xValue !== 'number') {
2539
+ return;
2540
+ }
2541
+ // When no color field, use single legend to avoid tooltip duplication with y-axis labels
2542
+ const legend = colorField && row[colorField] !== undefined ? String(row[colorField]) : !colorField ? 'Bar' : String(yValue);
2543
+ if (!colorIndex.has(legend)) {
2544
+ colorIndex.set(legend, currentColorIndex++);
2545
+ }
2546
+ const color = resolveColor(legend, colorIndex.get(legend), colorValue, markProps.color, colorMap, undefined, undefined, isDarkTheme);
2547
+ barData.push({
2548
+ x: xValue,
2549
+ y: yValue,
2550
+ legend,
2551
+ color
2552
+ });
2553
+ });
2554
+ }
2555
+ const titles = getVegaLiteTitles(spec);
2556
+ const annotations = extractAnnotations(spec);
2557
+ const tickConfig = extractTickConfig(spec);
2558
+ var _encoding_color_legend_disable;
2559
+ const result = {
2560
+ data: barData,
2561
+ chartTitle: titles.chartTitle,
2562
+ xAxisTitle: titles.xAxisTitle,
2563
+ yAxisTitle: titles.yAxisTitle,
2564
+ ...titles.titleStyles ? titles.titleStyles : {},
2565
+ // Hide legend for single-series horizontal bars (no color encoding)
2566
+ hideLegend: !colorField ? true : (_encoding_color_legend_disable = (_encoding_color1 = encoding.color) === null || _encoding_color1 === void 0 ? void 0 : (_encoding_color_legend = _encoding_color1.legend) === null || _encoding_color_legend === void 0 ? void 0 : _encoding_color_legend.disable) !== null && _encoding_color_legend_disable !== void 0 ? _encoding_color_legend_disable : false
2567
+ };
2568
+ if (annotations.length > 0) {
2569
+ result.annotations = annotations;
2570
+ }
2571
+ if (tickConfig.tickValues) {
2572
+ result.tickValues = tickConfig.tickValues;
2573
+ }
2574
+ if (tickConfig.xAxisTickCount) {
2575
+ result.xAxisTickCount = tickConfig.xAxisTickCount;
2576
+ }
2577
+ if (tickConfig.yAxisTickCount) {
2578
+ result.yAxisTickCount = tickConfig.yAxisTickCount;
2579
+ }
2580
+ return result;
2581
+ }
2582
+ /**
2583
+ * Transforms Vega-Lite specification to Fluent AreaChart props
2584
+ *
2585
+ * Area charts use the same data structure as line charts but with filled areas.
2586
+ * Supports temporal/quantitative x-axis and quantitative y-axis with color-encoded series
2587
+ *
2588
+ * Vega-Lite Stacking Behavior:
2589
+ * - If y.stack is null or undefined with no color encoding: mode = 'tozeroy' (fill to zero baseline)
2590
+ * - If y.stack is 'zero' or color encoding exists: mode = 'tonexty' (stacked areas)
2591
+ * - Multiple series with color encoding automatically stack
2592
+ *
2593
+ * @param spec - Vega-Lite specification
2594
+ * @param colorMap - Color mapping ref for consistent coloring
2595
+ * @param isDarkTheme - Whether dark theme is active
2596
+ * @returns AreaChartProps for rendering
2597
+ */ export function transformVegaLiteToAreaChartProps(spec, colorMap, isDarkTheme) {
2598
+ var _encoding_color, _encoding_y;
2599
+ // Area charts use the same structure as line charts in Fluent Charts
2600
+ // The only difference is the component renders with filled areas
2601
+ const lineChartProps = transformVegaLiteToLineChartProps(spec, colorMap, isDarkTheme);
2602
+ // Determine stacking mode based on Vega-Lite spec
2603
+ const unitSpecs = normalizeSpec(spec);
2604
+ // Use findPrimaryLineSpec to skip auxiliary layers (like rect for color fill bars)
2605
+ const primarySpec = findPrimaryLineSpec(unitSpecs);
2606
+ const encoding = (primarySpec === null || primarySpec === void 0 ? void 0 : primarySpec.encoding) || {};
2607
+ // Check if stacking is enabled
2608
+ // In Vega-Lite, area charts stack by default when color encoding is present
2609
+ // stack can be explicitly set to null to disable stacking
2610
+ const hasColorEncoding = !!((_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.field);
2611
+ const stackConfig = (_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.stack;
2612
+ const isStacked = stackConfig !== null && (stackConfig === 'zero' || hasColorEncoding);
2613
+ // Set mode: 'tozeroy' for single series, 'tonexty' for stacked
2614
+ const mode = isStacked ? 'tonexty' : 'tozeroy';
2615
+ return {
2616
+ ...lineChartProps,
2617
+ mode
2618
+ };
2619
+ }
2620
+ /**
2621
+ * Transforms Vega-Lite specification to Fluent ScatterChart props
2622
+ *
2623
+ * Supports scatter plots with quantitative x and y axes and color-encoded series
2624
+ *
2625
+ * @param spec - Vega-Lite specification
2626
+ * @param colorMap - Color mapping ref for consistent coloring
2627
+ * @param isDarkTheme - Whether dark theme is active
2628
+ * @returns ScatterChartProps for rendering
2629
+ */ export function transformVegaLiteToScatterChartProps(spec, colorMap, isDarkTheme) {
2630
+ var _encoding_x, _encoding_y, _encoding_y1, _encoding_y2, _encoding_y_axis, _encoding_y3, _encoding_color_legend, _encoding_color;
2631
+ // Initialize transformation context
2632
+ const { dataValues, encoding, markProps } = initializeTransformContext(spec);
2633
+ // Extract field names
2634
+ const { xField, yField, colorField, sizeField } = extractEncodingFields(encoding);
2635
+ if (!xField || !yField) {
2636
+ throw new Error('VegaLiteSchemaAdapter: Both x and y encodings are required for scatter charts');
2637
+ }
2638
+ const isXTemporal = ((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.type) === 'temporal';
2639
+ const isYTemporal = ((_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.type) === 'temporal';
2640
+ // Check if y-values are strings (nominal/ordinal) and build ordinal mapping
2641
+ const yIsNominal = ((_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.type) === 'nominal' || ((_encoding_y2 = encoding.y) === null || _encoding_y2 === void 0 ? void 0 : _encoding_y2.type) === 'ordinal';
2642
+ const yOrdinalMap = new Map();
2643
+ const yOrdinalLabels = [];
2644
+ if (yIsNominal) {
2645
+ // Collect unique y-values in order
2646
+ dataValues.forEach((row)=>{
2647
+ const yVal = row[yField];
2648
+ if (yVal !== undefined) {
2649
+ const key = String(yVal);
2650
+ if (!yOrdinalMap.has(key)) {
2651
+ yOrdinalMap.set(key, yOrdinalMap.size);
2652
+ yOrdinalLabels.push(key);
2653
+ }
2654
+ }
2655
+ });
2656
+ }
2657
+ // Group data by series (color encoding)
2658
+ const groupedData = {};
2659
+ dataValues.forEach((row)=>{
2660
+ const seriesName = colorField && row[colorField] !== undefined ? String(row[colorField]) : 'default';
2661
+ if (!groupedData[seriesName]) {
2662
+ groupedData[seriesName] = [];
2663
+ }
2664
+ groupedData[seriesName].push(row);
2665
+ });
2666
+ const seriesNames = Object.keys(groupedData);
2667
+ const colorIndex = new Map();
2668
+ let currentColorIndex = 0;
2669
+ const chartData = seriesNames.map((seriesName, index)=>{
2670
+ var _encoding_color_scale, _encoding_color;
2671
+ if (!colorIndex.has(seriesName)) {
2672
+ colorIndex.set(seriesName, currentColorIndex++);
2673
+ }
2674
+ const seriesData = groupedData[seriesName];
2675
+ const points = seriesData.map((row)=>{
2676
+ const xValue = parseValue(row[xField], isXTemporal);
2677
+ const yValue = parseValue(row[yField], isYTemporal);
2678
+ const markerSize = sizeField && row[sizeField] !== undefined ? Number(row[sizeField]) : undefined;
2679
+ // Map nominal y-values to numeric indices
2680
+ let numericY;
2681
+ if (yIsNominal && typeof yValue === 'string') {
2682
+ var _yOrdinalMap_get;
2683
+ numericY = (_yOrdinalMap_get = yOrdinalMap.get(yValue)) !== null && _yOrdinalMap_get !== void 0 ? _yOrdinalMap_get : 0;
2684
+ } else {
2685
+ numericY = typeof yValue === 'number' ? yValue : 0;
2686
+ }
2687
+ return {
2688
+ x: typeof xValue === 'number' || xValue instanceof Date ? xValue : String(xValue),
2689
+ y: numericY,
2690
+ ...markerSize !== undefined && {
2691
+ markerSize
2692
+ }
2693
+ };
2694
+ });
2695
+ // Get color for this series
2696
+ const colorValue = colorField && ((_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : (_encoding_color_scale = _encoding_color.scale) === null || _encoding_color_scale === void 0 ? void 0 : _encoding_color_scale.range) && Array.isArray(encoding.color.scale.range) ? encoding.color.scale.range[index] : markProps.color;
2697
+ const color = typeof colorValue === 'string' ? colorValue : resolveColor(seriesName, colorIndex.get(seriesName), undefined, undefined, colorMap, undefined, undefined, isDarkTheme);
2698
+ return {
2699
+ legend: seriesName,
2700
+ data: points,
2701
+ color,
2702
+ legendShape: 'circle'
2703
+ };
2704
+ });
2705
+ const titles = getVegaLiteTitles(spec);
2706
+ const annotations = extractAnnotations(spec);
2707
+ const tickConfig = extractTickConfig(spec);
2708
+ // Check for log scale on Y-axis
2709
+ const yAxisType = extractYAxisType(encoding);
2710
+ // Extract y-axis formatting and domain props
2711
+ const yAxisTickFormat = (_encoding_y3 = encoding.y) === null || _encoding_y3 === void 0 ? void 0 : (_encoding_y_axis = _encoding_y3.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.format;
2712
+ const { yMinValue, yMaxValue } = extractYMinMax(encoding, dataValues);
2713
+ // Extract axis category ordering
2714
+ const categoryOrderProps = extractAxisCategoryOrderProps(encoding);
2715
+ var _encoding_color_legend_disable;
2716
+ const result = {
2717
+ data: {
2718
+ chartTitle: titles.chartTitle,
2719
+ scatterChartData: chartData
2720
+ },
2721
+ xAxisTitle: titles.xAxisTitle,
2722
+ yAxisTitle: titles.yAxisTitle,
2723
+ ...titles.titleStyles ? titles.titleStyles : {},
2724
+ ...yAxisTickFormat && {
2725
+ yAxisTickFormat
2726
+ },
2727
+ ...yMinValue !== undefined && {
2728
+ yMinValue
2729
+ },
2730
+ ...yMaxValue !== undefined && {
2731
+ yMaxValue
2732
+ },
2733
+ ...yAxisType && {
2734
+ yScaleType: yAxisType
2735
+ },
2736
+ // For nominal y-axis, provide tick values and labels
2737
+ ...yIsNominal && yOrdinalLabels.length > 0 && {
2738
+ yAxisTickValues: Array.from({
2739
+ length: yOrdinalLabels.length
2740
+ }, (_, i)=>i),
2741
+ yAxisTickFormat: (val)=>{
2742
+ var _yOrdinalLabels_val;
2743
+ return (_yOrdinalLabels_val = yOrdinalLabels[val]) !== null && _yOrdinalLabels_val !== void 0 ? _yOrdinalLabels_val : String(val);
2744
+ },
2745
+ yMinValue: -0.5,
2746
+ yMaxValue: yOrdinalLabels.length - 0.5
2747
+ },
2748
+ ...categoryOrderProps,
2749
+ hideLegend: (_encoding_color_legend_disable = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : (_encoding_color_legend = _encoding_color.legend) === null || _encoding_color_legend === void 0 ? void 0 : _encoding_color_legend.disable) !== null && _encoding_color_legend_disable !== void 0 ? _encoding_color_legend_disable : false
2750
+ };
2751
+ if (annotations.length > 0) {
2752
+ result.annotations = annotations;
2753
+ }
2754
+ if (tickConfig.tickValues) {
2755
+ result.tickValues = tickConfig.tickValues;
2756
+ }
2757
+ if (tickConfig.xAxisTickCount) {
2758
+ result.xAxisTickCount = tickConfig.xAxisTickCount;
2759
+ }
2760
+ if (tickConfig.yAxisTickCount) {
2761
+ result.yAxisTickCount = tickConfig.yAxisTickCount;
2762
+ }
2763
+ return result;
2764
+ }
2765
+ /**
2766
+ * Transforms Vega-Lite specification to Fluent DonutChart props
2767
+ *
2768
+ * Supports pie/donut charts with arc marks and theta encoding
2769
+ *
2770
+ * @param spec - Vega-Lite specification
2771
+ * @param colorMap - Color mapping ref for consistent coloring
2772
+ * @param isDarkTheme - Whether dark theme is active
2773
+ * @returns DonutChartProps for rendering
2774
+ */ export function transformVegaLiteToDonutChartProps(spec, colorMap, isDarkTheme) {
2775
+ // Initialize transformation context
2776
+ const { dataValues, encoding, primarySpec } = initializeTransformContext(spec);
2777
+ // Extract field names
2778
+ const { thetaField, colorField } = extractEncodingFields(encoding);
2779
+ if (!thetaField) {
2780
+ throw new Error('VegaLiteSchemaAdapter: Theta encoding is required for donut charts');
2781
+ }
2782
+ // Extract color configuration
2783
+ const { colorScheme, colorRange } = extractColorConfig(encoding);
2784
+ // Extract innerRadius from mark properties if available
2785
+ const mark = primarySpec.mark;
2786
+ const innerRadius = typeof mark === 'object' && (mark === null || mark === void 0 ? void 0 : mark.innerRadius) !== undefined ? mark.innerRadius : 0;
2787
+ const chartData = [];
2788
+ const colorIndex = new Map();
2789
+ let currentColorIndex = 0;
2790
+ dataValues.forEach((row)=>{
2791
+ const value = row[thetaField];
2792
+ const legend = colorField && row[colorField] !== undefined ? String(row[colorField]) : String(value);
2793
+ if (value === undefined || typeof value !== 'number') {
2794
+ return;
2795
+ }
2796
+ if (!colorIndex.has(legend)) {
2797
+ colorIndex.set(legend, currentColorIndex++);
2798
+ }
2799
+ chartData.push({
2800
+ legend,
2801
+ data: value,
2802
+ color: resolveColor(legend, colorIndex.get(legend), undefined, undefined, colorMap, colorScheme, colorRange, isDarkTheme)
2803
+ });
2804
+ });
2805
+ const titles = getVegaLiteTitles(spec);
2806
+ return {
2807
+ data: {
2808
+ chartTitle: titles.chartTitle,
2809
+ chartData
2810
+ },
2811
+ innerRadius,
2812
+ width: typeof spec.width === 'number' ? spec.width : undefined,
2813
+ height: typeof spec.height === 'number' ? spec.height : undefined,
2814
+ ...titles.titleStyles ? titles.titleStyles : {}
2815
+ };
2816
+ }
2817
+ /**
2818
+ * Transforms Vega-Lite specification to Fluent HeatMapChart props
2819
+ *
2820
+ * Supports heatmaps with rect marks and x/y/color encodings
2821
+ *
2822
+ * @param spec - Vega-Lite specification
2823
+ * @param colorMap - Color mapping ref for consistent coloring
2824
+ * @param isDarkTheme - Whether dark theme is active
2825
+ * @returns HeatMapChartProps for rendering
2826
+ */ export function transformVegaLiteToHeatMapChartProps(spec, colorMap, isDarkTheme) {
2827
+ var _encoding_color, _encoding_color1, _encoding_x, _encoding_x1, _encoding_color_scale, _encoding_color2, _encoding_color_scale1, _encoding_color3;
2828
+ // Initialize transformation context
2829
+ const { dataValues, encoding } = initializeTransformContext(spec);
2830
+ // Extract field names
2831
+ const { xField, yField, colorField } = extractEncodingFields(encoding);
2832
+ if (!xField || !yField || !colorField) {
2833
+ throw new Error('VegaLiteSchemaAdapter: x, y, and color encodings are required for heatmap charts');
2834
+ }
2835
+ const heatmapDataPoints = [];
2836
+ let minValue = Number.POSITIVE_INFINITY;
2837
+ let maxValue = Number.NEGATIVE_INFINITY;
2838
+ // Check if color values are nominal (strings) rather than quantitative (numbers)
2839
+ const isNominalColor = ((_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.type) === 'nominal' || ((_encoding_color1 = encoding.color) === null || _encoding_color1 === void 0 ? void 0 : _encoding_color1.type) === 'ordinal' || dataValues.some((row)=>row[colorField] !== undefined && typeof row[colorField] !== 'number');
2840
+ const nominalColorMap = new Map();
2841
+ dataValues.forEach((row)=>{
2842
+ const xValue = row[xField];
2843
+ const yValue = row[yField];
2844
+ const colorValue = row[colorField];
2845
+ if (isInvalidValue(xValue) || isInvalidValue(yValue) || isInvalidValue(colorValue)) {
2846
+ return;
2847
+ }
2848
+ let value;
2849
+ if (isNominalColor) {
2850
+ // Map nominal color values to sequential numeric indices
2851
+ const key = String(colorValue);
2852
+ if (!nominalColorMap.has(key)) {
2853
+ nominalColorMap.set(key, nominalColorMap.size);
2854
+ }
2855
+ value = nominalColorMap.get(key);
2856
+ } else {
2857
+ value = typeof colorValue === 'number' ? colorValue : 0;
2858
+ }
2859
+ minValue = Math.min(minValue, value);
2860
+ maxValue = Math.max(maxValue, value);
2861
+ heatmapDataPoints.push({
2862
+ x: xValue,
2863
+ y: yValue,
2864
+ value,
2865
+ rectText: isNominalColor ? String(colorValue) : value
2866
+ });
2867
+ });
2868
+ // Validate that we have complete grid data
2869
+ if (heatmapDataPoints.length === 0) {
2870
+ throw new Error('VegaLiteSchemaAdapter: Heatmap requires data points with x, y, and color values');
2871
+ }
2872
+ // Extract unique x and y values and create complete grid
2873
+ const uniqueXValues = new Set(heatmapDataPoints.map((p)=>String(p.x)));
2874
+ const uniqueYValues = new Set(heatmapDataPoints.map((p)=>String(p.y)));
2875
+ // Build a map of existing data points for quick lookup
2876
+ const dataPointMap = new Map();
2877
+ const rectTextMap = new Map();
2878
+ heatmapDataPoints.forEach((point)=>{
2879
+ const key = `${String(point.x)}|${String(point.y)}`;
2880
+ dataPointMap.set(key, point.value);
2881
+ var _point_rectText;
2882
+ rectTextMap.set(key, (_point_rectText = point.rectText) !== null && _point_rectText !== void 0 ? _point_rectText : point.value);
2883
+ });
2884
+ // Generate complete grid - fill missing cells with 0
2885
+ const completeGridDataPoints = [];
2886
+ let xValuesArray = Array.from(uniqueXValues);
2887
+ const yValuesArray = Array.from(uniqueYValues);
2888
+ // Sort x-values chronologically if they appear to be dates
2889
+ const isXTemporal = ((_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.type) === 'temporal' || ((_encoding_x1 = encoding.x) === null || _encoding_x1 === void 0 ? void 0 : _encoding_x1.type) === 'ordinal';
2890
+ if (isXTemporal) {
2891
+ const firstX = xValuesArray[0];
2892
+ const parsedDate = new Date(firstX);
2893
+ if (!isNaN(parsedDate.getTime())) {
2894
+ // Values are parseable as dates — sort chronologically
2895
+ xValuesArray = xValuesArray.sort((a, b)=>new Date(a).getTime() - new Date(b).getTime());
2896
+ }
2897
+ }
2898
+ yValuesArray.forEach((yVal)=>{
2899
+ xValuesArray.forEach((xVal)=>{
2900
+ const key = `${xVal}|${yVal}`;
2901
+ var _dataPointMap_get;
2902
+ const value = (_dataPointMap_get = dataPointMap.get(key)) !== null && _dataPointMap_get !== void 0 ? _dataPointMap_get : 0; // Use 0 for missing cells
2903
+ // Update min/max to include filled values
2904
+ if (value !== 0 || dataPointMap.has(key)) {
2905
+ minValue = Math.min(minValue, value);
2906
+ maxValue = Math.max(maxValue, value);
2907
+ }
2908
+ var _rectTextMap_get;
2909
+ completeGridDataPoints.push({
2910
+ x: xVal,
2911
+ y: yVal,
2912
+ value,
2913
+ rectText: (_rectTextMap_get = rectTextMap.get(key)) !== null && _rectTextMap_get !== void 0 ? _rectTextMap_get : value
2914
+ });
2915
+ });
2916
+ });
2917
+ const heatmapData = {
2918
+ legend: '',
2919
+ data: completeGridDataPoints,
2920
+ value: 0
2921
+ };
2922
+ const titles = getVegaLiteTitles(spec);
2923
+ // Create color scale domain and range
2924
+ let domainValues = [];
2925
+ let rangeValues = [];
2926
+ // Check for named color scheme or custom range from encoding
2927
+ const colorScheme = (_encoding_color2 = encoding.color) === null || _encoding_color2 === void 0 ? void 0 : (_encoding_color_scale = _encoding_color2.scale) === null || _encoding_color_scale === void 0 ? void 0 : _encoding_color_scale.scheme;
2928
+ const customRange = (_encoding_color3 = encoding.color) === null || _encoding_color3 === void 0 ? void 0 : (_encoding_color_scale1 = _encoding_color3.scale) === null || _encoding_color_scale1 === void 0 ? void 0 : _encoding_color_scale1.range;
2929
+ if (isNominalColor && nominalColorMap.size > 0) {
2930
+ // For nominal colors, use categorical color scale
2931
+ const numCategories = nominalColorMap.size;
2932
+ domainValues = Array.from({
2933
+ length: numCategories
2934
+ }, (_, i)=>i);
2935
+ if (customRange && customRange.length >= numCategories) {
2936
+ rangeValues = customRange.slice(0, numCategories);
2937
+ } else {
2938
+ // Use distinct categorical colors for each category
2939
+ for(let i = 0; i < numCategories; i++){
2940
+ rangeValues.push(getVegaColor(i, colorScheme, customRange, isDarkTheme !== null && isDarkTheme !== void 0 ? isDarkTheme : false));
2941
+ }
2942
+ }
2943
+ } else {
2944
+ // Quantitative color scale
2945
+ const steps = 5;
2946
+ for(let i = 0; i < steps; i++){
2947
+ const t = i / (steps - 1);
2948
+ domainValues.push(minValue + (maxValue - minValue) * t);
2949
+ }
2950
+ if (customRange && customRange.length > 0) {
2951
+ rangeValues = customRange.length >= steps ? customRange.slice(0, steps) : customRange;
2952
+ } else if (colorScheme) {
2953
+ const schemeColors = getSequentialSchemeColors(colorScheme, steps);
2954
+ if (schemeColors) {
2955
+ var _encoding_color4, _encoding_color_scale2, _encoding_color5;
2956
+ const isReversed = ((_encoding_color4 = encoding.color) === null || _encoding_color4 === void 0 ? void 0 : _encoding_color4.sort) === 'descending' || ((_encoding_color5 = encoding.color) === null || _encoding_color5 === void 0 ? void 0 : (_encoding_color_scale2 = _encoding_color5.scale) === null || _encoding_color_scale2 === void 0 ? void 0 : _encoding_color_scale2.reverse) === true;
2957
+ rangeValues = isReversed ? schemeColors.reverse() : schemeColors;
2958
+ }
2959
+ }
2960
+ // Fall back to default blue-to-red gradient if no scheme matched
2961
+ if (rangeValues.length === 0) {
2962
+ for(let i = 0; i < steps; i++){
2963
+ const t = i / (steps - 1);
2964
+ if (isDarkTheme) {
2965
+ const r = Math.round(0 + 255 * t);
2966
+ const g = Math.round(100 + (165 - 100) * t);
2967
+ const b = Math.round(255 - 255 * t);
2968
+ rangeValues.push(`rgb(${r}, ${g}, ${b})`);
2969
+ } else {
2970
+ const r = Math.round(0 + 255 * t);
2971
+ const g = Math.round(150 - 150 * t);
2972
+ const b = Math.round(255 - 255 * t);
2973
+ rangeValues.push(`rgb(${r}, ${g}, ${b})`);
2974
+ }
2975
+ }
2976
+ }
2977
+ }
2978
+ var _spec_height;
2979
+ return {
2980
+ chartTitle: titles.chartTitle,
2981
+ data: [
2982
+ heatmapData
2983
+ ],
2984
+ domainValuesForColorScale: domainValues,
2985
+ rangeValuesForColorScale: rangeValues,
2986
+ xAxisTitle: titles.xAxisTitle,
2987
+ yAxisTitle: titles.yAxisTitle,
2988
+ ...titles.titleStyles ? titles.titleStyles : {},
2989
+ width: spec.width,
2990
+ height: (_spec_height = spec.height) !== null && _spec_height !== void 0 ? _spec_height : DEFAULT_CHART_HEIGHT,
2991
+ hideLegend: true,
2992
+ showYAxisLables: true,
2993
+ sortOrder: 'none',
2994
+ hideTickOverlap: true,
2995
+ noOfCharsToTruncate: xValuesArray.length > 20 ? 6 : xValuesArray.length > 10 ? 10 : DEFAULT_TRUNCATE_CHARS,
2996
+ showYAxisLablesTooltip: true,
2997
+ wrapXAxisLables: true
2998
+ };
2999
+ }
3000
+ /**
3001
+ * Helper function to get bin center for display
3002
+ */ function getBinCenter(bin) {
3003
+ return (bin.x0 + bin.x1) / 2;
3004
+ }
3005
+ /**
3006
+ * Helper function to calculate histogram aggregation function
3007
+ *
3008
+ * @param aggregate - Aggregation type (count, sum, mean, min, max)
3009
+ * @param bin - Binned data values
3010
+ * @returns Aggregated value
3011
+ */ function calculateHistogramAggregate(aggregate, bin) {
3012
+ switch(aggregate){
3013
+ case 'sum':
3014
+ return d3Sum(bin);
3015
+ case 'mean':
3016
+ case 'average':
3017
+ var _d3Mean;
3018
+ return bin.length === 0 ? 0 : (_d3Mean = d3Mean(bin)) !== null && _d3Mean !== void 0 ? _d3Mean : 0;
3019
+ case 'min':
3020
+ var _d3Min;
3021
+ return (_d3Min = d3Min(bin)) !== null && _d3Min !== void 0 ? _d3Min : 0;
3022
+ case 'max':
3023
+ var _d3Max;
3024
+ return (_d3Max = d3Max(bin)) !== null && _d3Max !== void 0 ? _d3Max : 0;
3025
+ case 'count':
3026
+ default:
3027
+ return bin.length;
3028
+ }
3029
+ }
3030
+ /**
3031
+ * Transforms Vega-Lite specification to Fluent VerticalBarChart props for histogram rendering
3032
+ *
3033
+ * Supports histograms with binned x-axis and aggregated y-axis
3034
+ * Vega-Lite syntax: `{ "mark": "bar", "encoding": { "x": { "field": "value", "bin": true }, "y": { "aggregate": "count" } } }`
3035
+ *
3036
+ * @param spec - Vega-Lite specification
3037
+ * @param colorMap - Color mapping ref for consistent coloring
3038
+ * @param isDarkTheme - Whether dark theme is active
3039
+ * @returns VerticalBarChartProps for rendering histogram
3040
+ */ export function transformVegaLiteToHistogramProps(spec, colorMap, isDarkTheme) {
3041
+ var _encoding_y, _encoding_x, _encoding_color, _dataValues_, _encoding_y1, _encoding_y_axis, _encoding_y2;
3042
+ // Initialize transformation context
3043
+ const { dataValues, encoding } = initializeTransformContext(spec);
3044
+ // Extract field names
3045
+ const { xField } = extractEncodingFields(encoding);
3046
+ const yAggregate = ((_encoding_y = encoding.y) === null || _encoding_y === void 0 ? void 0 : _encoding_y.aggregate) || 'count';
3047
+ const binConfig = (_encoding_x = encoding.x) === null || _encoding_x === void 0 ? void 0 : _encoding_x.bin;
3048
+ if (!xField || !binConfig) {
3049
+ throw new Error('VegaLiteSchemaAdapter: Histogram requires x encoding with bin property');
3050
+ }
3051
+ // Validate data
3052
+ validateDataArray(dataValues, xField, 'Histogram');
3053
+ validateNoNestedArrays(dataValues, xField);
3054
+ // Extract numeric values from the field
3055
+ const allValues = dataValues.map((row)=>row[xField]).filter((val)=>!isInvalidValue(val));
3056
+ const values = allValues.filter((val)=>typeof val === 'number');
3057
+ if (values.length === 0) {
3058
+ // Provide helpful error message based on actual data type
3059
+ const sampleValue = allValues[0];
3060
+ const actualType = typeof sampleValue;
3061
+ let suggestion = '';
3062
+ if (actualType === 'string') {
3063
+ // Check if strings contain numbers
3064
+ const hasEmbeddedNumbers = allValues.some((val)=>typeof val === 'string' && /\d/.test(val));
3065
+ if (hasEmbeddedNumbers) {
3066
+ suggestion = ' The data contains strings with embedded numbers (e.g., "40 salads"). ' + 'Consider extracting the numeric values first, or change the encoding type to "nominal" or "ordinal" for a categorical bar chart.';
3067
+ } else {
3068
+ suggestion = ` The data contains categorical strings (e.g., "${sampleValue}"). ` + 'Change the x encoding type to "nominal" or "ordinal" for a categorical bar chart, ' + 'or remove bin: true to create a simple bar chart.';
3069
+ }
3070
+ } else if (actualType === 'undefined') {
3071
+ suggestion = ' The field may not exist in the data.';
3072
+ }
3073
+ throw new Error(`VegaLiteSchemaAdapter: No numeric values found for histogram binning on field "${xField}". ` + `Found ${actualType} values instead.${suggestion}`);
3074
+ }
3075
+ // Create bins using d3
3076
+ const [minVal, maxVal] = d3Extent(values);
3077
+ const binGenerator = d3Bin().domain([
3078
+ minVal,
3079
+ maxVal
3080
+ ]);
3081
+ // Apply bin configuration
3082
+ if (typeof binConfig === 'object') {
3083
+ if (binConfig.maxbins) {
3084
+ binGenerator.thresholds(binConfig.maxbins);
3085
+ }
3086
+ if (binConfig.extent) {
3087
+ binGenerator.domain(binConfig.extent);
3088
+ }
3089
+ }
3090
+ const bins = binGenerator(values);
3091
+ // Calculate histogram data points
3092
+ const legend = ((_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : _encoding_color.field) ? String((_dataValues_ = dataValues[0]) === null || _dataValues_ === void 0 ? void 0 : _dataValues_[encoding.color.field]) : 'Frequency';
3093
+ const color = resolveColor(legend, 0, undefined, undefined, colorMap, undefined, undefined, isDarkTheme);
3094
+ const yField = (_encoding_y1 = encoding.y) === null || _encoding_y1 === void 0 ? void 0 : _encoding_y1.field;
3095
+ const histogramData = bins.map((bin)=>{
3096
+ const x = getBinCenter(bin);
3097
+ let y;
3098
+ if (yAggregate !== 'count' && yField) {
3099
+ // For non-count aggregates, collect y-field values for rows whose x-value falls in this bin
3100
+ const yValues = dataValues.filter((row)=>{
3101
+ const xVal = Number(row[xField]);
3102
+ return !isNaN(xVal) && xVal >= bin.x0 && xVal < bin.x1;
3103
+ }).map((row)=>Number(row[yField])).filter((v)=>!isNaN(v));
3104
+ // Include the last bin's upper bound (x1 is inclusive for the last bin)
3105
+ if (bin === bins[bins.length - 1]) {
3106
+ const extraRows = dataValues.filter((row)=>Number(row[xField]) === bin.x1).map((row)=>Number(row[yField])).filter((v)=>!isNaN(v));
3107
+ yValues.push(...extraRows);
3108
+ }
3109
+ y = calculateHistogramAggregate(yAggregate, yValues);
3110
+ } else {
3111
+ y = calculateHistogramAggregate(yAggregate, bin);
3112
+ }
3113
+ const xAxisCalloutData = `[${bin.x0} - ${bin.x1})`;
3114
+ return {
3115
+ x,
3116
+ y,
3117
+ legend,
3118
+ color,
3119
+ xAxisCalloutData
3120
+ };
3121
+ });
3122
+ const titles = getVegaLiteTitles(spec);
3123
+ const annotations = extractAnnotations(spec);
3124
+ const yAxisTickFormat = (_encoding_y2 = encoding.y) === null || _encoding_y2 === void 0 ? void 0 : (_encoding_y_axis = _encoding_y2.axis) === null || _encoding_y_axis === void 0 ? void 0 : _encoding_y_axis.format;
3125
+ return {
3126
+ data: histogramData,
3127
+ chartTitle: titles.chartTitle,
3128
+ xAxisTitle: titles.xAxisTitle || xField,
3129
+ yAxisTitle: titles.yAxisTitle || yAggregate,
3130
+ ...titles.titleStyles ? titles.titleStyles : {},
3131
+ roundCorners: true,
3132
+ hideTickOverlap: true,
3133
+ maxBarWidth: DEFAULT_MAX_BAR_WIDTH,
3134
+ ...annotations.length > 0 && {
3135
+ annotations
3136
+ },
3137
+ ...yAxisTickFormat && {
3138
+ yAxisTickFormat
3139
+ },
3140
+ mode: 'histogram'
3141
+ };
3142
+ }
3143
+ /**
3144
+ * Transforms Vega-Lite specification with theta/radius encodings to Fluent PolarChart props
3145
+ * Supports line, point, and area marks with polar coordinates
3146
+ *
3147
+ * @param spec - Vega-Lite specification with theta and radius encodings
3148
+ * @param colorMap - Color mapping ref for consistent coloring
3149
+ * @param isDarkTheme - Whether dark theme is active
3150
+ * @returns PolarChartProps for rendering with Fluent PolarChart component
3151
+ */ export function transformVegaLiteToPolarChartProps(spec, colorMap, isDarkTheme) {
3152
+ var _encoding_theta, _encoding_color_legend, _encoding_color;
3153
+ // Initialize transformation context
3154
+ const { dataValues, encoding, markProps, primarySpec } = initializeTransformContext(spec);
3155
+ // Extract field names
3156
+ const { thetaField, radiusField, colorField } = extractEncodingFields(encoding);
3157
+ // Validate polar encodings
3158
+ if (!thetaField || !radiusField) {
3159
+ throw new Error('VegaLiteSchemaAdapter: Both theta and radius encodings are required for polar charts');
3160
+ }
3161
+ validateDataArray(dataValues, thetaField, 'PolarChart');
3162
+ validateDataArray(dataValues, radiusField, 'PolarChart');
3163
+ // Determine mark type for polar chart series type
3164
+ const mark = primarySpec.mark;
3165
+ const markType = typeof mark === 'string' ? mark : mark === null || mark === void 0 ? void 0 : mark.type;
3166
+ // Arc marks with theta+radius should be treated as area polar (radial/rose charts)
3167
+ const isAreaMark = markType === 'area' || markType === 'arc';
3168
+ const isLineMark = markType === 'line';
3169
+ // Extract color configuration
3170
+ const { colorScheme, colorRange } = extractColorConfig(encoding);
3171
+ // Group data by series (color field)
3172
+ const seriesMap = new Map();
3173
+ const colorIndex = new Map();
3174
+ let currentColorIndex = 0;
3175
+ dataValues.forEach((row)=>{
3176
+ const thetaValue = row[thetaField];
3177
+ const radiusValue = row[radiusField];
3178
+ // Skip invalid values
3179
+ if (isInvalidValue(thetaValue) || isInvalidValue(radiusValue)) {
3180
+ return;
3181
+ }
3182
+ const seriesName = colorField && row[colorField] !== undefined ? String(row[colorField]) : 'default';
3183
+ if (!colorIndex.has(seriesName)) {
3184
+ colorIndex.set(seriesName, currentColorIndex++);
3185
+ }
3186
+ if (!seriesMap.has(seriesName)) {
3187
+ seriesMap.set(seriesName, []);
3188
+ }
3189
+ // Convert theta value - handle different types
3190
+ let theta;
3191
+ if (typeof thetaValue === 'number') {
3192
+ // Numeric theta - assume degrees
3193
+ theta = thetaValue;
3194
+ } else {
3195
+ // Categorical theta
3196
+ theta = String(thetaValue);
3197
+ }
3198
+ // Convert radius value
3199
+ const r = typeof radiusValue === 'number' ? radiusValue : Number(radiusValue);
3200
+ seriesMap.get(seriesName).push({
3201
+ theta,
3202
+ r
3203
+ });
3204
+ });
3205
+ // Convert series map to polar chart data array
3206
+ const polarData = [];
3207
+ seriesMap.forEach((dataPoints, seriesName)=>{
3208
+ const color = resolveColor(seriesName, colorIndex.get(seriesName), undefined, markProps.color, colorMap, colorScheme, colorRange, isDarkTheme);
3209
+ const curveOption = mapInterpolateToCurve(markProps.interpolate);
3210
+ // Build line options with curve, strokeDash, and strokeWidth
3211
+ const lineOptions = {};
3212
+ if (curveOption) {
3213
+ lineOptions.curve = curveOption;
3214
+ }
3215
+ if (markProps.strokeDash) {
3216
+ lineOptions.strokeDasharray = markProps.strokeDash.join(' ');
3217
+ }
3218
+ if (markProps.strokeWidth) {
3219
+ lineOptions.strokeWidth = markProps.strokeWidth;
3220
+ }
3221
+ if (isAreaMark) {
3222
+ const series = {
3223
+ type: 'areapolar',
3224
+ legend: seriesName,
3225
+ color,
3226
+ data: dataPoints,
3227
+ ...Object.keys(lineOptions).length > 0 && {
3228
+ lineOptions
3229
+ }
3230
+ };
3231
+ polarData.push(series);
3232
+ } else if (isLineMark) {
3233
+ const series = {
3234
+ type: 'linepolar',
3235
+ legend: seriesName,
3236
+ color,
3237
+ data: dataPoints,
3238
+ ...Object.keys(lineOptions).length > 0 && {
3239
+ lineOptions
3240
+ }
3241
+ };
3242
+ polarData.push(series);
3243
+ } else {
3244
+ // Default to scatter polar for point marks
3245
+ const series = {
3246
+ type: 'scatterpolar',
3247
+ legend: seriesName,
3248
+ color,
3249
+ data: dataPoints
3250
+ };
3251
+ polarData.push(series);
3252
+ }
3253
+ });
3254
+ // Extract chart titles
3255
+ const titles = getVegaLiteTitles(spec);
3256
+ // Build axis props from encoding
3257
+ const radialAxis = {};
3258
+ const angularAxis = {};
3259
+ // Determine angular axis category order if theta is categorical
3260
+ const thetaType = (_encoding_theta = encoding.theta) === null || _encoding_theta === void 0 ? void 0 : _encoding_theta.type;
3261
+ if (thetaType === 'nominal' || thetaType === 'ordinal') {
3262
+ // Get unique theta values in order for category order
3263
+ const thetaValues = Array.from(new Set(dataValues.map((row)=>String(row[thetaField]))));
3264
+ angularAxis.categoryOrder = thetaValues;
3265
+ }
3266
+ var _encoding_color_legend_disable;
3267
+ return {
3268
+ data: polarData,
3269
+ ...titles.chartTitle && {
3270
+ chartTitle: titles.chartTitle
3271
+ },
3272
+ ...titles.titleStyles ? titles.titleStyles : {},
3273
+ width: typeof spec.width === 'number' ? spec.width : undefined,
3274
+ height: typeof spec.height === 'number' ? spec.height : 400,
3275
+ hideLegend: (_encoding_color_legend_disable = (_encoding_color = encoding.color) === null || _encoding_color === void 0 ? void 0 : (_encoding_color_legend = _encoding_color.legend) === null || _encoding_color_legend === void 0 ? void 0 : _encoding_color_legend.disable) !== null && _encoding_color_legend_disable !== void 0 ? _encoding_color_legend_disable : false,
3276
+ radialAxis,
3277
+ angularAxis
3278
+ };
3279
+ }