@visactor/vbi 0.4.17 → 0.4.20

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 (60) hide show
  1. package/dist/builder/adapters/index.d.ts +1 -0
  2. package/dist/builder/adapters/vquery-vseed/build-vquery.d.ts +3 -0
  3. package/dist/builder/adapters/vquery-vseed/build-vseed.d.ts +3 -0
  4. package/dist/builder/adapters/vquery-vseed/index.d.ts +7 -0
  5. package/dist/builder/adapters/vquery-vseed/types.d.ts +4 -0
  6. package/dist/builder/{vbi-builder.d.ts → builder.d.ts} +8 -8
  7. package/dist/builder/features/chart-type/chart-type-builder.d.ts +20 -1
  8. package/dist/builder/features/chart-type/dimension-encoding.d.ts +4 -0
  9. package/dist/builder/features/chart-type/measure-encoding.d.ts +4 -0
  10. package/dist/builder/features/chart-type/reapply-dimension-encodings.d.ts +2 -0
  11. package/dist/builder/features/chart-type/reapply-measure-encodings.d.ts +2 -0
  12. package/dist/builder/features/dimensions/dim-builder.d.ts +3 -2
  13. package/dist/builder/features/dimensions/dim-node-builder.d.ts +32 -1
  14. package/dist/builder/features/havingFilter/having-builder.d.ts +2 -2
  15. package/dist/builder/features/havingFilter/having-node-builder.d.ts +10 -1
  16. package/dist/builder/features/measures/mea-builder.d.ts +3 -2
  17. package/dist/builder/features/measures/mea-node-builder.d.ts +33 -3
  18. package/dist/builder/features/whereFilter/where-builder.d.ts +2 -2
  19. package/dist/builder/features/whereFilter/where-node-builder.d.ts +11 -2
  20. package/dist/builder/index.d.ts +2 -1
  21. package/dist/builder/modules/build.d.ts +2 -2
  22. package/dist/builder/modules/index.d.ts +2 -5
  23. package/dist/builder/modules/is-empty.d.ts +1 -1
  24. package/dist/index.cjs +1578 -386
  25. package/dist/index.d.ts +6 -3
  26. package/dist/index.js +1567 -384
  27. package/dist/pipeline/vqueryDSL/aggregateMap.d.ts +50 -0
  28. package/dist/pipeline/vqueryDSL/buildOrderBy.d.ts +2 -0
  29. package/dist/pipeline/vqueryDSL/index.d.ts +2 -3
  30. package/dist/pipeline/vqueryDSL/resolveDatePredicate.d.ts +7 -0
  31. package/dist/pipeline/vqueryDSL/types.d.ts +6 -6
  32. package/dist/types/builder/VBIInterface.d.ts +7 -7
  33. package/dist/types/builder/adapter.d.ts +23 -0
  34. package/dist/types/builder/build-vseed.d.ts +3 -0
  35. package/dist/types/builder/context.d.ts +2 -2
  36. package/dist/types/builder/index.d.ts +4 -2
  37. package/dist/types/builder/observe.d.ts +2 -1
  38. package/dist/types/connector/query.d.ts +1 -0
  39. package/dist/types/dsl/dimensions/aggregate.d.ts +15 -0
  40. package/dist/types/dsl/dimensions/dimensions.d.ts +62 -0
  41. package/dist/types/dsl/encoding.d.ts +2 -2
  42. package/dist/types/dsl/havingFilter/having.d.ts +20 -1
  43. package/dist/types/dsl/index.d.ts +6 -4
  44. package/dist/types/dsl/measures/aggregate.d.ts +14 -2
  45. package/dist/types/dsl/measures/measures.d.ts +177 -8
  46. package/dist/types/dsl/sort.d.ts +13 -0
  47. package/dist/types/dsl/vbi/vbi.d.ts +103 -7
  48. package/dist/types/dsl/whereFilter/date.d.ts +95 -0
  49. package/dist/types/dsl/whereFilter/filters.d.ts +142 -5
  50. package/dist/utils/filter-guards.d.ts +2 -2
  51. package/dist/vbi/create-vbi.d.ts +14 -14
  52. package/dist/vbi/from/from-vbi-dsl-input.d.ts +4 -3
  53. package/dist/vbi/from/set-base-dsl-fields.d.ts +2 -2
  54. package/dist/vbi/generate-empty-dsl.d.ts +2 -2
  55. package/dist/vbi/normalize/types.d.ts +3 -3
  56. package/dist/vbi.d.ts +1 -14
  57. package/package.json +5 -5
  58. package/dist/builder/modules/build-vquery.d.ts +0 -4
  59. package/dist/builder/modules/build-vseed.d.ts +0 -8
  60. package/dist/builder/modules/create-builder-features.d.ts +0 -14
package/dist/index.js CHANGED
@@ -12,228 +12,6 @@ const getConnector = async (id)=>{
12
12
  if ('function' == typeof connector) return connector();
13
13
  return connector;
14
14
  };
