@kanaries/graphic-walker 0.4.0 → 0.4.2
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 +5 -1
- package/dist/assets/buildMetricTable.worker-5555966a.js.map +1 -0
- package/dist/components/dataTable/index.d.ts +2 -1
- package/dist/components/leafletRenderer/ChoroplethRenderer.d.ts +21 -0
- package/dist/components/leafletRenderer/POIRenderer.d.ts +19 -0
- package/dist/components/leafletRenderer/encodings.d.ts +7 -0
- package/dist/components/leafletRenderer/geoConfigPanel.d.ts +3 -0
- package/dist/components/leafletRenderer/index.d.ts +14 -0
- package/dist/components/leafletRenderer/tooltip.d.ts +9 -0
- package/dist/components/leafletRenderer/utils.d.ts +2 -0
- package/dist/components/pivotTable/index.d.ts +2 -1
- package/dist/components/pivotTable/inteface.d.ts +6 -2
- package/dist/components/pivotTable/leftTree.d.ts +1 -0
- package/dist/components/pivotTable/topTree.d.ts +2 -0
- package/dist/components/pivotTable/utils.d.ts +1 -2
- package/dist/config.d.ts +3 -2
- package/dist/graphic-walker.es.js +37802 -30402
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +145 -137
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/interfaces.d.ts +27 -0
- package/dist/renderer/specRenderer.d.ts +2 -1
- package/dist/services.d.ts +7 -1
- package/dist/store/commonStore.d.ts +6 -0
- package/dist/store/visualSpecStore.d.ts +180 -4
- package/dist/utils/save.d.ts +1 -0
- package/dist/workers/buildPivotTable.d.ts +7 -0
- package/package.json +14 -2
- package/src/App.tsx +18 -4
- package/src/components/askViz/index.tsx +2 -1
- package/src/components/dataTable/index.tsx +7 -5
- package/src/components/leafletRenderer/ChoroplethRenderer.tsx +293 -0
- package/src/components/leafletRenderer/POIRenderer.tsx +170 -0
- package/src/components/leafletRenderer/encodings.ts +194 -0
- package/src/components/leafletRenderer/geoConfigPanel.tsx +197 -0
- package/src/components/leafletRenderer/index.tsx +67 -0
- package/src/components/leafletRenderer/tooltip.tsx +24 -0
- package/src/components/leafletRenderer/utils.ts +52 -0
- package/src/components/limitSetting.tsx +8 -6
- package/src/components/pivotTable/index.tsx +171 -67
- package/src/components/pivotTable/inteface.ts +6 -2
- package/src/components/pivotTable/leftTree.tsx +24 -11
- package/src/components/pivotTable/metricTable.tsx +15 -10
- package/src/components/pivotTable/topTree.tsx +50 -17
- package/src/components/pivotTable/utils.ts +70 -11
- package/src/components/sizeSetting.tsx +9 -7
- package/src/components/visualConfig/index.tsx +17 -1
- package/src/computation/serverComputation.ts +8 -3
- package/src/config.ts +27 -16
- package/src/dataSource/table.tsx +9 -9
- package/src/fields/aestheticFields.tsx +4 -0
- package/src/fields/fieldsContext.tsx +3 -0
- package/src/fields/posFields/index.tsx +8 -2
- package/src/global.d.ts +4 -4
- package/src/hooks/index.ts +8 -0
- package/src/index.tsx +11 -9
- package/src/interfaces.ts +34 -0
- package/src/locales/en-US.json +27 -2
- package/src/locales/ja-JP.json +27 -2
- package/src/locales/zh-CN.json +27 -2
- package/src/renderer/hooks.ts +2 -48
- package/src/renderer/index.tsx +24 -1
- package/src/renderer/pureRenderer.tsx +26 -13
- package/src/renderer/specRenderer.tsx +45 -30
- package/src/services.ts +32 -23
- package/src/shadow-dom.tsx +7 -0
- package/src/store/commonStore.ts +29 -1
- package/src/store/visualSpecStore.ts +40 -24
- package/src/utils/save.ts +28 -1
- package/src/visualSettings/index.tsx +58 -7
- package/src/workers/buildMetricTable.worker.js +27 -0
- package/src/workers/buildPivotTable.ts +27 -0
package/src/locales/zh-CN.json
CHANGED
|
@@ -39,7 +39,14 @@
|
|
|
39
39
|
"arc": "弧形",
|
|
40
40
|
"boxplot": "统计箱",
|
|
41
41
|
"table": "表格",
|
|
42
|
-
"text": "文本"
|
|
42
|
+
"text": "文本",
|
|
43
|
+
"poi": "兴趣点",
|
|
44
|
+
"choropleth": "区域图"
|
|
45
|
+
},
|
|
46
|
+
"coord_system": {
|
|
47
|
+
"__enum__": "坐标系统",
|
|
48
|
+
"generic": "通用",
|
|
49
|
+
"geographic": "地理"
|
|
43
50
|
},
|
|
44
51
|
"layout_type": {
|
|
45
52
|
"__enum__": "尺寸模式",
|
|
@@ -77,7 +84,10 @@
|
|
|
77
84
|
"radius": "半径",
|
|
78
85
|
"filters": "筛选器",
|
|
79
86
|
"details": "信息",
|
|
80
|
-
"text": "文本"
|
|
87
|
+
"text": "文本",
|
|
88
|
+
"longitude": "经度",
|
|
89
|
+
"latitude": "纬度",
|
|
90
|
+
"geoId": "地理 ID"
|
|
81
91
|
},
|
|
82
92
|
"aggregator": {
|
|
83
93
|
"sum": "求和",
|
|
@@ -168,6 +178,21 @@
|
|
|
168
178
|
"size_setting": {
|
|
169
179
|
"width": "宽度",
|
|
170
180
|
"height": "高度"
|
|
181
|
+
},
|
|
182
|
+
"table": {
|
|
183
|
+
"summary": "显示摘要"
|
|
184
|
+
},
|
|
185
|
+
"geography": "地理信息配置",
|
|
186
|
+
"geography_settings": {
|
|
187
|
+
"geoKey": "地理 ID",
|
|
188
|
+
"format": "数据格式",
|
|
189
|
+
"geojson": "GeoJSON",
|
|
190
|
+
"topojson": "TopoJSON",
|
|
191
|
+
"objectKey": "提取要素键",
|
|
192
|
+
"jsonInputPlaceholder": "在此粘贴 {{format}}",
|
|
193
|
+
"href": "{{format}} URL",
|
|
194
|
+
"hrefPlaceholder": "输入在线资源地址",
|
|
195
|
+
"load": "加载"
|
|
171
196
|
}
|
|
172
197
|
},
|
|
173
198
|
"DatasetFields": {
|
package/src/renderer/hooks.ts
CHANGED
|
@@ -4,13 +4,11 @@ import type { DeepReadonly, IFilterField, IRow, IViewField, IDataQueryWorkflowSt
|
|
|
4
4
|
import { useGlobalStore } from '../store';
|
|
5
5
|
import { useAppRootContext } from '../components/appRoot';
|
|
6
6
|
import { toWorkflow } from '../utils/workflow';
|
|
7
|
-
import { dataQueryClient } from '../computation/clientComputation';
|
|
8
7
|
import { dataQueryServer } from '../computation/serverComputation';
|
|
9
|
-
import { useDebounceValue } from '../hooks';
|
|
10
8
|
|
|
11
9
|
export const useComputationFunc = (): IComputationFunction => {
|
|
12
10
|
const { vizStore } = useGlobalStore();
|
|
13
|
-
return vizStore.
|
|
11
|
+
return vizStore.computationFunction;
|
|
14
12
|
};
|
|
15
13
|
|
|
16
14
|
interface UseRendererProps {
|
|
@@ -40,14 +38,12 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
|
|
|
40
38
|
filters,
|
|
41
39
|
defaultAggregated,
|
|
42
40
|
sort,
|
|
43
|
-
limit
|
|
41
|
+
limit,
|
|
44
42
|
computationFunction,
|
|
45
43
|
} = props;
|
|
46
44
|
const [computing, setComputing] = useState(false);
|
|
47
45
|
const taskIdRef = useRef(0);
|
|
48
46
|
|
|
49
|
-
const limit = useDebounceValue(storeLimit);
|
|
50
|
-
|
|
51
47
|
const workflow = useMemo(() => {
|
|
52
48
|
return toWorkflow(
|
|
53
49
|
filters,
|
|
@@ -65,48 +61,6 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
|
|
|
65
61
|
|
|
66
62
|
const appRef = useAppRootContext();
|
|
67
63
|
|
|
68
|
-
// useEffect(() => {
|
|
69
|
-
// if (computationMode !== 'client') {
|
|
70
|
-
// return;
|
|
71
|
-
// }
|
|
72
|
-
// if (!data) {
|
|
73
|
-
// console.warn('useRenderer error: prop `data` is required for "client" mode, but none is found.');
|
|
74
|
-
// return;
|
|
75
|
-
// }
|
|
76
|
-
// if (!allFields) {
|
|
77
|
-
// console.warn('useRenderer error: prop `fields` is required for "client" mode, but none is found.');
|
|
78
|
-
// return;
|
|
79
|
-
// }
|
|
80
|
-
// const taskId = ++taskIdRef.current;
|
|
81
|
-
// appRef.current?.updateRenderStatus('computing');
|
|
82
|
-
// setComputing(true);
|
|
83
|
-
// dataQueryClient(data, allFields, workflow).then(data => {
|
|
84
|
-
// if (taskId !== taskIdRef.current) {
|
|
85
|
-
// return;
|
|
86
|
-
// }
|
|
87
|
-
// appRef.current?.updateRenderStatus('rendering');
|
|
88
|
-
// unstable_batchedUpdates(() => {
|
|
89
|
-
// setComputing(false);
|
|
90
|
-
// setViewData(data);
|
|
91
|
-
// setParsedWorkflow(workflow);
|
|
92
|
-
// });
|
|
93
|
-
// }).catch((err) => {
|
|
94
|
-
// if (taskId !== taskIdRef.current) {
|
|
95
|
-
// return;
|
|
96
|
-
// }
|
|
97
|
-
// appRef.current?.updateRenderStatus('error');
|
|
98
|
-
// console.error(err);
|
|
99
|
-
// unstable_batchedUpdates(() => {
|
|
100
|
-
// setComputing(false);
|
|
101
|
-
// setViewData([]);
|
|
102
|
-
// setParsedWorkflow([]);
|
|
103
|
-
// });
|
|
104
|
-
// });
|
|
105
|
-
// return () => {
|
|
106
|
-
// taskIdRef.current++;
|
|
107
|
-
// };
|
|
108
|
-
// }, [computationMode, data, allFields, workflow]);
|
|
109
|
-
|
|
110
64
|
useEffect(() => {
|
|
111
65
|
const taskId = ++taskIdRef.current;
|
|
112
66
|
appRef.current?.updateRenderStatus('computing');
|
package/src/renderer/index.tsx
CHANGED
|
@@ -8,8 +8,9 @@ import { useGlobalStore } from '../store';
|
|
|
8
8
|
import { IReactVegaHandler } from '../vis/react-vega';
|
|
9
9
|
import { unstable_batchedUpdates } from 'react-dom';
|
|
10
10
|
import { useRenderer } from './hooks';
|
|
11
|
-
import { initEncoding } from '../
|
|
11
|
+
import { initEncoding } from '../utils/save';
|
|
12
12
|
import { useChartIndexControl } from '../utils/chartIndexControl';
|
|
13
|
+
import { LEAFLET_DEFAULT_HEIGHT, LEAFLET_DEFAULT_WIDTH } from '../components/leafletRenderer';
|
|
13
14
|
import { initVisualConfig } from '../utils/save';
|
|
14
15
|
|
|
15
16
|
interface RendererProps {
|
|
@@ -102,6 +103,27 @@ const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, r
|
|
|
102
103
|
[vizStore]
|
|
103
104
|
);
|
|
104
105
|
|
|
106
|
+
const isSpatial = viewConfig.coordSystem === 'geographic';
|
|
107
|
+
|
|
108
|
+
const sizeRef = useRef(viewConfig.size);
|
|
109
|
+
sizeRef.current = viewConfig.size;
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (isSpatial) {
|
|
113
|
+
const prevSizeConfig = sizeRef.current;
|
|
114
|
+
if (sizeRef.current.width < LEAFLET_DEFAULT_WIDTH || sizeRef.current.height < LEAFLET_DEFAULT_HEIGHT) {
|
|
115
|
+
vizStore.setChartLayout({
|
|
116
|
+
mode: sizeRef.current.mode,
|
|
117
|
+
width: Math.max(prevSizeConfig.width, LEAFLET_DEFAULT_WIDTH),
|
|
118
|
+
height: Math.max(prevSizeConfig.height, LEAFLET_DEFAULT_HEIGHT),
|
|
119
|
+
});
|
|
120
|
+
return () => {
|
|
121
|
+
vizStore.setChartLayout(prevSizeConfig);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [isSpatial, vizStore]);
|
|
126
|
+
|
|
105
127
|
return (
|
|
106
128
|
<SpecRenderer
|
|
107
129
|
name={chart?.name}
|
|
@@ -115,6 +137,7 @@ const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, r
|
|
|
115
137
|
visualConfig={viewConfig}
|
|
116
138
|
onGeomClick={handleGeomClick}
|
|
117
139
|
onChartResize={handleChartResize}
|
|
140
|
+
computationFunction={computationFunction}
|
|
118
141
|
/>
|
|
119
142
|
);
|
|
120
143
|
});
|
|
@@ -3,6 +3,7 @@ import { unstable_batchedUpdates } from 'react-dom';
|
|
|
3
3
|
import { toJS } from 'mobx';
|
|
4
4
|
import { observer } from 'mobx-react-lite';
|
|
5
5
|
import { ShadowDom } from '../shadow-dom';
|
|
6
|
+
import LeafletRenderer from '../components/leafletRenderer';
|
|
6
7
|
import { withAppRoot } from '../components/appRoot';
|
|
7
8
|
import type {
|
|
8
9
|
IDarkMode,
|
|
@@ -54,7 +55,6 @@ const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function
|
|
|
54
55
|
const defaultAggregated = visualConfig?.defaultAggregated ?? false;
|
|
55
56
|
|
|
56
57
|
const [viewData, setViewData] = useState<IRow[]>([]);
|
|
57
|
-
|
|
58
58
|
const { allFields, viewDimensions, viewMeasures, filters } = useMemo(() => {
|
|
59
59
|
const viewDimensions: IViewField[] = [];
|
|
60
60
|
const viewMeasures: IViewField[] = [];
|
|
@@ -86,7 +86,7 @@ const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function
|
|
|
86
86
|
limit: limit ?? -1,
|
|
87
87
|
computationFunction: computation,
|
|
88
88
|
});
|
|
89
|
-
|
|
89
|
+
console.log(computation)
|
|
90
90
|
// Dependencies that should not trigger effect individually
|
|
91
91
|
const latestFromRef = useRef({ data });
|
|
92
92
|
latestFromRef.current = { data };
|
|
@@ -99,20 +99,33 @@ const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function
|
|
|
99
99
|
}
|
|
100
100
|
}, [waiting]);
|
|
101
101
|
|
|
102
|
+
const { coordSystem = 'generic' } = visualConfig;
|
|
103
|
+
const isSpatial = coordSystem === 'geographic';
|
|
104
|
+
|
|
102
105
|
return (
|
|
103
106
|
<ShadowDom>
|
|
104
107
|
<div className="relative">
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
{isSpatial && (
|
|
109
|
+
<LeafletRenderer
|
|
110
|
+
data={data}
|
|
111
|
+
draggableFieldState={visualState}
|
|
112
|
+
visualConfig={visualConfig}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
{isSpatial || (
|
|
116
|
+
<SpecRenderer
|
|
117
|
+
name={name}
|
|
118
|
+
loading={waiting}
|
|
119
|
+
data={viewData}
|
|
120
|
+
ref={ref}
|
|
121
|
+
themeKey={themeKey}
|
|
122
|
+
dark={dark}
|
|
123
|
+
draggableFieldState={visualState}
|
|
124
|
+
visualConfig={visualConfig}
|
|
125
|
+
locale={locale ?? 'en-US'}
|
|
126
|
+
computationFunction={computation}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
116
129
|
</div>
|
|
117
130
|
</ShadowDom>
|
|
118
131
|
);
|
|
@@ -3,8 +3,9 @@ import { Resizable } from 're-resizable';
|
|
|
3
3
|
import React, { forwardRef, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import PivotTable from '../components/pivotTable';
|
|
6
|
+
import LeafletRenderer from '../components/leafletRenderer';
|
|
6
7
|
import ReactVega, { IReactVegaHandler } from '../vis/react-vega';
|
|
7
|
-
import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig, VegaGlobalConfig } from '../interfaces';
|
|
8
|
+
import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig, VegaGlobalConfig, IComputationFunction } from '../interfaces';
|
|
8
9
|
import LoadingLayer from '../components/loadingLayer';
|
|
9
10
|
import { useCurrentMediaTheme } from '../utils/media';
|
|
10
11
|
import { builtInThemes } from '../vis/theme';
|
|
@@ -20,17 +21,18 @@ interface SpecRendererProps {
|
|
|
20
21
|
onGeomClick?: ((values: any, e: any) => void) | undefined;
|
|
21
22
|
onChartResize?: ((width: number, height: number) => void) | undefined;
|
|
22
23
|
locale?: string;
|
|
24
|
+
computationFunction: IComputationFunction;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Sans-store renderer of GraphicWalker.
|
|
26
28
|
* This is a pure component, which means it will not depend on any global state.
|
|
27
29
|
*/
|
|
28
30
|
const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
|
|
29
|
-
{ name, themeKey, dark, data, loading, draggableFieldState, visualConfig, onGeomClick, onChartResize, locale },
|
|
31
|
+
{ name, themeKey, dark, data, loading, draggableFieldState, visualConfig, onGeomClick, onChartResize, locale, computationFunction },
|
|
30
32
|
ref
|
|
31
33
|
) {
|
|
32
34
|
// const { draggableFieldState, visualConfig } = vizStore;
|
|
33
|
-
const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, format: _format, background, zeroScale, resolve } = visualConfig;
|
|
35
|
+
const { geoms, coordSystem = 'generic', interactiveScale, defaultAggregated, stack, showActions, size, format: _format, background, zeroScale, resolve } = visualConfig;
|
|
34
36
|
|
|
35
37
|
const rows = draggableFieldState.rows;
|
|
36
38
|
const columns = draggableFieldState.columns;
|
|
@@ -108,10 +110,13 @@ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
|
|
|
108
110
|
loading={loading}
|
|
109
111
|
themeKey={themeKey}
|
|
110
112
|
dark={dark}
|
|
113
|
+
computationFunction={computationFunction}
|
|
111
114
|
/>
|
|
112
115
|
);
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
const isSpatial = coordSystem === 'geographic';
|
|
119
|
+
|
|
115
120
|
return (
|
|
116
121
|
<Resizable
|
|
117
122
|
className={enableResize ? 'border-blue-400 border-2 overflow-hidden' : ''}
|
|
@@ -139,33 +144,43 @@ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
|
|
|
139
144
|
}}
|
|
140
145
|
>
|
|
141
146
|
{loading && <LoadingLayer />}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
147
|
+
{isSpatial && (
|
|
148
|
+
<LeafletRenderer
|
|
149
|
+
data={data}
|
|
150
|
+
draggableFieldState={draggableFieldState}
|
|
151
|
+
visualConfig={visualConfig}
|
|
152
|
+
vegaConfig={vegaConfig}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
{isSpatial || (
|
|
156
|
+
<ReactVega
|
|
157
|
+
name={name}
|
|
158
|
+
vegaConfig={vegaConfig}
|
|
159
|
+
// format={format}
|
|
160
|
+
layoutMode={size.mode}
|
|
161
|
+
interactiveScale={interactiveScale}
|
|
162
|
+
geomType={geoms[0]}
|
|
163
|
+
defaultAggregate={defaultAggregated}
|
|
164
|
+
stack={stack}
|
|
165
|
+
dataSource={data}
|
|
166
|
+
rows={rows}
|
|
167
|
+
columns={columns}
|
|
168
|
+
color={color[0]}
|
|
169
|
+
theta={theta[0]}
|
|
170
|
+
radius={radius[0]}
|
|
171
|
+
shape={shape[0]}
|
|
172
|
+
opacity={opacity[0]}
|
|
173
|
+
size={sizeChannel[0]}
|
|
174
|
+
details={details}
|
|
175
|
+
text={text[0]}
|
|
176
|
+
showActions={showActions}
|
|
177
|
+
width={size.width - 12 * 4}
|
|
178
|
+
height={size.height - 12 * 4}
|
|
179
|
+
ref={ref}
|
|
180
|
+
onGeomClick={onGeomClick}
|
|
181
|
+
locale={locale}
|
|
182
|
+
/>
|
|
183
|
+
)}
|
|
169
184
|
</Resizable>
|
|
170
185
|
);
|
|
171
186
|
});
|
package/src/services.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toJS } from 'mobx';
|
|
2
|
-
import { IRow, IMutField, Specification, IFilterFiledSimple, IExpression } from './interfaces';
|
|
2
|
+
import { IRow, IMutField, IViewField, Specification, IFilterFiledSimple, IExpression } from './interfaces';
|
|
3
|
+
import { INestNode } from "./components/pivotTable/inteface";
|
|
3
4
|
/* eslint import/no-webpack-loader-syntax:0 */
|
|
4
5
|
// @ts-ignore
|
|
5
6
|
// eslint-disable-next-line
|
|
@@ -11,6 +12,7 @@ import { IRow, IMutField, Specification, IFilterFiledSimple, IExpression } from
|
|
|
11
12
|
import FilterWorker from './workers/filter.worker?worker&inline';
|
|
12
13
|
import TransformDataWorker from './workers/transform.worker?worker&inline';
|
|
13
14
|
import ViewQueryWorker from './workers/viewQuery.worker?worker&inline';
|
|
15
|
+
import BuildMetricTableWorker from './workers/buildMetricTable.worker?worker&inline';
|
|
14
16
|
import SortWorker from './workers/sort.worker?worker&inline';
|
|
15
17
|
|
|
16
18
|
import { IViewQuery } from './lib/viewQuery';
|
|
@@ -105,22 +107,12 @@ interface PreAnalysisParams {
|
|
|
105
107
|
// }
|
|
106
108
|
// }
|
|
107
109
|
|
|
108
|
-
let filterWorker: Worker | null = null;
|
|
109
|
-
let filterWorkerAutoTerminator: NodeJS.Timeout | null = null;
|
|
110
110
|
|
|
111
111
|
export const applyFilter = async (data: IRow[], filters: readonly IFilterFiledSimple[]): Promise<IRow[]> => {
|
|
112
112
|
if (filters.length === 0) return data;
|
|
113
|
-
|
|
114
|
-
clearTimeout(filterWorkerAutoTerminator);
|
|
115
|
-
filterWorkerAutoTerminator = null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (filterWorker === null) {
|
|
119
|
-
filterWorker = new FilterWorker();
|
|
120
|
-
}
|
|
121
|
-
|
|
113
|
+
const worker = new FilterWorker();
|
|
122
114
|
try {
|
|
123
|
-
const res: IRow[] = await workerService(
|
|
115
|
+
const res: IRow[] = await workerService(worker, {
|
|
124
116
|
dataSource: data,
|
|
125
117
|
filters: toJS(filters),
|
|
126
118
|
});
|
|
@@ -130,15 +122,7 @@ export const applyFilter = async (data: IRow[], filters: readonly IFilterFiledSi
|
|
|
130
122
|
// @ts-ignore @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
|
|
131
123
|
throw new Error('Uncaught error in FilterWorker', { cause: error });
|
|
132
124
|
} finally {
|
|
133
|
-
|
|
134
|
-
clearTimeout(filterWorkerAutoTerminator);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
filterWorkerAutoTerminator = setTimeout(() => {
|
|
138
|
-
filterWorker?.terminate();
|
|
139
|
-
filterWorker = null;
|
|
140
|
-
filterWorkerAutoTerminator = null;
|
|
141
|
-
}, 60_000); // Destroy the worker when no request is received for 60 secs
|
|
125
|
+
worker.terminate();
|
|
142
126
|
}
|
|
143
127
|
};
|
|
144
128
|
|
|
@@ -171,7 +155,32 @@ export const applyViewQuery = async (data: IRow[], query: IViewQuery): Promise<I
|
|
|
171
155
|
} finally {
|
|
172
156
|
worker.terminate();
|
|
173
157
|
}
|
|
174
|
-
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const buildPivotTableService = async (dimsInRow: IViewField[],
|
|
161
|
+
dimsInColumn: IViewField[],
|
|
162
|
+
allData: IRow[],
|
|
163
|
+
aggData: IRow[],
|
|
164
|
+
collapsedKeyList: string[],
|
|
165
|
+
showTableSummary: boolean
|
|
166
|
+
): Promise<{lt: INestNode, tt: INestNode, metric: (IRow | null)[][]}> => {
|
|
167
|
+
const worker = new BuildMetricTableWorker();
|
|
168
|
+
try {
|
|
169
|
+
const res: {lt: INestNode, tt: INestNode, metric: (IRow | null)[][]} = await workerService(worker, {
|
|
170
|
+
dimsInRow,
|
|
171
|
+
dimsInColumn,
|
|
172
|
+
allData,
|
|
173
|
+
aggData,
|
|
174
|
+
collapsedKeyList,
|
|
175
|
+
showTableSummary
|
|
176
|
+
});
|
|
177
|
+
return res;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw new Error('Uncaught error in TableBuilderDataWorker', { cause: error });
|
|
180
|
+
} finally {
|
|
181
|
+
worker.terminate();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
175
184
|
|
|
176
185
|
export const applySort = async (
|
|
177
186
|
data: IRow[],
|
package/src/shadow-dom.tsx
CHANGED
|
@@ -36,6 +36,13 @@ export const ShadowDom: React.FC<IShadowDomProps> = function ShadowDom ({ onMoun
|
|
|
36
36
|
<root.div {...attrs} mode="open" ref={rootRef}>
|
|
37
37
|
<style>{tailwindStyle}</style>
|
|
38
38
|
<style>{style}</style>
|
|
39
|
+
{/* Leaflet CSS file */}
|
|
40
|
+
<link
|
|
41
|
+
rel="stylesheet"
|
|
42
|
+
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
|
43
|
+
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
|
44
|
+
crossOrigin=""
|
|
45
|
+
/>
|
|
39
46
|
{shadowRoot && (
|
|
40
47
|
<StyleSheetManager target={shadowRoot}>
|
|
41
48
|
<ShadowDomContext.Provider value={{ root: shadowRoot }}>
|
package/src/store/commonStore.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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 { INestNode } from '../components/pivotTable/inteface';
|
|
4
5
|
|
|
5
6
|
export class CommonStore {
|
|
6
7
|
public datasets: IDataSet[] = [];
|
|
@@ -15,15 +16,18 @@ export class CommonStore {
|
|
|
15
16
|
public showDataConfig: boolean = false;
|
|
16
17
|
public showCodeExportPanel: boolean = false;
|
|
17
18
|
public showVisualConfigPanel: boolean = false;
|
|
19
|
+
public showGeoJSONConfigPanel: boolean = false;
|
|
18
20
|
public filters: Filters = {};
|
|
19
21
|
public segmentKey: ISegmentKey = ISegmentKey.vis;
|
|
22
|
+
public tableCollapsedHeaderMap: Map<string, INestNode["path"]> = new Map();
|
|
20
23
|
constructor () {
|
|
21
24
|
this.datasets = [];
|
|
22
25
|
this.dataSources = [];
|
|
23
26
|
makeAutoObservable(this, {
|
|
24
27
|
dataSources: observable.ref,
|
|
25
28
|
tmpDataSource: observable.ref,
|
|
26
|
-
filters: observable.ref
|
|
29
|
+
filters: observable.ref,
|
|
30
|
+
tableCollapsedHeaderMap: observable.ref,
|
|
27
31
|
});
|
|
28
32
|
}
|
|
29
33
|
public get currentDataset (): DataSet {
|
|
@@ -68,6 +72,30 @@ export class CommonStore {
|
|
|
68
72
|
public setShowVisualConfigPanel (show: boolean) {
|
|
69
73
|
this.showVisualConfigPanel = show;
|
|
70
74
|
}
|
|
75
|
+
public updateTableCollapsedHeader (node: INestNode) {
|
|
76
|
+
const {uniqueKey, height} = node;
|
|
77
|
+
if (height < 1) return;
|
|
78
|
+
const updatedMap = new Map(this.tableCollapsedHeaderMap)
|
|
79
|
+
// if some child nodes of the incoming node are collapsed, remove them first
|
|
80
|
+
updatedMap.forEach((existingPath, existingKey) => {
|
|
81
|
+
if (existingKey.startsWith(uniqueKey) && existingKey.length > uniqueKey.length) {
|
|
82
|
+
updatedMap.delete(existingKey)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
if (!updatedMap.has(uniqueKey)) {
|
|
86
|
+
updatedMap.set(uniqueKey, node.path)
|
|
87
|
+
} else {
|
|
88
|
+
updatedMap.delete(uniqueKey)
|
|
89
|
+
}
|
|
90
|
+
this.tableCollapsedHeaderMap = updatedMap
|
|
91
|
+
}
|
|
92
|
+
public resetTableCollapsedHeader () {
|
|
93
|
+
const updatedMap: Map<string, INestNode["path"]> = new Map();
|
|
94
|
+
this.tableCollapsedHeaderMap = updatedMap;
|
|
95
|
+
}
|
|
96
|
+
public setShowGeoJSONConfigPanel (show: boolean) {
|
|
97
|
+
this.showGeoJSONConfigPanel = show;
|
|
98
|
+
}
|
|
71
99
|
public closeEmbededMenu () {
|
|
72
100
|
this.vizEmbededMenu.show = false;
|
|
73
101
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from 'mobx';
|
|
1
|
+
import { IReactionDisposer, makeAutoObservable, observable, computed, reaction, toJS } from 'mobx';
|
|
2
2
|
import produce from 'immer';
|
|
3
|
+
import { feature } from 'topojson-client';
|
|
4
|
+
import type { FeatureCollection } from "geojson";
|
|
3
5
|
import {
|
|
4
6
|
DataSet,
|
|
5
7
|
DraggableFieldState,
|
|
6
8
|
IFilterRule,
|
|
9
|
+
IGeographicData,
|
|
7
10
|
ISortMode,
|
|
8
11
|
IStackMode,
|
|
9
12
|
IViewField,
|
|
@@ -25,6 +28,7 @@ import {
|
|
|
25
28
|
initVisualConfig,
|
|
26
29
|
forwardVisualConfigs,
|
|
27
30
|
visSpecDecoder,
|
|
31
|
+
initEncoding,
|
|
28
32
|
} from '../utils/save';
|
|
29
33
|
import { CommonStore } from './commonStore';
|
|
30
34
|
import { createCountField } from '../utils';
|
|
@@ -68,24 +72,6 @@ function geomAdapter(geom: string) {
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
export function initEncoding(): DraggableFieldState {
|
|
72
|
-
return {
|
|
73
|
-
dimensions: [],
|
|
74
|
-
measures: [],
|
|
75
|
-
rows: [],
|
|
76
|
-
columns: [],
|
|
77
|
-
color: [],
|
|
78
|
-
opacity: [],
|
|
79
|
-
size: [],
|
|
80
|
-
shape: [],
|
|
81
|
-
radius: [],
|
|
82
|
-
theta: [],
|
|
83
|
-
details: [],
|
|
84
|
-
filters: [],
|
|
85
|
-
text: [],
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
75
|
function stackValueTransform(vlValue: string | undefined | null): IStackMode {
|
|
90
76
|
if (vlValue === 'center') return 'center';
|
|
91
77
|
if (vlValue === 'normalize') return 'normalize';
|
|
@@ -117,6 +103,12 @@ function isDraggableStateEmpty(state: DeepReadonly<DraggableFieldState>): boolea
|
|
|
117
103
|
return Object.values(state).every((value) => value.length === 0);
|
|
118
104
|
}
|
|
119
105
|
|
|
106
|
+
function withTimeout<T extends any[], U>(f: (...args: T) => Promise<U>, timeout: number){
|
|
107
|
+
return (...args: T) => Promise.race([f(...args), new Promise<never>((_, reject) => {
|
|
108
|
+
setTimeout(() => reject(new Error('timeout')), timeout)
|
|
109
|
+
})])
|
|
110
|
+
}
|
|
111
|
+
|
|
120
112
|
export class VizSpecStore {
|
|
121
113
|
// public fields: IViewField[] = [];
|
|
122
114
|
private commonStore: CommonStore;
|
|
@@ -159,7 +151,7 @@ export class VizSpecStore {
|
|
|
159
151
|
public canRedo = false;
|
|
160
152
|
public editingFilterIdx: number | null = null;
|
|
161
153
|
// TODO
|
|
162
|
-
public
|
|
154
|
+
public computationFunction: IComputationFunction = async () => [];
|
|
163
155
|
constructor(commonStore: CommonStore) {
|
|
164
156
|
this.commonStore = commonStore;
|
|
165
157
|
this.draggableFieldState = initEncoding();
|
|
@@ -174,7 +166,7 @@ export class VizSpecStore {
|
|
|
174
166
|
);
|
|
175
167
|
makeAutoObservable(this, {
|
|
176
168
|
visList: observable.shallow,
|
|
177
|
-
|
|
169
|
+
computationFunction: observable.ref,
|
|
178
170
|
// @ts-expect-error private fields are not supported
|
|
179
171
|
reactions: false,
|
|
180
172
|
});
|
|
@@ -387,14 +379,17 @@ export class VizSpecStore {
|
|
|
387
379
|
public setVisualConfig<K extends keyof IVisualConfig>(configKey: K, value: IVisualConfig[K]) {
|
|
388
380
|
this.useMutable(({ config }) => {
|
|
389
381
|
switch (true) {
|
|
390
|
-
case ['defaultAggregated', 'defaultStack', 'showActions', 'interactiveScale'].includes(configKey): {
|
|
382
|
+
case ['defaultAggregated', 'defaultStack', 'showActions', 'interactiveScale', 'scaleIncludeUnmatchedChoropleth'].includes(configKey): {
|
|
391
383
|
return ((config as unknown as { [k: string]: boolean })[configKey] = Boolean(value));
|
|
392
384
|
}
|
|
393
385
|
case configKey === 'geoms' && Array.isArray(value):
|
|
386
|
+
case configKey === "showTableSummary":
|
|
387
|
+
case configKey === "coordSystem":
|
|
394
388
|
case configKey === 'size' && typeof value === 'object':
|
|
395
389
|
case configKey === 'sorted':
|
|
396
390
|
case configKey === 'zeroScale':
|
|
397
391
|
case configKey === 'background':
|
|
392
|
+
case configKey === 'resolve':
|
|
398
393
|
case configKey === 'limit':
|
|
399
394
|
case configKey === 'stack': {
|
|
400
395
|
return (config[configKey] = value);
|
|
@@ -510,6 +505,10 @@ export class VizSpecStore {
|
|
|
510
505
|
|
|
511
506
|
encodings.columns = encodings.rows;
|
|
512
507
|
encodings.rows = fieldsInCup as typeof encodings.rows; // assume this as writable
|
|
508
|
+
|
|
509
|
+
const fieldsInCup2 = encodings.longitude;
|
|
510
|
+
encodings.longitude = encodings.latitude;
|
|
511
|
+
encodings.latitude = fieldsInCup2 as typeof encodings.latitude; // assume this as writable
|
|
513
512
|
});
|
|
514
513
|
}
|
|
515
514
|
public createBinField(stateKey: keyof DraggableFieldState, index: number, binType: 'bin' | 'binCount'): string {
|
|
@@ -818,6 +817,23 @@ export class VizSpecStore {
|
|
|
818
817
|
const content = parseGWContent(raw);
|
|
819
818
|
this.importStoInfo(content);
|
|
820
819
|
}
|
|
820
|
+
|
|
821
|
+
public setGeographicData(data: IGeographicData, geoKey: string) {
|
|
822
|
+
const geoJSON = data.type === 'GeoJSON' ? data.data : feature(data.data, data.objectKey || Object.keys(data.data.objects)[0]) as unknown as FeatureCollection;
|
|
823
|
+
if (!('features' in geoJSON)) {
|
|
824
|
+
console.error('Invalid GeoJSON: GeoJSON must be a FeatureCollection, but got', geoJSON);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
this.useMutable(({ config }) => {
|
|
828
|
+
config.geojson = geoJSON;
|
|
829
|
+
config.geoKey = geoKey;
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
public updateGeoKey(key: string) {
|
|
833
|
+
this.useMutable(({ config }) => {
|
|
834
|
+
config.geoKey = key;
|
|
835
|
+
});
|
|
836
|
+
}
|
|
821
837
|
|
|
822
838
|
private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
|
|
823
839
|
const updatedVisList = visList.map((visSpec) => {
|
|
@@ -875,7 +891,7 @@ export class VizSpecStore {
|
|
|
875
891
|
);
|
|
876
892
|
}
|
|
877
893
|
|
|
878
|
-
public setComputationFunction(f: IComputationFunction) {
|
|
879
|
-
this.
|
|
894
|
+
public setComputationFunction(f: IComputationFunction, timeout = 60000) {
|
|
895
|
+
this.computationFunction = withTimeout(f, timeout);
|
|
880
896
|
}
|
|
881
897
|
}
|