@kanaries/graphic-walker 0.2.15 → 0.2.17

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 (132) hide show
  1. package/dist/App.d.ts +4 -3
  2. package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
  3. package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
  4. package/dist/components/codeExport/index.d.ts +3 -0
  5. package/dist/components/loadingLayer.d.ts +2 -0
  6. package/dist/components/tabs/defaultTab.d.ts +1 -0
  7. package/dist/components/tabs/editableTab.d.ts +1 -2
  8. package/dist/dataSource/dataSelection/config.d.ts +1 -0
  9. package/dist/dataSource/dataSelection/utils.d.ts +2 -0
  10. package/dist/dataSource/index.d.ts +0 -1
  11. package/dist/datasets/tmp/test.json +1 -0
  12. package/dist/fields/encodeFields/singleEncodeEditor.d.ts +4 -4
  13. package/dist/graphic-walker.es.js +29228 -33650
  14. package/dist/graphic-walker.es.js.map +1 -1
  15. package/dist/graphic-walker.umd.js +218 -256
  16. package/dist/graphic-walker.umd.js.map +1 -1
  17. package/dist/index.d.ts +3 -3
  18. package/dist/interfaces.d.ts +52 -17
  19. package/dist/lib/execExp.d.ts +8 -0
  20. package/dist/lib/inferMeta.d.ts +2 -9
  21. package/dist/lib/insights/explainByChildren.d.ts +16 -0
  22. package/dist/lib/insights/explainBySelection.d.ts +5 -0
  23. package/dist/lib/insights/explainValue.d.ts +2 -0
  24. package/dist/lib/insights/utils.d.ts +11 -0
  25. package/dist/lib/interfaces.d.ts +23 -0
  26. package/dist/lib/op/aggregate.d.ts +3 -0
  27. package/dist/lib/op/bin.d.ts +3 -0
  28. package/dist/lib/op/fold.d.ts +3 -0
  29. package/dist/lib/op/stat.d.ts +8 -0
  30. package/dist/lib/viewQuery.d.ts +4 -0
  31. package/dist/models/visSpecHistory.d.ts +2 -0
  32. package/dist/renderer/index.d.ts +8 -7
  33. package/dist/renderer/specRenderer.d.ts +13 -0
  34. package/dist/services.d.ts +5 -31
  35. package/dist/store/commonStore.d.ts +6 -0
  36. package/dist/store/index.d.ts +3 -2
  37. package/dist/store/visualSpecStore.d.ts +11 -5
  38. package/dist/utils/autoMark.d.ts +1 -1
  39. package/dist/utils/dataPrep.d.ts +3 -2
  40. package/dist/utils/index.d.ts +3 -5
  41. package/dist/utils/save.d.ts +1 -2
  42. package/dist/vis/react-vega.d.ts +2 -22
  43. package/dist/vis/spec/aggregate.d.ts +4 -0
  44. package/dist/vis/spec/encode.d.ts +20 -0
  45. package/dist/vis/spec/field.d.ts +2 -0
  46. package/dist/vis/spec/mark.d.ts +7 -0
  47. package/dist/vis/spec/stack.d.ts +4 -0
  48. package/dist/vis/spec/tooltip.d.ts +4 -0
  49. package/dist/vis/spec/view.d.ts +73 -0
  50. package/dist/workers/transform.d.ts +2 -0
  51. package/package.json +5 -6
  52. package/src/App.tsx +56 -66
  53. package/src/components/codeExport/index.tsx +114 -0
  54. package/src/components/dataTable/index.tsx +10 -10
  55. package/src/components/loadingLayer.tsx +7 -0
  56. package/src/components/tabs/defaultTab.tsx +4 -2
  57. package/src/components/tabs/editableTab.tsx +74 -39
  58. package/src/dataSource/dataSelection/config.ts +11 -0
  59. package/src/dataSource/dataSelection/csvData.tsx +71 -39
  60. package/src/dataSource/dataSelection/gwFile.tsx +2 -2
  61. package/src/dataSource/dataSelection/utils.ts +28 -0
  62. package/src/dataSource/index.tsx +0 -17
  63. package/src/dataSource/utils.ts +8 -3
  64. package/src/fields/aestheticFields.tsx +1 -2
  65. package/src/fields/datasetFields/meaFields.tsx +12 -4
  66. package/src/fields/encodeFields/singleEncodeEditor.tsx +11 -12
  67. package/src/fields/fieldsContext.tsx +1 -0
  68. package/src/index.css +4 -4
  69. package/src/index.tsx +22 -22
  70. package/src/interfaces.ts +85 -49
  71. package/src/lib/execExp.ts +147 -0
  72. package/src/lib/inferMeta.ts +26 -29
  73. package/src/lib/insights/explainByChildren.ts +50 -0
  74. package/src/lib/insights/explainBySelection.ts +47 -0
  75. package/src/lib/insights/explainValue.ts +30 -0
  76. package/src/lib/insights/utils.ts +21 -0
  77. package/src/lib/interfaces.ts +33 -0
  78. package/src/lib/op/aggregate.ts +49 -0
  79. package/src/lib/op/bin.ts +25 -0
  80. package/src/lib/op/fold.ts +17 -0
  81. package/src/lib/op/stat.ts +46 -0
  82. package/src/lib/viewQuery.ts +22 -0
  83. package/src/locales/en-US.json +6 -3
  84. package/src/locales/i18n.ts +0 -1
  85. package/src/locales/ja-JP.json +4 -2
  86. package/src/locales/zh-CN.json +6 -3
  87. package/src/main.tsx +1 -1
  88. package/src/models/visSpecHistory.ts +14 -0
  89. package/src/renderer/index.tsx +58 -126
  90. package/src/renderer/specRenderer.tsx +121 -0
  91. package/src/segments/segmentNav.tsx +3 -16
  92. package/src/segments/visNav.tsx +17 -6
  93. package/src/services.ts +101 -67
  94. package/src/store/commonStore.ts +14 -9
  95. package/src/store/index.tsx +11 -4
  96. package/src/store/visualSpecStore.ts +89 -52
  97. package/src/utils/autoMark.ts +1 -1
  98. package/src/utils/dataPrep.ts +25 -2
  99. package/src/utils/index.ts +16 -17
  100. package/src/utils/normalization.ts +3 -1
  101. package/src/utils/save.ts +1 -2
  102. package/src/vis/react-vega.tsx +9 -340
  103. package/src/vis/spec/aggregate.ts +13 -0
  104. package/src/vis/spec/encode.ts +70 -0
  105. package/src/vis/spec/field.ts +10 -0
  106. package/src/vis/spec/mark.ts +30 -0
  107. package/src/vis/spec/stack.ts +11 -0
  108. package/src/vis/spec/tooltip.ts +16 -0
  109. package/src/vis/spec/view.ts +136 -0
  110. package/src/vis/theme.ts +12 -0
  111. package/src/visualSettings/index.tsx +10 -1
  112. package/src/workers/transform.ts +12 -0
  113. package/src/workers/transform.worker.js +13 -0
  114. package/src/workers/viewQuery.worker.js +16 -0
  115. package/dist/assets/explainer.worker-8428eb12.js.map +0 -1
  116. package/dist/dataSource/pannel.d.ts +0 -5
  117. package/dist/insightBoard/index.d.ts +0 -3
  118. package/dist/insightBoard/mainBoard.d.ts +0 -11
  119. package/dist/insightBoard/radioGroupButtons.d.ts +0 -12
  120. package/dist/insightBoard/selectionSpec.d.ts +0 -13
  121. package/dist/insightBoard/std2vegaSpec.d.ts +0 -12
  122. package/dist/insightBoard/utils.d.ts +0 -8
  123. package/dist/insights.d.ts +0 -61
  124. package/src/dataSource/pannel.tsx +0 -71
  125. package/src/insightBoard/index.tsx +0 -31
  126. package/src/insightBoard/mainBoard.tsx +0 -224
  127. package/src/insightBoard/radioGroupButtons.tsx +0 -57
  128. package/src/insightBoard/selectionSpec.ts +0 -113
  129. package/src/insightBoard/std2vegaSpec.ts +0 -184
  130. package/src/insightBoard/utils.ts +0 -32
  131. package/src/insights.ts +0 -408
  132. package/src/workers/explainer.worker.js +0 -76