15
- const applyUpdateToDoc = (doc, update, transactionOrigin)=>{
16
- applyUpdate(doc, update, transactionOrigin);
17
- };
18
- const encodeDocStateAsUpdate = (doc, targetStateVector)=>encodeStateAsUpdate(doc, targetStateVector);
19
- const buildVBIDSL = (dsl)=>dsl.toJSON();
20
- const buildSelect = (queryDSL, context)=>{
21
- const { vbiDSL } = context;
22
- const measures = vbiDSL.measures;
23
- const dimensions = vbiDSL.dimensions;
24
- const result = {
25
- ...queryDSL
26
- };
27
- const measureNodes = measures.filter((measure)=>MeasuresBuilder.isMeasureNode(measure));
28
- const measureSelects = measureNodes.map((measure)=>({
29
- field: measure.field,
30
- alias: measure.alias,
31
- aggr: measure.aggregate
32
- }));
33
- const dimensionNodes = dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension));
34
- const dimensionSelects = dimensionNodes.map((dimension)=>({
35
- field: dimension.field,
36
- alias: dimension.alias
37
- }));
38
- result.select = measureSelects.concat(dimensionSelects);
39
- return result;
40
- };
41
- const buildGroupBy = (queryDSL, context)=>{
42
- const result = {
43
- ...queryDSL
44
- };
45
- const { vbiDSL } = context;
46
- const dimensions = vbiDSL.dimensions;
47
- const dimensionNodes = dimensions.filter((dimension)=>DimensionsBuilder.isDimensionNode(dimension));
48
- result.groupBy = dimensionNodes.map((dimension)=>dimension.field);
49
- return result;
50
- };
51
- const buildWhere = (queryDSL, context)=>{
52
- const { vbiDSL } = context;
53
- const whereFilter = vbiDSL.whereFilter;
54
- if (!whereFilter || 0 === whereFilter.conditions.length) return queryDSL;
55
- const result = {
56
- ...queryDSL
57
- };
58
- result.where = mapGroupToCondition(whereFilter);
59
- return result;
60
- };
61
- function isWhereGroup(clause) {
62
- return 'op' in clause && 'conditions' in clause;
63
- }
64
- function mapClauseToCondition(clause) {
65
- if (isWhereGroup(clause)) return [
66
- mapGroupToCondition(clause)
67
- ];
68
- return mapFilterToCondition(clause);
69
- }
70
- function mapGroupToCondition(group) {
71
- return {
72
- op: group.op,
73
- conditions: group.conditions.flatMap(mapClauseToCondition)
74
- };
75
- }
76
- function mapFilterToCondition(filter) {
77
- if ('between' === filter.op || 'not between' === filter.op) return handleBetweenFilter(filter);
78
- return handleSimpleFilter(filter);
79
- }
80
- function handleBetweenFilter(filter) {
81
- const value = normalizeBetweenValue(filter.value);
82
- const lowerCondition = void 0 !== value.min && null !== value.min && '' !== value.min ? {
83
- field: filter.field,
84
- op: '<' === value.leftOp ? '>' : '>=',
85
- value: value.min
86
- } : void 0;
87
- const upperCondition = void 0 !== value.max && null !== value.max && '' !== value.max ? {
88
- field: filter.field,
89
- op: '<' === value.rightOp ? '<' : '<=',
90
- value: value.max
91
- } : void 0;
92
- if ('not between' === filter.op) {
93
- const outsideConditions = [
94
- lowerCondition && invertLowerBound(lowerCondition),
95
- upperCondition && invertUpperBound(upperCondition)
96
- ].filter(Boolean);
97
- if (outsideConditions.length <= 1) return outsideConditions;
98
- return [
99
- {
100
- op: 'or',
101
- conditions: outsideConditions
102
- }
103
- ];
104
- }
105
- return [
106
- lowerCondition,
107
- upperCondition
108
- ].filter(Boolean);
109
- }
110
- function normalizeBetweenValue(value) {
111
- if (Array.isArray(value)) return {
112
- min: value[0],
113
- max: value[1],
114
- leftOp: '<=',
115
- rightOp: '<='
116
- };
117
- if ('object' == typeof value && null !== value) return value;
118
- return {};
119
- }
120
- function invertLowerBound(condition) {
121
- return {
122
- field: condition.field,
123
- op: '>' === condition.op ? '<=' : '<',
124
- value: condition.value
125
- };
126
- }
127
- function invertUpperBound(condition) {
128
- return {
129
- field: condition.field,
130
- op: '<' === condition.op ? '>=' : '>',
131
- value: condition.value
132
- };
133
- }
134
- function handleSimpleFilter(filter) {
135
- let mappedOp = filter.op ?? '=';
136
- const value = filter.value;
137
- if (Array.isArray(value)) {
138
- if ('=' === mappedOp) mappedOp = 'in';
139
- if ('!=' === mappedOp) mappedOp = 'not in';
140
- }
141
- return [
142
- {
143
- field: filter.field,
144
- op: mappedOp,
145
- value
146
- }
147
- ];
148
- }
149
- const buildHaving = (queryDSL, context)=>{
150
- const { vbiDSL } = context;
151
- const havingFilter = vbiDSL.havingFilter;
152
- if (!havingFilter || 0 === havingFilter.conditions.length) return queryDSL;
153
- const result = {
154
- ...queryDSL
155
- };
156
- result.having = {
157
- op: havingFilter.op,
158
- conditions: havingFilter.conditions.flatMap(buildHaving_mapClauseToCondition)
159
- };
160
- return result;
161
- };
162
- function isHavingGroup(clause) {
163
- return 'op' in clause && 'conditions' in clause;
164
- }
165
- function buildHaving_mapClauseToCondition(clause) {
166
- if (isHavingGroup(clause)) return [
167
- buildHaving_mapGroupToCondition(clause)
168
- ];
169
- return buildHaving_mapFilterToCondition(clause);
170
- }
171
- function buildHaving_mapGroupToCondition(group) {
172
- return {
173
- op: group.op,
174
- conditions: group.conditions.flatMap(buildHaving_mapClauseToCondition)
175
- };
176
- }
177
- function buildHaving_mapFilterToCondition(filter) {
178
- const mappedOp = filter.op ?? '=';
179
- return [
180
- {
181
- field: filter.field,
182
- op: mappedOp,
183
- value: filter.value
184
- }
185
- ];
186
- }
187
- const buildLimit = (queryDSL, context)=>{
188
- const result = {
189
- ...queryDSL
190
- };
191
- const limit = context.vbiDSL.limit ?? 1000;
192
- result.limit = limit;
193
- return result;
194
- };
195
- const buildVQuery = (vbiDSL, builder)=>{
196
- const wrapper = (processor)=>(queryDSL)=>processor(queryDSL, {
197
- vbiDSL,
198
- builder
199
- });
200
- return pipe({}, wrapper(buildSelect), wrapper(buildGroupBy), wrapper(buildWhere), wrapper(buildHaving), wrapper(buildLimit));
201
- };
202
- const buildVQueryDSL = (dsl, builder)=>{
203
- const vbiDSL = buildVBIDSL(dsl);
204
- return buildVQuery(vbiDSL, builder);
205
- };
206
- const buildVSeedDSL = async ({ vbiDSL, queryDSL })=>{
207
- const connectorId = vbiDSL.connectorId;
208
- const connector = await getConnector(connectorId);
209
- const schema = await connector.discoverSchema();
210
- const queryResult = await connector.query({
211
- queryDSL,
212
- schema,
213
- connectorId
214
- });
215
- return {
216
- chartType: vbiDSL.chartType,
217
- dataset: queryResult.dataset,
218
- theme: vbiDSL.theme,
219
- locale: vbiDSL.locale
220
- };
221
- };
222
- const getCollectionLength = (value)=>{
223
- if (value instanceof external_yjs_Array) return value.length;
224
- if (Array.isArray(value)) return value.length;
225
- return 0;
226
- };
227
- const isEmptyVBIDSL = (dsl)=>{
228
- const dimensionsLength = getCollectionLength(dsl.get('dimensions'));
229
- const measuresLength = getCollectionLength(dsl.get('measures'));
230
- return 0 === dimensionsLength && 0 === measuresLength;
231
- };
232
- const getBuilderSchema = async (dsl)=>{
233
- const connectorId = dsl.get('connectorId');
234
- const connector = await getConnector(connectorId);
235
- return connector.discoverSchema();
236
- };
237
15
  class MeasureNodeBuilder {
238
16
  yMap;
239
17
  constructor(yMap){
@@ -245,6 +23,12 @@ class MeasureNodeBuilder {
245
23
  getField() {
246
24
  return this.yMap.get('field');
247
25
  }
26
+ getEncoding() {
27
+ return this.yMap.get('encoding');
28
+ }
29
+ getSort() {
30
+ return this.yMap.get('sort');
31
+ }
248
32
  setAlias(alias) {
249
33
  this.yMap.set('alias', alias);
250
34
  return this;
@@ -253,10 +37,29 @@ class MeasureNodeBuilder {
253
37
  this.yMap.set('encoding', encoding);
254
38
  return this;
255
39
  }
40
+ setSort(sort) {
41
+ this.yMap.set('sort', sort);
42
+ return this;
43
+ }
256
44
  setAggregate(aggregate) {
257
45
  this.yMap.set('aggregate', aggregate);
258
46
  return this;
259
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
+ }
260
63
  toJSON() {
261
64
  return this.yMap.toJSON();
262
65
  }
@@ -289,9 +92,369 @@ const normalizeMeasureNodeIds = (measures)=>{
289
92
  });
290
93
  };
291
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
+ };
292
453
  class MeasuresBuilder {
454
+ doc;
293
455
  dsl;
294
456
  constructor(doc, dsl){
457
+ this.doc = doc;
295
458
  this.dsl = dsl;
296
459
  doc.transact(()=>{
297
460
  const measures = getOrCreateMeasures(this.dsl);
@@ -299,38 +462,47 @@ class MeasuresBuilder {
299
462
  });
300
463
  }
301
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);
302
468
  const measure = {
303
469
  id: id_id.uuid(),
304
470
  alias: field,
305
471
  field,
306
- encoding: 'yAxis',
472
+ encoding,
307
473
  aggregate: {
308
474
  func: 'sum'
309
475
  }
310
476
  };
311
477
  const yMap = new external_yjs_Map();
312
- for (const [key, value] of Object.entries(measure))yMap.set(key, value);
313
- const measures = getOrCreateMeasures(this.dsl);
314
- measures.push([
315
- yMap
316
- ]);
317
- const node = new MeasureNodeBuilder(yMap);
318
- 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
+ });
319
487
  return this;
