@kanaries/graphic-walker 0.2.15 → 0.2.16

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 (97) hide show
  1. package/dist/App.d.ts +2 -0
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
  4. package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
  5. package/dist/components/codeExport/index.d.ts +3 -0
  6. package/dist/components/loadingLayer.d.ts +2 -0
  7. package/dist/components/tabs/defaultTab.d.ts +1 -0
  8. package/dist/components/tabs/editableTab.d.ts +1 -2
  9. package/dist/dataSource/dataSelection/config.d.ts +1 -0
  10. package/dist/dataSource/dataSelection/utils.d.ts +2 -0
  11. package/dist/datasets/tmp/test.json +1 -0
  12. package/dist/graphic-walker.es.js +23081 -22577
  13. package/dist/graphic-walker.es.js.map +1 -1
  14. package/dist/graphic-walker.umd.js +130 -130
  15. package/dist/graphic-walker.umd.js.map +1 -1
  16. package/dist/index.d.ts +3 -3
  17. package/dist/interfaces.d.ts +21 -1
  18. package/dist/lib/execExp.d.ts +8 -0
  19. package/dist/lib/interfaces.d.ts +22 -0
  20. package/dist/lib/op/aggregate.d.ts +3 -0
  21. package/dist/lib/op/bin.d.ts +3 -0
  22. package/dist/lib/op/fold.d.ts +3 -0
  23. package/dist/lib/op/stat.d.ts +8 -0
  24. package/dist/lib/viewQuery.d.ts +5 -0
  25. package/dist/models/visSpecHistory.d.ts +2 -0
  26. package/dist/renderer/index.d.ts +8 -7
  27. package/dist/renderer/specRenderer.d.ts +13 -0
  28. package/dist/services.d.ts +4 -1
  29. package/dist/store/commonStore.d.ts +6 -0
  30. package/dist/store/index.d.ts +3 -2
  31. package/dist/store/visualSpecStore.d.ts +11 -4
  32. package/dist/utils/dataPrep.d.ts +2 -0
  33. package/dist/utils/index.d.ts +3 -5
  34. package/dist/utils/save.d.ts +1 -2
  35. package/dist/vis/react-vega.d.ts +1 -22
  36. package/dist/vis/spec/aggregate.d.ts +4 -0
  37. package/dist/vis/spec/encode.d.ts +19 -0
  38. package/dist/vis/spec/field.d.ts +2 -0
  39. package/dist/vis/spec/mark.d.ts +7 -0
  40. package/dist/vis/spec/stack.d.ts +4 -0
  41. package/dist/vis/spec/view.d.ts +67 -0
  42. package/dist/workers/transform.d.ts +2 -0
  43. package/package.json +4 -3
  44. package/src/App.tsx +5 -2
  45. package/src/components/codeExport/index.tsx +114 -0
  46. package/src/components/dataTable/index.tsx +10 -10
  47. package/src/components/loadingLayer.tsx +7 -0
  48. package/src/components/tabs/defaultTab.tsx +4 -2
  49. package/src/components/tabs/editableTab.tsx +74 -39
  50. package/src/dataSource/dataSelection/config.ts +11 -0
  51. package/src/dataSource/dataSelection/csvData.tsx +71 -39
  52. package/src/dataSource/dataSelection/gwFile.tsx +2 -2
  53. package/src/dataSource/dataSelection/utils.ts +28 -0
  54. package/src/dataSource/utils.ts +8 -3
  55. package/src/fields/datasetFields/meaFields.tsx +12 -4
  56. package/src/index.css +4 -4
  57. package/src/index.tsx +22 -22
  58. package/src/interfaces.ts +26 -2
  59. package/src/lib/execExp.ts +147 -0
  60. package/src/lib/interfaces.ts +39 -0
  61. package/src/lib/op/aggregate.ts +49 -0
  62. package/src/lib/op/bin.ts +25 -0
  63. package/src/lib/op/fold.ts +17 -0
  64. package/src/lib/op/stat.ts +46 -0
  65. package/src/lib/viewQuery.ts +23 -0
  66. package/src/locales/en-US.json +4 -2
  67. package/src/locales/i18n.ts +0 -1
  68. package/src/locales/ja-JP.json +4 -2
  69. package/src/locales/zh-CN.json +4 -2
  70. package/src/main.tsx +1 -1
  71. package/src/models/visSpecHistory.ts +14 -0
  72. package/src/renderer/index.tsx +58 -126
  73. package/src/renderer/specRenderer.tsx +119 -0
  74. package/src/segments/segmentNav.tsx +3 -16
  75. package/src/segments/visNav.tsx +17 -6
  76. package/src/services.ts +37 -1
  77. package/src/store/commonStore.ts +14 -9
  78. package/src/store/index.tsx +11 -4
  79. package/src/store/visualSpecStore.ts +89 -50
  80. package/src/utils/dataPrep.ts +24 -0
  81. package/src/utils/index.ts +16 -17
  82. package/src/utils/normalization.ts +3 -1
  83. package/src/utils/save.ts +1 -2
  84. package/src/vis/react-vega.tsx +4 -340
  85. package/src/vis/spec/aggregate.ts +13 -0
  86. package/src/vis/spec/encode.ts +69 -0
  87. package/src/vis/spec/field.ts +10 -0
  88. package/src/vis/spec/mark.ts +30 -0
  89. package/src/vis/spec/stack.ts +11 -0
  90. package/src/vis/spec/view.ts +138 -0
  91. package/src/vis/theme.ts +12 -0
  92. package/src/visualSettings/index.tsx +10 -1
  93. package/src/workers/transform.ts +12 -0
  94. package/src/workers/transform.worker.js +13 -0
  95. package/src/workers/viewQuery.worker.js +16 -0
  96. package/dist/dataSource/pannel.d.ts +0 -5
  97. package/src/dataSource/pannel.tsx +0 -71
