@kanaries/graphic-walker 0.2.4 → 0.2.5

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 (71) hide show
  1. package/package.json +3 -2
  2. package/src/App.tsx +140 -0
  3. package/src/assets/kanaries.ico +0 -0
  4. package/src/components/clickMenu.tsx +29 -0
  5. package/src/components/container.tsx +16 -0
  6. package/src/components/dataTypeIcon.tsx +20 -0
  7. package/src/components/liteForm.tsx +16 -0
  8. package/src/components/modal.tsx +85 -0
  9. package/src/components/sizeSetting.tsx +95 -0
  10. package/src/components/tabs/pureTab.tsx +70 -0
  11. package/src/config.ts +57 -0
  12. package/src/constants.ts +1 -0
  13. package/src/dataSource/config.ts +62 -0
  14. package/src/dataSource/dataSelection/csvData.tsx +77 -0
  15. package/src/dataSource/dataSelection/gwFile.tsx +38 -0
  16. package/src/dataSource/dataSelection/index.tsx +57 -0
  17. package/src/dataSource/dataSelection/publicData.tsx +57 -0
  18. package/src/dataSource/index.tsx +78 -0
  19. package/src/dataSource/pannel.tsx +71 -0
  20. package/src/dataSource/table.tsx +125 -0
  21. package/src/dataSource/utils.ts +47 -0
  22. package/src/fields/aestheticFields.tsx +23 -0
  23. package/src/fields/components.tsx +159 -0
  24. package/src/fields/datasetFields/dimFields.tsx +45 -0
  25. package/src/fields/datasetFields/fieldPill.tsx +10 -0
  26. package/src/fields/datasetFields/index.tsx +28 -0
  27. package/src/fields/datasetFields/meaFields.tsx +58 -0
  28. package/src/fields/fieldsContext.tsx +59 -0
  29. package/src/fields/filterField/filterEditDialog.tsx +143 -0
  30. package/src/fields/filterField/filterPill.tsx +113 -0
  31. package/src/fields/filterField/index.tsx +61 -0
  32. package/src/fields/filterField/slider.tsx +236 -0
  33. package/src/fields/filterField/tabs.tsx +421 -0
  34. package/src/fields/obComponents/obFContainer.tsx +40 -0
  35. package/src/fields/obComponents/obPill.tsx +48 -0
  36. package/src/fields/posFields/index.tsx +33 -0
  37. package/src/fields/select.tsx +31 -0
  38. package/src/fields/utils.ts +31 -0
  39. package/src/index.css +13 -0
  40. package/src/index.tsx +12 -0
  41. package/src/insightBoard/index.tsx +30 -0
  42. package/src/insightBoard/mainBoard.tsx +203 -0
  43. package/src/insightBoard/radioGroupButtons.tsx +50 -0
  44. package/src/insightBoard/selectionSpec.ts +113 -0
  45. package/src/insightBoard/std2vegaSpec.ts +184 -0
  46. package/src/insightBoard/utils.ts +32 -0
  47. package/src/insights.ts +408 -0
  48. package/src/interfaces.ts +154 -0
  49. package/src/locales/en-US.json +140 -0
  50. package/src/locales/i18n.ts +50 -0
  51. package/src/locales/zh-CN.json +140 -0
  52. package/src/main.tsx +10 -0
  53. package/src/models/visSpecHistory.ts +129 -0
  54. package/src/renderer/index.tsx +104 -0
  55. package/src/segments/visNav.tsx +48 -0
  56. package/src/services.ts +139 -0
  57. package/src/store/commonStore.ts +158 -0
  58. package/src/store/index.tsx +53 -0
  59. package/src/store/visualSpecStore.ts +586 -0
  60. package/src/utils/autoMark.ts +34 -0
  61. package/src/utils/index.ts +251 -0
  62. package/src/utils/normalization.ts +158 -0
  63. package/src/utils/save.ts +46 -0
  64. package/src/vis/future-react-vega.tsx +193 -0
  65. package/src/vis/gen-vega.tsx +52 -0
  66. package/src/vis/react-vega.tsx +398 -0
  67. package/src/visualSettings/index.tsx +252 -0
  68. package/src/visualSettings/menubar.tsx +109 -0
  69. package/src/vite-env.d.ts +1 -0
  70. package/src/workers/explainer.worker.js +78 -0
  71. package/src/workers/filter.worker.js +70 -0
