@kanaries/graphic-walker 0.3.15 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/App.d.ts +9 -2
  2. package/dist/assets/filter.worker-f09fcd6f.js.map +1 -1
  3. package/dist/assets/sort.worker-f77540ac.js.map +1 -0
  4. package/dist/assets/transform.worker-bae8e910.js.map +1 -0
  5. package/dist/assets/{viewQuery.worker-03404216.js.map → viewQuery.worker-bdb6477c.js.map} +1 -1
  6. package/dist/components/askViz/index.d.ts +6 -0
  7. package/dist/components/askViz/schemaTransform.d.ts +2 -0
  8. package/dist/components/dataTable/index.d.ts +8 -5
  9. package/dist/components/limitSetting.d.ts +5 -0
  10. package/dist/components/pivotTable/store.d.ts +0 -2
  11. package/dist/components/spinner.d.ts +2 -0
  12. package/dist/computation/clientComputation.d.ts +3 -0
  13. package/dist/computation/serverComputation.d.ts +8 -0
  14. package/dist/config.d.ts +3 -1
  15. package/dist/fields/filterField/tabs.d.ts +2 -1
  16. package/dist/graphic-walker.es.js +16181 -15523
  17. package/dist/graphic-walker.es.js.map +1 -1
  18. package/dist/graphic-walker.umd.js +144 -144
  19. package/dist/graphic-walker.umd.js.map +1 -1
  20. package/dist/hooks/index.d.ts +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/interfaces.d.ts +93 -4
  23. package/dist/lib/execExp.d.ts +4 -4
  24. package/dist/lib/interfaces.d.ts +1 -0
  25. package/dist/lib/viewQuery.d.ts +2 -2
  26. package/dist/renderer/hooks.d.ts +10 -4
  27. package/dist/renderer/index.d.ts +2 -1
  28. package/dist/renderer/pureRenderer.d.ts +17 -1
  29. package/dist/renderer/specRenderer.d.ts +1 -0
  30. package/dist/services.d.ts +8 -4
  31. package/dist/store/commonStore.d.ts +2 -2
  32. package/dist/store/visualSpecStore.d.ts +58 -40
  33. package/dist/utils/save.d.ts +10 -2
  34. package/dist/utils/workflow.d.ts +3 -0
  35. package/dist/vis/react-vega.d.ts +3 -1
  36. package/dist/workers/sort.d.ts +2 -0
  37. package/dist/workers/sort.worker.d.ts +1 -0
  38. package/dist/workers/transform.d.ts +5 -2
  39. package/package.json +2 -2
  40. package/src/App.tsx +46 -7
  41. package/src/components/askViz/index.tsx +92 -0
  42. package/src/components/askViz/schemaTransform.ts +38 -0
  43. package/src/components/dataTable/index.tsx +51 -11
  44. package/src/components/limitSetting.tsx +38 -0
  45. package/src/components/pivotTable/index.tsx +0 -1
  46. package/src/components/pivotTable/store.tsx +0 -16
  47. package/src/components/spinner.tsx +14 -0
  48. package/src/components/toggle.tsx +2 -2
  49. package/src/components/visualConfig/index.tsx +78 -8
  50. package/src/computation/clientComputation.ts +55 -0
  51. package/src/computation/serverComputation.ts +153 -0
  52. package/src/config.ts +15 -2
  53. package/src/dataSource/datasetConfig/index.tsx +38 -6
  54. package/src/dataSource/table.tsx +11 -2
  55. package/src/fields/filterField/filterEditDialog.tsx +11 -10
  56. package/src/fields/filterField/tabs.tsx +178 -77
  57. package/src/hooks/index.ts +10 -0
  58. package/src/index.tsx +2 -0
  59. package/src/interfaces.ts +108 -5
  60. package/src/lib/execExp.ts +20 -11
  61. package/src/lib/interfaces.ts +1 -0
  62. package/src/lib/op/aggregate.ts +1 -1
  63. package/src/lib/viewQuery.ts +2 -2
  64. package/src/locales/en-US.json +12 -2
  65. package/src/locales/ja-JP.json +12 -2
  66. package/src/locales/zh-CN.json +12 -2
  67. package/src/main.tsx +1 -1
  68. package/src/renderer/hooks.ts +113 -49
  69. package/src/renderer/index.tsx +32 -18
  70. package/src/renderer/pureRenderer.tsx +44 -22
  71. package/src/renderer/specRenderer.tsx +24 -7
  72. package/src/services.ts +30 -9
  73. package/src/store/commonStore.ts +7 -7
  74. package/src/store/visualSpecStore.ts +300 -193
  75. package/src/utils/save.ts +81 -3
  76. package/src/utils/workflow.ts +148 -0
  77. package/src/vis/react-vega.tsx +21 -6
  78. package/src/vis/spec/aggregate.ts +3 -2
  79. package/src/vis/spec/stack.ts +7 -6
  80. package/src/visualSettings/index.tsx +22 -1
  81. package/src/workers/filter.worker.js +1 -1
  82. package/src/workers/sort.ts +22 -0
  83. package/src/workers/sort.worker.ts +21 -0
  84. package/src/workers/transform.ts +7 -8
  85. package/src/workers/transform.worker.js +2 -2
  86. package/src/workers/viewQuery.worker.js +2 -2
  87. package/dist/assets/transform.worker-a12fb3d8.js.map +0 -1