@@ -1,6 +1,6 @@
1
1
  import i18next from "i18next";
2
2
  import { COUNT_FIELD_ID } from "../constants";
3
- import { IRow, Filters, IMutField } from "../interfaces";
3
+ import { IRow, Filters, IMutField, IViewField } from "../interfaces";
4
4
  interface NRReturns {
5
5
  normalizedData: IRow[];
6
6
  maxMeasures: IRow;
@@ -236,26 +236,21 @@ export function applyFilters(dataSource: IRow[], filters: Filters): IRow[] {
236
236
  });
237
237
  }
238
238
 
239
- export function extendCountField(
240
- dataSource: IRow[],
241
- fields: IMutField[]
242
- ): {
243
- dataSource: IRow[];
244
- fields: IMutField[];
245
- } {
246
- const nextData = dataSource.map((r) => ({
247
- ...r,
248
- [COUNT_FIELD_ID]: 1,
249
- }));
250
- const nextFields = fields.concat({
239
+ export function createCountField(): IViewField {
240
+ return {
241
+ // viewId: "",
242
+ dragId: COUNT_FIELD_ID,
251
243
  fid: COUNT_FIELD_ID,
252
244
  name: i18next.t("constant.row_count"),
253
245
  analyticType: "measure",
254
246
  semanticType: "quantitative",
255
- });
256
- return {
257
- dataSource: nextData,
258
- fields: nextFields,
247
+ aggName: 'sum',
248
+ computed: true,
249
+ expressoion: {
250
+ op: 'one',
251
+ params: [],
252
+ as: COUNT_FIELD_ID,
253
+ }
259
254
  };
260
255
  }
261
256
 
@@ -276,4 +271,8 @@ export function makeNumbersBeautiful (nums: number[]): number[] {
276
271
  return nums.map((num) => {
277
272
  return Math.round(num / step) * step;
278
273
  })
274
+ }
275
+
276
+ export function classNames(...classes: string[]) {
277
+ return classes.filter(Boolean).join(' ')
279
278
  }
@@ -146,7 +146,9 @@ export function makeBinField(dataSource: IRow[], fid: string, binFid: string, bi
146
146
  if (bIndex === binSize) bIndex = binSize - 1;
147
147
  return {
148
148
  ...r,
149
- [binFid]: Number(((bIndex * step + _min)).toFixed(beaStep)),
149
+ [binFid]: [bIndex * step + _min, (bIndex + 1) * step + _min],
150
+
151
+ // [binFid]: Number(((bIndex * step + _min)).toFixed(beaStep)),
150
152
  };
151
153
  });
152
154
  }
package/src/utils/save.ts CHANGED
@@ -9,10 +9,9 @@ export function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[] {
9
9
  return list.map((l) => new VisSpecWithHistory(l));
10
10
  }
11
11
 
