@visactor/vbi 0.4.19 → 0.4.21

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 (52) hide show
  1. package/dist/builder/adapters/vquery-vseed/build-vquery.d.ts +2 -2
  2. package/dist/builder/adapters/vquery-vseed/build-vseed.d.ts +2 -2
  3. package/dist/builder/adapters/vquery-vseed/index.d.ts +3 -3
  4. package/dist/builder/builder.d.ts +6 -6
  5. package/dist/builder/features/chart-type/chart-type-builder.d.ts +20 -1
  6. package/dist/builder/features/chart-type/dimension-encoding.d.ts +4 -0
  7. package/dist/builder/features/chart-type/measure-encoding.d.ts +4 -0
  8. package/dist/builder/features/chart-type/reapply-dimension-encodings.d.ts +2 -0
  9. package/dist/builder/features/chart-type/reapply-measure-encodings.d.ts +2 -0
  10. package/dist/builder/features/dimensions/dim-builder.d.ts +3 -2
  11. package/dist/builder/features/dimensions/dim-node-builder.d.ts +32 -1
  12. package/dist/builder/features/havingFilter/having-builder.d.ts +2 -2
  13. package/dist/builder/features/measures/mea-builder.d.ts +3 -2
  14. package/dist/builder/features/measures/mea-node-builder.d.ts +33 -3
  15. package/dist/builder/features/whereFilter/where-builder.d.ts +2 -2
  16. package/dist/builder/features/whereFilter/where-node-builder.d.ts +11 -2
  17. package/dist/builder/index.d.ts +1 -1
  18. package/dist/builder/modules/build.d.ts +2 -2
  19. package/dist/builder/modules/index.d.ts +2 -2
  20. package/dist/builder/modules/is-empty.d.ts +1 -1
  21. package/dist/index.cjs +1497 -387
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +1489 -379
  24. package/dist/pipeline/vqueryDSL/aggregateMap.d.ts +23 -3
  25. package/dist/pipeline/vqueryDSL/buildOrderBy.d.ts +2 -0
  26. package/dist/pipeline/vqueryDSL/index.d.ts +2 -2
  27. package/dist/pipeline/vqueryDSL/resolveDatePredicate.d.ts +7 -0
  28. package/dist/pipeline/vqueryDSL/types.d.ts +6 -5
  29. package/dist/types/builder/VBIInterface.d.ts +5 -4
  30. package/dist/types/builder/adapter.d.ts +15 -13
  31. package/dist/types/builder/build-vseed.d.ts +3 -0
  32. package/dist/types/builder/context.d.ts +2 -2
  33. package/dist/types/builder/index.d.ts +4 -3
  34. package/dist/types/builder/observe.d.ts +2 -1
  35. package/dist/types/connector/query.d.ts +1 -0
  36. package/dist/types/dsl/dimensions/aggregate.d.ts +15 -0
  37. package/dist/types/dsl/dimensions/dimensions.d.ts +62 -0
  38. package/dist/types/dsl/encoding.d.ts +2 -2
  39. package/dist/types/dsl/havingFilter/having.d.ts +3 -3
  40. package/dist/types/dsl/index.d.ts +5 -3
  41. package/dist/types/dsl/measures/measures.d.ts +151 -4
  42. package/dist/types/dsl/sort.d.ts +13 -0
  43. package/dist/types/dsl/vbi/vbi.d.ts +90 -5
  44. package/dist/types/dsl/whereFilter/date.d.ts +95 -0
  45. package/dist/types/dsl/whereFilter/filters.d.ts +142 -5
  46. package/dist/utils/filter-guards.d.ts +2 -2
  47. package/dist/vbi/create-vbi.d.ts +6 -7
  48. package/dist/vbi/from/from-vbi-dsl-input.d.ts +3 -3
  49. package/dist/vbi/from/set-base-dsl-fields.d.ts +2 -2
  50. package/dist/vbi/generate-empty-dsl.d.ts +2 -2
  51. package/dist/vbi/normalize/types.d.ts +3 -3
  52. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -12,250 +12,6 @@ const getConnector = async (id)=>{
12
12
  if ('function' == typeof connector) return connector();
13
13
  return connector;
14
14
  };
