@kanaries/graphic-walker 0.2.4 → 0.2.6
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 +1 -0
- package/dist/graphic-walker.es.js +1 -1
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +1 -1
- package/dist/graphic-walker.umd.js.map +1 -1
- package/package.json +3 -2
- package/src/App.tsx +141 -0
- package/src/assets/kanaries.ico +0 -0
- package/src/components/clickMenu.tsx +29 -0
- package/src/components/container.tsx +16 -0
- package/src/components/dataTypeIcon.tsx +20 -0
- package/src/components/liteForm.tsx +16 -0
- package/src/components/modal.tsx +85 -0
- package/src/components/sizeSetting.tsx +95 -0
- package/src/components/tabs/pureTab.tsx +70 -0
- package/src/config.ts +57 -0
- package/src/constants.ts +1 -0
- package/src/dataSource/config.ts +62 -0
- package/src/dataSource/dataSelection/csvData.tsx +77 -0
- package/src/dataSource/dataSelection/gwFile.tsx +38 -0
- package/src/dataSource/dataSelection/index.tsx +57 -0
- package/src/dataSource/dataSelection/publicData.tsx +57 -0
- package/src/dataSource/index.tsx +78 -0
- package/src/dataSource/pannel.tsx +71 -0
- package/src/dataSource/table.tsx +125 -0
- package/src/dataSource/utils.ts +47 -0
- package/src/fields/aestheticFields.tsx +23 -0
- package/src/fields/components.tsx +159 -0
- package/src/fields/datasetFields/dimFields.tsx +45 -0
- package/src/fields/datasetFields/fieldPill.tsx +10 -0
- package/src/fields/datasetFields/index.tsx +28 -0
- package/src/fields/datasetFields/meaFields.tsx +58 -0
- package/src/fields/fieldsContext.tsx +59 -0
- package/src/fields/filterField/filterEditDialog.tsx +143 -0
- package/src/fields/filterField/filterPill.tsx +113 -0
- package/src/fields/filterField/index.tsx +61 -0
- package/src/fields/filterField/slider.tsx +236 -0
- package/src/fields/filterField/tabs.tsx +421 -0
- package/src/fields/obComponents/obFContainer.tsx +40 -0
- package/src/fields/obComponents/obPill.tsx +48 -0
- package/src/fields/posFields/index.tsx +33 -0
- package/src/fields/select.tsx +31 -0
- package/src/fields/utils.ts +31 -0
- package/src/index.css +13 -0
- package/src/index.tsx +12 -0
- package/src/insightBoard/index.tsx +30 -0
- package/src/insightBoard/mainBoard.tsx +203 -0
- package/src/insightBoard/radioGroupButtons.tsx +50 -0
- package/src/insightBoard/selectionSpec.ts +113 -0
- package/src/insightBoard/std2vegaSpec.ts +184 -0
- package/src/insightBoard/utils.ts +32 -0
- package/src/insights.ts +408 -0
- package/src/interfaces.ts +154 -0
- package/src/locales/en-US.json +140 -0
- package/src/locales/i18n.ts +50 -0
- package/src/locales/zh-CN.json +140 -0
- package/src/main.tsx +10 -0
- package/src/models/visSpecHistory.ts +129 -0
- package/src/renderer/index.tsx +104 -0
- package/src/segments/visNav.tsx +48 -0
- package/src/services.ts +139 -0
- package/src/store/commonStore.ts +158 -0
- package/src/store/index.tsx +53 -0
- package/src/store/visualSpecStore.ts +586 -0
- package/src/utils/autoMark.ts +34 -0
- package/src/utils/index.ts +251 -0
- package/src/utils/normalization.ts +158 -0
- package/src/utils/save.ts +46 -0
- package/src/vis/future-react-vega.tsx +193 -0
- package/src/vis/gen-vega.tsx +52 -0
- package/src/vis/react-vega.tsx +398 -0
- package/src/visualSettings/index.tsx +252 -0
- package/src/visualSettings/menubar.tsx +109 -0
- package/src/vite-env.d.ts +1 -0
- package/src/workers/explainer.worker.js +78 -0
- 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
|
+
}
|