320
488
  }
321
489
  remove(id) {
322
- const measures = getOrCreateMeasures(this.dsl);
323
- const index = locateMeasureIndexById(measures, id);
324
- 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
+ });
325
495
  return this;
326
496
  }
327
497
  update(id, callback) {
328
- const measures = getOrCreateMeasures(this.dsl);
329
- const index = locateMeasureIndexById(measures, id);
330
- if (-1 === index) throw new Error(`Measure with id "${id}" not found`);
331
- const measureYMap = measures.get(index);
332
- const node = new MeasureNodeBuilder(measureYMap);
333
- 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
+ });
334
506
  return this;
335
507
  }
336
508
  find(predicate) {
@@ -351,9 +523,9 @@ class MeasuresBuilder {
351
523
  }
352
524
  observe(callback) {
353
525
  const measures = getOrCreateMeasures(this.dsl);
354
- measures.observe(callback);
526
+ measures.observeDeep(callback);
355
527
  return ()=>{
356
- measures.unobserve(callback);
528
+ measures.unobserveDeep(callback);
357
529
  };
358
530
  }
359
531
  static isMeasureNode(node) {
@@ -374,10 +546,36 @@ class DimensionNodeBuilder {
374
546
  getField() {
375
547
  return this.yMap.get('field');
376
548
  }
549
+ getEncoding() {
550
+ return this.yMap.get('encoding');
551
+ }
552
+ getSort() {
553
+ return this.yMap.get('sort');
554
+ }
377
555
  setAlias(alias) {
378
556
  this.yMap.set('alias', alias);
379
557
  return this;
380
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
+ }
381
579
  toJSON() {
382
580
  return this.yMap.toJSON();
383
581
  }
@@ -395,44 +593,468 @@ const normalizeDimensionNodeIds = (dimensions)=>{
395
593
  });
396
594
  };
397
595
  const locateDimensionIndexById = (dimensions, dimensionId)=>dimensions.toArray().findIndex((item)=>item.get('id') === dimensionId);
398
- class DimensionsBuilder {
399
- dsl;
400
- constructor(doc, dsl){
401
- this.dsl = dsl;
402
- doc.transact(()=>{
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
+ };
1008
+ class DimensionsBuilder {
1009
+ doc;
1010
+ dsl;
1011
+ constructor(doc, dsl){
1012
+ this.doc = doc;
1013
+ this.dsl = dsl;
1014
+ doc.transact(()=>{
403
1015
  const dimensions = getOrCreateDimensions(this.dsl);
404
1016
  normalizeDimensionNodeIds(dimensions);
405
1017
  });
406
1018
  }
407
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);
408
1023
  const dimension = {
409
1024
  id: id_id.uuid(),
410
1025
  alias: field,
411
- field
1026
+ field,
1027
+ encoding
412
1028
  };
413
1029
  const yMap = new external_yjs_Map();
414
- for (const [key, value] of Object.entries(dimension))yMap.set(key, value);
415
- const dimensions = getOrCreateDimensions(this.dsl);
416
- dimensions.push([
417
- yMap
418
- ]);
419
- const node = new DimensionNodeBuilder(yMap);
420
- 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
+ });
421
1039
  return this;
422
1040
  }
423
1041
  remove(id) {
424
- const dimensions = getOrCreateDimensions(this.dsl);
425
- const index = locateDimensionIndexById(dimensions, id);
426
- 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
+ });
427
1047
  return this;
