@kanaries/graphic-walker 0.2.4 → 0.2.5
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/package.json +3 -2
- package/src/App.tsx +140 -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,104 @@
|
|
|
1
|
+
import { runInAction, toJS } from 'mobx';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { Resizable } from 're-resizable';
|
|
4
|
+
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
5
|
+
import { applyFilter } from '../services';
|
|
6
|
+
import { useGlobalStore } from '../store';
|
|
7
|
+
import ReactVega from '../vis/react-vega';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const ReactiveRenderer: React.FC = props => {
|
|
11
|
+
const { vizStore, commonStore } = useGlobalStore();
|
|
12
|
+
const { draggableFieldState, visualConfig } = vizStore;
|
|
13
|
+
const { geoms, interactiveScale, defaultAggregated, stack, showActions, size } = visualConfig;
|
|
14
|
+
const { currentDataset } = commonStore;
|
|
15
|
+
const { filters } = draggableFieldState;
|
|
16
|
+
|
|
17
|
+
const rows = toJS(draggableFieldState.rows)
|
|
18
|
+
const columns = toJS(draggableFieldState.columns)
|
|
19
|
+
const color = toJS(draggableFieldState.color)
|
|
20
|
+
const opacity = toJS(draggableFieldState.opacity)
|
|
21
|
+
const shape = toJS(draggableFieldState.shape)
|
|
22
|
+
const theta = toJS(draggableFieldState.theta)
|
|
23
|
+
const radius = toJS(draggableFieldState.radius)
|
|
24
|
+
const sizeChannel = toJS(draggableFieldState.size)
|
|
25
|
+
|
|
26
|
+
const rowLeftFacetFields = rows.slice(0, -1).filter(f => f.analyticType === 'dimension');
|
|
27
|
+
const colLeftFacetFields = columns.slice(0, -1).filter(f => f.analyticType === 'dimension');
|
|
28
|
+
|
|
29
|
+
const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
|
|
30
|
+
|
|
31
|
+
const onGeomClick = useCallback((values: any, e: any) => {
|
|
32
|
+
runInAction(() => {
|
|
33
|
+
commonStore.showEmbededMenu([e.pageX, e.pageY])
|
|
34
|
+
commonStore.setFilters(values);
|
|
35
|
+
})
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
// apply filters
|
|
39
|
+
const { dataSource } = currentDataset;
|
|
40
|
+
|
|
41
|
+
const [data, setData] = useState(dataSource);
|
|
42
|
+
const pendingPromiseRef = useRef<Promise<typeof data> | null>(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
setData(dataSource);
|
|
46
|
+
}, [dataSource]);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const p = filters.length === 0 ? Promise.resolve(dataSource) : applyFilter(dataSource, filters);
|
|
50
|
+
pendingPromiseRef.current = p;
|
|
51
|
+
|
|
52
|
+
p.then(d => {
|
|
53
|
+
if (p !== pendingPromiseRef.current) {
|
|
54
|
+
// This promise is out-of-date
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setData(d);
|
|
59
|
+
}).catch(err => {
|
|
60
|
+
console.error(err);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
pendingPromiseRef.current = null;
|
|
65
|
+
};
|
|
66
|
+
}, [dataSource, filters]);
|
|
67
|
+
|
|
68
|
+
return <Resizable className={(size.mode === 'fixed' && !hasFacet) ? "border-blue-400 border-2 overflow-hidden" : ""}
|
|
69
|
+
style={{ padding: '12px' }}
|
|
70
|
+
onResizeStop={(e, direction, ref, d) => {
|
|
71
|
+
vizStore.setChartLayout({
|
|
72
|
+
mode: 'fixed',
|
|
73
|
+
width: size.width + d.width,
|
|
74
|
+
height: size.height + d.height
|
|
75
|
+
})
|
|
76
|
+
}}
|
|
77
|
+
size={{
|
|
78
|
+
width: size.width + 'px',
|
|
79
|
+
height: size.height + 'px',
|
|
80
|
+
}}>
|
|
81
|
+
<ReactVega
|
|
82
|
+
layoutMode={size.mode}
|
|
83
|
+
interactiveScale={interactiveScale}
|
|
84
|
+
geomType={geoms[0]}
|
|
85
|
+
defaultAggregate={defaultAggregated}
|
|
86
|
+
stack={stack}
|
|
87
|
+
dataSource={data}
|
|
88
|
+
rows={rows}
|
|
89
|
+
columns={columns}
|
|
90
|
+
color={color[0]}
|
|
91
|
+
theta={theta[0]}
|
|
92
|
+
radius={radius[0]}
|
|
93
|
+
shape={shape[0]}
|
|
94
|
+
opacity={opacity[0]}
|
|
95
|
+
size={sizeChannel[0]}
|
|
96
|
+
onGeomClick={onGeomClick}
|
|
97
|
+
showActions={showActions}
|
|
98
|
+
width={size.width - 12 * 4}
|
|
99
|
+
height={size.height - 12 * 4}
|
|
100
|
+
/>
|
|
101
|
+
</Resizable>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default observer(ReactiveRenderer);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useCallback } from "react";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import PureTabs, { ITabOption } from "../components/tabs/pureTab";
|
|
4
|
+
import { useGlobalStore } from "../store";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const ADD_KEY = '_add';
|
|
8
|
+
|
|
9
|
+
const VisNav: React.FC = (props) => {
|
|
10
|
+
const { vizStore, commonStore } = useGlobalStore();
|
|
11
|
+
const { visIndex, visList } = vizStore;
|
|
12
|
+
const { currentDataset } = commonStore;
|
|
13
|
+
|
|
14
|
+
const tabs: ITabOption[] = visList.map((v) => ({
|
|
15
|
+
key: v.visId,
|
|
16
|
+
label: v.name?.[0] || 'vis',
|
|
17
|
+
options: v.name?.[1],
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
tabs.push({
|
|
21
|
+
key: ADD_KEY,
|
|
22
|
+
label: 'main.tablist.new'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const visSelectionHandler = useCallback((tabKey: string, tabIndex: number) => {
|
|
26
|
+
if (tabKey === ADD_KEY) {
|
|
27
|
+
vizStore.addVisualization();
|
|
28
|
+
vizStore.initMetaState(currentDataset)
|
|
29
|
+
} else {
|
|
30
|
+
vizStore.selectVisualization(tabIndex);
|
|
31
|
+
}
|
|
32
|
+
}, [currentDataset, vizStore])
|
|
33
|
+
|
|
34
|
+
const editLabelHandler = useCallback((content: string, tabIndex: number) => {
|
|
35
|
+
vizStore.setVisName(tabIndex, content)
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<PureTabs
|
|
40
|
+
selectedKey={visList[visIndex].visId}
|
|
41
|
+
tabs={tabs}
|
|
42
|
+
onEditLabel={editLabelHandler}
|
|
43
|
+
onSelected={visSelectionHandler}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default observer(VisNav);
|
package/src/services.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { toJS } from 'mobx';
|
|
2
|
+
import { View, Specification } from 'visual-insights';
|
|
3
|
+
import { IRow, Filters, SemanticType, IMeasure, IMutField, IFilterField } from './interfaces';
|
|
4
|
+
/* eslint import/no-webpack-loader-syntax:0 */
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
// eslint-disable-next-line
|
|
7
|
+
// import InsightSpaceWorker from './workers/InsightService.worker?worker';
|
|
8
|
+
/* eslint import/no-webpack-loader-syntax:0 */
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
// eslint-disable-next-line
|
|
11
|
+
import ExplainerWorker from './workers/explainer.worker?worker&inline';
|
|
12
|
+
import FilterWorker from './workers/filter.worker?worker&inline';
|
|
13
|
+
import { IExplaination, IMeasureWithStat } from './insights';
|
|
14
|
+
|
|
15
|
+
interface WorkerState {
|
|
16
|
+
eWorker: Worker | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const workerState: WorkerState = {
|
|
20
|
+
eWorker: null,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function workerService<T, R>(worker: Worker, data: R): Promise<T> {
|
|
24
|
+
return new Promise<T>((resolve, reject) => {
|
|
25
|
+
worker.postMessage(data);
|
|
26
|
+
worker.onmessage = (e: MessageEvent) => {
|
|
27
|
+
resolve(e.data);
|
|
28
|
+
};
|
|
29
|
+
worker.onerror = (e: ErrorEvent) => {
|
|
30
|
+
reject({
|
|
31
|
+
success: false,
|
|
32
|
+
message: e,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ExplainParams {
|
|
39
|
+
dimensions: string[];
|
|
40
|
+
measures: string[];
|
|
41
|
+
dataSource: IRow[];
|
|
42
|
+
filters?: Filters;
|
|
43
|
+
currentSpace: {
|
|
44
|
+
dimensions: string[];
|
|
45
|
+
measures: IMeasure[];
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface IVisSpace {
|
|
49
|
+
dataView: IRow[];
|
|
50
|
+
schema: Specification;
|
|
51
|
+
}
|
|
52
|
+
interface ExplainReturns {
|
|
53
|
+
explainations: IExplaination[];
|
|
54
|
+
valueExp: IMeasureWithStat[];
|
|
55
|
+
visSpaces: IVisSpace[];
|
|
56
|
+
fieldsWithSemanticType: Array<{ key: string; type: SemanticType }>;
|
|
57
|
+
}
|
|
58
|
+
export async function getExplaination(props: ExplainParams) {
|
|
59
|
+
const worker = workerState.eWorker;
|
|
60
|
+
if (worker === null) throw new Error('init worker first.')
|
|
61
|
+
let result: ExplainReturns = {
|
|
62
|
+
explainations: [],
|
|
63
|
+
valueExp: [],
|
|
64
|
+
visSpaces: [],
|
|
65
|
+
fieldsWithSemanticType: [],
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
result = await workerService<ExplainReturns, { type: string; data: ExplainParams }>(
|
|
69
|
+
worker,
|
|
70
|
+
{
|
|
71
|
+
type: 'getExplaination',
|
|
72
|
+
data: props
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
return result;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(error);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface PreAnalysisParams {
|
|
83
|
+
fields: IMutField[];
|
|
84
|
+
dataSource: IRow[];
|
|
85
|
+
}
|
|
86
|
+
export async function preAnalysis(props: PreAnalysisParams) {
|
|
87
|
+
if (workerState.eWorker !== null) {
|
|
88
|
+
workerState.eWorker.terminate();
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
workerState.eWorker = new ExplainerWorker() as Worker;
|
|
92
|
+
const tmp = await workerService<boolean, { type: string; data: PreAnalysisParams}>(workerState.eWorker, { type: 'preAnalysis', data: props });
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(error)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function destroyWorker() {
|
|
99
|
+
if (workerState.eWorker) {
|
|
100
|
+
workerState.eWorker.terminate();
|
|
101
|
+
workerState.eWorker = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let filterWorker: Worker | null = null;
|
|
106
|
+
let filterWorkerAutoTerminator: NodeJS.Timeout | null = null;
|
|
107
|
+
|
|
108
|
+
export const applyFilter = async (data: readonly IRow[], filters: readonly IFilterField[]): Promise<IRow[]> => {
|
|
109
|
+
if (filterWorkerAutoTerminator !== null) {
|
|
110
|
+
clearTimeout(filterWorkerAutoTerminator);
|
|
111
|
+
filterWorkerAutoTerminator = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (filterWorker === null) {
|
|
115
|
+
filterWorker = new FilterWorker();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const res: IRow[] = await workerService(filterWorker, {
|
|
120
|
+
dataSource: data,
|
|
121
|
+
filters: toJS(filters),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return res;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// @ts-ignore @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
|
|
127
|
+
throw new Error('Uncaught error in FilterWorker', { cause: error });
|
|
128
|
+
} finally {
|
|
129
|
+
if (filterWorkerAutoTerminator !== null) {
|
|
130
|
+
clearTimeout(filterWorkerAutoTerminator);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
filterWorkerAutoTerminator = setTimeout(() => {
|
|
134
|
+
filterWorker?.terminate();
|
|
135
|
+
filterWorker = null;
|
|
136
|
+
filterWorkerAutoTerminator = null;
|
|
137
|
+
}, 60_000); // Destroy the worker when no request is received for 60 secs
|
|
138
|
+
}
|
|
139
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { DataSet, Filters, IDataSet, IDataSetInfo, IDataSource, IMutField, IRow } from '../interfaces';
|
|
2
|
+
import { makeAutoObservable, observable, toJS } from 'mobx';
|
|
3
|
+
import { transData } from '../dataSource/utils';
|
|
4
|
+
import { extendCountField } from '../utils';
|
|
5
|
+
|
|
6
|
+
export class CommonStore {
|
|
7
|
+
public datasets: IDataSet[] = [];
|
|
8
|
+
public dataSources: IDataSource[] = [];
|
|
9
|
+
public dsIndex: number = 0;
|
|
10
|
+
public tmpDSName: string = '';
|
|
11
|
+
public tmpDSRawFields: IMutField[] = [];
|
|
12
|
+
public tmpDataSource: IRow[] = [];
|
|
13
|
+
public showDSPanel: boolean = false;
|
|
14
|
+
public showInsightBoard: boolean = false;
|
|
15
|
+
public vizEmbededMenu: { show: boolean; position: [number, number] } = { show: false, position: [0, 0] };
|
|
16
|
+
|
|
17
|
+
public filters: Filters = {};
|
|
18
|
+
constructor () {
|
|
19
|
+
this.datasets = [];
|
|
20
|
+
this.dataSources = [];
|
|
21
|
+
makeAutoObservable(this, {
|
|
22
|
+
dataSources: observable.ref,
|
|
23
|
+
tmpDataSource: observable.ref,
|
|
24
|
+
filters: observable.ref
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
public get currentDataset (): DataSet {
|
|
28
|
+
const datasetIndex = this.dsIndex;
|
|
29
|
+
if (this.datasets.length > 0) {
|
|
30
|
+
const dataSourceId = this.datasets[datasetIndex].dsId;
|
|
31
|
+
const dataSource = this.dataSources.find(d => d.id === dataSourceId);
|
|
32
|
+
const rawFields = toJS(this.datasets[datasetIndex].rawFields)
|
|
33
|
+
const base = extendCountField((dataSource ? dataSource.data : []), rawFields)
|
|
34
|
+
return {
|
|
35
|
+
...this.datasets[datasetIndex],
|
|
36
|
+
dataSource: base.dataSource,
|
|
37
|
+
rawFields: base.fields
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
id: '__null_ds__',
|
|
42
|
+
name: 'Empty Dataset',
|
|
43
|
+
rawFields: [],
|
|
44
|
+
dataSource: []
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
public setShowDSPanel (show: boolean) {
|
|
48
|
+
this.showDSPanel = show;
|
|
49
|
+
}
|
|
50
|
+
public setShowInsightBoard (show: boolean) {
|
|
51
|
+
this.showInsightBoard = show;
|
|
52
|
+
}
|
|
53
|
+
public showEmbededMenu (position: [number, number]) {
|
|
54
|
+
this.vizEmbededMenu.show = true;
|
|
55
|
+
this.vizEmbededMenu.position = position;
|
|
56
|
+
}
|
|
57
|
+
public closeEmbededMenu () {
|
|
58
|
+
this.vizEmbededMenu.show = false;
|
|
59
|
+
}
|
|
60
|
+
public initTempDS () {
|
|
61
|
+
this.tmpDSName = 'New Dataset'
|
|
62
|
+
this.tmpDSRawFields = [];
|
|
63
|
+
this.tmpDataSource = [];
|
|
64
|
+
}
|
|
65
|
+
public updateTempFields (fields: IMutField[]) {
|
|
66
|
+
this.tmpDSRawFields = fields;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public updateTempFieldAnalyticType (fieldKey: string, analyticType: IMutField['analyticType']) {
|
|
70
|
+
const field = this.tmpDSRawFields.find(f => f.fid === fieldKey);
|
|
71
|
+
if (field) {
|
|
72
|
+
field.analyticType = analyticType;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public updateTempName (name: string) {
|
|
77
|
+
this.tmpDSName = name;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public updateTempDS (rawData: IRow[]) {
|
|
81
|
+
const result = transData(rawData);
|
|
82
|
+
// TODO: need fix web-data-loader issue #2
|
|
83
|
+
this.tmpDataSource = result.dataSource.slice(0, -1);
|
|
84
|
+
this.tmpDSRawFields = result.fields;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public updateTempSTDDS (dataset: IDataSetInfo) {
|
|
88
|
+
this.tmpDataSource = dataset.dataSource;
|
|
89
|
+
this.tmpDSRawFields = dataset.rawFields;
|
|
90
|
+
this.tmpDSName = dataset.name;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public commitTempDS () {
|
|
94
|
+
const { tmpDSName, tmpDSRawFields, tmpDataSource } = this;
|
|
95
|
+
this.addAndUseDS({
|
|
96
|
+
dataSource: tmpDataSource,
|
|
97
|
+
rawFields: tmpDSRawFields,
|
|
98
|
+
name: tmpDSName
|
|
99
|
+
})
|
|
100
|
+
this.setShowDSPanel(false);
|
|
101
|
+
this.initTempDS();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public startDSBuildingTask () {
|
|
105
|
+
this.initTempDS();
|
|
106
|
+
this.showDSPanel = true;
|
|
107
|
+
}
|
|
108
|
+
public addAndUseDS(dataset: IDataSetInfo) {
|
|
109
|
+
const datasetId = this.addDS(dataset);
|
|
110
|
+
this.dsIndex = this.datasets.length - 1;
|
|
111
|
+
return datasetId
|
|
112
|
+
}
|
|
113
|
+
public addDS(dataset: IDataSetInfo) {
|
|
114
|
+
const timestamp = new Date().getTime();
|
|
115
|
+
const dataSetId = `dst-${timestamp}`
|
|
116
|
+
const dataSourceId = `dse-${timestamp}`;
|
|
117
|
+
this.dataSources.push({
|
|
118
|
+
id: dataSourceId,
|
|
119
|
+
data: dataset.dataSource
|
|
120
|
+
})
|
|
121
|
+
this.datasets.push({
|
|
122
|
+
id: dataSetId,
|
|
123
|
+
name: dataset.name,
|
|
124
|
+
rawFields: dataset.rawFields,
|
|
125
|
+
dsId: dataSourceId
|
|
126
|
+
})
|
|
127
|
+
return dataSetId;
|
|
128
|
+
}
|
|
129
|
+
public removeDS(datasetId: string) {
|
|
130
|
+
const datasetIndex = this.datasets.findIndex(d => d.id === datasetId);
|
|
131
|
+
if (datasetIndex > -1) {
|
|
132
|
+
const dataSourceId = this.datasets[datasetIndex].dsId;
|
|
133
|
+
const dataSourceIndex = this.dataSources.findIndex(d => d.id === dataSourceId);
|
|
134
|
+
this.dataSources.splice(dataSourceIndex, 1);
|
|
135
|
+
this.datasets.splice(datasetIndex, 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
public useDS(datasetId: string) {
|
|
139
|
+
const datasetIndex = this.datasets.findIndex(d => d.id === datasetId);
|
|
140
|
+
if (datasetIndex > -1) {
|
|
141
|
+
this.dsIndex = datasetIndex;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
public createPlaceholderDS() {
|
|
145
|
+
this.addDS({
|
|
146
|
+
name: '新数据源',
|
|
147
|
+
dataSource: [],
|
|
148
|
+
rawFields: []
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
public setFilters (props: Filters) {
|
|
152
|
+
this.filters = props;
|
|
153
|
+
}
|
|
154
|
+
public destroy () {
|
|
155
|
+
this.dataSources = [];
|
|
156
|
+
this.datasets = [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from 'react';
|
|
2
|
+
import { CommonStore } from './commonStore'
|
|
3
|
+
import { VizSpecStore } from './visualSpecStore'
|
|
4
|
+
|
|
5
|
+
interface GlobalStore {
|
|
6
|
+
commonStore: CommonStore;
|
|
7
|
+
vizStore: VizSpecStore;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const commonStore = new CommonStore();
|
|
11
|
+
const vizStore = new VizSpecStore(commonStore);
|
|
12
|
+
|
|
13
|
+
const initStore: GlobalStore = {
|
|
14
|
+
commonStore,
|
|
15
|
+
vizStore
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const StoreContext = React.createContext<GlobalStore>(null!);
|
|
19
|
+
|
|
20
|
+
export function destroyGWStore() {
|
|
21
|
+
initStore.commonStore.destroy();
|
|
22
|
+
initStore.vizStore.destroy();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function rebootGWStore() {
|
|
26
|
+
const cs = new CommonStore();
|
|
27
|
+
const vs = new VizSpecStore(cs);
|
|
28
|
+
initStore.commonStore = cs;
|
|
29
|
+
initStore.vizStore = vs;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class StoreWrapper extends React.Component<{ keepAlive?: boolean }> {
|
|
33
|
+
constructor(props: { keepAlive?: boolean }) {
|
|
34
|
+
super(props)
|
|
35
|
+
if (props.keepAlive) {
|
|
36
|
+
rebootGWStore();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
componentWillUnmount() {
|
|
40
|
+
if (!this.props.keepAlive) {
|
|
41
|
+
destroyGWStore();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
render() {
|
|
45
|
+
return <StoreContext.Provider value={initStore}>
|
|
46
|
+
{ this.props.children }
|
|
47
|
+
</StoreContext.Provider>
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useGlobalStore() {
|
|
52
|
+
return useContext(StoreContext);
|
|
53
|
+
}
|