@@ -0,0 +1,586 @@
1
+ import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from "mobx";
2
+ import produce from 'immer';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { Specification } from "visual-insights";
5
+ import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisualConfig } from "../interfaces";
6
+ import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
7
+ import { makeBinField, makeLogField } from "../utils/normalization";
8
+ import { VisSpecWithHistory } from "../models/visSpecHistory";
9
+ import { dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
10
+ import { CommonStore } from "./commonStore";
11
+
12
+ function getChannelSizeLimit (channel: string): number {
13
+ if (typeof CHANNEL_LIMIT[channel] === 'undefined') return Infinity;
14
+ return CHANNEL_LIMIT[channel];
15
+ }
16
+
17
+ function geomAdapter (geom: string) {
18
+ switch (geom) {
19
+ case 'interval':
20
+ return 'bar';
21
+ case 'line':
22
+ return 'line';
23
+ case 'boxplot':
24
+ return 'boxplot';
25
+ case 'area':
26
+ return 'area';
27
+ case 'point':
28
+ return 'point';
29
+ case 'arc':
30
+ return 'arc';
31
+ case 'circle':
32
+ return 'circle';
33
+ case 'heatmap':
34
+ return 'circle'
35
+ case 'rect':
36
+ return 'rect'
37
+ case 'tick':
38
+ default:
39
+ return 'tick'
40
+ }
41
+ }
42
+
43
+ function initEncoding(): DraggableFieldState {
44
+ return {
45
+ dimensions: [],
46
+ measures: [],
47
+ fields: [],
48
+ rows: [],
49
+ columns: [],
50
+ color: [],
51
+ opacity: [],
52
+ size: [],
53
+ shape: [],
54
+ radius: [],
55
+ theta: [],
56
+ filters: [],
57
+ };
58
+ }
59
+
60
+ function initVisualConfig (): IVisualConfig {
61
+ return {
62
+ defaultAggregated: true,
63
+ geoms: [GEMO_TYPES[0]!],
64
+ stack: 'stack',
65
+ showActions: false,
66
+ interactiveScale: false,
67
+ sorted: 'none',
68
+ size: {
69
+ mode: 'auto',
70
+ width: 320,
71
+ height: 200
72
+ }
73
+ }
74
+ }
75
+
76
+ type DeepReadonly<T extends Record<keyof any, any>> = {
77
+ readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
78
+ };
79
+
80
+
81
+ export class VizSpecStore {
82
+ // public fields: IViewField[] = [];
83
+ private commonStore: CommonStore;
84
+ /**
85
+ * This segment will always refers to the state of the active tab -
86
+ * `this.visList[this.visIndex].encodings`.
87
+ * Notice that observing rule of `this.visList` is `"shallow"`
88
+ * so mobx will NOT compare every deep value of `this.visList`,
89
+ * because the active tab is the only item in the list that may change.
90
+ * @readonly
91
+ * Assignment or mutable operations applied to ANY members of this segment
92
+ * is strictly FORBIDDEN.
93
+ * Members of it can only be got as READONLY objects.
94
+ *
95
+ * If you're trying to change the value of it and let mobx catch the action to trigger an update,
96
+ * please use `this.useMutable()` to access to a writable reference
97
+ * (an `immer` draft) of `this.visList[this.visIndex]`.
98
+ */
99
+ public readonly draggableFieldState: DeepReadonly<DraggableFieldState>;
100
+ private reactions: IReactionDisposer[] = []
101
+ /**
102
+ * This segment will always refers to the state of the active tab -
103
+ * `this.visList[this.visIndex].config`.
104
+ * Notice that observing rule of `this.visList` is `"shallow"`
105
+ * so mobx will NOT compare every deep value of `this.visList`,
106
+ * because the active tab is the only item in the list that may change.
107
+ * @readonly
108
+ * Assignment or mutable operations applied to ANY members of this segment
109
+ * is strictly FORBIDDEN.
110
+ * Members of it can only be got as READONLY objects.
111
+ *
112
+ * If you're trying to change the value of it and let mobx catch the action to trigger an update,
113
+ * please use `this.useMutable()` to access to a writable reference
114
+ * (an `immer` draft) of `this.visList[this.visIndex]`.
115
+ */
116
+ public readonly visualConfig: Readonly<IVisualConfig>;
117
+ public visList: VisSpecWithHistory[] = [];
118
+ public visIndex: number = 0;
119
+ public canUndo = false;
120
+ public canRedo = false;
121
+ public editingFilterIdx: number | null = null;
122
+ constructor (commonStore: CommonStore) {
123
+ this.commonStore = commonStore;
124
+ this.draggableFieldState = initEncoding();
125
+ this.visualConfig = initVisualConfig();
126
+ this.visList.push(new VisSpecWithHistory({
127
+ name: ['main.tablist.autoTitle', { idx: 1 }],
128
+ visId: uuidv4(),
129
+ config: this.visualConfig,
130
+ encodings: this.draggableFieldState,
131
+ }));
132
+ makeAutoObservable(this, {
133
+ visList: observable.shallow,
134
+ // @ts-expect-error private fields are not supported
135
+ reactions: false
136
+ });
137
+ this.reactions.push(
138
+ reaction(() => commonStore.currentDataset, (dataset) => {
139
+ // this.initState();
140
+ this.initMetaState(dataset);
141
+ }),
142
+ reaction(() => this.visList[this.visIndex], frame => {
143
+ // @ts-ignore Allow assignment here to trigger watch
144
+ this.draggableFieldState = frame.encodings;
145
+ // @ts-ignore Allow assignment here to trigger watch
146
+ this.visualConfig = frame.config;
147
+ this.canUndo = frame.canUndo;
148
+ this.canRedo = frame.canRedo;
149
+ }),
150
+ );
151
+ }
152
+ private __dangerous_is_inside_useMutable__ = false;
153
+ /**
154
+ * @important NEVER recursively call `useMutable()`
155
+ * because the state will be overwritten by the root `useMutable()` call,
156
+ * update caused by recursive `useMutable()` call will be reverted or lead to unexpected behaviors.
157
+ * Inline your invoking or just use block with IF statement to avoid this in your cases.
158
+ *
159
+ * Allow to change any deep member of `encodings` or `config`
160
+ * in the active tab `this.visList[this.visIndex]`.
161
+ *
162
+ * - `tab.encodings`
163
+ *
164
+ * A mutable reference of `this.draggableFieldState`
165
+ *
166
+ * - `tab.config`
167
+ *
168
+ * A mutable reference of `this.visualConfig`
169
+ */
170
+ private useMutable(
171
+ cb: (tab: {
172
+ encodings: DraggableFieldState;
173
+ config: IVisualConfig;
174
+ }) => void,
175
+ ) {
176
+ if (this.__dangerous_is_inside_useMutable__) {
177
+ throw new Error(
178
+ 'A recursive call of useMutable() is detected, '
179
+ + 'this is prevented because update will be overwritten by parent execution context.'
180
+ );
181
+ }
182
+
183
+ this.__dangerous_is_inside_useMutable__ = true;
184
+
185
+ const { encodings, config } = produce({
186
+ encodings: this.visList[this.visIndex].encodings,
187
+ config: this.visList[this.visIndex].config,
188
+ }, draft => { cb(draft) }); // notice that cb() may unexpectedly returns a non-nullable value
189
+
190
+ this.visList[this.visIndex].encodings = encodings;
191
+ this.visList[this.visIndex].config = config;
192
+
193
+ this.canUndo = this.visList[this.visIndex].canUndo;
194
+ this.canRedo = this.visList[this.visIndex].canRedo;
195
+
196
+ // @ts-ignore Allow assignment here to trigger watch
197
+ this.visualConfig = config;
198
+ // @ts-ignore Allow assignment here to trigger watch
199
+ this.draggableFieldState = encodings;
200
+
201
+ this.__dangerous_is_inside_useMutable__ = false;
202
+ }
203
+ public undo() {
204
+ if (this.visList[this.visIndex]?.undo()) {
205
+ this.canUndo = this.visList[this.visIndex].canUndo;
206
+ this.canRedo = this.visList[this.visIndex].canRedo;
207
+ // @ts-ignore Allow assignment here to trigger watch
208
+ this.visualConfig = this.visList[this.visIndex].config;
209
+ // @ts-ignore Allow assignment here to trigger watch
210
+ this.draggableFieldState = this.visList[this.visIndex].encodings;
211
+ }
212
+ }
213
+ public redo() {
214
+ if (this.visList[this.visIndex]?.redo()) {
215
+ this.canUndo = this.visList[this.visIndex].canUndo;
216
+ this.canRedo = this.visList[this.visIndex].canRedo;
217
+ // @ts-ignore Allow assignment here to trigger watch
218
+ this.visualConfig = this.visList[this.visIndex].config;
219
+ // @ts-ignore Allow assignment here to trigger watch
220
+ this.draggableFieldState = this.visList[this.visIndex].encodings;
221
+ }
222
+ }
223
+ private freezeHistory() {
224
+ this.visList[this.visIndex]?.rebase();
225
+ this.canUndo = this.visList[this.visIndex].canUndo;
226
+ this.canRedo = this.visList[this.visIndex].canRedo;
227
+ }
228
+ /**
229
+ * dimension fields in visualization
230
+ */
231
+ public get viewDimensions (): IViewField[] {
232
+ const { draggableFieldState } = this;
233
+ const state = toJS(draggableFieldState);
234
+ const fields: IViewField[] = [];
235
+ (Object.keys(state) as (keyof DraggableFieldState)[])
236
+ .filter(dkey => !MetaFieldKeys.includes(dkey))
237
+ .forEach(dkey => {
238
+ fields.push(...state[dkey].filter(f => f.analyticType === 'dimension'))
239
+ })
240
+ return fields;
241
+ }
242
+ /**
243
+ * dimension fields in visualization
244
+ */
245
+ public get viewMeasures (): IViewField[] {
246
+ const { draggableFieldState } = this;
247
+ const state = toJS(draggableFieldState);
248
+ const fields: IViewField[] = [];
249
+ (Object.keys(state) as (keyof DraggableFieldState)[])
250
+ .filter(dkey => !MetaFieldKeys.includes(dkey))
251
+ .forEach(dkey => {
252
+ fields.push(...state[dkey].filter(f => f.analyticType === 'measure'))
253
+ })
254
+ return fields;
255
+ }
256
+ public addVisualization () {
257
+ this.visList.push(new VisSpecWithHistory({
258
+ name: ['main.tablist.autoTitle', { idx: this.visList.length + 1 }],
259
+ visId: uuidv4(),
260
+ config: initVisualConfig(),
261
+ encodings: initEncoding()
262
+ }));
263
+ this.visIndex = this.visList.length - 1;
264
+ }
265
+ public selectVisualization (visIndex: number) {
266
+ this.visIndex = visIndex;
267
+ }
268
+ public setVisName (visIndex: number, name: string) {
269
+ this.useMutable(() => {
270
+ this.visList[visIndex].name = [name];
271
+ });
272
+ }
273
+ public initState () {
274
+ this.useMutable(tab => {
275
+ tab.encodings = initEncoding();
276
+ this.freezeHistory();
277
+ });
278
+ }
279
+ public initMetaState (dataset: DataSet) {
280
+ this.useMutable(({ encodings }) => {
281
+ encodings.fields = dataset.rawFields.map((f) => ({
282
+ dragId: uuidv4(),
283
+ fid: f.fid,
284
+ name: f.name || f.fid,
285
+ aggName: f.analyticType === 'measure' ? 'sum' : undefined,
286
+ analyticType: f.analyticType,
287
+ semanticType: f.semanticType
288
+ }));
289
+ encodings.dimensions = dataset.rawFields
290
+ .filter(f => f.analyticType === 'dimension')
291
+ .map((f) => ({
292
+ dragId: uuidv4(),
293
+ fid: f.fid,
294
+ name: f.name || f.fid,
295
+ semanticType: f.semanticType,
296
+ analyticType: f.analyticType,
297
+ }));
298
+ encodings.measures = dataset.rawFields
299
+ .filter(f => f.analyticType === 'measure')
300
+ .map((f) => ({
301
+ dragId: uuidv4(),
302
+ fid: f.fid,
303
+ name: f.name || f.fid,
304
+ analyticType: f.analyticType,
305
+ semanticType: f.semanticType,
306
+ aggName: 'sum'
307
+ }));
308
+ });
309
+
310
+ this.freezeHistory();
311
+ // this.draggableFieldState.measures.push({
312
+ // dragId: uuidv4(),
313
+ // fid: COUNT_FIELD_ID,
314
+ // name: '记录数',
315
+ // analyticType: 'measure',
316
+ // semanticType: 'quantitative',
317
+ // aggName: 'count'
318
+ // })
319
+ }
320
+ public clearState () {
321
+ this.useMutable(({ encodings }) => {
322
+ for (let key in encodings) {
323
+ if (!MetaFieldKeys.includes(key as keyof DraggableFieldState)) {
324
+ encodings[key] = [];
325
+ }
326
+ }
327
+ });
328
+ }
329
+ public setVisualConfig<K extends keyof IVisualConfig>(configKey: K, value: IVisualConfig[K]) {
330
+ this.useMutable(({ config }) => {
331
+ switch (true) {
332
+ case ['defaultAggregated', 'defaultStack', 'showActions', 'interactiveScale'].includes(configKey): {
333
+ return (config as unknown as {[k: string]: boolean})[configKey] = Boolean(value);
334
+ }
335
+ case configKey === 'geoms' && Array.isArray(value):
336
+ case configKey === 'size' && typeof value === 'object':
337
+ case configKey === 'sorted':
338
+ case configKey === 'stack':
339
+ {
340
+ return config[configKey] = value;
341
+ }
342
+ default: {
343
+ console.error('unknown key' + configKey);
344
+ }
345
+ }
346
+ });
347
+ }
348
+ public transformCoord (coord: 'cartesian' | 'polar') {
349
+ if (coord === 'polar') {
350
+
351
+ }
352
+ }
353
+ public setChartLayout(props: {mode: IVisualConfig['size']['mode'], width?: number, height?: number }) {
354
+ this.useMutable(({ config }) => {
355
+ const {
356
+ mode = config.size.mode,
357
+ width = config.size.width,
358
+ height = config.size.height
359
+ } = props;
360
+
361
+ config.size.mode = mode;
362
+ config.size.width = width;
363
+ config.size.height = height;
364
+ });
365
+ }
366
+ public reorderField(stateKey: keyof DraggableFieldState, sourceIndex: number, destinationIndex: number) {
367
+ if (MetaFieldKeys.includes(stateKey)) return;
368
+ if (sourceIndex === destinationIndex) return;
369
+
370
+ this.useMutable(({ encodings }) => {
371
+ const fields = encodings[stateKey];
372
+ const [field] = fields.splice(sourceIndex, 1);
373
+ fields.splice(destinationIndex, 0, field);
374
+ });
375
+ }
376
+ public moveField(sourceKey: keyof DraggableFieldState, sourceIndex: number, destinationKey: keyof DraggableFieldState, destinationIndex: number) {
377
+ if (sourceKey === 'filters') {
378
+ return this.removeField(sourceKey, sourceIndex);
379
+ } else if (destinationKey === 'filters') {
380
+ return this.appendFilter(destinationIndex, this.draggableFieldState[sourceKey][sourceIndex])
381
+ }
382
+
383
+ this.useMutable(({ encodings }) => {
384
+ let movingField: IViewField;
385
+ // 来源是不是metafield,是->clone;不是->直接删掉
386
+ if (MetaFieldKeys.includes(sourceKey)) {
387
+ // use a different dragId
388
+ movingField = {
389
+ ...toJS(encodings[sourceKey][sourceIndex]), // toJS will NOT shallow copy a object here
390
+ dragId: uuidv4(),
391
+ };
392
+ } else {
393
+ [movingField] = encodings[sourceKey].splice(sourceIndex, 1);
394
+ }
395
+ // 目的地是metafields的情况,只有在来源也是metafields时,会执行字段类型转化操作
396
+ if (MetaFieldKeys.includes(destinationKey)) {
397
+ if (!MetaFieldKeys.includes(sourceKey))return;
398
+ encodings[sourceKey].splice(sourceIndex, 1);
399
+ movingField.analyticType = destinationKey === 'dimensions' ? 'dimension' : 'measure';
400
+ }
401
+ const limitSize = getChannelSizeLimit(destinationKey);
402
+ const fixedDestinationIndex = Math.min(destinationIndex, limitSize - 1);
403
+ const overflowSize = Math.max(0, encodings[destinationKey].length + 1 - limitSize);
404
+ encodings[destinationKey].splice(fixedDestinationIndex, overflowSize, movingField);
405
+ });
406
+ }
407
+ public removeField(sourceKey: keyof DraggableFieldState, sourceIndex: number) {
408
+ if (MetaFieldKeys.includes(sourceKey))return;
409
+
410
+ this.useMutable(({ encodings }) => {
411
+ const fields = encodings[sourceKey];
412
+ fields.splice(sourceIndex, 1);
413
+ });
414
+ }
415
+ private appendFilter(index: number, data: IViewField) {
416
+ this.useMutable(({ encodings }) => {
417
+ encodings.filters.splice(index, 0, {
418
+ ...toJS(data),
419
+ dragId: uuidv4(),
420
+ rule: null,
421
+ });
422
+ this.editingFilterIdx = index;
423
+ });
424
+ }
425
+ public writeFilter(index: number, rule: IFilterRule | null) {
426
+ this.useMutable(({ encodings }) => {
427
+ encodings.filters[index].rule = rule;
428
+ });
429
+ }
430
+ public setFilterEditing(index: number) {
431
+ this.editingFilterIdx = index;
432
+ }
433
+ public closeFilterEditing() {
434
+ this.editingFilterIdx = null;
435
+ }
436
+ public transpose() {
437
+ this.useMutable(({ encodings }) => {
438
+ const fieldsInCup = encodings.columns;
439
+
440
+ encodings.columns = encodings.rows;
441
+ encodings.rows = fieldsInCup as typeof encodings.rows; // assume this as writable
442
+ });
443
+ }
444
+ public createBinField(stateKey: keyof DraggableFieldState, index: number) {
445
+ this.useMutable(({ encodings }) => {
446
+ const originField = encodings[stateKey][index]
447
+ const binField: IViewField = {
448
+ fid: uuidv4(),
449
+ dragId: uuidv4(),
450
+ name: `bin(${originField.name})`,
451
+ semanticType: 'ordinal',
452
+ analyticType: 'dimension',
453
+ };
454
+ encodings.dimensions.push(binField);
455
+ this.commonStore.currentDataset.dataSource = makeBinField(this.commonStore.currentDataset.dataSource, originField.fid, binField.fid)
456
+ });
457
+ }
458
+ public createLogField(stateKey: keyof DraggableFieldState, index: number) {
459
+ if (stateKey === 'filters') {
460
+ return;
461
+ }
462
+
463
+ this.useMutable(({ encodings }) => {
464
+ const originField = encodings[stateKey][index];
465
+ const logField: IViewField = {
466
+ fid: uuidv4(),
467
+ dragId: uuidv4(),
468
+ name: `log10(${originField.name})`,
469
+ semanticType: 'quantitative',
470
+ analyticType: originField.analyticType
471
+ };
472
+ encodings[stateKey].push(logField);
473
+ this.commonStore.currentDataset.dataSource = makeLogField(this.commonStore.currentDataset.dataSource, originField.fid, logField.fid)
474
+ });
475
+ }
476
+ public setFieldAggregator (stateKey: keyof DraggableFieldState, index: number, aggName: string) {
477
+ this.useMutable(({ encodings }) => {
478
+ const fields = encodings[stateKey];
479
+
480
+ if (fields[index]) {
481
+ encodings[stateKey][index].aggName = aggName;
482
+ }
483
+ });
484
+ }
485
+ public get sortCondition () {
486
+ const { rows, columns } = this.draggableFieldState;
487
+ const yField = rows.length > 0 ? rows[rows.length - 1] : null;
488
+ const xField = columns.length > 0 ? columns[columns.length - 1] : null;
489
+ if (xField !== null && xField.analyticType === 'dimension' && yField !== null && yField.analyticType === 'measure') {
490
+ return true
491
+ }
492
+ if (xField !== null && xField.analyticType === 'measure' && yField !== null && yField.analyticType === 'dimension') {
493
+ return true
494
+ }
495
+ return false;
496
+ }
497
+ public setFieldSort (stateKey: keyof DraggableFieldState, index: number, sortType: 'none' | 'ascending' | 'descending') {
498
+ this.useMutable(({ encodings }) => {
499
+ encodings[stateKey][index].sort = sortType;
500
+ });
501
+ }
502
+ public applyDefaultSort(sortType: 'none' | 'ascending' | 'descending' = 'ascending') {
503
+ this.useMutable(({ encodings }) => {
504
+ const { rows, columns } = encodings;
505
+ const yField = rows.length > 0 ? rows[rows.length - 1] : null;
506
+ const xField = columns.length > 0 ? columns[columns.length - 1] : null;
507
+
508
+ if (xField !== null && xField.analyticType === 'dimension' && yField !== null && yField.analyticType === 'measure') {
509
+ encodings.columns[columns.length - 1].sort = sortType;
510
+ return;
511
+ }
512
+ if (xField !== null && xField.analyticType === 'measure' && yField !== null && yField.analyticType === 'dimension') {
513
+ encodings.rows[rows.length - 1].sort = sortType;
514
+ return;
515
+ }
516
+ });
517
+ }
518
+ public appendField (destinationKey: keyof DraggableFieldState, field: IViewField | undefined) {
519
+ if (MetaFieldKeys.includes(destinationKey)) return;
520
+ if (typeof field === 'undefined') return;
521
+ if (destinationKey === 'filters') {
522
+ return;
523
+ }
524
+
525
+
526
+ this.useMutable(({ encodings }) => {
527
+ const cloneField = { ...toJS(field) };
528
+ cloneField.dragId = uuidv4();
529
+ encodings[destinationKey].push(cloneField);
530
+ });
531
+ }
532
+ public renderSpec (spec: Specification) {
533
+ const tab = this.visList[this.visIndex];
534
+
535
+ if (tab) {
536
+ const fields = tab.encodings.fields;
537
+ // thi
538
+ // const [xField, yField, ] = spec.position;
539
+ this.clearState();
540
+ if ((spec.geomType?.length ?? 0) > 0) {
541
+ this.setVisualConfig('geoms', spec.geomType!.map(g => geomAdapter(g)));
542
+ }
543
+ if ((spec.facets?.length ?? 0) > 0) {
544
+ const facets = (spec.facets || []).concat(spec.highFacets || []);
545
+ for (let facet of facets) {
546
+ this.appendField('rows', fields.find(f => f.fid === facet));
547
+ }
548
+ }
549
+ if (spec.position) {
550
+ const [cols, rows] = spec.position;
551
+ if (cols) this.appendField('columns', fields.find(f => f.fid === cols));
552
+ if (rows) this.appendField('rows', fields.find(f => f.fid === rows));
553
+ }
554
+ if ((spec.color?.length ?? 0) > 0) {
555
+ this.appendField('color', fields.find(f => f.fid === spec.color![0]));
556
+ }
557
+ if ((spec.size?.length ?? 0) > 0) {
558
+ this.appendField('size', fields.find(f => f.fid === spec.size![0]));
559
+ }
560
+ if ((spec.opacity?.length ?? 0) > 0) {
561
+ this.appendField('opacity', fields.find(f => f.fid === spec.opacity![0]));
562
+ }
563
+ }
564
+ }
565
+ public destroy () {
566
+ this.reactions.forEach(rec => {
567
+ rec();
568
+ })
569
+ }
570
+ public exportAsRaw () {
571
+ const pureVisList = dumpsGWPureSpec(this.visList);
572
+ return stringifyGWContent({
573
+ datasets: toJS(this.commonStore.datasets),
574
+ dataSources: this.commonStore.dataSources,
575
+ specList: pureVisList
576
+ })
577
+ }
578
+ public importRaw (raw: string) {
579
+ const content = parseGWContent(raw);
580
+ this.commonStore.datasets = content.datasets;
581
+ this.commonStore.dataSources = content.dataSources;
582
+ this.commonStore.dsIndex = Math.max(content.datasets.length - 1, 0);
583
+ this.visList = parseGWPureSpec(content.specList)
584
+ this.visIndex = 0;
585
+ }
586
+ }
@@ -0,0 +1,34 @@
1
+ import { ISemanticType } from "visual-insights";
2
+
3
+ /**
4
+ *
5
+ * @param semanticTypeList semanticTypeList.length <= 2,调用时,手动将columns 和 rows的最后一个元素组合传进来
6
+ * @returns geom(mark) type
7
+ */
8
+ export function autoMark(semanticTypeList: ISemanticType[]): string {
9
+ if (semanticTypeList.length < 2) {
10
+ if (semanticTypeList[0] === 'temporal') return 'tick'
11
+ return 'bar'
12
+ }
13
+ const couter: Map<ISemanticType, number> = new Map();
14
+ (['nominal', 'ordinal', 'quantitative', 'temporal'] as ISemanticType[]).forEach(s => {
15
+ couter.set(s, 0)
16
+ })
17
+ for (let st of semanticTypeList) {
18
+ // if (!couter.has(st)) {
19
+ // couter.set(st, 0);
20
+ // }
21
+ couter.set(st, couter.get(st)! + 1);
22
+ }
23
+ if (couter.get('nominal') === 1 || couter.get('ordinal') === 1) {
24
+ return 'bar'
25
+ }
26
+ if (couter.get('temporal') === 1 && couter.get('quantitative') === 1) {
27
+ return 'line'
28
+ }
29
+ if (couter.get('quantitative') === 2) {
30
+ return 'point'
31
+ }
32
+ return 'point'
33
+ // if (cou)
34
+ }