@@ -1,46 +1,70 @@
1
- import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from "mobx";
2
- import produce from "immer";
3
- import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisSpecForExport, IFilterFieldForExport, IVisualConfig, Specification } from "../interfaces";
4
- import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
5
- import { VisSpecWithHistory } from "../models/visSpecHistory";
6
- import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
7
- import { CommonStore } from "./commonStore";
8
- import { createCountField } from "../utils";
9
- import { nanoid } from "nanoid";
1
+ import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from 'mobx';
2
+ import produce from 'immer';
3
+ import {
4
+ DataSet,
5
+ DraggableFieldState,
6
+ IFilterRule,
7
+ ISortMode,
8
+ IStackMode,
9
+ IViewField,
10
+ IVisSpec,
11
+ IVisSpecForExport,
12
+ IFilterFieldForExport,
13
+ IVisualConfig,
14
+ Specification,
15
+ IComputationFunction,
16
+ } from '../interfaces';
17
+ import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from '../config';
18
+ import { VisSpecWithHistory } from '../models/visSpecHistory';
19
+ import {
20
+ IStoInfo,
21
+ dumpsGWPureSpec,
22
+ parseGWContent,
23
+ parseGWPureSpec,
24
+ stringifyGWContent,
25
+ initVisualConfig,
26
+ forwardVisualConfigs,
27
+ visSpecDecoder,
28
+ } from '../utils/save';
29
+ import { CommonStore } from './commonStore';
30
+ import { createCountField } from '../utils';
31
+ import { COUNT_FIELD_ID } from '../constants';
32
+ import { nanoid } from 'nanoid';
33
+ import { toWorkflow } from '../utils/workflow';
10
34
 
11
35
  function getChannelSizeLimit(channel: string): number {
12
- if (typeof CHANNEL_LIMIT[channel] === "undefined") return Infinity;
36
+ if (typeof CHANNEL_LIMIT[channel] === 'undefined') return Infinity;
13
37
  return CHANNEL_LIMIT[channel];
14
38
  }
15
39
 
16
40
  function uniqueId(): string {
17
- return "gw_" + nanoid(4);
41
+ return 'gw_' + nanoid(4);
18
42
  }
19
43
 
20
44
  function geomAdapter(geom: string) {
21
45
  switch (geom) {
22
- case "interval":
23
- case "bar":
24
- return "bar";
25
- case "line":
26
- return "line";
27
- case "boxplot":
28
- return "boxplot";
29
- case "area":
30
- return "area";
31
- case "point":
32
- return "point";
33
- case "arc":
34
- return "arc";
35
- case "circle":
36
- return "circle";
37
- case "heatmap":
38
- return "circle";
39
- case "rect":
40
- return "rect";
41
- case "tick":
46
+ case 'interval':
47
+ case 'bar':
48
+ return 'bar';
49
+ case 'line':
50
+ return 'line';
51
+ case 'boxplot':
52
+ return 'boxplot';
53
+ case 'area':
54
+ return 'area';
55
+ case 'point':
56
+ return 'point';
57
+ case 'arc':
58
+ return 'arc';
59
+ case 'circle':
60
+ return 'circle';
61
+ case 'heatmap':
62
+ return 'circle';
63
+ case 'rect':
64
+ return 'rect';
65
+ case 'tick':
42
66
  default:
43
- return "tick";
67
+ return 'tick';
44
68
  }
45
69
  }
46
70
 
@@ -62,42 +86,33 @@ export function initEncoding(): DraggableFieldState {
62
86
  };
63
87
  }
64
88
 
