@kanaries/graphic-walker 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +4 -3
- 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/dataSource/index.d.ts +0 -1
- package/dist/datasets/tmp/test.json +1 -0
- package/dist/fields/encodeFields/singleEncodeEditor.d.ts +4 -4
- package/dist/graphic-walker.es.js +29228 -33650
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +218 -256
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/interfaces.d.ts +52 -17
- package/dist/lib/execExp.d.ts +8 -0
- package/dist/lib/inferMeta.d.ts +2 -9
- package/dist/lib/insights/explainByChildren.d.ts +16 -0
- package/dist/lib/insights/explainBySelection.d.ts +5 -0
- package/dist/lib/insights/explainValue.d.ts +2 -0
- package/dist/lib/insights/utils.d.ts +11 -0
- package/dist/lib/interfaces.d.ts +23 -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 +4 -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 +5 -31
- package/dist/store/commonStore.d.ts +6 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/visualSpecStore.d.ts +11 -5
- package/dist/utils/autoMark.d.ts +1 -1
- package/dist/utils/dataPrep.d.ts +3 -2
- package/dist/utils/index.d.ts +3 -5
- package/dist/utils/save.d.ts +1 -2
- package/dist/vis/react-vega.d.ts +2 -22
- package/dist/vis/spec/aggregate.d.ts +4 -0
- package/dist/vis/spec/encode.d.ts +20 -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/tooltip.d.ts +4 -0
- package/dist/vis/spec/view.d.ts +73 -0
- package/dist/workers/transform.d.ts +2 -0
- package/package.json +5 -6
- package/src/App.tsx +56 -66
- 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/index.tsx +0 -17
- package/src/dataSource/utils.ts +8 -3
- package/src/fields/aestheticFields.tsx +1 -2
- package/src/fields/datasetFields/meaFields.tsx +12 -4
- package/src/fields/encodeFields/singleEncodeEditor.tsx +11 -12
- package/src/fields/fieldsContext.tsx +1 -0
- package/src/index.css +4 -4
- package/src/index.tsx +22 -22
- package/src/interfaces.ts +85 -49
- package/src/lib/execExp.ts +147 -0
- package/src/lib/inferMeta.ts +26 -29
- package/src/lib/insights/explainByChildren.ts +50 -0
- package/src/lib/insights/explainBySelection.ts +47 -0
- package/src/lib/insights/explainValue.ts +30 -0
- package/src/lib/insights/utils.ts +21 -0
- package/src/lib/interfaces.ts +33 -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 +22 -0
- package/src/locales/en-US.json +6 -3
- package/src/locales/i18n.ts +0 -1
- package/src/locales/ja-JP.json +4 -2
- package/src/locales/zh-CN.json +6 -3
- 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 +121 -0
- package/src/segments/segmentNav.tsx +3 -16
- package/src/segments/visNav.tsx +17 -6
- package/src/services.ts +101 -67
- package/src/store/commonStore.ts +14 -9
- package/src/store/index.tsx +11 -4
- package/src/store/visualSpecStore.ts +89 -52
- package/src/utils/autoMark.ts +1 -1
- package/src/utils/dataPrep.ts +25 -2
- 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 +9 -340
- package/src/vis/spec/aggregate.ts +13 -0
- package/src/vis/spec/encode.ts +70 -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/tooltip.ts +16 -0
- package/src/vis/spec/view.ts +136 -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/assets/explainer.worker-8428eb12.js.map +0 -1
- package/dist/dataSource/pannel.d.ts +0 -5
- package/dist/insightBoard/index.d.ts +0 -3
- package/dist/insightBoard/mainBoard.d.ts +0 -11
- package/dist/insightBoard/radioGroupButtons.d.ts +0 -12
- package/dist/insightBoard/selectionSpec.d.ts +0 -13
- package/dist/insightBoard/std2vegaSpec.d.ts +0 -12
- package/dist/insightBoard/utils.d.ts +0 -8
- package/dist/insights.d.ts +0 -61
- package/src/dataSource/pannel.tsx +0 -71
- package/src/insightBoard/index.tsx +0 -31
- package/src/insightBoard/mainBoard.tsx +0 -224
- package/src/insightBoard/radioGroupButtons.tsx +0 -57
- package/src/insightBoard/selectionSpec.ts +0 -113
- package/src/insightBoard/std2vegaSpec.ts +0 -184
- package/src/insightBoard/utils.ts +0 -32
- package/src/insights.ts +0 -408
- package/src/workers/explainer.worker.js +0 -76
package/src/main.tsx
CHANGED
|
@@ -27,6 +27,14 @@ export class VisSpecWithHistory {
|
|
|
27
27
|
|
|
28
28
|
private batchFlag = false;
|
|
29
29
|
|
|
30
|
+
public updateLatest(snapshot: Partial<Readonly<VisSpecWithHistory['snapshots'][0]>>) {
|
|
31
|
+
this.snapshots[this.cursor] = {
|
|
32
|
+
...this.snapshots[this.cursor],
|
|
33
|
+
...snapshot,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
30
38
|
private commit(snapshot: Partial<Readonly<VisSpecWithHistory['snapshots'][0]>>): void {
|
|
31
39
|
if (this.batchFlag) {
|
|
32
40
|
// batch this commit
|
|
@@ -120,6 +128,12 @@ export class VisSpecWithHistory {
|
|
|
120
128
|
});
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
public clone () {
|
|
132
|
+
const nextVSWH = new VisSpecWithHistory(this.frame);
|
|
133
|
+
nextVSWH.cursor = this.cursor;
|
|
134
|
+
return nextVSWH;
|
|
135
|
+
}
|
|
136
|
+
|
|
123
137
|
public exportGW (): IVisSpec {
|
|
124
138
|
return {
|
|
125
139
|
...this.frame
|
package/src/renderer/index.tsx
CHANGED
|
@@ -1,138 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
1
|
+
import { observer } from 'mobx-react-lite';
|
|
2
|
+
import React, { useState, useEffect, forwardRef } from 'react';
|
|
3
|
+
import { applyFilter, applyViewQuery, transformDataService } from '../services';
|
|
4
|
+
import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig } from '../interfaces';
|
|
5
|
+
import SpecRenderer from './specRenderer';
|
|
6
|
+
import { toJS } from 'mobx';
|
|
7
|
+
import { useGlobalStore } from '../store';
|
|
8
|
+
import { IReactVegaHandler } from '../vis/react-vega';
|
|
9
|
+
import { unstable_batchedUpdates } from 'react-dom';
|
|
10
|
+
import { initEncoding, initVisualConfig } from '../store/visualSpecStore';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
interface RendererProps {
|
|
13
|
+
themeKey?: IThemeKey;
|
|
14
|
+
dark?: IDarkMode;
|
|
15
|
+
}
|
|
16
|
+
const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, ref) {
|
|
17
|
+
const { themeKey, dark } = props;
|
|
18
|
+
const [waiting, setWaiting] = useState<boolean>(false);
|
|
14
19
|
const { vizStore, commonStore } = useGlobalStore();
|
|
15
|
-
const {
|
|
16
|
-
const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, exploration } = visualConfig;
|
|
20
|
+
const { allFields, viewFilters, viewDimensions, viewMeasures } = vizStore;
|
|
17
21
|
const { currentDataset } = commonStore;
|
|
18
|
-
const { filters } = draggableFieldState;
|
|
19
|
-
|
|
20
|
-
const rows = toJS(draggableFieldState.rows);
|
|
21
|
-
const columns = toJS(draggableFieldState.columns);
|
|
22
|
-
const color = toJS(draggableFieldState.color);
|
|
23
|
-
const opacity = toJS(draggableFieldState.opacity);
|
|
24
|
-
const shape = toJS(draggableFieldState.shape);
|
|
25
|
-
const theta = toJS(draggableFieldState.theta);
|
|
26
|
-
const radius = toJS(draggableFieldState.radius);
|
|
27
|
-
const sizeChannel = toJS(draggableFieldState.size);
|
|
28
|
-
|
|
29
|
-
const rowLeftFacetFields = rows.slice(0, -1).filter((f) => f.analyticType === "dimension");
|
|
30
|
-
const colLeftFacetFields = columns.slice(0, -1).filter((f) => f.analyticType === "dimension");
|
|
31
|
-
|
|
32
|
-
const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
|
|
33
|
-
|
|
34
|
-
const shouldTriggerMenu = exploration.mode === "none";
|
|
35
|
-
|
|
36
|
-
const handleGeomClick = useCallback(
|
|
37
|
-
(values: any, e: any) => {
|
|
38
|
-
if (shouldTriggerMenu) {
|
|
39
|
-
e.stopPropagation();
|
|
40
|
-
runInAction(() => {
|
|
41
|
-
commonStore.showEmbededMenu([e.pageX, e.pageY]);
|
|
42
|
-
commonStore.setFilters(values);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
[shouldTriggerMenu]
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// apply filters
|
|
50
22
|
const { dataSource } = currentDataset;
|
|
23
|
+
const [viewConfig, setViewConfig] = useState<IVisualConfig>(initVisualConfig);
|
|
24
|
+
const [encodings, setEncodings] = useState<DeepReadonly<DraggableFieldState>>(initEncoding);
|
|
51
25
|
|
|
52
|
-
const [
|
|
53
|
-
const pendingPromiseRef = useRef<Promise<typeof data> | null>(null);
|
|
26
|
+
const [viewData, setViewData] = useState<IRow[]>([]);
|
|
54
27
|
|
|
55
28
|
useEffect(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
29
|
+
setWaiting(true);
|
|
30
|
+
applyFilter(dataSource, viewFilters)
|
|
31
|
+
.then((data) => transformDataService(data, allFields))
|
|
32
|
+
.then((d) => {
|
|
33
|
+
// setViewData(d);
|
|
34
|
+
const dims = viewDimensions;
|
|
35
|
+
const meas = viewMeasures;
|
|
36
|
+
const config = toJS(vizStore.visualConfig);
|
|
37
|
+
return applyViewQuery(d, dims.concat(meas), {
|
|
38
|
+
op: config.defaultAggregated ? 'aggregate' : 'raw',
|
|
39
|
+
groupBy: dims.map((f) => f.fid),
|
|
40
|
+
agg: Object.fromEntries(meas.map((f) => [f.fid, f.aggName as any])),
|
|
41
|
+
});
|
|
42
|
+
})
|
|
43
|
+
.then((data) => {
|
|
44
|
+
unstable_batchedUpdates(() => {
|
|
45
|
+
setViewData(data);
|
|
46
|
+
setWaiting(false);
|
|
47
|
+
setEncodings(toJS(vizStore.draggableFieldState));
|
|
48
|
+
setViewConfig(toJS(vizStore.visualConfig));
|
|
49
|
+
});
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => {
|
|
52
|
+
console.error(err);
|
|
53
|
+
setWaiting(false);
|
|
54
|
+
});
|
|
55
|
+
}, [dataSource, viewFilters, allFields, viewDimensions, viewMeasures]);
|
|
73
56
|
|
|
74
|
-
return () => {
|
|
75
|
-
pendingPromiseRef.current = null;
|
|
76
|
-
};
|
|
77
|
-
}, [dataSource, filters]);
|
|
78
|
-
const enableResize = size.mode === "fixed" && !hasFacet;
|
|
79
57
|
return (
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}}
|
|
90
|
-
enable={
|
|
91
|
-
enableResize
|
|
92
|
-
? undefined
|
|
93
|
-
: {
|
|
94
|
-
top: false,
|
|
95
|
-
right: false,
|
|
96
|
-
bottom: false,
|
|
97
|
-
left: false,
|
|
98
|
-
topRight: false,
|
|
99
|
-
bottomRight: false,
|
|
100
|
-
bottomLeft: false,
|
|
101
|
-
topLeft: false,
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
size={{
|
|
105
|
-
width: size.width + "px",
|
|
106
|
-
height: size.height + "px",
|
|
107
|
-
}}
|
|
108
|
-
>
|
|
109
|
-
<ReactVega
|
|
110
|
-
layoutMode={size.mode}
|
|
111
|
-
interactiveScale={interactiveScale}
|
|
112
|
-
geomType={geoms[0]}
|
|
113
|
-
defaultAggregate={defaultAggregated}
|
|
114
|
-
stack={stack}
|
|
115
|
-
dataSource={data}
|
|
116
|
-
rows={rows}
|
|
117
|
-
columns={columns}
|
|
118
|
-
color={color[0]}
|
|
119
|
-
theta={theta[0]}
|
|
120
|
-
radius={radius[0]}
|
|
121
|
-
shape={shape[0]}
|
|
122
|
-
opacity={opacity[0]}
|
|
123
|
-
size={sizeChannel[0]}
|
|
124
|
-
showActions={showActions}
|
|
125
|
-
width={size.width - 12 * 4}
|
|
126
|
-
height={size.height - 12 * 4}
|
|
127
|
-
ref={ref}
|
|
128
|
-
brushEncoding={exploration.mode === "brush" ? exploration.brushDirection : "none"}
|
|
129
|
-
selectEncoding={exploration.mode === "point" ? "default" : "none"}
|
|
130
|
-
onGeomClick={handleGeomClick}
|
|
131
|
-
themeKey={themeKey}
|
|
132
|
-
dark={dark}
|
|
133
|
-
/>
|
|
134
|
-
</Resizable>
|
|
58
|
+
<SpecRenderer
|
|
59
|
+
loading={waiting}
|
|
60
|
+
data={viewData}
|
|
61
|
+
ref={ref}
|
|
62
|
+
themeKey={themeKey}
|
|
63
|
+
dark={dark}
|
|
64
|
+
draggableFieldState={encodings}
|
|
65
|
+
visualConfig={viewConfig}
|
|
66
|
+
/>
|
|
135
67
|
);
|
|
136
68
|
});
|
|
137
69
|
|
|
138
|
-
export default observer(
|
|
70
|
+
export default observer(Renderer);
|
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
const details = draggableFieldState.details;
|
|
35
|
+
|
|
36
|
+
const rowLeftFacetFields = useMemo(() => rows.slice(0, -1).filter((f) => f.analyticType === 'dimension'), [rows]);
|
|
37
|
+
const colLeftFacetFields = useMemo(
|
|
38
|
+
() => columns.slice(0, -1).filter((f) => f.analyticType === 'dimension'),
|
|
39
|
+
[columns]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
|
|
43
|
+
|
|
44
|
+
const shouldTriggerMenu = exploration.mode === 'none';
|
|
45
|
+
|
|
46
|
+
const handleGeomClick = useCallback(
|
|
47
|
+
(values: any, e: any) => {
|
|
48
|
+
if (shouldTriggerMenu) {
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
runInAction(() => {
|
|
51
|
+
commonStore.showEmbededMenu([e.pageX, e.pageY]);
|
|
52
|
+
commonStore.setFilters(values);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[shouldTriggerMenu]
|
|
57
|
+
);
|
|
58
|
+
const enableResize = size.mode === 'fixed' && !hasFacet;
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Resizable
|
|
62
|
+
className={enableResize ? 'border-blue-400 border-2 overflow-hidden' : ''}
|
|
63
|
+
style={{ padding: '12px' }}
|
|
64
|
+
onResizeStop={(e, direction, ref, d) => {
|
|
65
|
+
vizStore.setChartLayout({
|
|
66
|
+
mode: 'fixed',
|
|
67
|
+
width: size.width + d.width,
|
|
68
|
+
height: size.height + d.height,
|
|
69
|
+
});
|
|
70
|
+
}}
|
|
71
|
+
enable={
|
|
72
|
+
enableResize
|
|
73
|
+
? undefined
|
|
74
|
+
: {
|
|
75
|
+
top: false,
|
|
76
|
+
right: false,
|
|
77
|
+
bottom: false,
|
|
78
|
+
left: false,
|
|
79
|
+
topRight: false,
|
|
80
|
+
bottomRight: false,
|
|
81
|
+
bottomLeft: false,
|
|
82
|
+
topLeft: false,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
size={{
|
|
86
|
+
width: size.width + 'px',
|
|
87
|
+
height: size.height + 'px',
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{loading && <LoadingLayer />}
|
|
91
|
+
<ReactVega
|
|
92
|
+
layoutMode={size.mode}
|
|
93
|
+
interactiveScale={interactiveScale}
|
|
94
|
+
geomType={geoms[0]}
|
|
95
|
+
defaultAggregate={defaultAggregated}
|
|
96
|
+
stack={stack}
|
|
97
|
+
dataSource={data}
|
|
98
|
+
rows={rows}
|
|
99
|
+
columns={columns}
|
|
100
|
+
color={color[0]}
|
|
101
|
+
theta={theta[0]}
|
|
102
|
+
radius={radius[0]}
|
|
103
|
+
shape={shape[0]}
|
|
104
|
+
opacity={opacity[0]}
|
|
105
|
+
size={sizeChannel[0]}
|
|
106
|
+
details={details}
|
|
107
|
+
showActions={showActions}
|
|
108
|
+
width={size.width - 12 * 4}
|
|
109
|
+
height={size.height - 12 * 4}
|
|
110
|
+
ref={ref}
|
|
111
|
+
brushEncoding={exploration.mode === 'brush' ? exploration.brushDirection : 'none'}
|
|
112
|
+
selectEncoding={exploration.mode === 'point' ? 'default' : 'none'}
|
|
113
|
+
onGeomClick={handleGeomClick}
|
|
114
|
+
themeKey={themeKey}
|
|
115
|
+
dark={dark}
|
|
116
|
+
/>
|
|
117
|
+
</Resizable>
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { toJS } from 'mobx';
|
|
2
|
-
import {
|
|
3
|
-
import { IRow, Filters, SemanticType, IMeasure, IMutField, IFilterField } from './interfaces';
|
|
2
|
+
import { IRow, IMutField, IFilterField, Specification } from './interfaces';
|
|
4
3
|
/* eslint import/no-webpack-loader-syntax:0 */
|
|
5
4
|
// @ts-ignore
|
|
6
5
|
// eslint-disable-next-line
|
|
@@ -8,17 +7,19 @@ import { IRow, Filters, SemanticType, IMeasure, IMutField, IFilterField } from '
|
|
|
8
7
|
/* eslint import/no-webpack-loader-syntax:0 */
|
|
9
8
|
// @ts-ignore
|
|
10
9
|
// eslint-disable-next-line
|
|
11
|
-
import ExplainerWorker from './workers/explainer.worker?worker&inline';
|
|
10
|
+
// import ExplainerWorker from './workers/explainer.worker?worker&inline';
|
|
12
11
|
import FilterWorker from './workers/filter.worker?worker&inline';
|
|
13
|
-
import
|
|
12
|
+
import TransformDataWorker from './workers/transform.worker?worker&inline';
|
|
13
|
+
import ViewQueryWorker from './workers/viewQuery.worker?worker&inline';
|
|
14
|
+
import { IViewQuery } from './lib/viewQuery';
|
|
14
15
|
|
|
15
|
-
interface WorkerState {
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
// interface WorkerState {
|
|
17
|
+
// eWorker: Worker | null;
|
|
18
|
+
// }
|
|
18
19
|
|
|
19
|
-
const workerState: WorkerState = {
|
|
20
|
-
|
|
21
|
-
}
|
|
20
|
+
// const workerState: WorkerState = {
|
|
21
|
+
// eWorker: null,
|
|
22
|
+
// }
|
|
22
23
|
|
|
23
24
|
function workerService<T, R>(worker: Worker, data: R): Promise<T> {
|
|
24
25
|
return new Promise<T>((resolve, reject) => {
|
|
@@ -35,77 +36,78 @@ function workerService<T, R>(worker: Worker, data: R): Promise<T> {
|
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
interface ExplainParams {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
39
|
+
// interface ExplainParams {
|
|
40
|
+
// dimensions: string[];
|
|
41
|
+
// measures: string[];
|
|
42
|
+
// dataSource: IRow[];
|
|
43
|
+
// filters?: Filters;
|
|
44
|
+
// currentSpace: {
|
|
45
|
+
// dimensions: string[];
|
|
46
|
+
// measures: IMeasure[];
|
|
47
|
+
// };
|
|
48
|
+
// }
|
|
48
49
|
export interface IVisSpace {
|
|
49
50
|
dataView: IRow[];
|
|
50
51
|
schema: Specification;
|
|
51
52
|
}
|
|
52
|
-
interface ExplainReturns {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
export async function getExplaination(props: ExplainParams) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
53
|
+
// interface ExplainReturns {
|
|
54
|
+
// explainations: IExplaination[];
|
|
55
|
+
// valueExp: IMeasureWithStat[];
|
|
56
|
+
// visSpaces: IVisSpace[];
|
|
57
|
+
// fieldsWithSemanticType: Array<{ key: string; type: SemanticType }>;
|
|
58
|
+
// }
|
|
59
|
+
// export async function getExplaination(props: ExplainParams) {
|
|
60
|
+
// const worker = workerState.eWorker;
|
|
61
|
+
// if (worker === null) throw new Error('init worker first.')
|
|
62
|
+
// let result: ExplainReturns = {
|
|
63
|
+
// explainations: [],
|
|
64
|
+
// valueExp: [],
|
|
65
|
+
// visSpaces: [],
|
|
66
|
+
// fieldsWithSemanticType: [],
|
|
67
|
+
// };
|
|
68
|
+
// try {
|
|
69
|
+
// result = await workerService<ExplainReturns, { type: string; data: ExplainParams }>(
|
|
70
|
+
// worker,
|
|
71
|
+
// {
|
|
72
|
+
// type: 'getExplaination',
|
|
73
|
+
// data: props
|
|
74
|
+
// }
|
|
75
|
+
// );
|
|
76
|
+
// return result;
|
|
77
|
+
// } catch (error) {
|
|
78
|
+
// console.error(error);
|
|
79
|
+
// }
|
|
80
|
+
// return result;
|
|
81
|
+
// }
|
|
81
82
|
|
|
82
83
|
interface PreAnalysisParams {
|
|
83
84
|
fields: IMutField[];
|
|
84
85
|
dataSource: IRow[];
|
|
85
86
|
}
|
|
86
|
-
export async function preAnalysis(props: PreAnalysisParams) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
87
|
+
// export async function preAnalysis(props: PreAnalysisParams) {
|
|
88
|
+
// if (workerState.eWorker !== null) {
|
|
89
|
+
// workerState.eWorker.terminate();
|
|
90
|
+
// }
|
|
91
|
+
// try {
|
|
92
|
+
// workerState.eWorker = new ExplainerWorker() as Worker;
|
|
93
|
+
// const tmp = await workerService<boolean, { type: string; data: PreAnalysisParams}>(workerState.eWorker, { type: 'preAnalysis', data: props });
|
|
94
|
+
// } catch (error) {
|
|
95
|
+
// console.error(error)
|
|
96
|
+
// }
|
|
97
|
+
// }
|
|
97
98
|
|
|
98
|
-
export function destroyWorker() {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
99
|
+
// export function destroyWorker() {
|
|
100
|
+
// if (workerState.eWorker) {
|
|
101
|
+
// workerState.eWorker.terminate();
|
|
102
|
+
// workerState.eWorker = null;
|
|
103
|
+
// }
|
|
104
|
+
// }
|
|
104
105
|
|
|
105
106
|
let filterWorker: Worker | null = null;
|
|
106
107
|
let filterWorkerAutoTerminator: NodeJS.Timeout | null = null;
|
|
107
108
|
|
|
108
|
-
export const applyFilter = async (data:
|
|
109
|
+
export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]): Promise<IRow[]> => {
|
|
110
|
+
if (filters.length === 0) return data;
|
|
109
111
|
if (filterWorkerAutoTerminator !== null) {
|
|
110
112
|
clearTimeout(filterWorkerAutoTerminator);
|
|
111
113
|
filterWorkerAutoTerminator = null;
|
|
@@ -137,3 +139,35 @@ export const applyFilter = async (data: readonly IRow[], filters: readonly IFilt
|
|
|
137
139
|
}, 60_000); // Destroy the worker when no request is received for 60 secs
|
|
138
140
|
}
|
|
139
141
|
};
|
|
142
|
+
|
|
143
|
+
export const transformDataService = async (data: IRow[], columns: IMutField[]): Promise<IRow[]> => {
|
|
144
|
+
if (columns.length === 0 || data.length === 0) return data;
|
|
145
|
+
const worker = new TransformDataWorker();
|
|
146
|
+
try {
|
|
147
|
+
const res: IRow[] = await workerService(worker, {
|
|
148
|
+
dataSource: data,
|
|
149
|
+
columns: toJS(columns),
|
|
150
|
+
});
|
|
151
|
+
return res;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw new Error('Uncaught error in TransformDataWorker', { cause: error });
|
|
154
|
+
} finally {
|
|
155
|
+
worker.terminate();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const applyViewQuery = async (data: IRow[], metas: IMutField[], query: IViewQuery): Promise<IRow[]> => {
|
|
160
|
+
const worker = new ViewQueryWorker();
|
|
161
|
+
try {
|
|
162
|
+
const res: IRow[] = await workerService(worker, {
|
|
163
|
+
dataSource: data,
|
|
164
|
+
metas: toJS(metas),
|
|
165
|
+
query: toJS(query),
|
|
166
|
+
});
|
|
167
|
+
return res;
|
|
168
|
+
} catch (err) {
|
|
169
|
+
throw new Error('Uncaught error in ViewQueryWorker', { cause: err });
|
|
170
|
+
} finally {
|
|
171
|
+
worker.terminate();
|
|
172
|
+
}
|
|
173
|
+
}
|