@fluentui/react-charts 9.3.11 → 9.3.13

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