65
- export function initVisualConfig(): IVisualConfig {
66
- return {
67
- defaultAggregated: true,
68
- geoms: [GEMO_TYPES[0]!],
69
- stack: "stack",
70
- showActions: false,
71
- interactiveScale: false,
72
- sorted: "none",
73
- zeroScale: true,
74
- size: {
75
- mode: "auto",
76
- width: 320,
77
- height: 200,
78
- },
79
- format: {
80
- numberFormat: undefined,
81
- timeFormat: undefined,
82
- normalizedNumberFormat: undefined
83
- }
84
- };
89
+ function stackValueTransform(vlValue: string | undefined | null): IStackMode {
90
+ if (vlValue === 'center') return 'center';
91
+ if (vlValue === 'normalize') return 'normalize';
92
+ if (vlValue === 'zero') return 'zero';
93
+ return 'none';
94
+ }
95
+
96
+ function sortValueTransform(vlValue: object | string | null): ISortMode {
97
+ let order: string = 'none';
98
+ if (typeof vlValue === 'string') {
99
+ order = vlValue;
100
+ } else if (vlValue && vlValue instanceof Object) {
101
+ order = vlValue['order'] ?? 'ascending';
102
+ }
103
+ if (order !== 'none') {
104
+ const channels: string[] = ['x', 'y', 'color', 'size', 'opacity'];
105
+ // TODO: support all sorting config in vl
106
+ if (order.startsWith('-') || order === 'descending') return 'descending';
107
+ if (channels.indexOf(order) > -1 || order === 'ascending') return 'ascending';
108
+ }
109
+ return 'none';
85
110
  }
86
111
 
87
112
  type DeepReadonly<T extends Record<keyof any, any>> = {
88
113
  readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
89
114
  };
90
115
 
91
- const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpecForExport[] => {
92
- return backwards.map((content) => ({
93
- ...content,
94
- config: {
95
- ...initVisualConfig(),
96
- ...content.config,
97
- },
98
- }));
99
- };
100
-
101
116
  function isDraggableStateEmpty(state: DeepReadonly<DraggableFieldState>): boolean {
102
117
  return Object.values(state).every((value) => value.length === 0);
103
118
  }
@@ -143,6 +158,8 @@ export class VizSpecStore {
143
158
  public canUndo = false;
144
159
  public canRedo = false;
145
160
  public editingFilterIdx: number | null = null;
161
+ // TODO
162
+ public computationFuction: IComputationFunction = async () => [];
146
163
  constructor(commonStore: CommonStore) {
147
164
  this.commonStore = commonStore;
148
165
  this.draggableFieldState = initEncoding();
@@ -157,6 +174,7 @@ export class VizSpecStore {
157
174
  );
158
175
  makeAutoObservable(this, {
159
176
  visList: observable.shallow,
177
+ computationFuction: observable.ref,
160
178
  // @ts-expect-error private fields are not supported
161
179
  reactions: false,
162
180
  });
@@ -204,8 +222,7 @@ export class VizSpecStore {
204
222
  private useMutable(cb: (tab: { encodings: DraggableFieldState; config: IVisualConfig }) => void) {
205
223
  if (this.__dangerous_is_inside_useMutable__) {
206
224
  throw new Error(
207
- "A recursive call of useMutable() is detected, " +
208
- "this is prevented because update will be overwritten by parent execution context."
225
+ 'A recursive call of useMutable() is detected, ' + 'this is prevented because update will be overwritten by parent execution context.'
209
226
  );
210
227
  }
211
228
 
@@ -264,12 +281,12 @@ export class VizSpecStore {
264
281
  */
265
282
  public get viewDimensions(): IViewField[] {
266
283
  const { draggableFieldState } = this;
267
- const state = toJS(draggableFieldState);
284
+ const { filters, ...state } = toJS(draggableFieldState);
268
285
  const fields: IViewField[] = [];
269
286
  (Object.keys(state) as (keyof DraggableFieldState)[])
270
287
  .filter((dkey) => !MetaFieldKeys.includes(dkey))
271
288
  .forEach((dkey) => {
272
- fields.push(...state[dkey].filter((f) => f.analyticType === "dimension"));
289
+ fields.push(...state[dkey].filter((f) => f.analyticType === 'dimension'));
273
290
  });
274
291
  return fields;
275
292
  }
@@ -278,12 +295,12 @@ export class VizSpecStore {
278
295
  */
279
296
  public get viewMeasures(): IViewField[] {
280
297
  const { draggableFieldState } = this;
281
- const state = toJS(draggableFieldState);
298
+ const { filters, ...state } = toJS(draggableFieldState);
282
299
  const fields: IViewField[] = [];
283
300
  (Object.keys(state) as (keyof DraggableFieldState)[])
284
301
  .filter((dkey) => !MetaFieldKeys.includes(dkey))
285
302
  .forEach((dkey) => {
286
- fields.push(...state[dkey].filter((f) => f.analyticType === "measure"));
303
+ fields.push(...state[dkey].filter((f) => f.analyticType === 'measure'));
287
304
  });
288
305
  return fields;
289
306
  }
@@ -299,7 +316,6 @@ export class VizSpecStore {
299
316
  return state.filters;
300
317
  }
301
318
 
302
-
303
319
  public addVisualization(defaultName?: string) {
304
320
  const name = defaultName || 'Chart ' + (this.visList.length + 1);
305
321
  this.visList.push(
@@ -318,8 +334,8 @@ export class VizSpecStore {
318
334
  public setVisName(visIndex: number, name: string) {
319
335
  this.visList[visIndex] = this.visList[visIndex].clone();
320
336
  this.visList[visIndex].updateLatest({
321
- name
322
- })
337
+ name,
338
+ });
323
339
  }
324
340
  public initState() {
325
341
  this.useMutable((tab) => {
@@ -331,7 +347,7 @@ export class VizSpecStore {
331
347
  const countField = createCountField();
332
348
  this.useMutable(({ encodings }) => {
333
349
  encodings.dimensions = dataset.rawFields
334
- .filter((f) => f.analyticType === "dimension")
350
+ .filter((f) => f.analyticType === 'dimension')
335
351
  .map((f) => ({
336
352
  dragId: uniqueId(),
337
353
  fid: f.fid,
@@ -341,7 +357,7 @@ export class VizSpecStore {
341
357
  analyticType: f.analyticType,
342
358
  }));
343
359
  encodings.measures = dataset.rawFields
344
- .filter((f) => f.analyticType === "measure")
360
+ .filter((f) => f.analyticType === 'measure')
345
361
  .map((f) => ({
346
362
  dragId: uniqueId(),
347
363
  fid: f.fid,
@@ -349,13 +365,16 @@ export class VizSpecStore {
349
365
  basename: f.basename || f.name || f.fid,
350
366
  analyticType: f.analyticType,
351
367
  semanticType: f.semanticType,
352
- aggName: "sum",
368
+ aggName: 'sum',
353
369
  }));
354
370
  encodings.measures.push(countField);
355
371
  });
356
372
 
357
373
  this.freezeHistory();
358
374
  }
375
+ /**
376
+ * clear all config in draggable state
377
+ */
359
378
  public clearState() {
360
379
  this.useMutable(({ encodings }) => {
361
380
  for (let key in encodings) {
@@ -368,31 +387,33 @@ export class VizSpecStore {
368
387
  public setVisualConfig<K extends keyof IVisualConfig>(configKey: K, value: IVisualConfig[K]) {
369
388
  this.useMutable(({ config }) => {
370
389
  switch (true) {
371
- case ["defaultAggregated", "defaultStack", "showActions", "interactiveScale"].includes(configKey): {
390
+ case ['defaultAggregated', 'defaultStack', 'showActions', 'interactiveScale'].includes(configKey): {
372
391
  return ((config as unknown as { [k: string]: boolean })[configKey] = Boolean(value));
373
392
  }
374
- case configKey === "geoms" && Array.isArray(value):
375
- case configKey === "size" && typeof value === "object":
376
- case configKey === "sorted":
377
- case configKey === "zeroScale":
378
- case configKey === "stack": {
393
+ case configKey === 'geoms' && Array.isArray(value):
394
+ case configKey === 'size' && typeof value === 'object':
395
+ case configKey === 'sorted':
396
+ case configKey === 'zeroScale':
397
+ case configKey === 'background':
398
+ case configKey === 'limit':
399
+ case configKey === 'stack': {
379
400
  return (config[configKey] = value);
380
401
  }
381
- case configKey === 'format' && typeof value === "object": {
382
- return config[configKey] = value
402
+ case configKey === 'format' && typeof value === 'object': {
403
+ return (config[configKey] = value);
383
404
  }
384
405
 
385
406
  default: {
386
- console.error("[unknown key] " + configKey + " You should registered visualConfig at setVisualConfig");
407
+ console.error('[unknown key] ' + configKey + ' You should registered visualConfig at setVisualConfig');
387
408
  }
388
409
  }
389
410
  });
390
411
  }
391
- public transformCoord(coord: "cartesian" | "polar") {
392
- if (coord === "polar") {
412
+ public transformCoord(coord: 'cartesian' | 'polar') {
413
+ if (coord === 'polar') {
393
414
  }
394
415
  }
395
- public setChartLayout(props: { mode: IVisualConfig["size"]["mode"]; width?: number; height?: number }) {
416
+ public setChartLayout(props: { mode: IVisualConfig['size']['mode']; width?: number; height?: number }) {
396
417
  this.useMutable(({ config }) => {
397
418
  const { mode = config.size.mode, width = config.size.width, height = config.size.height } = props;
398
419
 
@@ -411,15 +432,10 @@ export class VizSpecStore {
411
432
  fields.splice(destinationIndex, 0, field);
412
433
  });
413
434
  }
414
- public moveField(
415
- sourceKey: keyof DraggableFieldState,
416
- sourceIndex: number,
417
- destinationKey: keyof DraggableFieldState,
418
- destinationIndex: number
419
- ) {
420
- if (sourceKey === "filters") {
435
+ public moveField(sourceKey: keyof DraggableFieldState, sourceIndex: number, destinationKey: keyof DraggableFieldState, destinationIndex: number) {
436
+ if (sourceKey === 'filters') {
421
437
  return this.removeField(sourceKey, sourceIndex);
422
- } else if (destinationKey === "filters") {
438
+ } else if (destinationKey === 'filters') {
423
439
  return this.appendFilter(destinationIndex, this.draggableFieldState[sourceKey][sourceIndex]);
424
440
  }
425
441
 
@@ -439,7 +455,7 @@ export class VizSpecStore {
439
455
  if (MetaFieldKeys.includes(destinationKey)) {
440
456
  if (!MetaFieldKeys.includes(sourceKey)) return;
441
457
  encodings[sourceKey].splice(sourceIndex, 1);
442
- movingField.analyticType = destinationKey === "dimensions" ? "dimension" : "measure";
458
+ movingField.analyticType = destinationKey === 'dimensions' ? 'dimension' : 'measure';
443
459
  }
444
460
  const limitSize = getChannelSizeLimit(destinationKey);
445
461
  const fixedDestinationIndex = Math.min(destinationIndex, limitSize - 1);
@@ -457,10 +473,7 @@ export class VizSpecStore {
457
473
  }
458
474
  public replaceField(sourceKey: keyof DraggableFieldState, sourceIndex: number, fid: string) {
459
475
  if (MetaFieldKeys.includes(sourceKey)) return;
460
- const enteringField = [
461
- ...this.draggableFieldState.dimensions,
462
- ...this.draggableFieldState.measures
463
- ].find(which => which.fid === fid);
476
+ const enteringField = [...this.draggableFieldState.dimensions, ...this.draggableFieldState.measures].find((which) => which.fid === fid);
464
477
  if (!enteringField) {
465
478
  return;
466
479
  }
@@ -499,16 +512,23 @@ export class VizSpecStore {
499
512
  encodings.rows = fieldsInCup as typeof encodings.rows; // assume this as writable
500
513
  });
501
514
  }
502
- public createBinField(stateKey: keyof DraggableFieldState, index: number, binType: 'bin' | 'binCount') {
515
+ public createBinField(stateKey: keyof DraggableFieldState, index: number, binType: 'bin' | 'binCount'): string {
516
+ const newVarKey = uniqueId();
517
+ const state = this.draggableFieldState;
518
+ const existedRelatedBinField = state.dimensions.find(
519
+ (f) => f.computed && f.expression && f.expression.op === binType && f.expression.params[0].value === state[stateKey][index].fid
520
+ );
521
+ if (existedRelatedBinField) {
522
+ return existedRelatedBinField.fid;
523
+ }
503
524
  this.useMutable(({ encodings }) => {
504
525
  const originField = encodings[stateKey][index];
505
- const newVarKey = uniqueId();
506
526
  const binField: IViewField = {
507
527
  fid: newVarKey,
508
528
  dragId: newVarKey,
509
529
  name: `${binType}(${originField.name})`,
510
- semanticType: "ordinal",
511
- analyticType: "dimension",
530
+ semanticType: 'ordinal',
531
+ analyticType: 'dimension',
512
532
  computed: true,
513
533
  expression: {
514
534
  op: binType,
@@ -516,16 +536,17 @@ export class VizSpecStore {
516
536
  params: [
517
537
  {
518
538
  type: 'field',
519
- value: originField.fid
520
- }
521
- ]
522
- }
539
+ value: originField.fid,
540
+ },
541
+ ],
542
+ },
523
543
  };
524
544
  encodings.dimensions.push(binField);
525
545
  });
546
+ return newVarKey;
526
547
  }
527
548
  public createLogField(stateKey: keyof DraggableFieldState, index: number, scaleType: 'log10' | 'log2') {
528
- if (stateKey === "filters") {
549
+ if (stateKey === 'filters') {
529
550
  return;
530
551
  }
531
552
 
@@ -536,7 +557,7 @@ export class VizSpecStore {
536
557
  fid: newVarKey,
537
558
  dragId: newVarKey,
538
559
  name: `${scaleType}(${originField.name})`,
539
- semanticType: "quantitative",
560
+ semanticType: 'quantitative',
540
561
  analyticType: originField.analyticType,
541
562
  aggName: 'sum',
542
563
  computed: true,
@@ -546,10 +567,10 @@ export class VizSpecStore {
546
567
  params: [
547
568
  {
548
569
  type: 'field',
549
- value: originField.fid
550
- }
551
- ]
552
- }
570
+ value: originField.fid,
571
+ },
572
+ ],
573
+ },
553
574
  };
554
575
  encodings[stateKey].push(logField);
555
576
  });
@@ -567,74 +588,150 @@ export class VizSpecStore {
567
588
  const { rows, columns } = this.draggableFieldState;
568
589
  const yField = rows.length > 0 ? rows[rows.length - 1] : null;
569
590
  const xField = columns.length > 0 ? columns[columns.length - 1] : null;
570
- if (
571
- xField !== null &&
572
- xField.analyticType === "dimension" &&
573
- yField !== null &&
574
- yField.analyticType === "measure"
575
- ) {
591
+ if (xField !== null && xField.analyticType === 'dimension' && yField !== null && yField.analyticType === 'measure') {
576
592
  return true;
577
593
  }
578
- if (
579
- xField !== null &&
580
- xField.analyticType === "measure" &&
581
- yField !== null &&
582
- yField.analyticType === "dimension"
583
- ) {
594
+ if (xField !== null && xField.analyticType === 'measure' && yField !== null && yField.analyticType === 'dimension') {
584
595
  return true;
585
596
  }
586
597
  return false;
587
598
  }
588
- public setFieldSort(
589
- stateKey: keyof DraggableFieldState,
590
- index: number,
591
- sortType: "none" | "ascending" | "descending"
592
- ) {
599
+ public setFieldSort(stateKey: keyof DraggableFieldState, index: number, sortType: ISortMode) {
593
600
  this.useMutable(({ encodings }) => {
594
601
  encodings[stateKey][index].sort = sortType;
595
602
  });
596
603
  }
597
- public applyDefaultSort(sortType: "none" | "ascending" | "descending" = "ascending") {
604
+ public applyDefaultSort(sortType: ISortMode = 'ascending') {
598
605
  this.useMutable(({ encodings }) => {
599
606
  const { rows, columns } = encodings;
600
607
  const yField = rows.length > 0 ? rows[rows.length - 1] : null;
601
608
  const xField = columns.length > 0 ? columns[columns.length - 1] : null;
602
609
 
603
- if (
604
- xField !== null &&
605
- xField.analyticType === "dimension" &&
606
- yField !== null &&
607
- yField.analyticType === "measure"
608
- ) {
610
+ if (xField !== null && xField.analyticType === 'dimension' && yField !== null && yField.analyticType === 'measure') {
609
611
  encodings.columns[columns.length - 1].sort = sortType;
610
612
  return;
611
613
  }
612
- if (
613
- xField !== null &&
614
- xField.analyticType === "measure" &&
615
- yField !== null &&
616
- yField.analyticType === "dimension"
617
- ) {
614
+ if (xField !== null && xField.analyticType === 'measure' && yField !== null && yField.analyticType === 'dimension') {
618
615
  encodings.rows[rows.length - 1].sort = sortType;
619
616
  return;
620
617
  }
621
618
  });
622
619
  }
623
- public appendField(destinationKey: keyof DraggableFieldState, field: IViewField | undefined) {
620
+ public appendField(destinationKey: keyof DraggableFieldState, field: IViewField | undefined, overrideAttr?: Record<string, any>) {
624
621
  if (MetaFieldKeys.includes(destinationKey)) return;
625
- if (typeof field === "undefined") return;
626
- if (destinationKey === "filters") {
622
+ if (typeof field === 'undefined') return;
623
+ if (destinationKey === 'filters') {
627
624
  return;
628
625
  }
629
626
 
630
627
  this.useMutable(({ encodings }) => {
631
- const cloneField = { ...toJS(field) };
628
+ const cloneField = { ...toJS(field), ...overrideAttr };
632
629
  cloneField.dragId = uniqueId();
633
630
  encodings[destinationKey].push(cloneField);
634
631
  });
635
632
  }
636
- public setVizFormatConfig (formatKey: keyof IVisualConfig['format'], value?: string) {
637
- this.visualConfig[formatKey] = value
633
+ public setVizFormatConfig(formatKey: keyof IVisualConfig['format'], value?: string) {
634
+ this.visualConfig[formatKey] = value;
635
+ }
636
+ public renderVLSubset(vlStruct: any) {
637
+ const tab = this.visList[this.visIndex];
638
+ this.clearState();
639
+ this.setVisualConfig('defaultAggregated', false);
640
+ this.setVisualConfig('stack', 'stack');
641
+ // this.setVisualConfig('sorted', 'none')
642
+ this.applyDefaultSort('none');
643
+
644
+ if (!tab) return;
645
+ const fields = tab.encodings.dimensions.concat(tab.encodings.measures);
646
+ const countField = fields.find((f) => f.fid === COUNT_FIELD_ID);
647
+ const renderVLFacet = (vlFacet) => {
648
+ if (vlFacet.facet) {
649
+ this.appendField('rows', fields.find((f) => f.fid === vlFacet.facet.field) || countField, { analyticType: 'dimension' });
650
+ }
651
+ if (vlFacet.row) {
652
+ this.appendField('rows', fields.find((f) => f.fid === vlFacet.row.field) || countField, { analyticType: 'dimension' });
653
+ }
654
+ if (vlFacet.column) {
655
+ this.appendField('columns', fields.find((f) => f.fid === vlFacet.column.field) || countField, { analyticType: 'dimension' });
656
+ }
657
+ };
658
+ const isValidAggregate = (aggName) => aggName && ['sum', 'count', 'max', 'min', 'mean', 'median', 'variance', 'stdev'].includes(aggName);
659
+ const renderVLSpec = (vlSpec) => {
660
+ if (typeof vlSpec.mark === 'string') {
661
+ this.setVisualConfig('geoms', [geomAdapter(vlSpec.mark)]);
662
+ } else {
663
+ this.setVisualConfig('geoms', [geomAdapter(vlSpec.mark.type)]);
664
+ }
665
+ if (vlSpec.encoding.x) {
666
+ const field = fields.find((f) => f.fid === vlSpec.encoding.x.field) || countField;
667
+ this.appendField('columns', field, { analyticType: 'dimension' });
668
+ if (isValidAggregate(vlSpec.encoding.x.aggregate) || field === countField) {
669
+ this.setVisualConfig('defaultAggregated', true);
670
+ this.setFieldAggregator('columns', this.draggableFieldState.columns.length - 1, vlSpec.encoding.x.aggregate);
671
+ }
672
+ if (vlSpec.encoding.x.bin) {
673
+ const binFid = this.createBinField('columns', this.draggableFieldState.columns.length - 1, 'bin');
674
+ this.replaceField('columns', this.draggableFieldState.columns.length - 1, binFid);
675
+ }
676
+ if (vlSpec.encoding.x.stack) {
677
+ this.setVisualConfig('stack', stackValueTransform(vlSpec.encoding.x.stack));
678
+ }
679
+ }
680
+ if (vlSpec.encoding.y) {
681
+ const field = fields.find((f) => f.fid === vlSpec.encoding.y.field) || countField;
682
+ this.appendField('rows', field, { analyticType: 'measure' });
683
+ if (isValidAggregate(vlSpec.encoding.y.aggregate) || field === countField) {
684
+ this.setVisualConfig('defaultAggregated', true);
685
+ this.setFieldAggregator('rows', this.draggableFieldState.rows.length - 1, vlSpec.encoding.y.aggregate);
686
+ }
687
+ if (vlSpec.encoding.y.bin) {
688
+ const binFid = this.createBinField('rows', this.draggableFieldState.rows.length - 1, 'bin');
689
+ this.replaceField('rows', this.draggableFieldState.rows.length - 1, binFid);
690
+ }
691
+ if (vlSpec.encoding.y.stack) {
692
+ this.setVisualConfig('stack', stackValueTransform(vlSpec.encoding.y.stack));
693
+ }
694
+ }
695
+
696
+ (['color', 'opacity', 'shape', 'size', 'details', 'theta', 'text', 'radius'] as (keyof DraggableFieldState)[]).forEach((ch) => {
697
+ if (vlSpec.encoding[ch]) {
698
+ const field = fields.find((f) => f.fid === vlSpec.encoding[ch].field) || countField;
699
+ this.appendField(
700
+ ch,
701
+ field,
702
+ field !== countField && ['color', 'opacity', 'size', 'radius'].includes(ch)
703
+ ? { analyticType: 'dimension' }
704
+ : field === countField || ['theta'].includes(ch)
705
+ ? { analyticType: 'measure' }
706
+ : {}
707
+ );
708
+ const aggregate = isValidAggregate(vlSpec.encoding[ch].aggregate);
709
+ if ((['theta', 'radius'].includes(ch) && aggregate) || field === countField) {
710
+ this.setVisualConfig('defaultAggregated', true);
711
+ if (aggregate) {
712
+ this.setFieldAggregator(ch, this.draggableFieldState[ch].length - 1, vlSpec.encoding[ch].aggregate);
713
+ }
714
+ }
715
+ }
716
+ });
717
+ ['x', 'y', 'facet'].forEach((ch) => {
718
+ if (vlSpec.encoding[ch] && vlSpec.encoding[ch].sort) {
719
+ this.applyDefaultSort(sortValueTransform(vlSpec.encoding[ch].sort));
720
+ }
721
+ });
722
+ if (vlSpec.encoding.order && vlSpec.encoding.order.sort) {
723
+ this.applyDefaultSort(sortValueTransform(vlSpec.encoding.order.sort));
724
+ }
725
+ };
726
+ if (vlStruct.encoding && vlStruct.mark) {
727
+ renderVLFacet(vlStruct.encoding);
728
+ renderVLSpec(vlStruct);
729
+ } else if (vlStruct.spec) {
730
+ if (vlStruct.facet) {
731
+ renderVLFacet(vlStruct.facet);
732
+ }
733
+ renderVLSpec(vlStruct.spec);
734
+ }
638
735
  }
639
736
  public renderSpec(spec: Specification) {
640
737
  const tab = this.visList[this.visIndex];
@@ -647,7 +744,7 @@ export class VizSpecStore {
647
744
  this.setVisualConfig('defaultAggregated', Boolean(spec.aggregate));
648
745
  if ((spec.geomType?.length ?? 0) > 0) {
649
746
  this.setVisualConfig(
650
- "geoms",
747
+ 'geoms',
651
748
  spec.geomType!.map((g) => geomAdapter(g))
652
749
  );
653
750
  }
@@ -655,7 +752,7 @@ export class VizSpecStore {
655
752
  const facets = (spec.facets || []).concat(spec.highFacets || []);
656
753
  for (let facet of facets) {
657
754
  this.appendField(
658
- "rows",
755
+ 'rows',
659
756
  fields.find((f) => f.fid === facet)
660
757
  );
661
758
  }
@@ -664,30 +761,30 @@ export class VizSpecStore {
664
761
  const [cols, rows] = spec.position;
665
762
  if (cols)
666
763
  this.appendField(
667
- "columns",
764
+ 'columns',
668
765
  fields.find((f) => f.fid === cols)
669
766
  );
670
767
  if (rows)
671
768
  this.appendField(
672
- "rows",
769
+ 'rows',
673
770
  fields.find((f) => f.fid === rows)
674
771
  );
675
772
  }
676
773
  if ((spec.color?.length ?? 0) > 0) {
677
774
  this.appendField(
678
- "color",
775
+ 'color',
679
776
  fields.find((f) => f.fid === spec.color![0])
680
777
  );
681
778
  }
682
779
  if ((spec.size?.length ?? 0) > 0) {
683
780
  this.appendField(
684
- "size",
781
+ 'size',
685
782
  fields.find((f) => f.fid === spec.size![0])
686
783
  );
687
784
  }
688
785
  if ((spec.opacity?.length ?? 0) > 0) {
689
786
  this.appendField(
690
- "opacity",
787
+ 'opacity',
691
788
  fields.find((f) => f.fid === spec.opacity![0])
692
789
  );
693
790
  }
@@ -710,8 +807,8 @@ export class VizSpecStore {
710
807
  const pureVisList = dumpsGWPureSpec(this.visList);
711
808
  return this.visSpecEncoder(pureVisList);
712
809
  }
713
- public importStoInfo (stoInfo: IStoInfo) {
714
- this.visList = parseGWPureSpec(this.visSpecDecoder(forwardVisualConfigs(stoInfo.specList)));
810
+ public importStoInfo(stoInfo: IStoInfo) {
811
+ this.visList = parseGWPureSpec(visSpecDecoder(forwardVisualConfigs(stoInfo.specList)));
715
812
  this.visIndex = 0;
716
813
  this.commonStore.datasets = stoInfo.datasets;
717
814
  this.commonStore.dataSources = stoInfo.dataSources;
@@ -725,50 +822,60 @@ export class VizSpecStore {
725
822
  private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
726
823
  const updatedVisList = visList.map((visSpec) => {
727
824
  const updatedFilters = visSpec.encodings.filters.map((filter) => {
728
- if (filter.rule?.type === "one of") {
729
- const rule = {
730
- ...filter.rule,
731
- value: Array.from(filter.rule.value)
732
- }
825
+ if (filter.rule?.type === 'one of') {
826
+ const rule = {
827
+ ...filter.rule,
828
+ value: Array.from(filter.rule.value),
829
+ };
733
830
  return {
734
- ...filter,
735
- rule
736
- }
737
- }
831
+ ...filter,
832
+ rule,
833
+ };
834
+ }
738
835
  return filter as IFilterFieldForExport;
739
836
  });
740
837
  return {
741
838
  ...visSpec,
742
839
  encodings: {
743
840
  ...visSpec.encodings,
744
- filters: updatedFilters
745
- }
746
- }
841
+ filters: updatedFilters,
842
+ },
843
+ };
747
844
  });
748
845
  return updatedVisList;
749
846
  }
750
- private visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
751
- const updatedVisList = visList.map((visSpec) => {
752
- const updatedFilters = visSpec.encodings.filters.map((filter) => {
753
- if (filter.rule?.type === "one of" && Array.isArray(filter.rule.value)) {
754
- return {
755
- ...filter,
756
- rule: {
757
- ...filter.rule,
758
- value: new Set(filter.rule.value)
759
- }
760
- }
761
- }
762
- return filter;
763
- })
764
- return {
765
- ...visSpec,
766
- encodings: {
767
- ...visSpec.encodings,
768
- filters: updatedFilters
769
- }
770
- } as IVisSpec;
771
- });
772
- return updatedVisList;
847
+ public get limit() {
848
+ return this.visualConfig.limit;
849
+ }
850
+
851
+ public setLimit(value: number) {
852
+ this.setVisualConfig('limit', value);
853
+ }
854
+
855
+ public get sort() {
856
+ const { rows, columns } = this.draggableFieldState;
857
+ if (rows.length && !rows.find((x) => x.analyticType === 'measure')) {
858
+ return rows[rows.length - 1].sort || 'none';
859
+ }
860
+ if (columns.length && !columns.find((x) => x.analyticType === 'measure')) {
861
+ return columns[columns.length - 1].sort || 'none';
862
+ }
863
+ return 'none';
864
+ }
865
+
866
+ public getWorkflow() {
867
+ return toWorkflow(
868
+ this.viewFilters,
869
+ this.allFields,
870
+ this.viewDimensions,
871
+ this.viewMeasures,
872
+ this.visualConfig.defaultAggregated,
873
+ this.sort,
874
+ this.limit > 0 ? this.limit : undefined
875
+ );
876
+ }
877
+
878
+ public setComputationFunction(f: IComputationFunction) {
879
+ this.computationFuction = f;
773
880
  }
774
881
  }