@@ -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;
@@ -41,6 +40,7 @@ interface ReactVegaProps {
41
40
  shape?: IViewField;
42
41
  theta?: IViewField;
43
42
  radius?: IViewField;
43
+ details?: Readonly<IViewField[]>;
44
44
  showActions: boolean;
45
45
  layoutMode: string;
46
46
  width: number;
@@ -52,14 +52,7 @@ interface ReactVegaProps {
52
52
  themeKey?: IThemeKey;
53
53
  dark?: IDarkMode;
54
54
  }
55
- const NULL_FIELD: IViewField = {
56
- dragId: '',
57
- fid: '',
58
- name: '',
59
- semanticType: 'quantitative',
60
- analyticType: 'measure',
61
- aggName: 'sum'
62
- }
55
+
63
56
  const click$ = new Subject<ScenegraphEvent>();
64
57
  const selection$ = new Subject<any>();
65
58
  const geomClick$ = selection$.pipe(
@@ -71,9 +64,6 @@ const geomClick$ = selection$.pipe(
71
64
  return false
72
65
  })
73
66
  );
74
- function getFieldType(field: IViewField): 'quantitative' | 'nominal' | 'ordinal' | 'temporal' {
75
- return field.semanticType
76
- }
77
67
 
78
68
  const BRUSH_SIGNAL_NAME = "__gw_brush__";