428
1048
  }
429
1049
  update(id, callback) {
430
- const dimensions = getOrCreateDimensions(this.dsl);
431
- const index = locateDimensionIndexById(dimensions, id);
432
- if (-1 === index) throw new Error(`Dimension with id "${id}" not found`);
433
- const dimensionYMap = dimensions.get(index);
434
- const node = new DimensionNodeBuilder(dimensionYMap);
435
- 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
+ });
436
1058
  return this;
437
1059
  }
438
1060
  find(predicate) {
@@ -453,9 +1075,9 @@ class DimensionsBuilder {
453
1075
  }
454
1076
  observe(callback) {
455
1077
  const dimensions = getOrCreateDimensions(this.dsl);
456
- dimensions.observe(callback);
1078
+ dimensions.observeDeep(callback);
457
1079
  return ()=>{
458
- dimensions.unobserve(callback);
1080
+ dimensions.unobserveDeep(callback);
459
1081
  };
460
1082
  }
461
1083
  static isDimensionNode(node) {
@@ -465,9 +1087,27 @@ class DimensionsBuilder {
465
1087
  return 'children' in node;
466
1088
  }
467
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
+ };
468
1106
  class ChartTypeBuilder {
1107
+ doc;
469
1108
  dsl;
470
- constructor(_doc, dsl){
1109
+ constructor(doc, dsl){
1110
+ this.doc = doc;
471
1111
  this.dsl = dsl;
472
1112
  }
473
1113
  observe(callback) {
@@ -480,11 +1120,29 @@ class ChartTypeBuilder {
480
1120
  };
481
1121
  }
482
1122
  changeChartType(chartType) {
483
- 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
+ });
484
1128
  }
485
1129
  getChartType() {
486
1130
  return this.dsl.get('chartType') || 'table';
487
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
+ }
488
1146
  toJSON() {
489
1147
  return this.dsl.get('chartType') || 'table';
490
1148
  }
@@ -492,25 +1150,35 @@ class ChartTypeBuilder {
492
1150
  return [
493
1151
  ChartTypeEnum.Table,
494
1152
  ChartTypeEnum.PivotTable,
495
- ChartTypeEnum.Line,
496
1153
  ChartTypeEnum.Column,
497
- ChartTypeEnum.ColumnPercent,
498
1154
  ChartTypeEnum.ColumnParallel,
499
- ChartTypeEnum.BarPercent,
1155
+ ChartTypeEnum.ColumnPercent,
1156
+ ChartTypeEnum.Bar,
500
1157
  ChartTypeEnum.BarParallel,
1158
+ ChartTypeEnum.BarPercent,
1159
+ ChartTypeEnum.Line,
501
1160
  ChartTypeEnum.Area,
502
1161
  ChartTypeEnum.AreaPercent,
503
1162
  ChartTypeEnum.DualAxis,
504
1163
  ChartTypeEnum.Scatter,
505
- ChartTypeEnum.Rose,
506
- ChartTypeEnum.RoseParallel,
507
1164
  ChartTypeEnum.Pie,
508
1165
  ChartTypeEnum.Donut,
1166
+ ChartTypeEnum.Rose,
1167
+ ChartTypeEnum.RoseParallel,
509
1168
  ChartTypeEnum.Radar,
510
1169
  ChartTypeEnum.Funnel,
511
1170
  ChartTypeEnum.Heatmap,
512
1171
  ChartTypeEnum.Boxplot,
513
- 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
514
1182
  ];
515
1183
  }
516
1184
  }
