@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.
- package/dist/App.d.ts +2 -0
- package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
- package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
- package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
- package/dist/components/codeExport/index.d.ts +3 -0
- package/dist/components/loadingLayer.d.ts +2 -0
- package/dist/components/tabs/defaultTab.d.ts +1 -0
- package/dist/components/tabs/editableTab.d.ts +1 -2
- package/dist/dataSource/dataSelection/config.d.ts +1 -0
- package/dist/dataSource/dataSelection/utils.d.ts +2 -0
- package/dist/datasets/tmp/test.json +1 -0
- package/dist/graphic-walker.es.js +23081 -22577
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +130 -130
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/interfaces.d.ts +21 -1
- package/dist/lib/execExp.d.ts +8 -0
- package/dist/lib/interfaces.d.ts +22 -0
- package/dist/lib/op/aggregate.d.ts +3 -0
- package/dist/lib/op/bin.d.ts +3 -0
- package/dist/lib/op/fold.d.ts +3 -0
- package/dist/lib/op/stat.d.ts +8 -0
- package/dist/lib/viewQuery.d.ts +5 -0
- package/dist/models/visSpecHistory.d.ts +2 -0
- package/dist/renderer/index.d.ts +8 -7
- package/dist/renderer/specRenderer.d.ts +13 -0
- package/dist/services.d.ts +4 -1
- package/dist/store/commonStore.d.ts +6 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/visualSpecStore.d.ts +11 -4
- package/dist/utils/dataPrep.d.ts +2 -0
- package/dist/utils/index.d.ts +3 -5
- package/dist/utils/save.d.ts +1 -2
- package/dist/vis/react-vega.d.ts +1 -22
- package/dist/vis/spec/aggregate.d.ts +4 -0
- package/dist/vis/spec/encode.d.ts +19 -0
- package/dist/vis/spec/field.d.ts +2 -0
- package/dist/vis/spec/mark.d.ts +7 -0
- package/dist/vis/spec/stack.d.ts +4 -0
- package/dist/vis/spec/view.d.ts +67 -0
- package/dist/workers/transform.d.ts +2 -0
- package/package.json +4 -3
- package/src/App.tsx +5 -2
- package/src/components/codeExport/index.tsx +114 -0
- package/src/components/dataTable/index.tsx +10 -10
- package/src/components/loadingLayer.tsx +7 -0
- package/src/components/tabs/defaultTab.tsx +4 -2
- package/src/components/tabs/editableTab.tsx +74 -39
- package/src/dataSource/dataSelection/config.ts +11 -0
- package/src/dataSource/dataSelection/csvData.tsx +71 -39
- package/src/dataSource/dataSelection/gwFile.tsx +2 -2
- package/src/dataSource/dataSelection/utils.ts +28 -0
- package/src/dataSource/utils.ts +8 -3
- package/src/fields/datasetFields/meaFields.tsx +12 -4
- package/src/index.css +4 -4
- package/src/index.tsx +22 -22
- package/src/interfaces.ts +26 -2
- package/src/lib/execExp.ts +147 -0
- package/src/lib/interfaces.ts +39 -0
- package/src/lib/op/aggregate.ts +49 -0
- package/src/lib/op/bin.ts +25 -0
- package/src/lib/op/fold.ts +17 -0
- package/src/lib/op/stat.ts +46 -0
- package/src/lib/viewQuery.ts +23 -0
- package/src/locales/en-US.json +4 -2
- package/src/locales/i18n.ts +0 -1
- package/src/locales/ja-JP.json +4 -2
- package/src/locales/zh-CN.json +4 -2
- package/src/main.tsx +1 -1
- package/src/models/visSpecHistory.ts +14 -0
- package/src/renderer/index.tsx +58 -126
- package/src/renderer/specRenderer.tsx +119 -0
- package/src/segments/segmentNav.tsx +3 -16
- package/src/segments/visNav.tsx +17 -6
- package/src/services.ts +37 -1
- package/src/store/commonStore.ts +14 -9
- package/src/store/index.tsx +11 -4
- package/src/store/visualSpecStore.ts +89 -50
- package/src/utils/dataPrep.ts +24 -0
- package/src/utils/index.ts +16 -17
- package/src/utils/normalization.ts +3 -1
- package/src/utils/save.ts +1 -2
- package/src/vis/react-vega.tsx +4 -340
- package/src/vis/spec/aggregate.ts +13 -0
- package/src/vis/spec/encode.ts +69 -0
- package/src/vis/spec/field.ts +10 -0
- package/src/vis/spec/mark.ts +30 -0
- package/src/vis/spec/stack.ts +11 -0
- package/src/vis/spec/view.ts +138 -0
- package/src/vis/theme.ts +12 -0
- package/src/visualSettings/index.tsx +10 -1
- package/src/workers/transform.ts +12 -0
- package/src/workers/transform.worker.js +13 -0
- package/src/workers/viewQuery.worker.js +16 -0
- package/dist/dataSource/pannel.d.ts +0 -5
- package/src/dataSource/pannel.tsx +0 -71
package/src/utils/index.ts
CHANGED
|
@@ -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
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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]:
|
|
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[];
|
package/src/vis/react-vega.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|