79
69
  const POINT_SIGNAL_NAME = "__gw_point__";
@@ -85,301 +75,7 @@ interface ParamStoreEntry {
85
75
  data: any;
86
76
  }
87
77
 
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
78
 
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
79
  const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVega (props, ref) {
384
80
  const {
385
81
  dataSource = [],
@@ -402,11 +98,10 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
402
98
  height,
403
99
  selectEncoding,
404
100
  brushEncoding,
101
+ details = [],
405
102
  themeKey = 'vega',
406
103
  dark = 'media'
407
104
  } = props;
408
- // const container = useRef<HTMLDivElement>(null);
409
- // const containers = useRef<(HTMLDivElement | null)[]>([]);
410
105
  const [viewPlaceholders, setViewPlaceholders] = useState<React.MutableRefObject<HTMLDivElement>[]>([]);
411
106
  const { i18n } = useTranslation();
412
107
  const mediaTheme = useCurrentMediaTheme(dark);
@@ -496,6 +191,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
496
191
  column: colFacetField,
497
192
  xOffset: NULL_FIELD,
498
193
  yOffset: NULL_FIELD,
194
+ details,
499
195
  defaultAggregated: defaultAggregate,
500
196
  stack,
501
197
  geomType,
@@ -504,28 +200,12 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
504
200
  enableCrossFilter: false,
505
201
  asCrossFilterTrigger: false,
506
202
  });
507
- // if (layoutMode === 'fixed') {
508
- // spec.width = 800;
509
- // spec.height = 600;
510
- // }
203
+
511
204
  spec.mark = singleView.mark;
512
205
  if ('encoding' in singleView) {
513
206
  spec.encoding = singleView.encoding;
514
207
  }
515
208
 
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
209
  if ('params' in singleView) {
530
210
  spec.params.push(...singleView.params!);
531
211
  }
@@ -580,6 +260,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
580
260
  column: colFacetField,
581
261
  xOffset: NULL_FIELD,