@@ -521,7 +1189,7 @@ function createWhereGroup(op = 'and', groupId = 'root') {
521
1189
  yMap.set('conditions', new external_yjs_Array());
522
1190
  return yMap;
523
1191
  }
524
- function where_utils_isWhereGroup(yMap) {
1192
+ function isWhereGroup(yMap) {
525
1193
  return void 0 !== yMap.get('op') && void 0 !== yMap.get('conditions');
526
1194
  }
527
1195
  function findEntry(collection, entryId) {
@@ -533,7 +1201,7 @@ function findEntry(collection, entryId) {
533
1201
  index,
534
1202
  item
535
1203
  };
536
- if (where_utils_isWhereGroup(item)) {
1204
+ if (isWhereGroup(item)) {
537
1205
  const nestedCollection = item.get('conditions');
538
1206
  const nestedMatch = findEntry(nestedCollection, entryId);
539
1207
  if (nestedMatch) return nestedMatch;
@@ -566,6 +1234,15 @@ class WhereFilterNodeBuilder {
566
1234
  this.yMap.set('value', value);
567
1235
  return this;
568
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
+ }
569
1246
  toJSON() {
570
1247
  return this.yMap.toJSON();
571
1248
  }
@@ -726,7 +1403,7 @@ class WhereFilterBuilder {
726
1403
  };
727
1404
  }
728
1405
  static isGroup(yMap) {
729
- return where_utils_isWhereGroup(yMap);
1406
+ return isWhereGroup(yMap);
730
1407
  }
731
1408
  static isNode(yMap) {
732
1409
  return void 0 !== yMap.get('field');
@@ -739,7 +1416,7 @@ function createHavingGroup(op = 'and', groupId = 'root') {
739
1416
  yMap.set('conditions', new external_yjs_Array());
740
1417
  return yMap;
741
1418
  }
742
- function having_utils_isHavingGroup(yMap) {
1419
+ function isHavingGroup(yMap) {
743
1420
  return void 0 !== yMap.get('op') && void 0 !== yMap.get('conditions');
744
1421
  }
745
1422
  function having_utils_findEntry(collection, entryId) {
@@ -751,7 +1428,7 @@ function having_utils_findEntry(collection, entryId) {
751
1428
  index,
752
1429
  item
753
1430
  };
754
- if (having_utils_isHavingGroup(item)) {
1431
+ if (isHavingGroup(item)) {
755
1432
  const nestedCollection = item.get('conditions');
756
1433
  const nestedMatch = having_utils_findEntry(nestedCollection, entryId);
757
1434
  if (nestedMatch) return nestedMatch;
@@ -772,6 +1449,9 @@ class HavingFilterNodeBuilder {
772
1449
  getOperator() {
773
1450
  return this.yMap.get('op');
774
1451
  }
1452
+ getAggregate() {
1453
+ return this.yMap.get('aggregate');
1454
+ }
775
1455
  setValue(value) {
776
1456
  this.yMap.set('value', value);
777
1457
  return this;
@@ -780,6 +1460,10 @@ class HavingFilterNodeBuilder {
780
1460
  this.yMap.set('op', operator);
781
1461
  return this;
782
1462
  }
1463
+ setAggregate(aggregate) {
1464
+ this.yMap.set('aggregate', aggregate);
1465
+ return this;
1466
+ }
783
1467
  toJSON() {
784
1468
  return this.yMap.toJSON();
785
1469
  }
@@ -806,6 +1490,9 @@ class HavingGroupBuilder {
806
1490
  const yMap = new external_yjs_Map();
807
1491
  yMap.set('id', id_id.uuid());
808
1492
  yMap.set('field', field);
1493
+ yMap.set('aggregate', {
1494
+ func: 'sum'
1495
+ });
809
1496
  this.getConditions().push([
810
1497
  yMap
811
1498
  ]);
@@ -863,6 +1550,9 @@ class HavingFilterBuilder {
863
1550
  const yMap = new external_yjs_Map();
864
1551
  yMap.set('id', id_id.uuid());
865
1552
  yMap.set('field', field);
1553
+ yMap.set('aggregate', {
1554
+ func: 'sum'
1555
+ });
866
1556
  this.getConditions().push([
867
1557
  yMap
868
1558
  ]);
@@ -940,7 +1630,7 @@ class HavingFilterBuilder {
940
1630
  };
941
1631
  }
942
1632
  static isGroup(yMap) {
943
- return having_utils_isHavingGroup(yMap);
1633
+ return isHavingGroup(yMap);
944
1634
  }
945
1635
  static isNode(yMap) {
946
1636
  return void 0 !== yMap.get('field');
@@ -1039,20 +1729,531 @@ class undo_manager_UndoManager {
1039
1729
  this.manager.clear(clearUndoStack, clearRedoStack);
1040
1730
  }
1041
1731
  }
1042
- const createBuilderFeatures = (doc, dsl)=>({
1043
- undoManager: new undo_manager_UndoManager(dsl),
1044
- chartType: new ChartTypeBuilder(doc, dsl),
1045
- measures: new MeasuresBuilder(doc, dsl),
1046
- dimensions: new DimensionsBuilder(doc, dsl),
1047
- havingFilter: new HavingFilterBuilder(doc, dsl),
1048
- whereFilter: new WhereFilterBuilder(doc, dsl),
1049
- theme: new ThemeBuilder(doc, dsl),
1050
- locale: new LocaleBuilder(doc, dsl),
1051
- limit: new LimitBuilder(doc, dsl)
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
+ };
1052
1796
  });
1053
- class VBIBuilder {
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
+ });
2233
+ const applyUpdateToDoc = (doc, update, transactionOrigin)=>{
2234
+ applyUpdate(doc, update, transactionOrigin);
2235
+ };
2236
+ const encodeDocStateAsUpdate = (doc, targetStateVector)=>encodeStateAsUpdate(doc, targetStateVector);
2237
+ const buildVBIChartDSL = (dsl)=>dsl.toJSON();
2238
+ const getCollectionLength = (value)=>{
2239
+ if (value instanceof external_yjs_Array) return value.length;
2240
+ if (Array.isArray(value)) return value.length;
2241
+ return 0;
2242
+ };
2243
+ const isEmptyVBIChartDSL = (dsl)=>{
2244
+ const dimensionsLength = getCollectionLength(dsl.get('dimensions'));
2245
+ const measuresLength = getCollectionLength(dsl.get('measures'));
2246
+ return 0 === dimensionsLength && 0 === measuresLength;
2247
+ };
2248
+ const getBuilderSchema = async (dsl)=>{
2249
+ const connectorId = dsl.get('connectorId');
2250
+ const connector = await getConnector(connectorId);
2251
+ return connector.discoverSchema();
2252
+ };
2253
+ class VBIChartBuilder {
1054
2254
  doc;
1055
2255
  dsl;
2256
+ adapters;
1056
2257
  chartType;
1057
2258
  measures;
1058
2259
  dimensions;
@@ -1062,39 +2263,52 @@ class VBIBuilder {
1062
2263
  locale;
1063
2264
  limit;
1064
2265
  undoManager;
1065
- constructor(doc){
2266
+ constructor(doc, options){
1066
2267
  this.doc = doc;
1067
2268
  this.dsl = doc.getMap('dsl');
1068
- const features = createBuilderFeatures(doc, this.dsl);
1069
- this.undoManager = features.undoManager;
1070
- this.chartType = features.chartType;
1071
- this.measures = features.measures;
1072
- this.dimensions = features.dimensions;
1073
- this.havingFilter = features.havingFilter;
1074
- this.whereFilter = features.whereFilter;
1075
- this.theme = features.theme;
1076
- this.locale = features.locale;
1077
- this.limit = features.limit;
2269
+ this.adapters = resolveVBIChartBuilderAdapters(options?.adapters);
2270
+ this.undoManager = new undo_manager_UndoManager(this.dsl);
2271
+ this.chartType = new ChartTypeBuilder(doc, this.dsl);
2272
+ this.measures = new MeasuresBuilder(doc, this.dsl);
2273
+ this.dimensions = new DimensionsBuilder(doc, this.dsl);
2274
+ this.havingFilter = new HavingFilterBuilder(doc, this.dsl);
2275
+ this.whereFilter = new WhereFilterBuilder(doc, this.dsl);
2276
+ this.theme = new ThemeBuilder(doc, this.dsl);
2277
+ this.locale = new LocaleBuilder(doc, this.dsl);
2278
+ this.limit = new LimitBuilder(doc, this.dsl);
1078
2279
  }
1079
2280
  applyUpdate = (update, transactionOrigin)=>applyUpdateToDoc(this.doc, update, transactionOrigin);
1080
2281
  encodeStateAsUpdate = (targetStateVector)=>encodeDocStateAsUpdate(this.doc, targetStateVector);
1081
- buildVSeed = async ()=>{
2282
+ buildVSeed = async (options = {})=>{
1082
2283
  const vbiDSL = this.build();
1083
- const queryDSL = this.buildVQuery();
1084
- return buildVSeedDSL({
2284
+ const queryDSL = this.adapters.buildVQuery({
2285
+ dsl: this.dsl,
1085
2286
  vbiDSL,
1086
- queryDSL
2287
+ builder: this
2288
+ });
2289
+ return this.adapters.buildVSeed({
2290
+ dsl: this.dsl,
2291
+ vbiDSL,
2292
+ queryDSL,
2293
+ options,
2294
+ builder: this
2295
+ });
2296
+ };
2297
+ buildVQuery = ()=>{
2298
+ const vbiDSL = this.build();
2299
+ return this.adapters.buildVQuery({
2300
+ dsl: this.dsl,
2301
+ vbiDSL,
2302
+ builder: this
1087
2303
  });
1088
2304
  };
1089
- buildVQuery = ()=>buildVQueryDSL(this.dsl, this);
1090
- build = ()=>buildVBIDSL(this.dsl);
1091
- isEmpty = ()=>isEmptyVBIDSL(this.dsl);
2305
+ build = ()=>buildVBIChartDSL(this.dsl);
2306
+ isEmpty = ()=>isEmptyVBIChartDSL(this.dsl);
1092
2307
  getSchema = async ()=>getBuilderSchema(this.dsl);
1093
2308
  }
1094
2309
  const shouldEnsureIdForObject = (obj, ensureId)=>{
1095
2310
  if (true === ensureId) return true;
1096
- if ('field' === ensureId) return 'string' == typeof obj.field;
1097
- return false;
2311
+ return 'field' === ensureId && 'string' == typeof obj.field;
1098
2312
  };
1099
2313
  const toYMap = (obj, ensureId = false)=>{
1100
2314
  const yMap = new external_yjs_Map();
@@ -1102,23 +2316,9 @@ const toYMap = (obj, ensureId = false)=>{
1102
2316
  for (const [key, value] of Object.entries(obj)){
1103
2317
  if (('conditions' === key || 'children' === key) && Array.isArray(value)) {
1104
2318
  const yArr = new external_yjs_Array();
1105
- for (const child of value){
1106
- if (child instanceof external_yjs_Map) {
1107
- yArr.push([
1108
- child
1109
- ]);
1110
- continue;
1111
- }
1112
- if ('object' == typeof child && null !== child) {
1113
- yArr.push([
1114
- toYMap(child, ensureId)
1115
- ]);
1116
- continue;
1117
- }
1118
- yArr.push([
1119
- child
1120
- ]);
1121
- }
2319
+ for (const child of value)yArr.push([
2320
+ toYMap(child, ensureId)
2321
+ ]);
1122
2322
  yMap.set(key, yArr);
1123
2323
  continue;
1124
2324
  }
@@ -1128,65 +2328,34 @@ const toYMap = (obj, ensureId = false)=>{
1128
2328
  };
1129
2329
  const ensureYArray = (arr, ensureId = false)=>{
1130
2330
  if (!arr) return new external_yjs_Array();
1131
- if (arr instanceof external_yjs_Array) return arr;
1132
2331
  const yArr = new external_yjs_Array();
1133
- for (const item of arr){
1134
- if (item instanceof external_yjs_Map) {
1135
- yArr.push([
1136
- item
1137
- ]);
1138
- continue;
1139
- }
1140
- if ('object' == typeof item && null !== item) {
1141
- yArr.push([
1142
- toYMap(item, ensureId)
1143
- ]);
1144
- continue;
1145
- }
1146
- yArr.push([
1147
- item
1148
- ]);
1149
- }
2332
+ for (const item of arr)yArr.push([
2333
+ toYMap(item, ensureId)
2334
+ ]);
1150
2335
  return yArr;
1151
2336
  };
1152
- const getDefaultWhereFilter = ()=>({
2337
+ const ensureWhereGroup = (whereFilter)=>{
2338
+ const source = whereFilter ?? {
1153
2339
  id: 'root',
1154
2340
  op: 'and',
1155
2341
  conditions: []
1156
- });
1157
- const isFilterGroupInput = (value)=>'object' == typeof value && null !== value;
1158
- const ensureWhereGroup = (whereFilter)=>{
1159
- const sourceWhereFilter = whereFilter instanceof external_yjs_Map || isFilterGroupInput(whereFilter) ? whereFilter : getDefaultWhereFilter();
1160
- const whereGroup = sourceWhereFilter instanceof external_yjs_Map ? sourceWhereFilter : createWhereGroup();
1161
- if (sourceWhereFilter instanceof external_yjs_Map) {
1162
- if (!(whereGroup.get('conditions') instanceof external_yjs_Array)) whereGroup.set('conditions', new external_yjs_Array());
1163
- if (!whereGroup.get('id')) whereGroup.set('id', 'root');
1164
- if (!whereGroup.get('op')) whereGroup.set('op', 'and');
1165
- return whereGroup;
1166
- }
1167
- whereGroup.set('id', sourceWhereFilter.id ?? 'root');
1168
- whereGroup.set('op', sourceWhereFilter.op ?? 'and');
1169
- 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));
1170
2347
  return whereGroup;
1171
2348
  };
1172
- const getDefaultHavingFilter = ()=>({
2349
+ const ensureHavingGroup = (havingFilter)=>{
2350
+ const source = havingFilter ?? {
1173
2351
  id: 'root',
1174
2352
  op: 'and',
1175
2353
  conditions: []
1176
- });
1177
- const ensure_having_group_isFilterGroupInput = (value)=>'object' == typeof value && null !== value;
1178
- const ensureHavingGroup = (havingFilter)=>{
1179
- const sourceHavingFilter = havingFilter instanceof external_yjs_Map || ensure_having_group_isFilterGroupInput(havingFilter) ? havingFilter : getDefaultHavingFilter();
1180
- const havingGroup = sourceHavingFilter instanceof external_yjs_Map ? sourceHavingFilter : createHavingGroup();
1181
- if (sourceHavingFilter instanceof external_yjs_Map) {
1182
- if (!(havingGroup.get('conditions') instanceof external_yjs_Array)) havingGroup.set('conditions', new external_yjs_Array());
1183
- if (!havingGroup.get('id')) havingGroup.set('id', 'root');
1184
- if (!havingGroup.get('op')) havingGroup.set('op', 'and');
1185
- return havingGroup;
1186
- }
1187
- havingGroup.set('id', sourceHavingFilter.id ?? 'root');
1188
- havingGroup.set('op', sourceHavingFilter.op ?? 'and');
1189
- 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));
1190
2359
  return havingGroup;
1191
2360
  };
1192
2361
  const setBaseDSLFields = (dsl, vbi)=>{
@@ -1197,7 +2366,7 @@ const setBaseDSLFields = (dsl, vbi)=>{
1197
2366
  if (vbi.locale) dsl.set('locale', vbi.locale);
1198
2367
  if (void 0 !== vbi.version) dsl.set('version', vbi.version);
1199
2368
  };
1200
- const fromVBIDSLInput = (vbi)=>{
2369
+ const createChartBuilderFromVBIChartDSLInput = (vbi, options)=>{
1201
2370
  const doc = new Doc();
1202
2371
  const dsl = doc.getMap('dsl');
1203
2372
  doc.transact(()=>{
@@ -1207,9 +2376,9 @@ const fromVBIDSLInput = (vbi)=>{
1207
2376
  dsl.set('measures', ensureYArray(vbi.measures, 'field'));
1208
2377
  dsl.set('dimensions', ensureYArray(vbi.dimensions, 'field'));
1209
2378
  });
1210
- return new VBIBuilder(doc);
2379
+ return new VBIChartBuilder(doc, options);
1211
2380
  };
1212
- const generateEmptyDSL = (connectorId)=>({
2381
+ const generateEmptyChartDSL = (connectorId)=>({
1213
2382
  connectorId,
1214
2383
  chartType: 'table',
1215
2384
  measures: [],
@@ -1228,13 +2397,27 @@ const generateEmptyDSL = (connectorId)=>({
1228
2397
  locale: 'zh-CN',
1229
2398
  version: 0
1230
2399
  });
1231
- const createVBI = ()=>({
2400
+ const mergeBuilderOptions = (base, overrides)=>{
2401
+ if (!base) return overrides;
2402
+ if (!overrides) return base;
2403
+ return {
2404
+ ...base,
2405
+ ...overrides,
2406
+ adapters: {
2407
+ ...base.adapters,
2408
+ ...overrides.adapters
2409
+ }
2410
+ };
2411
+ };
2412
+ function createVBI(defaultBuilderOptions) {
2413
+ const createChart = (vbi, builderOptions)=>createChartBuilderFromVBIChartDSLInput(vbi, mergeBuilderOptions(defaultBuilderOptions, builderOptions));
2414
+ return {
1232
2415
  connectorMap: connectorMap,
1233
2416
  registerConnector: registerConnector,
1234
2417
  getConnector: getConnector,
1235
- generateEmptyDSL: generateEmptyDSL,
1236
- from: fromVBIDSLInput,
1237
- create: fromVBIDSLInput
1238
- });
2418
+ generateEmptyChartDSL: generateEmptyChartDSL,
2419
+ createChart
2420
+ };
2421
+ }
1239
2422
  const VBI = createVBI();
1240
- export { ChartTypeBuilder, DimensionsBuilder, HavingFilterBuilder, LimitBuilder, LocaleBuilder, MeasuresBuilder, ThemeBuilder, undo_manager_UndoManager as UndoManager, VBI, VBIBuilder, WhereFilterBuilder, buildVQuery, findTreeNodesBy, id_id as id, isVBIFilter, isVBIHavingFilter, isVBIHavingGroup, isVBIWhereGroup, preorderTraverse };
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 };