15
- const VBI_TO_VQUERY_AGGR_FUNC_MAP = {
16
- count: 'count',
17
- countDistinct: 'count_distinct',
18
- sum: 'sum',
19
- avg: 'avg',
20
- min: 'min',
21
- max: 'max',
22
- variance: 'variance',
23
- variancePop: 'variance_pop',
24
- stddev: 'stddev',
25
- median: 'median',
26
- quantile: 'quantile'
27
- };
28
- const mapAggregateForVQuery = (aggregate)=>{
29
- if (!aggregate) return aggregate;
30
- const mappedFunc = VBI_TO_VQUERY_AGGR_FUNC_MAP[aggregate.func] ?? aggregate.func;
31
- return {
32
- ...aggregate,
33
- func: mappedFunc
34
- };
35
- };
36
- const buildSelect = (queryDSL, context)=>{
37
- const { vbiDSL } = context;
38
- const measures = vbiDSL.measures;
39
- const dimensions = vbiDSL.dimensions;
40
- const result = {
41
- ...queryDSL
42
- };
43
- const measureNodes = measures.filter((measure)=>MeasuresBuilder.isMeasureNode(measure));
44
- const measureSelects = measureNodes.map((measure)=>({
45
- field: measure.field,
46
- alias: measure.alias,
47
- aggr: mapAggregateForVQuery(measure.aggregate)
48
- }));
49
- const dimensionNodes = dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension));
50
- const dimensionSelects = dimensionNodes.map((dimension)=>({
51
- field: dimension.field,
52
- alias: dimension.alias
53
- }));
54
- result.select = [
55
- ...measureSelects,
56
- ...dimensionSelects
57
- ];
58
- return result;
59
- };
60
- const buildGroupBy = (queryDSL, context)=>{
61
- const result = {
62
- ...queryDSL
63
- };
64
- const { vbiDSL } = context;
65
- const dimensions = vbiDSL.dimensions;
66
- const dimensionNodes = dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension));
67
- result.groupBy = dimensionNodes.map((dimension)=>dimension.field);
68
- return result;
69
- };
70
- const buildWhere = (queryDSL, context)=>{
71
- const { vbiDSL } = context;
72
- const whereFilter = vbiDSL.whereFilter;
73
- if (!whereFilter || 0 === whereFilter.conditions.length) return queryDSL;
74
- const result = {
75
- ...queryDSL
76
- };
77
- result.where = mapGroupToCondition(whereFilter);
78
- return result;
79
- };
80
- function isWhereGroup(clause) {
81
- return 'op' in clause && 'conditions' in clause;
82
- }
83
- function mapClauseToCondition(clause) {
84
- if (isWhereGroup(clause)) return [
85
- mapGroupToCondition(clause)
86
- ];
87
- return mapFilterToCondition(clause);
88
- }
89
- function mapGroupToCondition(group) {
90
- return {
91
- op: group.op,
92
- conditions: group.conditions.flatMap(mapClauseToCondition)
93
- };
94
- }
95
- function mapFilterToCondition(filter) {
96
- if ('between' === filter.op || 'not between' === filter.op) return handleBetweenFilter(filter);
97
- return handleSimpleFilter(filter);
98
- }
99
- function handleBetweenFilter(filter) {
100
- const value = normalizeBetweenValue(filter.value);
101
- const lowerCondition = void 0 !== value.min && null !== value.min && '' !== value.min ? {
102
- field: filter.field,
103
- op: '<' === value.leftOp ? '>' : '>=',
104
- value: value.min
105
- } : void 0;
106
- const upperCondition = void 0 !== value.max && null !== value.max && '' !== value.max ? {
107
- field: filter.field,
108
- op: '<' === value.rightOp ? '<' : '<=',
109
- value: value.max
110
- } : void 0;
111
- if ('not between' === filter.op) {
112
- const outsideConditions = [
113
- lowerCondition && invertLowerBound(lowerCondition),
114
- upperCondition && invertUpperBound(upperCondition)
115
- ].filter(Boolean);
116
- if (outsideConditions.length <= 1) return outsideConditions;
117
- return [
118
- {
119
- op: 'or',
120
- conditions: outsideConditions
121
- }
122
- ];
123
- }
124
- return [
125
- lowerCondition,
126
- upperCondition
127
- ].filter(Boolean);
128
- }
129
- function normalizeBetweenValue(value) {
130
- if (Array.isArray(value)) return {
131
- min: value[0],
132
- max: value[1],
133
- leftOp: '<=',
134
- rightOp: '<='
135
- };
136
- if ('object' == typeof value && null !== value) return value;
137
- return {};
138
- }
139
- function invertLowerBound(condition) {
140
- return {
141
- field: condition.field,
142
- op: '>' === condition.op ? '<=' : '<',
143
- value: condition.value
144
- };
145
- }
146
- function invertUpperBound(condition) {
147
- return {
148
- field: condition.field,
149
- op: '<' === condition.op ? '>=' : '>',
150
- value: condition.value
151
- };
152
- }
153
- function handleSimpleFilter(filter) {
154
- let mappedOp = filter.op ?? '=';
155
- const value = filter.value;
156
- if (Array.isArray(value)) {
157
- if ('=' === mappedOp) mappedOp = 'in';
158
- if ('!=' === mappedOp) mappedOp = 'not in';
159
- }
160
- return [
161
- {
162
- field: filter.field,
163
- op: mappedOp,
164
- value
165
- }
166
- ];
167
- }
168
- const DEFAULT_HAVING_AGGREGATE = {
169
- func: 'sum'
170
- };
171
- const buildHaving = (queryDSL, context)=>{
172
- const { vbiDSL } = context;
173
- const havingFilter = vbiDSL.havingFilter;
174
- if (!havingFilter || 0 === havingFilter.conditions.length) return queryDSL;
175
- const result = {
176
- ...queryDSL
177
- };
178
- result.having = {
179
- op: havingFilter.op,
180
- conditions: havingFilter.conditions.flatMap(buildHaving_mapClauseToCondition)
181
- };
182
- return result;
183
- };
184
- function isHavingGroup(clause) {
185
- return 'op' in clause && 'conditions' in clause;
186
- }
187
- function buildHaving_mapClauseToCondition(clause) {
188
- if (isHavingGroup(clause)) return [
189
- buildHaving_mapGroupToCondition(clause)
190
- ];
191
- return buildHaving_mapFilterToCondition(clause);
192
- }
193
- function buildHaving_mapGroupToCondition(group) {
194
- return {
195
- op: group.op,
196
- conditions: group.conditions.flatMap(buildHaving_mapClauseToCondition)
197
- };
198
- }
199
- function buildHaving_mapFilterToCondition(filter) {
200
- const mappedOp = normalizeOperator(filter.op, filter.value);
201
- const aggregate = mapAggregateForVQuery(filter.aggregate ?? DEFAULT_HAVING_AGGREGATE);
202
- return [
203
- {
204
- field: filter.field,
205
- aggr: aggregate,
206
- op: mappedOp,
207
- value: filter.value
208
- }
209
- ];
210
- }
211
- function normalizeOperator(op, value) {
212
- let mappedOp = op ?? '=';
213
- if (Array.isArray(value)) {
214
- if ('=' === mappedOp) mappedOp = 'in';
215
- if ('!=' === mappedOp) mappedOp = 'not in';
216
- }
217
- return mappedOp;
218
- }
219
- const buildLimit = (queryDSL, context)=>{
220
- const result = {
221
- ...queryDSL
222
- };
223
- const limit = context.vbiDSL.limit ?? 1000;
224
- result.limit = limit;
225
- return result;
226
- };
227
- const buildVQuery = (vbiDSL, builder)=>{
228
- const wrapper = (processor)=>(queryDSL)=>processor(queryDSL, {
229
- vbiDSL,
230
- builder
231
- });
232
- return pipe({}, wrapper(buildSelect), wrapper(buildGroupBy), wrapper(buildWhere), wrapper(buildHaving), wrapper(buildLimit));
233
- };
234
- const buildVQueryDSL = ({ vbiDSL, builder })=>buildVQuery(vbiDSL, builder);
235
- const buildVSeedDSL = async ({ vbiDSL, queryDSL })=>{
236
- const connectorId = vbiDSL.connectorId;
237
- const connector = await getConnector(connectorId);
238
- const schema = await connector.discoverSchema();
239
- const queryResult = await connector.query({
240
- queryDSL,
241
- schema,
242
- connectorId
243
- });
244
- return {
245
- chartType: vbiDSL.chartType,
246
- dataset: queryResult.dataset,
247
- theme: vbiDSL.theme,
248
- locale: vbiDSL.locale
249
- };
250
- };
251
- const defaultVBIBuilderAdapters = {
252
- buildVQuery: buildVQueryDSL,
253
- buildVSeed: buildVSeedDSL
254
- };
255
- const resolveVBIBuilderAdapters = (adapters)=>({
256
- buildVQuery: adapters?.buildVQuery ?? defaultVBIBuilderAdapters.buildVQuery,
257
- buildVSeed: adapters?.buildVSeed ?? defaultVBIBuilderAdapters.buildVSeed
258
- });
259
15
  class MeasureNodeBuilder {
260
16
  yMap;
261
17
  constructor(yMap){
@@ -267,6 +23,12 @@ class MeasureNodeBuilder {
267
23
  getField() {
268
24
  return this.yMap.get('field');
269
25
  }
26
+ getEncoding() {
27
+ return this.yMap.get('encoding');
28
+ }
29
+ getSort() {
30
+ return this.yMap.get('sort');
31
+ }
270
32
  setAlias(alias) {
271
33
  this.yMap.set('alias', alias);
272
34
  return this;
@@ -275,10 +37,29 @@ class MeasureNodeBuilder {
275
37
  this.yMap.set('encoding', encoding);
276
38
  return this;
277
39
  }
40
+ setSort(sort) {
41
+ this.yMap.set('sort', sort);
42
+ return this;
43
+ }
278
44
  setAggregate(aggregate) {
279
45
  this.yMap.set('aggregate', aggregate);
280
46
  return this;
281
47
  }
48
+ setFormat(format) {
49
+ this.yMap.set('format', format);
50
+ return this;
51
+ }
52
+ getFormat() {
53
+ return this.yMap.get('format');
54
+ }
55
+ clearFormat() {
56
+ this.yMap.delete('format');
57
+ return this;
58
+ }
59
+ clearSort() {
60
+ this.yMap.delete('sort');
61
+ return this;
62
+ }
282
63
  toJSON() {
283
64
  return this.yMap.toJSON();
284
65
  }
@@ -311,9 +92,369 @@ const normalizeMeasureNodeIds = (measures)=>{
311
92
  });
312
93
  };
313
94
  const locateMeasureIndexById = (measures, measureId)=>measures.toArray().findIndex((item)=>item.get('id') === measureId);
95
+ const MEASURE_ENCODING_SUPPORT = {
96
+ table: [
97
+ 'column'
98
+ ],
99
+ pivotTable: [
100
+ 'detail'
101
+ ],
102
+ column: [
103
+ 'yAxis',
104
+ 'detail',
105
+ 'color',
106
+ 'label',
107
+ 'tooltip'
108
+ ],
109
+ columnParallel: [
110
+ 'yAxis',
111
+ 'detail',
112
+ 'color',
113
+ 'label',
114
+ 'tooltip'
115
+ ],
116
+ columnPercent: [
117
+ 'yAxis',
118
+ 'detail',
119
+ 'color',
120
+ 'label',
121
+ 'tooltip'
122
+ ],
123
+ line: [
124
+ 'yAxis',
125
+ 'detail',
126
+ 'color',
127
+ 'label',
128
+ 'tooltip'
129
+ ],
130
+ area: [
131
+ 'yAxis',
132
+ 'detail',
133
+ 'color',
134
+ 'label',
135
+ 'tooltip'
136
+ ],
137
+ areaPercent: [
138
+ 'yAxis',
139
+ 'detail',
140
+ 'color',
141
+ 'label',
142
+ 'tooltip'
143
+ ],
144
+ bar: [
145
+ 'xAxis',
146
+ 'detail',
147
+ 'color',
148
+ 'label',
149
+ 'tooltip'
150
+ ],
151
+ barParallel: [
152
+ 'xAxis',
153
+ 'detail',
154
+ 'color',
155
+ 'label',
156
+ 'tooltip'
157
+ ],
158
+ barPercent: [
159
+ 'xAxis',
160
+ 'detail',
161
+ 'color',
162
+ 'label',
163
+ 'tooltip'
164
+ ],
165
+ dualAxis: [
166
+ 'primaryYAxis',
167
+ 'secondaryYAxis',
168
+ 'color',
169
+ 'label',
170
+ 'tooltip'
171
+ ],
172
+ scatter: [
173
+ 'xAxis',
174
+ 'yAxis',
175
+ 'size',
176
+ 'color',
177
+ 'label',
178
+ 'tooltip'
179
+ ],
180
+ pie: [
181
+ 'angle',
182
+ 'detail',
183
+ 'color',
184
+ 'label',
185
+ 'tooltip'
186
+ ],
187
+ donut: [
188
+ 'angle',
189
+ 'detail',
190
+ 'color',
191
+ 'label',
192
+ 'tooltip'
193
+ ],
194
+ rose: [
195
+ 'radius',
196
+ 'detail',
197
+ 'color',
198
+ 'label',
199
+ 'tooltip'
200
+ ],
201
+ roseParallel: [
202
+ 'radius',
203
+ 'detail',
204
+ 'color',
205
+ 'label',
206
+ 'tooltip'
207
+ ],
208
+ radar: [
209
+ 'radius',
210
+ 'detail',
211
+ 'color',
212
+ 'label',
213
+ 'tooltip'
214
+ ],
215
+ funnel: [
216
+ 'size',
217
+ 'detail',
218
+ 'color',
219
+ 'label',
220
+ 'tooltip'
221
+ ],
222
+ heatmap: [
223
+ 'color',
224
+ 'detail',
225
+ 'label',
226
+ 'tooltip'
227
+ ],
228
+ histogram: [
229
+ 'value',
230
+ 'x0',
231
+ 'x1',
232
+ 'yAxis',
233
+ 'detail',
234
+ 'color',
235
+ 'label',
236
+ 'tooltip'
237
+ ],
238
+ boxplot: [
239
+ 'value',
240
+ 'q1',
241
+ 'q3',
242
+ 'min',
243
+ 'max',
244
+ 'median',
245
+ 'outliers',
246
+ 'color',
247
+ 'label',
248
+ 'tooltip'
249
+ ],
250
+ treeMap: [
251
+ 'size',
252
+ 'detail',
253
+ 'color',
254
+ 'label',
255
+ 'tooltip'
256
+ ],
257
+ sunburst: [
258
+ 'size',
259
+ 'detail',
260
+ 'color',
261
+ 'label',
262
+ 'tooltip'
263
+ ],
264
+ circlePacking: [
265
+ 'size',
266
+ 'detail',
267
+ 'color',
268
+ 'label',
269
+ 'tooltip'
270
+ ],
271
+ raceBar: [
272
+ 'xAxis',
273
+ 'detail',
274
+ 'color',
275
+ 'label',
276
+ 'tooltip'
277
+ ],
278
+ raceColumn: [
279
+ 'yAxis',
280
+ 'detail',
281
+ 'color',
282
+ 'label',
283
+ 'tooltip'
284
+ ],
285
+ raceLine: [
286
+ 'yAxis',
287
+ 'detail',
288
+ 'color',
289
+ 'label',
290
+ 'tooltip'
291
+ ],
292
+ raceScatter: [
293
+ 'xAxis',
294
+ 'yAxis',
295
+ 'size',
296
+ 'color',
297
+ 'label',
298
+ 'tooltip'
299
+ ],
300
+ racePie: [
301
+ 'angle',
302
+ 'detail',
303
+ 'color',
304
+ 'label',
305
+ 'tooltip'
306
+ ],
307
+ raceDonut: [
308
+ 'angle',
309
+ 'detail',
310
+ 'color',
311
+ 'label',
312
+ 'tooltip'
313
+ ]
314
+ };
315
+ const repeatEncoding = (first, rest = first)=>(index)=>0 === index ? first : rest;
316
+ const STRATEGY_BY_CHART_TYPE = {
317
+ [ChartTypeEnum.Table]: {
318
+ supported: MEASURE_ENCODING_SUPPORT.table,
319
+ recommend: repeatEncoding('column')
320
+ },
321
+ [ChartTypeEnum.PivotTable]: {
322
+ supported: MEASURE_ENCODING_SUPPORT.pivotTable,
323
+ recommend: repeatEncoding('detail')
324
+ },
325
+ [ChartTypeEnum.Column]: {
326
+ supported: MEASURE_ENCODING_SUPPORT.column,
327
+ recommend: repeatEncoding('yAxis')
328
+ },
329
+ [ChartTypeEnum.ColumnParallel]: {
330
+ supported: MEASURE_ENCODING_SUPPORT.columnParallel,
331
+ recommend: repeatEncoding('yAxis')
332
+ },
333
+ [ChartTypeEnum.ColumnPercent]: {
334
+ supported: MEASURE_ENCODING_SUPPORT.columnPercent,
335
+ recommend: repeatEncoding('yAxis')
336
+ },
337
+ [ChartTypeEnum.Line]: {
338
+ supported: MEASURE_ENCODING_SUPPORT.line,
339
+ recommend: repeatEncoding('yAxis')
340
+ },
341
+ [ChartTypeEnum.Area]: {
342
+ supported: MEASURE_ENCODING_SUPPORT.area,
343
+ recommend: repeatEncoding('yAxis')
344
+ },
345
+ [ChartTypeEnum.AreaPercent]: {
346
+ supported: MEASURE_ENCODING_SUPPORT.areaPercent,
347
+ recommend: repeatEncoding('yAxis')
348
+ },
349
+ [ChartTypeEnum.Bar]: {
350
+ supported: MEASURE_ENCODING_SUPPORT.bar,
351
+ recommend: repeatEncoding('xAxis')
352
+ },
353
+ [ChartTypeEnum.BarParallel]: {
354
+ supported: MEASURE_ENCODING_SUPPORT.barParallel,
355
+ recommend: repeatEncoding('xAxis')
356
+ },
357
+ [ChartTypeEnum.BarPercent]: {
358
+ supported: MEASURE_ENCODING_SUPPORT.barPercent,
359
+ recommend: repeatEncoding('xAxis')
360
+ },
361
+ [ChartTypeEnum.DualAxis]: {
362
+ supported: MEASURE_ENCODING_SUPPORT.dualAxis,
363
+ recommend: (index)=>0 === index ? 'primaryYAxis' : 'secondaryYAxis'
364
+ },
365
+ [ChartTypeEnum.Scatter]: {
366
+ supported: MEASURE_ENCODING_SUPPORT.scatter,
367
+ recommend: (index)=>0 === index ? 'xAxis' : 'yAxis'
368
+ },
369
+ [ChartTypeEnum.Pie]: {
370
+ supported: MEASURE_ENCODING_SUPPORT.pie,
371
+ recommend: repeatEncoding('angle')
372
+ },
373
+ [ChartTypeEnum.Donut]: {
374
+ supported: MEASURE_ENCODING_SUPPORT.donut,
375
+ recommend: repeatEncoding('angle')
376
+ },
377
+ [ChartTypeEnum.Rose]: {
378
+ supported: MEASURE_ENCODING_SUPPORT.rose,
379
+ recommend: repeatEncoding('radius')
380
+ },
381
+ [ChartTypeEnum.RoseParallel]: {
382
+ supported: MEASURE_ENCODING_SUPPORT.roseParallel,
383
+ recommend: repeatEncoding('radius')
384
+ },
385
+ [ChartTypeEnum.Radar]: {
386
+ supported: MEASURE_ENCODING_SUPPORT.radar,
387
+ recommend: repeatEncoding('radius')
388
+ },
389
+ [ChartTypeEnum.Funnel]: {
390
+ supported: MEASURE_ENCODING_SUPPORT.funnel,
391
+ recommend: repeatEncoding('size')
392
+ },
393
+ [ChartTypeEnum.Heatmap]: {
394
+ supported: MEASURE_ENCODING_SUPPORT.heatmap,
395
+ recommend: repeatEncoding('color')
396
+ },
397
+ [ChartTypeEnum.Histogram]: {
398
+ supported: MEASURE_ENCODING_SUPPORT.histogram,
399
+ recommend: repeatEncoding('value')
400
+ },
401
+ [ChartTypeEnum.Boxplot]: {
402
+ supported: MEASURE_ENCODING_SUPPORT.boxplot,
403
+ recommend: repeatEncoding('value')
404
+ },
405
+ [ChartTypeEnum.TreeMap]: {
406
+ supported: MEASURE_ENCODING_SUPPORT.treeMap,
407
+ recommend: repeatEncoding('size')
408
+ },
409
+ [ChartTypeEnum.Sunburst]: {
410
+ supported: MEASURE_ENCODING_SUPPORT.sunburst,
411
+ recommend: repeatEncoding('size')
412
+ },
413
+ [ChartTypeEnum.CirclePacking]: {
414
+ supported: MEASURE_ENCODING_SUPPORT.circlePacking,
415
+ recommend: repeatEncoding('size')
416
+ },
417
+ [ChartTypeEnum.RaceBar]: {
418
+ supported: MEASURE_ENCODING_SUPPORT.raceBar,
419
+ recommend: repeatEncoding('xAxis')
420
+ },
421
+ [ChartTypeEnum.RaceColumn]: {
422
+ supported: MEASURE_ENCODING_SUPPORT.raceColumn,
423
+ recommend: repeatEncoding('yAxis')
424
+ },
425
+ [ChartTypeEnum.RaceLine]: {
426
+ supported: MEASURE_ENCODING_SUPPORT.raceLine,
427
+ recommend: repeatEncoding('yAxis')
428
+ },
429
+ [ChartTypeEnum.RaceScatter]: {
430
+ supported: MEASURE_ENCODING_SUPPORT.raceScatter,
431
+ recommend: (index)=>0 === index ? 'xAxis' : 'yAxis'
432
+ },
433
+ [ChartTypeEnum.RacePie]: {
434
+ supported: MEASURE_ENCODING_SUPPORT.racePie,
435
+ recommend: repeatEncoding('angle')
436
+ },
437
+ [ChartTypeEnum.RaceDonut]: {
438
+ supported: MEASURE_ENCODING_SUPPORT.raceDonut,
439
+ recommend: repeatEncoding('angle')
440
+ }
441
+ };
442
+ const DEFAULT_STRATEGY = STRATEGY_BY_CHART_TYPE[ChartTypeEnum.Table];
443
+ const getSupportedMeasureEncodingsForChartType = (chartType)=>[
444
+ ...(STRATEGY_BY_CHART_TYPE[chartType] || DEFAULT_STRATEGY).supported
445
+ ];
446
+ const getRecommendedMeasureEncodingsForChartType = (chartType, measureCount)=>{
447
+ const strategy = STRATEGY_BY_CHART_TYPE[chartType] || DEFAULT_STRATEGY;
448
+ if (measureCount <= 0) return [];
449
+ return Array.from({
450
+ length: measureCount
451
+ }, (_, index)=>strategy.recommend(index));
452
+ };
314
453
  class MeasuresBuilder {
454
+ doc;
315
455
  dsl;
316
456
  constructor(doc, dsl){
457
+ this.doc = doc;
317
458
  this.dsl = dsl;
318
459
  doc.transact(()=>{
319
460
  const measures = getOrCreateMeasures(this.dsl);
@@ -321,38 +462,47 @@ class MeasuresBuilder {
321
462
  });
322
463
  }
323
464
  add(field, callback) {
465
+ const measures = getOrCreateMeasures(this.dsl);
466
+ const chartType = this.dsl.get('chartType') || 'table';
467
+ const [encoding] = getRecommendedMeasureEncodingsForChartType(chartType, measures.length + 1).slice(-1);
324
468
  const measure = {
325
469
  id: id_id.uuid(),
326
470
  alias: field,
327
471
  field,
328
- encoding: 'yAxis',
472
+ encoding,
329
473
  aggregate: {
330
474
  func: 'sum'
331
475
  }
332
476
  };
333
477
  const yMap = new external_yjs_Map();
334
- for (const [key, value] of Object.entries(measure))yMap.set(key, value);
335
- const measures = getOrCreateMeasures(this.dsl);
336
- measures.push([
337
- yMap
338
- ]);
339
- const node = new MeasureNodeBuilder(yMap);
340
- callback(node);
478
+ this.doc.transact(()=>{
479
+ for (const [key, value] of Object.entries(measure))yMap.set(key, value);
480
+ const measures = getOrCreateMeasures(this.dsl);
481
+ measures.push([
482
+ yMap
483
+ ]);
484
+ const node = new MeasureNodeBuilder(yMap);
485
+ callback(node);
486
+ });
341
487
  return this;
342
488
  }
343
489
  remove(id) {
344
- const measures = getOrCreateMeasures(this.dsl);
345
- const index = locateMeasureIndexById(measures, id);
346
- if (-1 !== index) measures.delete(index, 1);
490
+ this.doc.transact(()=>{
491
+ const measures = getOrCreateMeasures(this.dsl);
492
+ const index = locateMeasureIndexById(measures, id);
493
+ if (-1 !== index) measures.delete(index, 1);
494
+ });
347
495
  return this;
348
496
  }
349
497
  update(id, callback) {
350
- const measures = getOrCreateMeasures(this.dsl);
351
- const index = locateMeasureIndexById(measures, id);
352
- if (-1 === index) throw new Error(`Measure with id "${id}" not found`);
353
- const measureYMap = measures.get(index);
354
- const node = new MeasureNodeBuilder(measureYMap);
355
- callback(node);
498
+ this.doc.transact(()=>{
499
+ const measures = getOrCreateMeasures(this.dsl);
500
+ const index = locateMeasureIndexById(measures, id);
501
+ if (-1 === index) throw new Error(`Measure with id "${id}" not found`);
502
+ const measureYMap = measures.get(index);
503
+ const node = new MeasureNodeBuilder(measureYMap);
504
+ callback(node);
505
+ });
356
506
  return this;
357
507
  }
358
508
  find(predicate) {
@@ -373,9 +523,9 @@ class MeasuresBuilder {
373
523
  }
374
524
  observe(callback) {
375
525
  const measures = getOrCreateMeasures(this.dsl);
376
- measures.observe(callback);
526
+ measures.observeDeep(callback);
377
527
  return ()=>{
378
- measures.unobserve(callback);
528
+ measures.unobserveDeep(callback);
379
529
  };
380
530
  }
381
531
  static isMeasureNode(node) {
@@ -396,10 +546,36 @@ class DimensionNodeBuilder {
396
546
  getField() {
397
547
  return this.yMap.get('field');
398
548
  }
399
- setAlias(alias) {
549
+ getEncoding() {
550
+ return this.yMap.get('encoding');
551
+ }
552
+ getSort() {
553
+ return this.yMap.get('sort');
554
+ }
555
+ setAlias(alias) {
400
556
  this.yMap.set('alias', alias);
401
557
  return this;
402
558
  }
559
+ setEncoding(encoding) {
560
+ this.yMap.set('encoding', encoding);
561
+ return this;
562
+ }
563
+ setSort(sort) {
564
+ this.yMap.set('sort', sort);
565
+ return this;
566
+ }
567
+ setAggregate(aggregate) {
568
+ this.yMap.set('aggregate', aggregate);
569
+ return this;
570
+ }
571
+ clearAggregate() {
572
+ this.yMap.delete('aggregate');
573
+ return this;
574
+ }
575
+ clearSort() {
576
+ this.yMap.delete('sort');
577
+ return this;
578
+ }
403
579
  toJSON() {
404
580
  return this.yMap.toJSON();
405
581
  }
@@ -417,9 +593,423 @@ const normalizeDimensionNodeIds = (dimensions)=>{
417
593
  });
418
594
  };
419
595
  const locateDimensionIndexById = (dimensions, dimensionId)=>dimensions.toArray().findIndex((item)=>item.get('id') === dimensionId);
596
+ const DIMENSION_ENCODING_SUPPORT = {
597
+ table: [
598
+ 'column'
599
+ ],
600
+ pivotTable: [
601
+ 'row',
602
+ 'column'
603
+ ],
604
+ column: [
605
+ 'xAxis',
606
+ 'color',
607
+ 'detail',
608
+ 'tooltip',
609
+ 'label',
610
+ 'row',
611
+ 'column'
612
+ ],
613
+ columnParallel: [
614
+ 'xAxis',
615
+ 'color',
616
+ 'detail',
617
+ 'tooltip',
618
+ 'label',
619
+ 'row',
620
+ 'column'
621
+ ],
622
+ columnPercent: [
623
+ 'xAxis',
624
+ 'color',
625
+ 'detail',
626
+ 'tooltip',
627
+ 'label',
628
+ 'row',
629
+ 'column'
630
+ ],
631
+ line: [
632
+ 'xAxis',
633
+ 'color',
634
+ 'detail',
635
+ 'tooltip',
636
+ 'label',
637
+ 'row',
638
+ 'column'
639
+ ],
640
+ area: [
641
+ 'xAxis',
642
+ 'color',
643
+ 'detail',
644
+ 'tooltip',
645
+ 'label',
646
+ 'row',
647
+ 'column'
648
+ ],
649
+ areaPercent: [
650
+ 'xAxis',
651
+ 'color',
652
+ 'detail',
653
+ 'tooltip',
654
+ 'label',
655
+ 'row',
656
+ 'column'
657
+ ],
658
+ dualAxis: [
659
+ 'xAxis',
660
+ 'color',
661
+ 'detail',
662
+ 'tooltip',
663
+ 'label',
664
+ 'row',
665
+ 'column'
666
+ ],
667
+ bar: [
668
+ 'yAxis',
669
+ 'color',
670
+ 'detail',
671
+ 'tooltip',
672
+ 'label',
673
+ 'row',
674
+ 'column'
675
+ ],
676
+ barParallel: [
677
+ 'yAxis',
678
+ 'color',
679
+ 'detail',
680
+ 'tooltip',
681
+ 'label',
682
+ 'row',
683
+ 'column'
684
+ ],
685
+ barPercent: [
686
+ 'yAxis',
687
+ 'color',
688
+ 'detail',
689
+ 'tooltip',
690
+ 'label',
691
+ 'row',
692
+ 'column'
693
+ ],
694
+ pie: [
695
+ 'color',
696
+ 'detail',
697
+ 'tooltip',
698
+ 'label',
699
+ 'row',
700
+ 'column'
701
+ ],
702
+ donut: [
703
+ 'color',
704
+ 'detail',
705
+ 'tooltip',
706
+ 'label',
707
+ 'row',
708
+ 'column'
709
+ ],
710
+ funnel: [
711
+ 'color',
712
+ 'detail',
713
+ 'tooltip',
714
+ 'label',
715
+ 'row',
716
+ 'column'
717
+ ],
718
+ scatter: [
719
+ 'color',
720
+ 'detail',
721
+ 'tooltip',
722
+ 'label',
723
+ 'row',
724
+ 'column'
725
+ ],
726
+ rose: [
727
+ 'angle',
728
+ 'color',
729
+ 'detail',
730
+ 'tooltip',
731
+ 'label',
732
+ 'row',
733
+ 'column'
734
+ ],
735
+ roseParallel: [
736
+ 'angle',
737
+ 'color',
738
+ 'detail',
739
+ 'tooltip',
740
+ 'label',
741
+ 'row',
742
+ 'column'
743
+ ],
744
+ radar: [
745
+ 'angle',
746
+ 'color',
747
+ 'detail',
748
+ 'tooltip',
749
+ 'label',
750
+ 'row',
751
+ 'column'
752
+ ],
753
+ heatmap: [
754
+ 'xAxis',
755
+ 'yAxis',
756
+ 'color',
757
+ 'detail',
758
+ 'tooltip',
759
+ 'label',
760
+ 'row',
761
+ 'column'
762
+ ],
763
+ boxplot: [
764
+ 'xAxis',
765
+ 'color',
766
+ 'tooltip',
767
+ 'label',
768
+ 'row',
769
+ 'column'
770
+ ],
771
+ histogram: [
772
+ 'color',
773
+ 'detail',
774
+ 'tooltip',
775
+ 'label',
776
+ 'row',
777
+ 'column'
778
+ ],
779
+ treeMap: [
780
+ 'hierarchy',
781
+ 'detail',
782
+ 'tooltip',
783
+ 'label',
784
+ 'row',
785
+ 'column'
786
+ ],
787
+ sunburst: [
788
+ 'hierarchy',
789
+ 'detail',
790
+ 'tooltip',
791
+ 'label',
792
+ 'row',
793
+ 'column'
794
+ ],
795
+ circlePacking: [
796
+ 'hierarchy',
797
+ 'detail',
798
+ 'tooltip',
799
+ 'label',
800
+ 'row',
801
+ 'column'
802
+ ],
803
+ raceBar: [
804
+ 'player',
805
+ 'yAxis',
806
+ 'color',
807
+ 'detail',
808
+ 'tooltip',
809
+ 'label',
810
+ 'row',
811
+ 'column'
812
+ ],
813
+ raceColumn: [
814
+ 'player',
815
+ 'xAxis',
816
+ 'color',
817
+ 'detail',
818
+ 'tooltip',
819
+ 'label',
820
+ 'row',
821
+ 'column'
822
+ ],
823
+ raceLine: [
824
+ 'player',
825
+ 'xAxis',
826
+ 'color',
827
+ 'detail',
828
+ 'tooltip',
829
+ 'label',
830
+ 'row',
831
+ 'column'
832
+ ],
833
+ raceScatter: [
834
+ 'player',
835
+ 'color',
836
+ 'detail',
837
+ 'tooltip',
838
+ 'label',
839
+ 'row',
840
+ 'column'
841
+ ],
842
+ racePie: [
843
+ 'player',
844
+ 'color',
845
+ 'detail',
846
+ 'tooltip',
847
+ 'label',
848
+ 'row',
849
+ 'column'
850
+ ],
851
+ raceDonut: [
852
+ 'player',
853
+ 'color',
854
+ 'detail',
855
+ 'tooltip',
856
+ 'label',
857
+ 'row',
858
+ 'column'
859
+ ]
860
+ };
861
+ const dimension_encoding_repeatEncoding = (first, rest = first)=>(index)=>0 === index ? first : rest;
862
+ const alternateEncoding = (a, b)=>(index)=>index % 2 === 0 ? a : b;
863
+ const dimension_encoding_STRATEGY_BY_CHART_TYPE = {
864
+ [ChartTypeEnum.Table]: {
865
+ supported: DIMENSION_ENCODING_SUPPORT.table,
866
+ recommend: dimension_encoding_repeatEncoding('column')
867
+ },
868
+ [ChartTypeEnum.PivotTable]: {
869
+ supported: DIMENSION_ENCODING_SUPPORT.pivotTable,
870
+ recommend: alternateEncoding('column', 'row')
871
+ },
872
+ [ChartTypeEnum.Column]: {
873
+ supported: DIMENSION_ENCODING_SUPPORT.column,
874
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
875
+ },
876
+ [ChartTypeEnum.ColumnParallel]: {
877
+ supported: DIMENSION_ENCODING_SUPPORT.columnParallel,
878
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
879
+ },
880
+ [ChartTypeEnum.ColumnPercent]: {
881
+ supported: DIMENSION_ENCODING_SUPPORT.columnPercent,
882
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
883
+ },
884
+ [ChartTypeEnum.Line]: {
885
+ supported: DIMENSION_ENCODING_SUPPORT.line,
886
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
887
+ },
888
+ [ChartTypeEnum.Area]: {
889
+ supported: DIMENSION_ENCODING_SUPPORT.area,
890
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
891
+ },
892
+ [ChartTypeEnum.AreaPercent]: {
893
+ supported: DIMENSION_ENCODING_SUPPORT.areaPercent,
894
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
895
+ },
896
+ [ChartTypeEnum.DualAxis]: {
897
+ supported: DIMENSION_ENCODING_SUPPORT.dualAxis,
898
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
899
+ },
900
+ [ChartTypeEnum.Bar]: {
901
+ supported: DIMENSION_ENCODING_SUPPORT.bar,
902
+ recommend: dimension_encoding_repeatEncoding('yAxis', 'color')
903
+ },
904
+ [ChartTypeEnum.BarParallel]: {
905
+ supported: DIMENSION_ENCODING_SUPPORT.barParallel,
906
+ recommend: dimension_encoding_repeatEncoding('yAxis', 'color')
907
+ },
908
+ [ChartTypeEnum.BarPercent]: {
909
+ supported: DIMENSION_ENCODING_SUPPORT.barPercent,
910
+ recommend: dimension_encoding_repeatEncoding('yAxis', 'color')
911
+ },
912
+ [ChartTypeEnum.Pie]: {
913
+ supported: DIMENSION_ENCODING_SUPPORT.pie,
914
+ recommend: dimension_encoding_repeatEncoding('color')
915
+ },
916
+ [ChartTypeEnum.Donut]: {
917
+ supported: DIMENSION_ENCODING_SUPPORT.donut,
918
+ recommend: dimension_encoding_repeatEncoding('color')
919
+ },
920
+ [ChartTypeEnum.Funnel]: {
921
+ supported: DIMENSION_ENCODING_SUPPORT.funnel,
922
+ recommend: dimension_encoding_repeatEncoding('color')
923
+ },
924
+ [ChartTypeEnum.Scatter]: {
925
+ supported: DIMENSION_ENCODING_SUPPORT.scatter,
926
+ recommend: dimension_encoding_repeatEncoding('color')
927
+ },
928
+ [ChartTypeEnum.Rose]: {
929
+ supported: DIMENSION_ENCODING_SUPPORT.rose,
930
+ recommend: dimension_encoding_repeatEncoding('angle', 'color')
931
+ },
932
+ [ChartTypeEnum.RoseParallel]: {
933
+ supported: DIMENSION_ENCODING_SUPPORT.roseParallel,
934
+ recommend: dimension_encoding_repeatEncoding('angle', 'color')
935
+ },
936
+ [ChartTypeEnum.Radar]: {
937
+ supported: DIMENSION_ENCODING_SUPPORT.radar,
938
+ recommend: dimension_encoding_repeatEncoding('angle', 'color')
939
+ },
940
+ [ChartTypeEnum.Heatmap]: {
941
+ supported: DIMENSION_ENCODING_SUPPORT.heatmap,
942
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'yAxis')
943
+ },
944
+ [ChartTypeEnum.Boxplot]: {
945
+ supported: DIMENSION_ENCODING_SUPPORT.boxplot,
946
+ recommend: dimension_encoding_repeatEncoding('xAxis', 'color')
947
+ },
948
+ [ChartTypeEnum.Histogram]: {
949
+ supported: DIMENSION_ENCODING_SUPPORT.histogram,
950
+ recommend: dimension_encoding_repeatEncoding('color')
951
+ },
952
+ [ChartTypeEnum.TreeMap]: {
953
+ supported: DIMENSION_ENCODING_SUPPORT.treeMap,
954
+ recommend: dimension_encoding_repeatEncoding('hierarchy')
955
+ },
956
+ [ChartTypeEnum.Sunburst]: {
957
+ supported: DIMENSION_ENCODING_SUPPORT.sunburst,
958
+ recommend: dimension_encoding_repeatEncoding('hierarchy')
959
+ },
960
+ [ChartTypeEnum.CirclePacking]: {
961
+ supported: DIMENSION_ENCODING_SUPPORT.circlePacking,
962
+ recommend: dimension_encoding_repeatEncoding('hierarchy')
963
+ },
964
+ [ChartTypeEnum.RaceBar]: {
965
+ supported: DIMENSION_ENCODING_SUPPORT.raceBar,
966
+ recommend: (index)=>{
967
+ if (0 === index) return 'player';
968
+ if (1 === index) return 'yAxis';
969
+ return 'color';
970
+ }
971
+ },
972
+ [ChartTypeEnum.RaceColumn]: {
973
+ supported: DIMENSION_ENCODING_SUPPORT.raceColumn,
974
+ recommend: (index)=>{
975
+ if (0 === index) return 'player';
976
+ if (1 === index) return 'xAxis';
977
+ return 'color';
978
+ }
979
+ },
980
+ [ChartTypeEnum.RaceLine]: {
981
+ supported: DIMENSION_ENCODING_SUPPORT.raceLine,
982
+ recommend: (index)=>0 === index ? 'player' : 'color'
983
+ },
984
+ [ChartTypeEnum.RaceScatter]: {
985
+ supported: DIMENSION_ENCODING_SUPPORT.raceScatter,
986
+ recommend: (index)=>0 === index ? 'player' : 'color'
987
+ },
988
+ [ChartTypeEnum.RacePie]: {
989
+ supported: DIMENSION_ENCODING_SUPPORT.racePie,
990
+ recommend: (index)=>0 === index ? 'player' : 'color'
991
+ },
992
+ [ChartTypeEnum.RaceDonut]: {
993
+ supported: DIMENSION_ENCODING_SUPPORT.raceDonut,
994
+ recommend: (index)=>0 === index ? 'player' : 'color'
995
+ }
996
+ };
997
+ const dimension_encoding_DEFAULT_STRATEGY = dimension_encoding_STRATEGY_BY_CHART_TYPE[ChartTypeEnum.Table];
998
+ const getSupportedDimensionEncodingsForChartType = (chartType)=>[
999
+ ...(dimension_encoding_STRATEGY_BY_CHART_TYPE[chartType] || dimension_encoding_DEFAULT_STRATEGY).supported
1000
+ ];
1001
+ const getRecommendedDimensionEncodingsForChartType = (chartType, dimensionCount)=>{
1002
+ const strategy = dimension_encoding_STRATEGY_BY_CHART_TYPE[chartType] || dimension_encoding_DEFAULT_STRATEGY;
1003
+ if (dimensionCount <= 0) return [];
1004
+ return Array.from({
1005
+ length: dimensionCount
1006
+ }, (_, index)=>strategy.recommend(index));
1007
+ };
420
1008
  class DimensionsBuilder {
1009
+ doc;
421
1010
  dsl;
422
1011
  constructor(doc, dsl){
1012
+ this.doc = doc;
423
1013
  this.dsl = dsl;
424
1014
  doc.transact(()=>{
425
1015
  const dimensions = getOrCreateDimensions(this.dsl);
@@ -427,34 +1017,44 @@ class DimensionsBuilder {
427
1017
  });
428
1018
  }
429
1019
  add(field, callback) {
1020
+ const dimensions = getOrCreateDimensions(this.dsl);
1021
+ const chartType = this.dsl.get('chartType') || 'table';
1022
+ const [encoding] = getRecommendedDimensionEncodingsForChartType(chartType, dimensions.length + 1).slice(-1);
430
1023
  const dimension = {
431
1024
  id: id_id.uuid(),
432
1025
  alias: field,
433
- field
1026
+ field,
1027
+ encoding
434
1028
  };
435
1029
  const yMap = new external_yjs_Map();
436
- for (const [key, value] of Object.entries(dimension))yMap.set(key, value);
437
- const dimensions = getOrCreateDimensions(this.dsl);
438
- dimensions.push([
439
- yMap
440
- ]);
441
- const node = new DimensionNodeBuilder(yMap);
442
- callback(node);
1030
+ this.doc.transact(()=>{
1031
+ for (const [key, value] of Object.entries(dimension))yMap.set(key, value);
1032
+ const dimensions = getOrCreateDimensions(this.dsl);
1033
+ dimensions.push([
1034
+ yMap
1035
+ ]);
1036
+ const node = new DimensionNodeBuilder(yMap);
1037
+ callback(node);
1038
+ });
443
1039
  return this;
444
1040
  }
445
1041
  remove(id) {
446
- const dimensions = getOrCreateDimensions(this.dsl);
447
- const index = locateDimensionIndexById(dimensions, id);
448
- if (-1 !== index) dimensions.delete(index, 1);
1042
+ this.doc.transact(()=>{
1043
+ const dimensions = getOrCreateDimensions(this.dsl);
1044
+ const index = locateDimensionIndexById(dimensions, id);
1045
+ if (-1 !== index) dimensions.delete(index, 1);
1046
+ });
449
1047
  return this;
450
1048
  }
451
1049
  update(id, callback) {
452
- const dimensions = getOrCreateDimensions(this.dsl);
453
- const index = locateDimensionIndexById(dimensions, id);
454
- if (-1 === index) throw new Error(`Dimension with id "${id}" not found`);
455
- const dimensionYMap = dimensions.get(index);
456
- const node = new DimensionNodeBuilder(dimensionYMap);
457
- callback(node);
1050
+ this.doc.transact(()=>{
1051
+ const dimensions = getOrCreateDimensions(this.dsl);
1052
+ const index = locateDimensionIndexById(dimensions, id);
1053
+ if (-1 === index) throw new Error(`Dimension with id "${id}" not found`);
1054
+ const dimensionYMap = dimensions.get(index);
1055
+ const node = new DimensionNodeBuilder(dimensionYMap);
1056
+ callback(node);
1057
+ });
458
1058
  return this;
459
1059
  }
460
1060
  find(predicate) {
@@ -475,9 +1075,9 @@ class DimensionsBuilder {
475
1075
  }
476
1076
  observe(callback) {
477
1077
  const dimensions = getOrCreateDimensions(this.dsl);
478
- dimensions.observe(callback);
1078
+ dimensions.observeDeep(callback);
479
1079
  return ()=>{
480
- dimensions.unobserve(callback);
1080
+ dimensions.unobserveDeep(callback);
481
1081
  };
482
1082
  }
483
1083
  static isDimensionNode(node) {
@@ -487,9 +1087,27 @@ class DimensionsBuilder {
487
1087
  return 'children' in node;
488
1088
  }
489
1089
  }
1090
+ const reapplyDimensionEncodings = (dsl, chartType)=>{
1091
+ const dimensions = getOrCreateDimensions(dsl);
1092
+ const nodes = dimensions.toArray().filter((item)=>item instanceof external_yjs_Map && 'string' == typeof item.get('field'));
1093
+ const encodings = getRecommendedDimensionEncodingsForChartType(chartType, nodes.length);
1094
+ nodes.forEach((node, index)=>{
1095
+ node.set('encoding', encodings[index]);
1096
+ });
1097
+ };
1098
+ const reapplyMeasureEncodings = (dsl, chartType)=>{
1099
+ const measures = getOrCreateMeasures(dsl);
1100
+ const nodes = measures.toArray().filter((item)=>item instanceof external_yjs_Map && 'string' == typeof item.get('field'));
1101
+ const encodings = getRecommendedMeasureEncodingsForChartType(chartType, nodes.length);
1102
+ nodes.forEach((node, index)=>{
1103
+ node.set('encoding', encodings[index]);
1104
+ });
1105
+ };
490
1106
  class ChartTypeBuilder {
1107
+ doc;
491
1108
  dsl;
492
- constructor(_doc, dsl){
1109
+ constructor(doc, dsl){
1110
+ this.doc = doc;
493
1111
  this.dsl = dsl;
494
1112
  }
495
1113
  observe(callback) {
@@ -502,11 +1120,29 @@ class ChartTypeBuilder {
502
1120
  };
503
1121
  }
504
1122
  changeChartType(chartType) {
505
- this.dsl.set('chartType', chartType);
1123
+ this.doc.transact(()=>{
1124
+ this.dsl.set('chartType', chartType);
1125
+ reapplyDimensionEncodings(this.dsl, chartType);
1126
+ reapplyMeasureEncodings(this.dsl, chartType);
1127
+ });
506
1128
  }
507
1129
  getChartType() {
508
1130
  return this.dsl.get('chartType') || 'table';
509
1131
  }
1132
+ getSupportedDimensionEncodings() {
1133
+ return getSupportedDimensionEncodingsForChartType(this.getChartType());
1134
+ }
1135
+ getRecommendedDimensionEncodings(dimensionCount) {
1136
+ const resolvedCount = dimensionCount ?? this.dsl.get('dimensions')?.length;
1137
+ return getRecommendedDimensionEncodingsForChartType(this.getChartType(), resolvedCount);
1138
+ }
1139
+ getSupportedMeasureEncodings() {
1140
+ return getSupportedMeasureEncodingsForChartType(this.getChartType());
1141
+ }
1142
+ getRecommendedMeasureEncodings(measureCount) {
1143
+ const resolvedCount = measureCount ?? this.dsl.get('measures')?.length;
1144
+ return getRecommendedMeasureEncodingsForChartType(this.getChartType(), resolvedCount);
1145
+ }
510
1146
  toJSON() {
511
1147
  return this.dsl.get('chartType') || 'table';
512
1148
  }
@@ -514,25 +1150,35 @@ class ChartTypeBuilder {
514
1150
  return [
515
1151
  ChartTypeEnum.Table,
516
1152
  ChartTypeEnum.PivotTable,
517
- ChartTypeEnum.Line,
518
1153
  ChartTypeEnum.Column,
519
- ChartTypeEnum.ColumnPercent,
520
1154
  ChartTypeEnum.ColumnParallel,
521
- ChartTypeEnum.BarPercent,
1155
+ ChartTypeEnum.ColumnPercent,
1156
+ ChartTypeEnum.Bar,
522
1157
  ChartTypeEnum.BarParallel,
1158
+ ChartTypeEnum.BarPercent,
1159
+ ChartTypeEnum.Line,
523
1160
  ChartTypeEnum.Area,
524
1161
  ChartTypeEnum.AreaPercent,
525
1162
  ChartTypeEnum.DualAxis,
526
1163
  ChartTypeEnum.Scatter,
527
- ChartTypeEnum.Rose,
528
- ChartTypeEnum.RoseParallel,
529
1164
  ChartTypeEnum.Pie,
530
1165
  ChartTypeEnum.Donut,
1166
+ ChartTypeEnum.Rose,
1167
+ ChartTypeEnum.RoseParallel,
531
1168
  ChartTypeEnum.Radar,
532
1169
  ChartTypeEnum.Funnel,
533
1170
  ChartTypeEnum.Heatmap,
534
1171
  ChartTypeEnum.Boxplot,
535
- ChartTypeEnum.Histogram
1172
+ ChartTypeEnum.Histogram,
1173
+ ChartTypeEnum.TreeMap,
1174
+ ChartTypeEnum.Sunburst,
1175
+ ChartTypeEnum.CirclePacking,
1176
+ ChartTypeEnum.RaceBar,
1177
+ ChartTypeEnum.RaceColumn,
1178
+ ChartTypeEnum.RaceLine,
1179
+ ChartTypeEnum.RaceScatter,
1180
+ ChartTypeEnum.RacePie,
1181
+ ChartTypeEnum.RaceDonut
536
1182
  ];
537
1183
  }
538
1184
  }
@@ -543,7 +1189,7 @@ function createWhereGroup(op = 'and', groupId = 'root') {
543
1189
  yMap.set('conditions', new external_yjs_Array());
544
1190
  return yMap;
545
1191
  }
546
- function where_utils_isWhereGroup(yMap) {
1192
+ function isWhereGroup(yMap) {
547
1193
  return void 0 !== yMap.get('op') && void 0 !== yMap.get('conditions');
548
1194
  }
549
1195
  function findEntry(collection, entryId) {
@@ -555,7 +1201,7 @@ function findEntry(collection, entryId) {
555
1201
  index,
556
1202
  item
557
1203
  };
558
- if (where_utils_isWhereGroup(item)) {
1204
+ if (isWhereGroup(item)) {
559
1205
  const nestedCollection = item.get('conditions');
560
1206
  const nestedMatch = findEntry(nestedCollection, entryId);
561
1207
  if (nestedMatch) return nestedMatch;
@@ -588,6 +1234,15 @@ class WhereFilterNodeBuilder {
588
1234
  this.yMap.set('value', value);
589
1235
  return this;
590
1236
  }
1237
+ setDate(predicate) {
1238
+ this.yMap.set('op', 'date');
1239
+ this.yMap.set('value', predicate);
1240
+ return this;
1241
+ }
1242
+ getDate() {
1243
+ if ('date' !== this.yMap.get('op')) return;
1244
+ return this.yMap.get('value');
1245
+ }
591
1246
  toJSON() {
592
1247
  return this.yMap.toJSON();
593
1248
  }
@@ -748,7 +1403,7 @@ class WhereFilterBuilder {
748
1403
  };
749
1404
  }
750
1405
  static isGroup(yMap) {
751
- return where_utils_isWhereGroup(yMap);
1406
+ return isWhereGroup(yMap);
752
1407
  }
753
1408
  static isNode(yMap) {
754
1409
  return void 0 !== yMap.get('field');
@@ -761,7 +1416,7 @@ function createHavingGroup(op = 'and', groupId = 'root') {
761
1416
  yMap.set('conditions', new external_yjs_Array());
762
1417
  return yMap;
763
1418
  }
764
- function having_utils_isHavingGroup(yMap) {
1419
+ function isHavingGroup(yMap) {
765
1420
  return void 0 !== yMap.get('op') && void 0 !== yMap.get('conditions');
766
1421
  }
767
1422
  function having_utils_findEntry(collection, entryId) {
@@ -773,7 +1428,7 @@ function having_utils_findEntry(collection, entryId) {
773
1428
  index,
774
1429
  item
775
1430
  };
776
- if (having_utils_isHavingGroup(item)) {
1431
+ if (isHavingGroup(item)) {
777
1432
  const nestedCollection = item.get('conditions');
778
1433
  const nestedMatch = having_utils_findEntry(nestedCollection, entryId);
779
1434
  if (nestedMatch) return nestedMatch;
@@ -975,7 +1630,7 @@ class HavingFilterBuilder {
975
1630
  };
976
1631
  }
977
1632
  static isGroup(yMap) {
978
- return having_utils_isHavingGroup(yMap);
1633
+ return isHavingGroup(yMap);
979
1634
  }
980
1635
  static isNode(yMap) {
981
1636
  return void 0 !== yMap.get('field');
@@ -1074,17 +1729,518 @@ class undo_manager_UndoManager {
1074
1729
  this.manager.clear(clearUndoStack, clearRedoStack);
1075
1730
  }
1076
1731
  }
1732
+ const VBI_TO_VQUERY_MEASURE_AGGR_FUNC_MAP = {
1733
+ count: 'count',
1734
+ countDistinct: 'count_distinct',
1735
+ sum: 'sum',
1736
+ avg: 'avg',
1737
+ min: 'min',
1738
+ max: 'max',
1739
+ variance: 'variance',
1740
+ variancePop: 'variance_pop',
1741
+ stddev: 'stddev',
1742
+ median: 'median',
1743
+ quantile: 'quantile'
1744
+ };
1745
+ const VBI_TO_VQUERY_DIMENSION_AGGR_FUNC_MAP = {
1746
+ toYear: 'to_year',
1747
+ toQuarter: 'to_quarter',
1748
+ toMonth: 'to_month',
1749
+ toWeek: 'to_week',
1750
+ toDay: 'to_day',
1751
+ toHour: 'to_hour',
1752
+ toMinute: 'to_minute',
1753
+ toSecond: 'to_second'
1754
+ };
1755
+ const mapAggregateForVQuery = (aggregate)=>{
1756
+ if (!aggregate) return aggregate;
1757
+ const mappedFunc = VBI_TO_VQUERY_MEASURE_AGGR_FUNC_MAP[aggregate.func] ?? aggregate.func;
1758
+ return {
1759
+ ...aggregate,
1760
+ func: mappedFunc
1761
+ };
1762
+ };
1763
+ const mapDimensionAggregateForVQuery = (aggregate)=>{
1764
+ if (!aggregate) return aggregate;
1765
+ const mappedFunc = VBI_TO_VQUERY_DIMENSION_AGGR_FUNC_MAP[aggregate.func] ?? aggregate.func;
1766
+ return {
1767
+ ...aggregate,
1768
+ func: mappedFunc
1769
+ };
1770
+ };
1771
+ const buildSelect = (queryDSL, context)=>{
1772
+ const { vbiDSL } = context;
1773
+ const measures = vbiDSL.measures;
1774
+ const dimensions = vbiDSL.dimensions;
1775
+ const result = {
1776
+ ...queryDSL
1777
+ };
1778
+ const measureNodes = measures.filter((measure)=>MeasuresBuilder.isMeasureNode(measure));
1779
+ const measureSelects = measureNodes.map((measure)=>({
1780
+ field: measure.field,
1781
+ alias: measure.id,
1782
+ aggr: mapAggregateForVQuery(measure.aggregate)
1783
+ }));
1784
+ const dimensionNodes = dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension));
1785
+ const dimensionSelects = dimensionNodes.map((dimension)=>{
1786
+ const aggregate = mapDimensionAggregateForVQuery(dimension.aggregate);
1787
+ if (!aggregate) return {
1788
+ field: dimension.field,
1789
+ alias: dimension.id
1790
+ };
1791
+ return {
1792
+ field: dimension.field,
1793
+ alias: dimension.id,
1794
+ aggr: aggregate
1795
+ };
1796
+ });
1797
+ result.select = [
1798
+ ...measureSelects,
1799
+ ...dimensionSelects
1800
+ ];
1801
+ return result;
1802
+ };
1803
+ const buildGroupBy = (queryDSL, context)=>{
1804
+ const result = {
1805
+ ...queryDSL
1806
+ };
1807
+ const { vbiDSL } = context;
1808
+ const dimensions = vbiDSL.dimensions;
1809
+ const dimensionNodes = dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension));
1810
+ result.groupBy = dimensionNodes.map((dimension)=>dimension.aggregate ? dimension.id : dimension.field);
1811
+ return result;
1812
+ };
1813
+ function resolveDatePredicate(predicate, now) {
1814
+ switch(predicate.type){
1815
+ case 'range':
1816
+ return resolveRange(predicate);
1817
+ case 'period':
1818
+ return resolvePeriod(predicate);
1819
+ case 'relative':
1820
+ return resolveRelative(predicate, now ?? new Date());
1821
+ case 'current':
1822
+ return resolveCurrent(predicate, now ?? new Date());
1823
+ }
1824
+ }
1825
+ function resolveRange(p) {
1826
+ return {
1827
+ start: toDateString(p.start),
1828
+ end: toDateString(p.end),
1829
+ bounds: p.bounds ?? '[)'
1830
+ };
1831
+ }
1832
+ function resolvePeriod(p) {
1833
+ switch(p.unit){
1834
+ case 'year':
1835
+ return {
1836
+ start: `${p.year}-01-01`,
1837
+ end: `${p.year + 1}-01-01`,
1838
+ bounds: '[)'
1839
+ };
1840
+ case 'quarter':
1841
+ return resolveQuarter(p.year, p.quarter);
1842
+ case 'month':
1843
+ return resolveMonth(p.year, p.month);
1844
+ case 'week':
1845
+ return resolveISOWeek(p.year, p.week);
1846
+ case 'day':
1847
+ return resolveDay(p.date);
1848
+ }
1849
+ }
1850
+ function resolveQuarter(year, quarter) {
1851
+ const startMonth = (quarter - 1) * 3 + 1;
1852
+ const start = formatDate(year, startMonth, 1);
1853
+ const endDate = new Date(Date.UTC(year, startMonth - 1 + 3, 1));
1854
+ return {
1855
+ start,
1856
+ end: utcToDateString(endDate),
1857
+ bounds: '[)'
1858
+ };
1859
+ }
1860
+ function resolveMonth(year, month) {
1861
+ const start = formatDate(year, month, 1);
1862
+ const endDate = new Date(Date.UTC(year, month, 1));
1863
+ return {
1864
+ start,
1865
+ end: utcToDateString(endDate),
1866
+ bounds: '[)'
1867
+ };
1868
+ }
1869
+ function resolveISOWeek(year, week) {
1870
+ const jan4 = new Date(Date.UTC(year, 0, 4));
1871
+ const dayOfWeek = jan4.getUTCDay() || 7;
1872
+ const monday = new Date(jan4.getTime() - (dayOfWeek - 1) * 86400000);
1873
+ const weekStart = new Date(monday.getTime() + (week - 1) * 604800000);
1874
+ const weekEnd = new Date(weekStart.getTime() + 604800000);
1875
+ return {
1876
+ start: utcToDateString(weekStart),
1877
+ end: utcToDateString(weekEnd),
1878
+ bounds: '[)'
1879
+ };
1880
+ }
1881
+ function resolveDay(date) {
1882
+ const d = 'string' == typeof date ? new Date(date + 'T00:00:00Z') : date;
1883
+ const next = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 1));
1884
+ return {
1885
+ start: utcToDateString(d),
1886
+ end: utcToDateString(next),
1887
+ bounds: '[)'
1888
+ };
1889
+ }
1890
+ function resolveRelative(p, now) {
1891
+ const today = truncateToDay(now);
1892
+ const offset = 'last' === p.mode ? -p.amount : p.amount;
1893
+ const shifted = shiftDate(today, offset, p.unit);
1894
+ if ('last' === p.mode) return {
1895
+ start: utcToDateString(shifted),
1896
+ end: utcToDateString(today),
1897
+ bounds: '[)'
1898
+ };
1899
+ return {
1900
+ start: utcToDateString(today),
1901
+ end: utcToDateString(shifted),
1902
+ bounds: '[)'
1903
+ };
1904
+ }
1905
+ function resolveCurrent(p, now) {
1906
+ const offset = p.offset ?? 0;
1907
+ const base = truncateToDay(now);
1908
+ const periodStart = getPeriodStart(base, p.unit, offset);
1909
+ const periodEnd = shiftDate(periodStart, 1, p.unit);
1910
+ return {
1911
+ start: utcToDateString(periodStart),
1912
+ end: utcToDateString(periodEnd),
1913
+ bounds: '[)'
1914
+ };
1915
+ }
1916
+ function getPeriodStart(date, unit, offset) {
1917
+ const y = date.getUTCFullYear();
1918
+ const m = date.getUTCMonth();
1919
+ switch(unit){
1920
+ case 'year':
1921
+ return new Date(Date.UTC(y + offset, 0, 1));
1922
+ case 'quarter':
1923
+ {
1924
+ const q = Math.floor(m / 3);
1925
+ return new Date(Date.UTC(y, (q + offset) * 3, 1));
1926
+ }
1927
+ case 'month':
1928
+ return new Date(Date.UTC(y, m + offset, 1));
1929
+ case 'week':
1930
+ {
1931
+ const dow = date.getUTCDay() || 7;
1932
+ const monday = new Date(date.getTime() - (dow - 1) * 86400000);
1933
+ return new Date(monday.getTime() + 7 * offset * 86400000);
1934
+ }
1935
+ case 'day':
1936
+ return new Date(Date.UTC(y, m, date.getUTCDate() + offset));
1937
+ default:
1938
+ return date;
1939
+ }
1940
+ }
1941
+ function shiftDate(date, amount, unit) {
1942
+ const y = date.getUTCFullYear();
1943
+ const m = date.getUTCMonth();
1944
+ const d = date.getUTCDate();
1945
+ switch(unit){
1946
+ case 'year':
1947
+ return new Date(Date.UTC(y + amount, m, d));
1948
+ case 'quarter':
1949
+ return new Date(Date.UTC(y, m + 3 * amount, d));
1950
+ case 'month':
1951
+ return new Date(Date.UTC(y, m + amount, d));
1952
+ case 'week':
1953
+ return new Date(date.getTime() + 7 * amount * 86400000);
1954
+ case 'day':
1955
+ return new Date(Date.UTC(y, m, d + amount));
1956
+ default:
1957
+ return date;
1958
+ }
1959
+ }
1960
+ function truncateToDay(date) {
1961
+ return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
1962
+ }
1963
+ function toDateString(input) {
1964
+ if ('string' == typeof input) return input;
1965
+ return utcToDateString(input);
1966
+ }
1967
+ function utcToDateString(date) {
1968
+ return formatDate(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
1969
+ }
1970
+ function formatDate(year, month, day) {
1971
+ const y = String(year);
1972
+ const m = String(month).padStart(2, '0');
1973
+ const d = String(day).padStart(2, '0');
1974
+ return `${y}-${m}-${d}`;
1975
+ }
1976
+ const buildWhere = (queryDSL, context)=>{
1977
+ const { vbiDSL } = context;
1978
+ const whereFilter = vbiDSL.whereFilter;
1979
+ if (!whereFilter || 0 === whereFilter.conditions.length) return queryDSL;
1980
+ const result = {
1981
+ ...queryDSL
1982
+ };
1983
+ result.where = mapGroupToCondition(whereFilter);
1984
+ return result;
1985
+ };
1986
+ function buildWhere_isWhereGroup(clause) {
1987
+ return 'op' in clause && 'conditions' in clause;
1988
+ }
1989
+ function mapClauseToCondition(clause) {
1990
+ if (buildWhere_isWhereGroup(clause)) return [
1991
+ mapGroupToCondition(clause)
1992
+ ];
1993
+ return mapFilterToCondition(clause);
1994
+ }
1995
+ function mapGroupToCondition(group) {
1996
+ return {
1997
+ op: group.op,
1998
+ conditions: group.conditions.flatMap((c)=>mapClauseToCondition(c))
1999
+ };
2000
+ }
2001
+ function mapFilterToCondition(filter) {
2002
+ if ('date' === filter.op) return handleDateFilter(filter.field, filter.value);
2003
+ if ('between' === filter.op || 'not between' === filter.op) return handleBetweenFilter(filter);
2004
+ return handleSimpleFilter(filter);
2005
+ }
2006
+ function handleDateFilter(field, predicate) {
2007
+ const range = resolveDatePredicate(predicate);
2008
+ const startOp = '>=';
2009
+ const endOp = '[]' === range.bounds ? '<=' : '<';
2010
+ return [
2011
+ {
2012
+ field,
2013
+ op: startOp,
2014
+ value: range.start
2015
+ },
2016
+ {
2017
+ field,
2018
+ op: endOp,
2019
+ value: range.end
2020
+ }
2021
+ ];
2022
+ }
2023
+ function handleBetweenFilter(filter) {
2024
+ const value = normalizeBetweenValue(filter.value);
2025
+ const lowerCondition = void 0 !== value.min && null !== value.min && '' !== value.min ? {
2026
+ field: filter.field,
2027
+ op: '<' === value.leftOp ? '>' : '>=',
2028
+ value: value.min
2029
+ } : void 0;
2030
+ const upperCondition = void 0 !== value.max && null !== value.max && '' !== value.max ? {
2031
+ field: filter.field,
2032
+ op: '<' === value.rightOp ? '<' : '<=',
2033
+ value: value.max
2034
+ } : void 0;
2035
+ if ('not between' === filter.op) {
2036
+ const outsideConditions = [
2037
+ lowerCondition && invertLowerBound(lowerCondition),
2038
+ upperCondition && invertUpperBound(upperCondition)
2039
+ ].filter(Boolean);
2040
+ if (outsideConditions.length <= 1) return outsideConditions;
2041
+ return [
2042
+ {
2043
+ op: 'or',
2044
+ conditions: outsideConditions
2045
+ }
2046
+ ];
2047
+ }
2048
+ return [
2049
+ lowerCondition,
2050
+ upperCondition
2051
+ ].filter(Boolean);
2052
+ }
2053
+ function normalizeBetweenValue(value) {
2054
+ if (Array.isArray(value)) return {
2055
+ min: value[0],
2056
+ max: value[1],
2057
+ leftOp: '<=',
2058
+ rightOp: '<='
2059
+ };
2060
+ if ('object' == typeof value && null !== value) return value;
2061
+ return {};
2062
+ }
2063
+ function invertLowerBound(condition) {
2064
+ return {
2065
+ field: condition.field,
2066
+ op: '>' === condition.op ? '<=' : '<',
2067
+ value: condition.value
2068
+ };
2069
+ }
2070
+ function invertUpperBound(condition) {
2071
+ return {
2072
+ field: condition.field,
2073
+ op: '<' === condition.op ? '>=' : '>',
2074
+ value: condition.value
2075
+ };
2076
+ }
2077
+ function handleSimpleFilter(filter) {
2078
+ let mappedOp = filter.op;
2079
+ const value = filter.value;
2080
+ if (Array.isArray(value)) {
2081
+ if ('=' === mappedOp) mappedOp = 'in';
2082
+ if ('!=' === mappedOp) mappedOp = 'not in';
2083
+ }
2084
+ return [
2085
+ {
2086
+ field: filter.field,
2087
+ op: mappedOp,
2088
+ value
2089
+ }
2090
+ ];
2091
+ }
2092
+ const buildHaving = (queryDSL, context)=>{
2093
+ const { vbiDSL } = context;
2094
+ const havingFilter = vbiDSL.havingFilter;
2095
+ if (!havingFilter || 0 === havingFilter.conditions.length) return queryDSL;
2096
+ const result = {
2097
+ ...queryDSL
2098
+ };
2099
+ result.having = {
2100
+ op: havingFilter.op,
2101
+ conditions: havingFilter.conditions.flatMap(buildHaving_mapClauseToCondition)
2102
+ };
2103
+ return result;
2104
+ };
2105
+ function buildHaving_isHavingGroup(clause) {
2106
+ return 'op' in clause && 'conditions' in clause;
2107
+ }
2108
+ function buildHaving_mapClauseToCondition(clause) {
2109
+ if (buildHaving_isHavingGroup(clause)) return [
2110
+ buildHaving_mapGroupToCondition(clause)
2111
+ ];
2112
+ return buildHaving_mapFilterToCondition(clause);
2113
+ }
2114
+ function buildHaving_mapGroupToCondition(group) {
2115
+ return {
2116
+ op: group.op,
2117
+ conditions: group.conditions.flatMap(buildHaving_mapClauseToCondition)
2118
+ };
2119
+ }
2120
+ function buildHaving_mapFilterToCondition(filter) {
2121
+ const mappedOp = normalizeOperator(filter.op, filter.value);
2122
+ const aggregate = mapAggregateForVQuery(filter.aggregate);
2123
+ return [
2124
+ {
2125
+ field: filter.field,
2126
+ aggr: aggregate,
2127
+ op: mappedOp,
2128
+ value: filter.value
2129
+ }
2130
+ ];
2131
+ }
2132
+ function normalizeOperator(op, value) {
2133
+ let mappedOp = op;
2134
+ if (Array.isArray(value)) {
2135
+ if ('=' === mappedOp) mappedOp = 'in';
2136
+ if ('!=' === mappedOp) mappedOp = 'not in';
2137
+ }
2138
+ return mappedOp;
2139
+ }
2140
+ const toOrderItem = (node)=>({
2141
+ field: node.id,
2142
+ order: node.sort?.order
2143
+ });
2144
+ const buildOrderBy = (queryDSL, context)=>{
2145
+ const result = {
2146
+ ...queryDSL
2147
+ };
2148
+ const dimensions = context.vbiDSL.dimensions.filter(DimensionsBuilder.isDimensionNode);
2149
+ const measures = context.vbiDSL.measures.filter(MeasuresBuilder.isMeasureNode);
2150
+ const sortedDimensions = dimensions.filter((node)=>node.sort);
2151
+ const sortedMeasures = measures.filter((node)=>node.sort);
2152
+ const sortedNodes = [
2153
+ ...sortedDimensions,
2154
+ ...sortedMeasures
2155
+ ];
2156
+ if (sortedNodes.length > 0) {
2157
+ result.orderBy = sortedNodes.map(toOrderItem);
2158
+ return result;
2159
+ }
2160
+ const firstDimension = dimensions[0];
2161
+ if (firstDimension) result.orderBy = [
2162
+ {
2163
+ field: firstDimension.id,
2164
+ order: 'asc'
2165
+ }
2166
+ ];
2167
+ return result;
2168
+ };
2169
+ const buildLimit = (queryDSL, context)=>{
2170
+ const result = {
2171
+ ...queryDSL
2172
+ };
2173
+ const limit = context.vbiDSL.limit ?? 1000;
2174
+ result.limit = limit;
2175
+ return result;
2176
+ };
2177
+ const buildVQuery = (vbiDSL, builder)=>{
2178
+ const wrapper = (processor)=>(queryDSL)=>processor(queryDSL, {
2179
+ vbiDSL,
2180
+ builder
2181
+ });
2182
+ return pipe({}, wrapper(buildSelect), wrapper(buildGroupBy), wrapper(buildWhere), wrapper(buildHaving), wrapper(buildOrderBy), wrapper(buildLimit));
2183
+ };
2184
+ const buildVQueryDSL = ({ vbiDSL, builder })=>buildVQuery(vbiDSL, builder);
2185
+ const buildVSeedDSL = async ({ vbiDSL, queryDSL, options })=>{
2186
+ const connectorId = vbiDSL.connectorId;
2187
+ const connector = await getConnector(connectorId);
2188
+ const schema = await connector.discoverSchema();
2189
+ const queryResult = await connector.query({
2190
+ queryDSL,
2191
+ schema,
2192
+ connectorId,
2193
+ signal: options.signal
2194
+ });
2195
+ const measures = vbiDSL.measures.filter((measure)=>MeasuresBuilder.isMeasureNode(measure)).map((measure)=>{
2196
+ const nextMeasure = {
2197
+ id: measure.id,
2198
+ alias: measure.alias
2199
+ };
2200
+ if (measure.encoding) nextMeasure.encoding = measure.encoding;
2201
+ if (measure.format) if ('autoFormat' in measure.format && true === measure.format.autoFormat) nextMeasure.autoFormat = true;
2202
+ else {
2203
+ nextMeasure.autoFormat = false;
2204
+ nextMeasure.numFormat = Object.fromEntries(Object.entries(measure.format).filter(([key])=>'autoFormat' !== key));
2205
+ }
2206
+ return nextMeasure;
2207
+ });
2208
+ const dimensions = vbiDSL.dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension)).map((dimension)=>{
2209
+ const nextDimension = {
2210
+ id: dimension.id,
2211
+ alias: dimension.alias
2212
+ };
2213
+ if (dimension.encoding) nextDimension.encoding = dimension.encoding;
2214
+ return nextDimension;
2215
+ });
2216
+ return {
2217
+ chartType: vbiDSL.chartType,
2218
+ dataset: queryResult.dataset,
2219
+ dimensions,
2220
+ measures,
2221
+ theme: vbiDSL.theme,
2222
+ locale: vbiDSL.locale
2223
+ };
2224
+ };
2225
+ const defaultVBIChartBuilderAdapters = {
2226
+ buildVQuery: buildVQueryDSL,
2227
+ buildVSeed: buildVSeedDSL
2228
+ };
2229
+ const resolveVBIChartBuilderAdapters = (adapters)=>({
2230
+ buildVQuery: adapters?.buildVQuery ?? defaultVBIChartBuilderAdapters.buildVQuery,
2231
+ buildVSeed: adapters?.buildVSeed ?? defaultVBIChartBuilderAdapters.buildVSeed
2232
+ });
1077
2233
  const applyUpdateToDoc = (doc, update, transactionOrigin)=>{
1078
2234
  applyUpdate(doc, update, transactionOrigin);
1079
2235
  };
1080
2236
  const encodeDocStateAsUpdate = (doc, targetStateVector)=>encodeStateAsUpdate(doc, targetStateVector);
1081
- const buildVBIDSL = (dsl)=>dsl.toJSON();
2237
+ const buildVBIChartDSL = (dsl)=>dsl.toJSON();
1082
2238
  const getCollectionLength = (value)=>{
1083
2239
  if (value instanceof external_yjs_Array) return value.length;
1084
2240
  if (Array.isArray(value)) return value.length;
1085
2241
  return 0;
1086
2242
  };
1087
- const isEmptyVBIDSL = (dsl)=>{
2243
+ const isEmptyVBIChartDSL = (dsl)=>{
1088
2244
  const dimensionsLength = getCollectionLength(dsl.get('dimensions'));
1089
2245
  const measuresLength = getCollectionLength(dsl.get('measures'));
1090
2246
  return 0 === dimensionsLength && 0 === measuresLength;
@@ -1094,7 +2250,7 @@ const getBuilderSchema = async (dsl)=>{
1094
2250
  const connector = await getConnector(connectorId);
1095
2251
  return connector.discoverSchema();
1096
2252
  };
1097
- class VBIBuilder {
2253
+ class VBIChartBuilder {
1098
2254
  doc;
1099
2255
  dsl;
1100
2256
  adapters;
@@ -1110,7 +2266,7 @@ class VBIBuilder {
1110
2266
  constructor(doc, options){
1111
2267
  this.doc = doc;
1112
2268
  this.dsl = doc.getMap('dsl');
1113
- this.adapters = resolveVBIBuilderAdapters(options?.adapters);
2269
+ this.adapters = resolveVBIChartBuilderAdapters(options?.adapters);
1114
2270
  this.undoManager = new undo_manager_UndoManager(this.dsl);
1115
2271
  this.chartType = new ChartTypeBuilder(doc, this.dsl);
1116
2272
  this.measures = new MeasuresBuilder(doc, this.dsl);
@@ -1123,7 +2279,7 @@ class VBIBuilder {
1123
2279
  }
1124
2280
  applyUpdate = (update, transactionOrigin)=>applyUpdateToDoc(this.doc, update, transactionOrigin);
1125
2281
  encodeStateAsUpdate = (targetStateVector)=>encodeDocStateAsUpdate(this.doc, targetStateVector);
1126
- buildVSeed = async ()=>{
2282
+ buildVSeed = async (options = {})=>{
1127
2283
  const vbiDSL = this.build();
1128
2284
  const queryDSL = this.adapters.buildVQuery({
1129
2285
  dsl: this.dsl,
@@ -1134,6 +2290,7 @@ class VBIBuilder {
1134
2290
  dsl: this.dsl,
1135
2291
  vbiDSL,
1136
2292
  queryDSL,
2293
+ options,
1137
2294
  builder: this
1138
2295
  });
1139
2296
  };
@@ -1145,14 +2302,13 @@ class VBIBuilder {
1145
2302
  builder: this
1146
2303
  });
1147
2304
  };
1148
- build = ()=>buildVBIDSL(this.dsl);
1149
- isEmpty = ()=>isEmptyVBIDSL(this.dsl);
2305
+ build = ()=>buildVBIChartDSL(this.dsl);
2306
+ isEmpty = ()=>isEmptyVBIChartDSL(this.dsl);
1150
2307
  getSchema = async ()=>getBuilderSchema(this.dsl);
1151
2308
  }
1152
2309
  const shouldEnsureIdForObject = (obj, ensureId)=>{
1153
2310
  if (true === ensureId) return true;
1154
- if ('field' === ensureId) return 'string' == typeof obj.field;
1155
- return false;
2311
+ return 'field' === ensureId && 'string' == typeof obj.field;
1156
2312
  };
1157
2313
  const toYMap = (obj, ensureId = false)=>{
1158
2314
  const yMap = new external_yjs_Map();
@@ -1160,23 +2316,9 @@ const toYMap = (obj, ensureId = false)=>{
1160
2316
  for (const [key, value] of Object.entries(obj)){
1161
2317
  if (('conditions' === key || 'children' === key) && Array.isArray(value)) {
1162
2318
  const yArr = new external_yjs_Array();
1163
- for (const child of value){
1164
- if (child instanceof external_yjs_Map) {
1165
- yArr.push([
1166
- child
1167
- ]);
1168
- continue;
1169
- }
1170
- if ('object' == typeof child && null !== child) {
1171
- yArr.push([
1172
- toYMap(child, ensureId)
1173
- ]);
1174
- continue;
1175
- }
1176
- yArr.push([
1177
- child
1178
- ]);
1179
- }
2319
+ for (const child of value)yArr.push([
2320
+ toYMap(child, ensureId)
2321
+ ]);
1180
2322
  yMap.set(key, yArr);
1181
2323
  continue;
1182
2324
  }
@@ -1186,65 +2328,34 @@ const toYMap = (obj, ensureId = false)=>{
1186
2328
  };
1187
2329
  const ensureYArray = (arr, ensureId = false)=>{
1188
2330
  if (!arr) return new external_yjs_Array();
1189
- if (arr instanceof external_yjs_Array) return arr;
1190
2331
  const yArr = new external_yjs_Array();
1191
- for (const item of arr){
1192
- if (item instanceof external_yjs_Map) {
1193
- yArr.push([
1194
- item
1195
- ]);
1196
- continue;
1197
- }
1198
- if ('object' == typeof item && null !== item) {
1199
- yArr.push([
1200
- toYMap(item, ensureId)
1201
- ]);
1202
- continue;
1203
- }
1204
- yArr.push([
1205
- item
1206
- ]);
1207
- }
2332
+ for (const item of arr)yArr.push([
2333
+ toYMap(item, ensureId)
2334
+ ]);
1208
2335
  return yArr;
1209
2336
  };
1210
- const getDefaultWhereFilter = ()=>({
2337
+ const ensureWhereGroup = (whereFilter)=>{
2338
+ const source = whereFilter ?? {
1211
2339
  id: 'root',
1212
2340
  op: 'and',
1213
2341
  conditions: []
1214
- });
1215
- const isFilterGroupInput = (value)=>'object' == typeof value && null !== value;
1216
- const ensureWhereGroup = (whereFilter)=>{
1217
- const sourceWhereFilter = whereFilter instanceof external_yjs_Map || isFilterGroupInput(whereFilter) ? whereFilter : getDefaultWhereFilter();
1218
- const whereGroup = sourceWhereFilter instanceof external_yjs_Map ? sourceWhereFilter : createWhereGroup();
1219
- if (sourceWhereFilter instanceof external_yjs_Map) {
1220
- if (!(whereGroup.get('conditions') instanceof external_yjs_Array)) whereGroup.set('conditions', new external_yjs_Array());
1221
- if (!whereGroup.get('id')) whereGroup.set('id', 'root');
1222
- if (!whereGroup.get('op')) whereGroup.set('op', 'and');
1223
- return whereGroup;
1224
- }
1225
- whereGroup.set('id', sourceWhereFilter.id ?? 'root');
1226
- whereGroup.set('op', sourceWhereFilter.op ?? 'and');
1227
- whereGroup.set('conditions', ensureYArray(sourceWhereFilter.conditions, true));
2342
+ };
2343
+ const whereGroup = createWhereGroup();
2344
+ whereGroup.set('id', source.id);
2345
+ whereGroup.set('op', source.op);
2346
+ whereGroup.set('conditions', ensureYArray(source.conditions, true));
1228
2347
  return whereGroup;
1229
2348
  };
1230
- const getDefaultHavingFilter = ()=>({
2349
+ const ensureHavingGroup = (havingFilter)=>{
2350
+ const source = havingFilter ?? {
1231
2351
  id: 'root',
1232
2352
  op: 'and',
1233
2353
  conditions: []
1234
- });
1235
- const ensure_having_group_isFilterGroupInput = (value)=>'object' == typeof value && null !== value;
1236
- const ensureHavingGroup = (havingFilter)=>{
1237
- const sourceHavingFilter = havingFilter instanceof external_yjs_Map || ensure_having_group_isFilterGroupInput(havingFilter) ? havingFilter : getDefaultHavingFilter();
1238
- const havingGroup = sourceHavingFilter instanceof external_yjs_Map ? sourceHavingFilter : createHavingGroup();
1239
- if (sourceHavingFilter instanceof external_yjs_Map) {
1240
- if (!(havingGroup.get('conditions') instanceof external_yjs_Array)) havingGroup.set('conditions', new external_yjs_Array());
1241
- if (!havingGroup.get('id')) havingGroup.set('id', 'root');
1242
- if (!havingGroup.get('op')) havingGroup.set('op', 'and');
1243
- return havingGroup;
1244
- }
1245
- havingGroup.set('id', sourceHavingFilter.id ?? 'root');
1246
- havingGroup.set('op', sourceHavingFilter.op ?? 'and');
1247
- havingGroup.set('conditions', ensureYArray(sourceHavingFilter.conditions, true));
2354
+ };
2355
+ const havingGroup = createHavingGroup();
2356
+ havingGroup.set('id', source.id);
2357
+ havingGroup.set('op', source.op);
2358
+ havingGroup.set('conditions', ensureYArray(source.conditions, true));
1248
2359
  return havingGroup;
1249
2360
  };
1250
2361
  const setBaseDSLFields = (dsl, vbi)=>{
@@ -1255,7 +2366,7 @@ const setBaseDSLFields = (dsl, vbi)=>{
1255
2366
  if (vbi.locale) dsl.set('locale', vbi.locale);
1256
2367
  if (void 0 !== vbi.version) dsl.set('version', vbi.version);
1257
2368
  };
1258
- const fromVBIDSLInput = (vbi, options)=>{
2369
+ const createChartBuilderFromVBIChartDSLInput = (vbi, options)=>{
1259
2370
  const doc = new Doc();
1260
2371
  const dsl = doc.getMap('dsl');
1261
2372
  doc.transact(()=>{
@@ -1265,9 +2376,9 @@ const fromVBIDSLInput = (vbi, options)=>{
1265
2376
  dsl.set('measures', ensureYArray(vbi.measures, 'field'));
1266
2377
  dsl.set('dimensions', ensureYArray(vbi.dimensions, 'field'));
1267
2378
  });
1268
- return new VBIBuilder(doc, options);
2379
+ return new VBIChartBuilder(doc, options);
1269
2380
  };
1270
- const generateEmptyDSL = (connectorId)=>({
2381
+ const generateEmptyChartDSL = (connectorId)=>({
1271
2382
  connectorId,
1272
2383
  chartType: 'table',
1273
2384
  measures: [],
@@ -1299,15 +2410,14 @@ const mergeBuilderOptions = (base, overrides)=>{
1299
2410
  };
1300
2411
  };
1301
2412
  function createVBI(defaultBuilderOptions) {
1302
- const from = (vbi, builderOptions)=>fromVBIDSLInput(vbi, mergeBuilderOptions(defaultBuilderOptions, builderOptions));
2413
+ const createChart = (vbi, builderOptions)=>createChartBuilderFromVBIChartDSLInput(vbi, mergeBuilderOptions(defaultBuilderOptions, builderOptions));
1303
2414
  return {
1304
2415
  connectorMap: connectorMap,
1305
2416
  registerConnector: registerConnector,
1306
2417
  getConnector: getConnector,
1307
- generateEmptyDSL: generateEmptyDSL,
1308
- from,
1309
- create: from
2418
+ generateEmptyChartDSL: generateEmptyChartDSL,
2419
+ createChart
1310
2420
  };
1311
2421
  }
1312
2422
  const VBI = createVBI();
1313
- export { ChartTypeBuilder, DimensionsBuilder, HavingFilterBuilder, LimitBuilder, LocaleBuilder, MeasuresBuilder, ThemeBuilder, undo_manager_UndoManager as UndoManager, VBI, VBIBuilder, WhereFilterBuilder, buildVQuery, createVBI, defaultVBIBuilderAdapters, findTreeNodesBy, id_id as id, isVBIFilter, isVBIHavingFilter, isVBIHavingGroup, isVBIWhereGroup, preorderTraverse, resolveVBIBuilderAdapters };
2423
+ export { ChartTypeBuilder, DimensionsBuilder, HavingFilterBuilder, LimitBuilder, LocaleBuilder, MeasuresBuilder, ThemeBuilder, undo_manager_UndoManager as UndoManager, VBI, VBIChartBuilder, WhereFilterBuilder, buildVQuery, createVBI, defaultVBIChartBuilderAdapters, findTreeNodesBy, id_id as id, isVBIFilter, isVBIHavingFilter, isVBIHavingGroup, isVBIWhereGroup, preorderTraverse, resolveVBIChartBuilderAdapters };