12
- interface IStoInfo {
12
+ export interface IStoInfo {
13
13
  datasets: IDataSet[];
14
14
  specList: {
15
- /** 由于 gw 内部实现暂时未锁版本,这里获取到的信息可能会与当前实例内容有偏差,需要用初始值合入 */
16
15
  [K in keyof IVisSpec]: K extends "config" ? Partial<IVisSpec[K]> : IVisSpec[K];
17
16
  }[];
18
17
  dataSources: IDataSource[];
@@ -3,16 +3,15 @@ import embed from 'vega-embed';
3
3
  import { Subject, Subscription } from 'rxjs'
4
4
  import * as op from 'rxjs/operators';
5
5
  import type { ScenegraphEvent, View } from 'vega';
6
- import { ISemanticType } from 'visual-insights';
7
6
  import styled from 'styled-components';
8
- import { autoMark } from '../utils/autoMark';
9
- import { COUNT_FIELD_ID } from '../constants';
10
7
 
11
8
  import { IViewField, IRow, IStackMode, IDarkMode, IThemeKey } from '../interfaces';
12
9
  import { useTranslation } from 'react-i18next';
13
10
  import { getVegaTimeFormatRules } from './temporalFormat';
14
11
  import { builtInThemes } from './theme';
15
12
  import { useCurrentMediaTheme } from '../utils/media';
13
+ import { SingleViewProps, getSingleView } from './spec/view';
14
+ import { NULL_FIELD } from './spec/field';
16
15
 
17
16
  const CanvaContainer = styled.div<{rowSize: number; colSize: number;}>`
18
17
  display: grid;
@@ -52,14 +51,7 @@ interface ReactVegaProps {
52
51
  themeKey?: IThemeKey;
53
52
  dark?: IDarkMode;
54
53
  }
55
- const NULL_FIELD: IViewField = {
56
- dragId: '',
57
- fid: '',
58
- name: '',
59
- semanticType: 'quantitative',
60
- analyticType: 'measure',
61
- aggName: 'sum'
62
- }
54
+
63
55
  const click$ = new Subject<ScenegraphEvent>();
64
56
  const selection$ = new Subject<any>();
65
57
  const geomClick$ = selection$.pipe(
@@ -71,9 +63,6 @@ const geomClick$ = selection$.pipe(
71
63
  return false
72
64
  })
73
65
  );
74
- function getFieldType(field: IViewField): 'quantitative' | 'nominal' | 'ordinal' | 'temporal' {
75
- return field.semanticType
76
- }
77
66
 
78
67
  const BRUSH_SIGNAL_NAME = "__gw_brush__";
79
68
  const POINT_SIGNAL_NAME = "__gw_point__";
@@ -85,301 +74,7 @@ interface ParamStoreEntry {
85
74
  data: any;
86
75
  }
87
76
 
88
- interface SingleViewProps {
89
- x: IViewField;
90
- y: IViewField;
91
- color: IViewField;
92
- opacity: IViewField;
93
- size: IViewField;
94
- shape: IViewField,
95
- xOffset: IViewField;
96
- yOffset: IViewField;
97
- row: IViewField;
98
- column: IViewField;
99
- theta: IViewField;
100
- radius: IViewField;
101
- defaultAggregated: boolean;
102
- stack: IStackMode;
103
- geomType: string;
104
- enableCrossFilter: boolean;
105
- asCrossFilterTrigger: boolean;
106
- selectEncoding: 'default' | 'none';
107
- brushEncoding: 'x' | 'y' | 'default' | 'none';
108
- hideLegend?: boolean;
109
- }
110
-
111
- function availableChannels (geomType: string): Set<string> {
112
- if (geomType === 'arc') {
113
- return new Set(['opacity', 'color', 'size', 'theta', 'radius'])
114
- }
115
- return new Set(['column', 'opacity', 'color', 'row', 'size', 'x', 'y', 'xOffset', 'yOffset', 'shape'])
116
- }
117
- interface EncodeProps extends Pick<SingleViewProps, 'column' | 'opacity' | 'color' | 'row' | 'size' | 'x' | 'y' | 'xOffset' | 'yOffset' | 'shape' | 'theta' | 'radius'> {
118
- geomType: string;
119
- }
120
- function channelEncode(props: EncodeProps) {
121
- const avcs = availableChannels(props.geomType)
122
- const encoding: {[key: string]: any} = {}
123
- Object.keys(props).filter(c => avcs.has(c)).forEach(c => {
124
- if (props[c] !== NULL_FIELD) {
125
- encoding[c] = {
126
- field: props[c].fid,
127
- title: props[c].name,
128
- type: props[c].semanticType,
129
- }
130
- if (props[c].analyticType !== 'measure') {
131
- // if `aggregate` is set to null,
132
- // do not aggregate this field
133
- encoding[c].aggregate = null;
134
- }
135
- if (props[c].analyticType === 'measure') {
136
- encoding[c].type = 'quantitative'
137
- }
138
- }
139
- })
140
- // FIXME: temporal fix, only for x and y relative order
141
- if (encoding.x && encoding.y) {
142
- if ((props.x.sort && props.x.sort) || (props.y && props.y.sort)) {
143
- if (props.x.sort !== 'none' && (props.y.sort === 'none' || !Boolean(props.y.sort))) {
144
- encoding.x.sort = {
145
- encoding: 'y',
146
- order: props.x.sort
147
- }
148
- } else if (props.y.sort && props.y.sort !== 'none' && (props.x.sort === 'none' || !Boolean(props.x.sort))) {
149
- encoding.y.sort = {
150
- encoding: 'x',
151
- order: props.y.sort
152
- }
153
- }
154
- }
155
- }
156
- return encoding
157
- }
158
- function channelAggregate(encoding: {[key: string]: any}, fields: IViewField[]) {
159
- Object.values(encoding).forEach(c => {
160
- const targetField = fields.find(f => f.fid === c.field && !('aggregate' in c));
161
- if (targetField && targetField.fid === COUNT_FIELD_ID) {
162
- c.field = undefined;
163
- c.aggregate = 'count';
164
- c.title = 'Count'
165
- } else if (targetField && targetField.analyticType === 'measure') {
166
- c.title = `${targetField.aggName}(${targetField.name})`;
167
- c.aggregate = targetField.aggName;
168
- }
169
- })
170
- }
171
- function channelStack(encoding: {[key: string]: any}, stackMode: IStackMode) {
172
- if (stackMode === 'stack') return;
173
- if (encoding.x && encoding.x.type === 'quantitative') {
174
- encoding.x.stack = stackMode === 'none' ? null : 'normalize'
175
- }
176
- if (encoding.y && encoding.y.type === 'quantitative') {
177
- encoding.y.stack = stackMode === 'none' ? null : 'normalize'
178
- }
179
- }
180
- // TODO: xOffset等通道的特性不太稳定,建议后续vega相关特性稳定后,再使用。
181
- // 1. 场景太细,仅仅对对应的坐标轴是nominal(可能由ordinal)时,才可用
182
- // 2. 部分geom type会出现bug,如line,会出现组间的错误连接
183
- // "vega": "^5.22.0",
184
- // "vega-embed": "^6.20.8",
185
- // "vega-lite": "^5.2.0",
186
- function getSingleView(props: SingleViewProps) {
187
- const {
188
- x,
189
- y,
190
- color,
191
- opacity,
192
- size,
193
- shape,
194
- theta,
195
- radius,
196
- row,
197
- column,
198
- xOffset,
199
- yOffset,
200
- defaultAggregated,
201
- stack,
202
- geomType,
203
- selectEncoding,
204
- brushEncoding,
205
- enableCrossFilter,
206
- asCrossFilterTrigger,
207
- hideLegend = false,
208
- } = props
209
- const fields: IViewField[] = [x, y, color, opacity, size, shape, row, column, xOffset, yOffset, theta, radius]
210
- let markType = geomType;
211
- let config: any = {};
212
- if (hideLegend) {
213
- config.legend = {
214
- disable: true
215
- };
216
- }
217
- if (geomType === 'auto') {
218
- const types: ISemanticType[] = [];
219
- if (x !== NULL_FIELD) types.push(x.semanticType)//types.push(getFieldType(x));
220
- if (y !== NULL_FIELD) types.push(y.semanticType)//types.push(getFieldType(yField));
221
- markType = autoMark(types);
222
- }
223
-
224
- let encoding = channelEncode({ geomType: markType, x, y, color, opacity, size, shape, row, column, xOffset, yOffset, theta, radius })
225
- if (defaultAggregated) {
226
- channelAggregate(encoding, fields);
227
- }
228
- channelStack(encoding, stack);
229
- if (!enableCrossFilter || brushEncoding === 'none' && selectEncoding === 'none') {
230
- return {
231
- config,
232
- mark: {
233
- type: markType,
234
- opacity: 0.96,
235
- tooltip: true
236
- },
237
- encoding
238
- };
239
- }
240
- const mark = {
241
- type: markType,
242
- opacity: 0.96,
243
- tooltip: true
244
- };
245
-
246
- // TODO:
247
- // 鉴于 Vega 中使用 layer 会导致一些难以覆盖的预期外行为,
248
- // 破坏掉引入交互后视图的正确性,
249
- // 目前不使用 layer 来实现交互(注掉以下代码)。
250
- // 考虑 layer 的目的是 layer + condition 可以用于同时展现“全集”(context)和“选中”两层结构,尤其对于聚合数据有分析帮助;
251
- // 同时,不需要关心作为筛选器的是哪一张图。
252
- // 现在采用临时方案,区别产生筛选的来源,并对其他图仅借助 transform 展现筛选后的数据。
253
- // #[BEGIN bad-layer-interaction]
254
- // const shouldUseMultipleLayers = brushEncoding !== 'none' && Object.values(encoding).some(channel => {
255
- // return typeof channel.aggregate === 'string' && /* 这种 case 对应行数 */ typeof channel.field === 'string';
256
- // });
257
- // if (shouldUseMultipleLayers) {
258
- // if (['column', 'row'].some(key => key in encoding && encoding[key].length > 0)) {
259
- // // 这种情况 Vega 不能处理,是因为 Vega 不支持在 layer 中使用 column / row channel,
260
- // // 会导致渲染出来的视图无法与不使用交互时的结果不变。
261
- // return {
262
- // params: [
263
- // {
264
- // name: BRUSH_SIGNAL_NAME,
265
- // select: { type: 'interval', encodings: brushEncoding === 'default' ? undefined : [brushEncoding] },
266
- // },
267
- // ],
268
- // mark,
269
- // encoding: {
270
- // ...encoding,
271
- // color: {
272
- // condition: {
273
- // ...encoding.color,
274
- // param: BRUSH_SIGNAL_NAME,
275
- // },
276
- // value: '#888',
277
- // },
278
- // },
279
- // };
280
- // }
281
- // return {
282
- // layer: [
283
- // {
284
- // params: [
285
- // {
286
- // name: BRUSH_SIGNAL_NAME,
287
- // select: { type: 'interval', encodings: brushEncoding === 'default' ? undefined : [brushEncoding] },
288
- // },
289
- // ],
290
- // mark,
291
- // encoding: {
292
- // ...encoding,
293
- // color: 'color' in encoding ? {
294
- // condition: {
295
- // ...encoding.color,
296
- // test: 'false',
297
- // },
298
- // value: '#888',
299
- // } : {
300
- // value: '#888',
301
- // },
302
- // },
303
- // },
304
- // {
305
- // transform: [{ filter: { param: BRUSH_SIGNAL_NAME }}],
306
- // mark,
307
- // encoding: {
308
- // ...encoding,
309
- // color: encoding.color ?? { value: 'steelblue' },
310
- // },
311
- // },
312
- // ],
313
- // };
314
- // } else if (brushEncoding !== 'none') {
315
- // return {
316
- // params: [
317
- // {
318
- // name: BRUSH_SIGNAL_NAME,
319
- // select: { type: 'interval', encodings: brushEncoding === 'default' ? undefined : [brushEncoding] },
320
- // },
321
- // ],
322
- // mark,
323
- // encoding: {
324
- // ...encoding,
325
- // color: {
326
- // condition: {
327
- // ...encoding.color,
328
- // param: BRUSH_SIGNAL_NAME,
329
- // },
330
- // value: '#888',
331
- // },
332
- // },
333
- // };
334
- // }
335
- // #[END bad-layer-interaction]
336
-
337
- if (brushEncoding !== 'none') {
338
- return {
339
- config,
340
- transform: asCrossFilterTrigger ? [] : [
341
- { filter: { param: BRUSH_SIGNAL_NAME } }
342
- ],
343
- params: [
344
- // {
345
- // name: BRUSH_SIGNAL_DISPLAY_NAME,
346
- // select: { type: 'interval', encodings: brushEncoding === 'default' ? undefined : [brushEncoding] },
347
- // on: '__YOU_CANNOT_MODIFY_THIS_SIGNAL__',
348
- // },
349
- {
350
- name: BRUSH_SIGNAL_NAME,
351
- select: { type: 'interval', encodings: brushEncoding === 'default' ? undefined : [brushEncoding] },
352
- },
353
- ],
354
- mark,
355
- encoding,
356
- };
357
- }
358
77
 
359
- return {
360
- config,
361
- transform: asCrossFilterTrigger ? [] : [
362
- { filter: { param: POINT_SIGNAL_NAME } }
363
- ],
364
- params: [
365
- {
366
- name: POINT_SIGNAL_NAME,
367
- select: { type: 'point' },
368
- },
369
- ],
370
- mark,
371
- encoding: asCrossFilterTrigger ? {
372
- ...encoding,
373
- color: {
374
- condition: {
375
- ...encoding.color,
376
- param: POINT_SIGNAL_NAME,
377
- },
378
- value: '#888',
379
- },
380
- } : encoding,
381
- };
382
- }
383
78
  const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVega (props, ref) {
384
79
  const {
385
80
  dataSource = [],
@@ -405,8 +100,6 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
405
100
  themeKey = 'vega',
406
101
  dark = 'media'
407
102
  } = props;
408
- // const container = useRef<HTMLDivElement>(null);
409
- // const containers = useRef<(HTMLDivElement | null)[]>([]);
410
103
  const [viewPlaceholders, setViewPlaceholders] = useState<React.MutableRefObject<HTMLDivElement>[]>([]);
411
104
  const { i18n } = useTranslation();
412
105
  const mediaTheme = useCurrentMediaTheme(dark);
@@ -504,28 +197,12 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
504
197
  enableCrossFilter: false,
505
198
  asCrossFilterTrigger: false,
506
199
  });
507
- // if (layoutMode === 'fixed') {
508
- // spec.width = 800;
509
- // spec.height = 600;
510
- // }
200
+
511
201
  spec.mark = singleView.mark;
512
202
  if ('encoding' in singleView) {
513
203
  spec.encoding = singleView.encoding;
514
204
  }
515
205
 
516
- // #[BEGIN bad-layer-interaction]
517
- // if ('layer' in singleView) {
518
- // if ('params' in spec) {
519
- // const basicParams = spec['params'];
520
- // delete spec['params'];
521
- // singleView.layer![0].params = [...basicParams, ...singleView.layer![0].params ?? []];
522
- // }
523
- // spec.layer = singleView.layer;
524
- // } else if ('params' in singleView) {
525
- // spec.params.push(...singleView.params!);
526
- // }
527
- // #[END bad-layer-interaction]
528
-
529
206
  if ('params' in singleView) {
530
207
  spec.params.push(...singleView.params!);
531
208
  }
@@ -592,19 +269,6 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
592
269
  const node = i * colRepeatFields.length + j < viewPlaceholders.length ? viewPlaceholders[i * colRepeatFields.length + j].current : null
593
270
  let commonSpec = { ...spec };
594
271
 
595
- // #[BEGIN bad-layer-interaction]
596
- // if ('layer' in singleView) {
597
- // if ('params' in commonSpec) {
598
- // const { params: basicParams, ...spec } = commonSpec;
599
- // commonSpec = spec;
600
- // singleView.layer![0].params = [...basicParams, ...singleView.layer![0].params ?? []];
601
- // }
602
- // commonSpec.layer = singleView.layer;
603
- // } else if ('params' in singleView) {
604
- // commonSpec.params = [...commonSpec.params, ...singleView.params!];
605
- // }
606
- // #[END bad-layer-interaction]
607
-
608
272
  if ('params' in singleView) {
609
273
  commonSpec.params = [...commonSpec.params, ...singleView.params!];
610
274
  }
@@ -0,0 +1,13 @@
1
+ import { COUNT_FIELD_ID } from '../../constants';
2
+ import { IViewField } from '../../interfaces';
3
+
4
+ export function channelAggregate(encoding: { [key: string]: any }, fields: IViewField[]) {
5
+ Object.values(encoding).forEach((c) => {
6
+ const targetField = fields.find((f) => f.fid === c.field && !('aggregate' in c));
7
+ if (targetField && targetField.fid === COUNT_FIELD_ID) {
8
+ c.title = 'Count';
9
+ } else if (targetField && targetField.analyticType === 'measure') {
10
+ c.title = `${targetField.aggName}(${targetField.name})`;
11
+ }
12
+ });
13
+ }
@@ -0,0 +1,69 @@
1
+ import { IViewField } from '../../interfaces';
2
+ import { NULL_FIELD } from './field';
3
+ export interface IEncodeProps {
4
+ geomType: string;
5
+ x: IViewField;
6
+ y: IViewField;
7
+ color: IViewField;
8
+ opacity: IViewField;
9
+ size: IViewField;
10
+ shape: IViewField;
11
+ xOffset: IViewField;
12
+ yOffset: IViewField;
13
+ row: IViewField;
14
+ column: IViewField;
15
+ theta: IViewField;
16
+ radius: IViewField;
17
+ }
18
+ function availableChannels(geomType: string): Set<string> {
19
+ if (geomType === 'arc') {
20
+ return new Set(['opacity', 'color', 'size', 'theta', 'radius']);
21
+ }
22
+ return new Set(['column', 'opacity', 'color', 'row', 'size', 'x', 'y', 'xOffset', 'yOffset', 'shape']);
23
+ }
24
+ export function channelEncode(props: IEncodeProps) {
25
+ const avcs = availableChannels(props.geomType);
26
+ const encoding: { [key: string]: any } = {};
27
+ Object.keys(props)
28
+ .filter((c) => avcs.has(c))
29
+ .forEach((c) => {
30
+ if (props[c] !== NULL_FIELD) {
31
+ encoding[c] = {
32
+ field: props[c].fid,
33
+ title: props[c].name,
34
+ type: props[c].semanticType,
35
+ };
36
+ if (props[c].analyticType !== 'measure') {
37
+ // if `aggregate` is set to null,
38
+ // do not aggregate this field
39
+ encoding[c].aggregate = null;
40
+ }
41
+ if (props[c].analyticType === 'measure') {
42
+ encoding[c].type = 'quantitative';
43
+ }
44
+ }
45
+ });
46
+ // FIXME: temporal fix, only for x and y relative order
47
+ if (encoding.x) {
48
+ encoding.x.axis = { labelOverlap: true }
49
+ }
50
+ if (encoding && encoding.y) {
51
+ encoding.y.axis = { labelOverlap: true }
52
+ }
53
+ if (encoding.x && encoding.y) {
54
+ if ((props.x.sort && props.x.sort) || (props.y && props.y.sort)) {
55
+ if (props.x.sort !== 'none' && (props.y.sort === 'none' || !Boolean(props.y.sort))) {
56
+ encoding.x.sort = {
57
+ encoding: 'y',
58
+ order: props.x.sort,
59
+ };
60
+ } else if (props.y.sort && props.y.sort !== 'none' && (props.x.sort === 'none' || !Boolean(props.x.sort))) {
61
+ encoding.y.sort = {
62
+ encoding: 'x',
63
+ order: props.y.sort,
64
+ };
65
+ }
66
+ }
67
+ }
68
+ return encoding;
69
+ }
@@ -0,0 +1,10 @@
1
+ import { IViewField } from "../../interfaces";
2
+
3
+ export const NULL_FIELD: IViewField = {
4
+ dragId: '',
5
+ fid: '',
6
+ name: '',
7
+ semanticType: 'quantitative',
8
+ analyticType: 'measure',
9
+ aggName: 'sum',
10
+ };
@@ -0,0 +1,30 @@
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
+ couter.set(st, couter.get(st)! + 1);
19
+ }
20
+ if (couter.get("nominal") === 1 || couter.get("ordinal") === 1) {
21
+ return "bar";
22
+ }
23
+ if (couter.get("temporal") === 1 && couter.get("quantitative") === 1) {
24
+ return "line";
25
+ }
26
+ if (couter.get("quantitative") === 2) {
27
+ return "point";
28
+ }
29
+ return "point";
30
+ }
@@ -0,0 +1,11 @@
1
+ import { IStackMode } from "../../interfaces";
2
+
3
+ export function channelStack(encoding: { [key: string]: any }, stackMode: IStackMode) {
4
+ if (stackMode === 'stack') return;
5
+ if (encoding.x && encoding.x.type === 'quantitative') {
6
+ encoding.x.stack = stackMode === 'none' ? null : 'normalize';
7
+ }
8
+ if (encoding.y && encoding.y.type === 'quantitative') {
9
+ encoding.y.stack = stackMode === 'none' ? null : 'normalize';
10
+ }
11
+ }