582
262
  yOffset: NULL_FIELD,
263
+ details,
583
264
  defaultAggregated: defaultAggregate,
584
265
  stack,
585
266
  geomType,
@@ -592,19 +273,6 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
592
273
  const node = i * colRepeatFields.length + j < viewPlaceholders.length ? viewPlaceholders[i * colRepeatFields.length + j].current : null
593
274
  let commonSpec = { ...spec };
594
275
 
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
276
  if ('params' in singleView) {
609
277
  commonSpec.params = [...commonSpec.params, ...singleView.params!];
610
278
  }
@@ -704,6 +372,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
704
372
  brushEncoding,
705
373
  crossFilterTriggerIdx,
706
374
  themeConfig,
375
+ details
707
376
  ]);
708
377
 
709
378
  useImperativeHandle(ref, () => ({
@@ -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,70 @@
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
+ details: Readonly<IViewField[]>;
18
+ }
19
+ function availableChannels(geomType: string): Set<string> {
20
+ if (geomType === 'arc') {
21
+ return new Set(['opacity', 'color', 'size', 'theta', 'radius']);
22
+ }
23
+ return new Set(['column', 'opacity', 'color', 'row', 'size', 'x', 'y', 'xOffset', 'yOffset', 'shape']);
24
+ }
25
+ export function channelEncode(props: IEncodeProps) {
26
+ const avcs = availableChannels(props.geomType);
27
+ const encoding: { [key: string]: any } = {};
28
+ Object.keys(props)
29
+ .filter((c) => avcs.has(c))
30
+ .forEach((c) => {
31
+ if (props[c] !== NULL_FIELD) {
32
+ encoding[c] = {
33
+ field: props[c].fid,
34
+ title: props[c].name,
35
+ type: props[c].semanticType,
36
+ };
37
+ if (props[c].analyticType !== 'measure') {
38
+ // if `aggregate` is set to null,
39
+ // do not aggregate this field
40
+ encoding[c].aggregate = null;
41
+ }
42
+ if (props[c].analyticType === 'measure') {
43
+ encoding[c].type = 'quantitative';
44
+ }
45
+ }
46
+ });
47
+ // FIXME: temporal fix, only for x and y relative order
48
+ if (encoding.x) {
49
+ encoding.x.axis = { labelOverlap: true }
50
+ }
51
+ if (encoding && encoding.y) {
52
+ encoding.y.axis = { labelOverlap: true }
53
+ }
54
+ if (encoding.x && encoding.y) {
55
+ if ((props.x.sort && props.x.sort) || (props.y && props.y.sort)) {
56
+ if (props.x.sort !== 'none' && (props.y.sort === 'none' || !Boolean(props.y.sort))) {
57
+ encoding.x.sort = {
58
+ encoding: 'y',
59
+ order: props.x.sort,
60
+ };
61
+ } else if (props.y.sort && props.y.sort !== 'none' && (props.x.sort === 'none' || !Boolean(props.x.sort))) {
62
+ encoding.y.sort = {
63
+ encoding: 'x',
64
+ order: props.y.sort,
65
+ };
66
+ }
67
+ }
68
+ }
69
+ return encoding;
70
+ }
@@ -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 "../../interfaces";
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
+ }
@@ -0,0 +1,16 @@
1
+ import { IViewField } from "../../interfaces";
2
+
3
+ export function addTooltipEncode (encoding: {[key: string]: any}, details: Readonly<IViewField[]> = []) {
4
+ const encs = Object.keys(encoding).filter(ck => ck !== 'tooltip').map(ck => {
5
+ return {
6
+ field: encoding[ck].field,
7
+ type: encoding[ck].type,
8
+ title: encoding[ck].title
9
+ }
10
+ }).concat(details.map(f => ({
11
+ field: f.fid,
12
+ title: f.name,
13
+ type: f.semanticType
14
+ })))
15
+ encoding.tooltip = encs
16
+ }