@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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { runInAction } from 'mobx';
|
|
2
|
+
import { Resizable } from 're-resizable';
|
|
3
|
+
import React, { useCallback, forwardRef, useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useGlobalStore } from '../store';
|
|
6
|
+
import ReactVega, { IReactVegaHandler } from '../vis/react-vega';
|
|
7
|
+
import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig } from '../interfaces';
|
|
8
|
+
import LoadingLayer from '../components/loadingLayer';
|
|
9
|
+
|
|
10
|
+
interface SpecRendererProps {
|
|
11
|
+
themeKey?: IThemeKey;
|
|
12
|
+
dark?: IDarkMode;
|
|
13
|
+
data: IRow[];
|
|
14
|
+
loading: boolean;
|
|
15
|
+
draggableFieldState: DeepReadonly<DraggableFieldState>;
|
|
16
|
+
visualConfig: IVisualConfig;
|
|
17
|
+
}
|
|
18
|
+
const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
|
|
19
|
+
{ themeKey, dark, data, loading, draggableFieldState, visualConfig },
|
|
20
|
+
ref
|
|
21
|
+
) {
|
|
22
|
+
const { vizStore, commonStore } = useGlobalStore();
|
|
23
|
+
// const { draggableFieldState, visualConfig } = vizStore;
|
|
24
|
+
const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, exploration } = visualConfig;
|
|
25
|
+
|
|
26
|
+
const rows = draggableFieldState.rows;
|
|
27
|
+
const columns = draggableFieldState.columns;
|
|
28
|
+
const color = draggableFieldState.color;
|
|
29
|
+
const opacity = draggableFieldState.opacity;
|
|
30
|
+
const shape = draggableFieldState.shape;
|
|
31
|
+
const theta = draggableFieldState.theta;
|
|
32
|
+
const radius = draggableFieldState.radius;
|
|
33
|
+
const sizeChannel = draggableFieldState.size;
|
|
34
|
+
|
|
35
|
+
const rowLeftFacetFields = useMemo(() => rows.slice(0, -1).filter((f) => f.analyticType === 'dimension'), [rows]);
|
|
36
|
+
const colLeftFacetFields = useMemo(
|
|
37
|
+
() => columns.slice(0, -1).filter((f) => f.analyticType === 'dimension'),
|
|
38
|
+
[columns]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
|
|
42
|
+
|
|
43
|
+
const shouldTriggerMenu = exploration.mode === 'none';
|
|
44
|
+
|
|
45
|
+
const handleGeomClick = useCallback(
|
|
46
|
+
(values: any, e: any) => {
|
|
47
|
+
if (shouldTriggerMenu) {
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
runInAction(() => {
|
|
50
|
+
commonStore.showEmbededMenu([e.pageX, e.pageY]);
|
|
51
|
+
commonStore.setFilters(values);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
[shouldTriggerMenu]
|
|
56
|
+
);
|
|
57
|
+
const enableResize = size.mode === 'fixed' && !hasFacet;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Resizable
|
|
61
|
+
className={enableResize ? 'border-blue-400 border-2 overflow-hidden' : ''}
|
|
62
|
+
style={{ padding: '12px' }}
|
|
63
|
+
onResizeStop={(e, direction, ref, d) => {
|
|
64
|
+
vizStore.setChartLayout({
|
|
65
|
+
mode: 'fixed',
|
|
66
|
+
width: size.width + d.width,
|
|
67
|
+
height: size.height + d.height,
|
|
68
|
+
});
|
|
69
|
+
}}
|
|
70
|
+
enable={
|
|
71
|
+
enableResize
|
|
72
|
+
? undefined
|
|
73
|
+
: {
|
|
74
|
+
top: false,
|
|
75
|
+
right: false,
|
|
76
|
+
bottom: false,
|
|
77
|
+
left: false,
|
|
78
|
+
topRight: false,
|
|
79
|
+
bottomRight: false,
|
|
80
|
+
bottomLeft: false,
|
|
81
|
+
topLeft: false,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
size={{
|
|
85
|
+
width: size.width + 'px',
|
|
86
|
+
height: size.height + 'px',
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{loading && <LoadingLayer />}
|
|
90
|
+
<ReactVega
|
|
91
|
+
layoutMode={size.mode}
|
|
92
|
+
interactiveScale={interactiveScale}
|
|
93
|
+
geomType={geoms[0]}
|
|
94
|
+
defaultAggregate={defaultAggregated}
|
|
95
|
+
stack={stack}
|
|
96
|
+
dataSource={data}
|
|
97
|
+
rows={rows}
|
|
98
|
+
columns={columns}
|
|
99
|
+
color={color[0]}
|
|
100
|
+
theta={theta[0]}
|
|
101
|
+
radius={radius[0]}
|
|
102
|
+
shape={shape[0]}
|
|
103
|
+
opacity={opacity[0]}
|
|
104
|
+
size={sizeChannel[0]}
|
|
105
|
+
showActions={showActions}
|
|
106
|
+
width={size.width - 12 * 4}
|
|
107
|
+
height={size.height - 12 * 4}
|
|
108
|
+
ref={ref}
|
|
109
|
+
brushEncoding={exploration.mode === 'brush' ? exploration.brushDirection : 'none'}
|
|
110
|
+
selectEncoding={exploration.mode === 'point' ? 'default' : 'none'}
|
|
111
|
+
onGeomClick={handleGeomClick}
|
|
112
|
+
themeKey={themeKey}
|
|
113
|
+
dark={dark}
|
|
114
|
+
/>
|
|
115
|
+
</Resizable>
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export default SpecRenderer;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useCallback } from "react";
|
|
2
2
|
import { observer } from "mobx-react-lite";
|
|
3
3
|
import DefaultTab, { ITabOption } from "../components/tabs/defaultTab";
|
|
4
4
|
import { useGlobalStore } from "../store";
|
|
5
|
-
import {
|
|
5
|
+
import { ChartPieIcon, CircleStackIcon } from "@heroicons/react/24/outline";
|
|
6
6
|
import { ISegmentKey } from "../interfaces";
|
|
7
7
|
import { useTranslation } from "react-i18next";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const ADD_KEY = '_add';
|
|
11
|
-
|
|
12
9
|
const SegmentNav: React.FC = (props) => {
|
|
13
10
|
const { vizStore, commonStore } = useGlobalStore();
|
|
14
|
-
const {
|
|
15
|
-
const { currentDataset, segmentKey } = commonStore;
|
|
11
|
+
const { segmentKey } = commonStore;
|
|
16
12
|
const { t } = useTranslation();
|
|
17
13
|
|
|
18
14
|
const tabs: ITabOption[] = [
|
|
@@ -30,15 +26,6 @@ const SegmentNav: React.FC = (props) => {
|
|
|
30
26
|
}
|
|
31
27
|
]
|
|
32
28
|
|
|
33
|
-
const visSelectionHandler = useCallback((tabKey: string, tabIndex: number) => {
|
|
34
|
-
if (tabKey === ADD_KEY) {
|
|
35
|
-
vizStore.addVisualization();
|
|
36
|
-
vizStore.initMetaState(currentDataset)
|
|
37
|
-
} else {
|
|
38
|
-
vizStore.selectVisualization(tabIndex);
|
|
39
|
-
}
|
|
40
|
-
}, [currentDataset, vizStore])
|
|
41
|
-
|
|
42
29
|
const editLabelHandler = useCallback((content: string, tabIndex: number) => {
|
|
43
30
|
vizStore.setVisName(tabIndex, content)
|
|
44
31
|
}, [])
|
package/src/segments/visNav.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useCallback } from "react";
|
|
1
|
+
import React, { useCallback, useEffect } from "react";
|
|
2
2
|
import { observer } from "mobx-react-lite";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
3
4
|
import EditableTabs, { ITabOption } from "../components/tabs/editableTab";
|
|
4
5
|
import { useGlobalStore } from "../store";
|
|
5
6
|
|
|
@@ -11,25 +12,35 @@ const VisNav: React.FC = (props) => {
|
|
|
11
12
|
const { visIndex, visList } = vizStore;
|
|
12
13
|
const { currentDataset } = commonStore;
|
|
13
14
|
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
|
|
14
17
|
const tabs: ITabOption[] = visList.map((v) => ({
|
|
15
18
|
key: v.visId,
|
|
16
|
-
label: v.name
|
|
17
|
-
|
|
19
|
+
label: v.name ?? 'vis',
|
|
20
|
+
editable: true
|
|
18
21
|
}));
|
|
19
22
|
|
|
20
23
|
tabs.push({
|
|
21
24
|
key: ADD_KEY,
|
|
22
|
-
label: 'main.tablist.new'
|
|
25
|
+
label: t('main.tablist.new')
|
|
23
26
|
});
|
|
24
27
|
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (visList.length === 1) {
|
|
30
|
+
// should set the first vis name when the component is mounted
|
|
31
|
+
vizStore.setVisName(0, t('main.tablist.auto_title', { idx: 1 }));
|
|
32
|
+
}
|
|
33
|
+
// no need to add deps here
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
25
36
|
const visSelectionHandler = useCallback((tabKey: string, tabIndex: number) => {
|
|
26
37
|
if (tabKey === ADD_KEY) {
|
|
27
|
-
vizStore.addVisualization();
|
|
38
|
+
vizStore.addVisualization(t('main.tablist.auto_title', { idx: visList.length + 1 }));
|
|
28
39
|
vizStore.initMetaState(currentDataset)
|
|
29
40
|
} else {
|
|
30
41
|
vizStore.selectVisualization(tabIndex);
|
|
31
42
|
}
|
|
32
|
-
}, [currentDataset, vizStore])
|
|
43
|
+
}, [currentDataset, vizStore, visList.length])
|
|
33
44
|
|
|
34
45
|
const editLabelHandler = useCallback((content: string, tabIndex: number) => {
|
|
35
46
|
vizStore.setVisName(tabIndex, content)
|
package/src/services.ts
CHANGED
|
@@ -10,7 +10,10 @@ import { IRow, Filters, SemanticType, IMeasure, IMutField, IFilterField } from '
|
|
|
10
10
|
// eslint-disable-next-line
|
|
11
11
|
import ExplainerWorker from './workers/explainer.worker?worker&inline';
|
|
12
12
|
import FilterWorker from './workers/filter.worker?worker&inline';
|
|
13
|
+
import TransformDataWorker from './workers/transform.worker?worker&inline';
|
|
14
|
+
import ViewQueryWorker from './workers/viewQuery.worker?worker&inline';
|
|
13
15
|
import { IExplaination, IMeasureWithStat } from './insights';
|
|
16
|
+
import { IViewQuery, queryView } from './lib/viewQuery';
|
|
14
17
|
|
|
15
18
|
interface WorkerState {
|
|
16
19
|
eWorker: Worker | null;
|
|
@@ -105,7 +108,8 @@ export function destroyWorker() {
|
|
|
105
108
|
let filterWorker: Worker | null = null;
|
|
106
109
|
let filterWorkerAutoTerminator: NodeJS.Timeout | null = null;
|
|
107
110
|
|
|
108
|
-
export const applyFilter = async (data:
|
|
111
|
+
export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]): Promise<IRow[]> => {
|
|
112
|
+
if (filters.length === 0) return data;
|
|
109
113
|
if (filterWorkerAutoTerminator !== null) {
|
|
110
114
|
clearTimeout(filterWorkerAutoTerminator);
|
|
111
115
|
filterWorkerAutoTerminator = null;
|
|
@@ -137,3 +141,35 @@ export const applyFilter = async (data: readonly IRow[], filters: readonly IFilt
|
|
|
137
141
|
}, 60_000); // Destroy the worker when no request is received for 60 secs
|
|
138
142
|
}
|
|
139
143
|
};
|
|
144
|
+
|
|
145
|
+
export const transformDataService = async (data: IRow[], columns: IMutField[]): Promise<IRow[]> => {
|
|
146
|
+
if (columns.length === 0 || data.length === 0) return data;
|
|
147
|
+
const worker = new TransformDataWorker();
|
|
148
|
+
try {
|
|
149
|
+
const res: IRow[] = await workerService(worker, {
|
|
150
|
+
dataSource: data,
|
|
151
|
+
columns: toJS(columns),
|
|
152
|
+
});
|
|
153
|
+
return res;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
throw new Error('Uncaught error in TransformDataWorker', { cause: error });
|
|
156
|
+
} finally {
|
|
157
|
+
worker.terminate();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const applyViewQuery = async (data: IRow[], metas: IMutField[], query: IViewQuery): Promise<IRow[]> => {
|
|
162
|
+
const worker = new ViewQueryWorker();
|
|
163
|
+
try {
|
|
164
|
+
const res: IRow[] = await workerService(worker, {
|
|
165
|
+
dataSource: data,
|
|
166
|
+
metas: toJS(metas),
|
|
167
|
+
query: toJS(query),
|
|
168
|
+
});
|
|
169
|
+
return res;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
throw new Error('Uncaught error in ViewQueryWorker', { cause: err });
|
|
172
|
+
} finally {
|
|
173
|
+
worker.terminate();
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/store/commonStore.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DataSet, Filters, IDataSet, IDataSetInfo, IDataSource, IMutField, IRow, ISegmentKey } from '../interfaces';
|
|
2
2
|
import { makeAutoObservable, observable, toJS } from 'mobx';
|
|
3
3
|
import { transData } from '../dataSource/utils';
|
|
4
|
-
import { extendCountField } from '../utils';
|
|
5
4
|
|
|
6
5
|
export class CommonStore {
|
|
7
6
|
public datasets: IDataSet[] = [];
|
|
@@ -14,6 +13,7 @@ export class CommonStore {
|
|
|
14
13
|
public showInsightBoard: boolean = false;
|
|
15
14
|
public vizEmbededMenu: { show: boolean; position: [number, number] } = { show: false, position: [0, 0] };
|
|
16
15
|
public showDataConfig: boolean = false;
|
|
16
|
+
public showCodeExportPanel: boolean = false;
|
|
17
17
|
public filters: Filters = {};
|
|
18
18
|
public segmentKey: ISegmentKey = ISegmentKey.vis;
|
|
19
19
|
constructor () {
|
|
@@ -30,12 +30,12 @@ export class CommonStore {
|
|
|
30
30
|
if (this.datasets.length > 0) {
|
|
31
31
|
const dataSourceId = this.datasets[datasetIndex].dsId;
|
|
32
32
|
const dataSource = this.dataSources.find(d => d.id === dataSourceId);
|
|
33
|
-
const rawFields = toJS(this.datasets[datasetIndex].rawFields)
|
|
34
|
-
const base = extendCountField((dataSource ? dataSource.data : []), rawFields)
|
|
33
|
+
const rawFields = toJS(this.datasets[datasetIndex].rawFields)//.concat(createCountField())
|
|
34
|
+
// const base = extendCountField((dataSource ? dataSource.data : []), rawFields)
|
|
35
35
|
return {
|
|
36
36
|
...this.datasets[datasetIndex],
|
|
37
|
-
dataSource:
|
|
38
|
-
rawFields
|
|
37
|
+
dataSource: dataSource?.data ?? [],
|
|
38
|
+
rawFields
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
return {
|
|
@@ -61,6 +61,9 @@ export class CommonStore {
|
|
|
61
61
|
this.vizEmbededMenu.show = true;
|
|
62
62
|
this.vizEmbededMenu.position = position;
|
|
63
63
|
}
|
|
64
|
+
public setShowCodeExportPanel (show: boolean) {
|
|
65
|
+
this.showCodeExportPanel = show;
|
|
66
|
+
}
|
|
64
67
|
public closeEmbededMenu () {
|
|
65
68
|
this.vizEmbededMenu.show = false;
|
|
66
69
|
}
|
|
@@ -112,11 +115,13 @@ export class CommonStore {
|
|
|
112
115
|
|
|
113
116
|
public updateTempDS (rawData: IRow[]) {
|
|
114
117
|
const result = transData(rawData);
|
|
115
|
-
|
|
116
|
-
this.tmpDataSource = result.dataSource.slice(0, -1);
|
|
118
|
+
this.tmpDataSource = result.dataSource;
|
|
117
119
|
this.tmpDSRawFields = result.fields;
|
|
118
120
|
}
|
|
119
|
-
|
|
121
|
+
/**
|
|
122
|
+
* update temp dataset (standard) with dataset info
|
|
123
|
+
* @param dataset
|
|
124
|
+
*/
|
|
120
125
|
public updateTempSTDDS (dataset: IDataSetInfo) {
|
|
121
126
|
this.tmpDataSource = dataset.dataSource;
|
|
122
127
|
this.tmpDSRawFields = dataset.rawFields;
|
|
@@ -176,7 +181,7 @@ export class CommonStore {
|
|
|
176
181
|
}
|
|
177
182
|
public createPlaceholderDS() {
|
|
178
183
|
this.addDS({
|
|
179
|
-
name: '
|
|
184
|
+
name: 'new dataset',
|
|
180
185
|
dataSource: [],
|
|
181
186
|
rawFields: []
|
|
182
187
|
})
|
package/src/store/index.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React, { useContext
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
2
|
import { CommonStore } from './commonStore'
|
|
3
3
|
import { VizSpecStore } from './visualSpecStore'
|
|
4
4
|
|
|
5
|
-
interface
|
|
5
|
+
export interface IGlobalStore {
|
|
6
6
|
commonStore: CommonStore;
|
|
7
7
|
vizStore: VizSpecStore;
|
|
8
8
|
}
|
|
@@ -10,12 +10,12 @@ interface GlobalStore {
|
|
|
10
10
|
const commonStore = new CommonStore();
|
|
11
11
|
const vizStore = new VizSpecStore(commonStore);
|
|
12
12
|
|
|
13
|
-
const initStore:
|
|
13
|
+
const initStore: IGlobalStore = {
|
|
14
14
|
commonStore,
|
|
15
15
|
vizStore
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const StoreContext = React.createContext<
|
|
18
|
+
const StoreContext = React.createContext<IGlobalStore>(null!);
|
|
19
19
|
|
|
20
20
|
export function destroyGWStore() {
|
|
21
21
|
initStore.commonStore.destroy();
|
|
@@ -31,16 +31,23 @@ export function rebootGWStore() {
|
|
|
31
31
|
|
|
32
32
|
interface StoreWrapperProps {
|
|
33
33
|
keepAlive?: boolean;
|
|
34
|
+
storeRef?: React.MutableRefObject<IGlobalStore | null>;
|
|
34
35
|
}
|
|
35
36
|
export class StoreWrapper extends React.Component<StoreWrapperProps> {
|
|
36
37
|
constructor(props: StoreWrapperProps) {
|
|
37
38
|
super(props)
|
|
39
|
+
if (props.storeRef) {
|
|
40
|
+
props.storeRef.current = initStore;
|
|
41
|
+
}
|
|
38
42
|
if (props.keepAlive) {
|
|
39
43
|
rebootGWStore();
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
componentWillUnmount() {
|
|
43
47
|
if (!this.props.keepAlive) {
|
|
48
|
+
if (this.props.storeRef) {
|
|
49
|
+
this.props.storeRef.current = null;
|
|
50
|
+
}
|
|
44
51
|
destroyGWStore();
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -2,12 +2,14 @@ import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } fro
|
|
|
2
2
|
import produce from "immer";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
import { Specification } from "visual-insights";
|
|
5
|
-
import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisualConfig } from "../interfaces";
|
|
5
|
+
import { DataSet, DraggableFieldState, IExpression, IFilterRule, IViewField, IVisSpec, IVisualConfig } from "../interfaces";
|
|
6
6
|
import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
|
|
7
7
|
import { makeBinField, makeLogField } from "../utils/normalization";
|
|
8
8
|
import { VisSpecWithHistory } from "../models/visSpecHistory";
|
|
9
|
-
import { dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
|
|
9
|
+
import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
|
|
10
10
|
import { CommonStore } from "./commonStore";
|
|
11
|
+
import { createCountField } from "../utils";
|
|
12
|
+
import { IViewQuery } from "../lib/viewQuery";
|
|
11
13
|
|
|
12
14
|
function getChannelSizeLimit(channel: string): number {
|
|
13
15
|
if (typeof CHANNEL_LIMIT[channel] === "undefined") return Infinity;
|
|
@@ -40,7 +42,7 @@ function geomAdapter(geom: string) {
|
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
function initEncoding(): DraggableFieldState {
|
|
45
|
+
export function initEncoding(): DraggableFieldState {
|
|
44
46
|
return {
|
|
45
47
|
dimensions: [],
|
|
46
48
|
measures: [],
|
|
@@ -58,7 +60,7 @@ function initEncoding(): DraggableFieldState {
|
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
function initVisualConfig(): IVisualConfig {
|
|
63
|
+
export function initVisualConfig(): IVisualConfig {
|
|
62
64
|
return {
|
|
63
65
|
defaultAggregated: true,
|
|
64
66
|
geoms: [GEMO_TYPES[0]!],
|
|
@@ -92,6 +94,10 @@ const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["spec
|
|
|
92
94
|
}));
|
|
93
95
|
};
|
|
94
96
|
|
|
97
|
+
function isDraggableStateEmpty(state: DeepReadonly<DraggableFieldState>): boolean {
|
|
98
|
+
return Object.values(state).every((value) => value.length === 0);
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
export class VizSpecStore {
|
|
96
102
|
// public fields: IViewField[] = [];
|
|
97
103
|
private commonStore: CommonStore;
|
|
@@ -139,7 +145,7 @@ export class VizSpecStore {
|
|
|
139
145
|
this.visualConfig = initVisualConfig();
|
|
140
146
|
this.visList.push(
|
|
141
147
|
new VisSpecWithHistory({
|
|
142
|
-
name:
|
|
148
|
+
name: 'Chart 1',
|
|
143
149
|
visId: uuidv4(),
|
|
144
150
|
config: this.visualConfig,
|
|
145
151
|
encodings: this.draggableFieldState,
|
|
@@ -151,13 +157,6 @@ export class VizSpecStore {
|
|
|
151
157
|
reactions: false,
|
|
152
158
|
});
|
|
153
159
|
this.reactions.push(
|
|
154
|
-
reaction(
|
|
155
|
-
() => commonStore.currentDataset,
|
|
156
|
-
(dataset) => {
|
|
157
|
-
// this.initState();
|
|
158
|
-
this.initMetaState(dataset);
|
|
159
|
-
}
|
|
160
|
-
),
|
|
161
160
|
reaction(
|
|
162
161
|
() => this.visList[this.visIndex],
|
|
163
162
|
(frame) => {
|
|
@@ -168,6 +167,15 @@ export class VizSpecStore {
|
|
|
168
167
|
this.canUndo = frame.canUndo;
|
|
169
168
|
this.canRedo = frame.canRedo;
|
|
170
169
|
}
|
|
170
|
+
),
|
|
171
|
+
reaction(
|
|
172
|
+
() => commonStore.currentDataset,
|
|
173
|
+
(dataset) => {
|
|
174
|
+
// this.initState();
|
|
175
|
+
if (isDraggableStateEmpty(this.draggableFieldState) && dataset.dataSource.length > 0 && dataset.rawFields.length > 0) {
|
|
176
|
+
this.initMetaState(dataset);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
171
179
|
)
|
|
172
180
|
);
|
|
173
181
|
}
|
|
@@ -275,10 +283,24 @@ export class VizSpecStore {
|
|
|
275
283
|
});
|
|
276
284
|
return fields;
|
|
277
285
|
}
|
|
278
|
-
public
|
|
286
|
+
public get allFields(): IViewField[] {
|
|
287
|
+
const { draggableFieldState } = this;
|
|
288
|
+
const dimensions = toJS(draggableFieldState.dimensions);
|
|
289
|
+
const measures = toJS(draggableFieldState.measures);
|
|
290
|
+
return [...dimensions, ...measures];
|
|
291
|
+
}
|
|
292
|
+
public get viewFilters() {
|
|
293
|
+
const { draggableFieldState } = this;
|
|
294
|
+
const state = toJS(draggableFieldState);
|
|
295
|
+
return state.filters;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
public addVisualization(defaultName?: string) {
|
|
300
|
+
const name = defaultName || 'Chart ' + (this.visList.length + 1);
|
|
279
301
|
this.visList.push(
|
|
280
302
|
new VisSpecWithHistory({
|
|
281
|
-
name
|
|
303
|
+
name,
|
|
282
304
|
visId: uuidv4(),
|
|
283
305
|
config: initVisualConfig(),
|
|
284
306
|
encodings: initEncoding(),
|
|
@@ -290,9 +312,10 @@ export class VizSpecStore {
|
|
|
290
312
|
this.visIndex = visIndex;
|
|
291
313
|
}
|
|
292
314
|
public setVisName(visIndex: number, name: string) {
|
|
293
|
-
this.
|
|
294
|
-
|
|
295
|
-
|
|
315
|
+
this.visList[visIndex] = this.visList[visIndex].clone();
|
|
316
|
+
this.visList[visIndex].updateLatest({
|
|
317
|
+
name
|
|
318
|
+
})
|
|
296
319
|
}
|
|
297
320
|
public initState() {
|
|
298
321
|
this.useMutable((tab) => {
|
|
@@ -301,6 +324,7 @@ export class VizSpecStore {
|
|
|
301
324
|
});
|
|
302
325
|
}
|
|
303
326
|
public initMetaState(dataset: DataSet) {
|
|
327
|
+
const countField = createCountField();
|
|
304
328
|
this.useMutable(({ encodings }) => {
|
|
305
329
|
encodings.fields = dataset.rawFields.map((f) => ({
|
|
306
330
|
dragId: uuidv4(),
|
|
@@ -309,7 +333,8 @@ export class VizSpecStore {
|
|
|
309
333
|
aggName: f.analyticType === "measure" ? "sum" : undefined,
|
|
310
334
|
analyticType: f.analyticType,
|
|
311
335
|
semanticType: f.semanticType,
|
|
312
|
-
}));
|
|
336
|
+
}))//.concat(countField);
|
|
337
|
+
encodings.fields.push(countField);
|
|
313
338
|
encodings.dimensions = dataset.rawFields
|
|
314
339
|
.filter((f) => f.analyticType === "dimension")
|
|
315
340
|
.map((f) => ({
|
|
@@ -329,17 +354,10 @@ export class VizSpecStore {
|
|
|
329
354
|
semanticType: f.semanticType,
|
|
330
355
|
aggName: "sum",
|
|
331
356
|
}));
|
|
357
|
+
encodings.measures.push(countField);
|
|
332
358
|
});
|
|
333
359
|
|
|
334
360
|
this.freezeHistory();
|
|
335
|
-
// this.draggableFieldState.measures.push({
|
|
336
|
-
// dragId: uuidv4(),
|
|
337
|
-
// fid: COUNT_FIELD_ID,
|
|
338
|
-
// name: '记录数',
|
|
339
|
-
// analyticType: 'measure',
|
|
340
|
-
// semanticType: 'quantitative',
|
|
341
|
-
// aggName: 'count'
|
|
342
|
-
// })
|
|
343
361
|
}
|
|
344
362
|
public clearState() {
|
|
345
363
|
this.useMutable(({ encodings }) => {
|
|
@@ -489,44 +507,59 @@ export class VizSpecStore {
|
|
|
489
507
|
encodings.rows = fieldsInCup as typeof encodings.rows; // assume this as writable
|
|
490
508
|
});
|
|
491
509
|
}
|
|
492
|
-
public createBinField(stateKey: keyof DraggableFieldState, index: number) {
|
|
510
|
+
public createBinField(stateKey: keyof DraggableFieldState, index: number, binType: 'bin' | 'binCount') {
|
|
493
511
|
this.useMutable(({ encodings }) => {
|
|
494
512
|
const originField = encodings[stateKey][index];
|
|
513
|
+
const newVarKey = uuidv4();
|
|
495
514
|
const binField: IViewField = {
|
|
496
|
-
fid:
|
|
497
|
-
dragId:
|
|
498
|
-
name:
|
|
515
|
+
fid: newVarKey,
|
|
516
|
+
dragId: newVarKey,
|
|
517
|
+
name: `${binType}(${originField.name})`,
|
|
499
518
|
semanticType: "ordinal",
|
|
500
519
|
analyticType: "dimension",
|
|
520
|
+
computed: true,
|
|
521
|
+
expressoion: {
|
|
522
|
+
op: binType,
|
|
523
|
+
as: newVarKey,
|
|
524
|
+
params: [
|
|
525
|
+
{
|
|
526
|
+
type: 'field',
|
|
527
|
+
value: originField.fid
|
|
528
|
+
}
|
|
529
|
+
]
|
|
530
|
+
}
|
|
501
531
|
};
|
|
502
532
|
encodings.dimensions.push(binField);
|
|
503
|
-
this.commonStore.currentDataset.dataSource = makeBinField(
|
|
504
|
-
this.commonStore.currentDataset.dataSource,
|
|
505
|
-
originField.fid,
|
|
506
|
-
binField.fid
|
|
507
|
-
);
|
|
508
533
|
});
|
|
509
534
|
}
|
|
510
|
-
public createLogField(stateKey: keyof DraggableFieldState, index: number) {
|
|
535
|
+
public createLogField(stateKey: keyof DraggableFieldState, index: number, scaleType: 'log10' | 'log2') {
|
|
511
536
|
if (stateKey === "filters") {
|
|
512
537
|
return;
|
|
513
538
|
}
|
|
514
539
|
|
|
515
540
|
this.useMutable(({ encodings }) => {
|
|
516
541
|
const originField = encodings[stateKey][index];
|
|
542
|
+
const newVarKey = uuidv4();
|
|
517
543
|
const logField: IViewField = {
|
|
518
|
-
fid:
|
|
519
|
-
dragId:
|
|
520
|
-
name:
|
|
544
|
+
fid: newVarKey,
|
|
545
|
+
dragId: newVarKey,
|
|
546
|
+
name: `${scaleType}(${originField.name})`,
|
|
521
547
|
semanticType: "quantitative",
|
|
522
548
|
analyticType: originField.analyticType,
|
|
549
|
+
aggName: 'sum',
|
|
550
|
+
computed: true,
|
|
551
|
+
expressoion: {
|
|
552
|
+
op: scaleType,
|
|
553
|
+
as: newVarKey,
|
|
554
|
+
params: [
|
|
555
|
+
{
|
|
556
|
+
type: 'field',
|
|
557
|
+
value: originField.fid
|
|
558
|
+
}
|
|
559
|
+
]
|
|
560
|
+
}
|
|
523
561
|
};
|
|
524
562
|
encodings[stateKey].push(logField);
|
|
525
|
-
this.commonStore.currentDataset.dataSource = makeLogField(
|
|
526
|
-
this.commonStore.currentDataset.dataSource,
|
|
527
|
-
originField.fid,
|
|
528
|
-
logField.fid
|
|
529
|
-
);
|
|
530
563
|
});
|
|
531
564
|
}
|
|
532
565
|
public setFieldAggregator(stateKey: keyof DraggableFieldState, index: number, aggName: string) {
|
|
@@ -677,13 +710,19 @@ export class VizSpecStore {
|
|
|
677
710
|
specList: pureVisList,
|
|
678
711
|
});
|
|
679
712
|
}
|
|
713
|
+
public exportViewSpec() {
|
|
714
|
+
const pureVisList = dumpsGWPureSpec(this.visList);
|
|
715
|
+
return pureVisList
|
|
716
|
+
}
|
|
717
|
+
public importStoInfo (stoInfo: IStoInfo) {
|
|
718
|
+
this.visList = parseGWPureSpec(forwardVisualConfigs(stoInfo.specList));
|
|
719
|
+
this.visIndex = 0;
|
|
720
|
+
this.commonStore.datasets = stoInfo.datasets;
|
|
721
|
+
this.commonStore.dataSources = stoInfo.dataSources;
|
|
722
|
+
this.commonStore.dsIndex = Math.max(stoInfo.datasets.length - 1, 0);
|
|
723
|
+
}
|
|
680
724
|
public importRaw(raw: string) {
|
|
681
725
|
const content = parseGWContent(raw);
|
|
682
|
-
this.
|
|
683
|
-
this.commonStore.dataSources = content.dataSources;
|
|
684
|
-
this.commonStore.dsIndex = Math.max(content.datasets.length - 1, 0);
|
|
685
|
-
// 补上初始化新版本特性
|
|
686
|
-
this.visList = parseGWPureSpec(forwardVisualConfigs(content.specList));
|
|
687
|
-
this.visIndex = 0;
|
|
726
|
+
this.importStoInfo(content);
|
|
688
727
|
}
|
|
689
728
|
}
|
package/src/utils/dataPrep.ts
CHANGED
|
@@ -41,4 +41,28 @@ export function guardDataKeys (data: IRow[], metas: IMutField[]): {
|
|
|
41
41
|
safeData,
|
|
42
42
|
safeMetas
|
|
43
43
|
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SPLITOR = '__'
|
|
47
|
+
export function flatNestKeys (object: any): string[] {
|
|
48
|
+
const keys = Object.keys(object);
|
|
49
|
+
let flatColKeys: string[] = [];
|
|
50
|
+
for (let key of keys) {
|
|
51
|
+
if (typeof object[key] === 'object') {
|
|
52
|
+
const subKeys = flatNestKeys(object[key]);
|
|
53
|
+
flatColKeys = flatColKeys.concat(subKeys.map(k => `${key}${SPLITOR}${k}`));
|
|
54
|
+
} else {
|
|
55
|
+
flatColKeys.push(key)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return flatColKeys;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getValueByKeyPath (object: any, keyPath: string): any {
|
|
62
|
+
const keys = keyPath.split(SPLITOR);
|
|
63
|
+
let value = object;
|
|
64
|
+
for (let key of keys) {
|
|
65
|
+
value = value[key];
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
44
68
|
}
|