@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.
Files changed (73) hide show
  1. package/dist/App.d.ts +5 -1
  2. package/dist/assets/buildMetricTable.worker-5555966a.js.map +1 -0
  3. package/dist/components/dataTable/index.d.ts +2 -1
  4. package/dist/components/leafletRenderer/ChoroplethRenderer.d.ts +21 -0
  5. package/dist/components/leafletRenderer/POIRenderer.d.ts +19 -0
  6. package/dist/components/leafletRenderer/encodings.d.ts +7 -0
  7. package/dist/components/leafletRenderer/geoConfigPanel.d.ts +3 -0
  8. package/dist/components/leafletRenderer/index.d.ts +14 -0
  9. package/dist/components/leafletRenderer/tooltip.d.ts +9 -0
  10. package/dist/components/leafletRenderer/utils.d.ts +2 -0
  11. package/dist/components/pivotTable/index.d.ts +2 -1
  12. package/dist/components/pivotTable/inteface.d.ts +6 -2
  13. package/dist/components/pivotTable/leftTree.d.ts +1 -0
  14. package/dist/components/pivotTable/topTree.d.ts +2 -0
  15. package/dist/components/pivotTable/utils.d.ts +1 -2
  16. package/dist/config.d.ts +3 -2
  17. package/dist/graphic-walker.es.js +37802 -30402
  18. package/dist/graphic-walker.es.js.map +1 -1
  19. package/dist/graphic-walker.umd.js +145 -137
  20. package/dist/graphic-walker.umd.js.map +1 -1
  21. package/dist/hooks/index.d.ts +1 -0
  22. package/dist/interfaces.d.ts +27 -0
  23. package/dist/renderer/specRenderer.d.ts +2 -1
  24. package/dist/services.d.ts +7 -1
  25. package/dist/store/commonStore.d.ts +6 -0
  26. package/dist/store/visualSpecStore.d.ts +180 -4
  27. package/dist/utils/save.d.ts +1 -0
  28. package/dist/workers/buildPivotTable.d.ts +7 -0
  29. package/package.json +14 -2
  30. package/src/App.tsx +18 -4
  31. package/src/components/askViz/index.tsx +2 -1
  32. package/src/components/dataTable/index.tsx +7 -5
  33. package/src/components/leafletRenderer/ChoroplethRenderer.tsx +293 -0
  34. package/src/components/leafletRenderer/POIRenderer.tsx +170 -0
  35. package/src/components/leafletRenderer/encodings.ts +194 -0
  36. package/src/components/leafletRenderer/geoConfigPanel.tsx +197 -0
  37. package/src/components/leafletRenderer/index.tsx +67 -0
  38. package/src/components/leafletRenderer/tooltip.tsx +24 -0
  39. package/src/components/leafletRenderer/utils.ts +52 -0
  40. package/src/components/limitSetting.tsx +8 -6
  41. package/src/components/pivotTable/index.tsx +171 -67
  42. package/src/components/pivotTable/inteface.ts +6 -2
  43. package/src/components/pivotTable/leftTree.tsx +24 -11
  44. package/src/components/pivotTable/metricTable.tsx +15 -10
  45. package/src/components/pivotTable/topTree.tsx +50 -17
  46. package/src/components/pivotTable/utils.ts +70 -11
  47. package/src/components/sizeSetting.tsx +9 -7
  48. package/src/components/visualConfig/index.tsx +17 -1
  49. package/src/computation/serverComputation.ts +8 -3
  50. package/src/config.ts +27 -16
  51. package/src/dataSource/table.tsx +9 -9
  52. package/src/fields/aestheticFields.tsx +4 -0
  53. package/src/fields/fieldsContext.tsx +3 -0
  54. package/src/fields/posFields/index.tsx +8 -2
  55. package/src/global.d.ts +4 -4
  56. package/src/hooks/index.ts +8 -0
  57. package/src/index.tsx +11 -9
  58. package/src/interfaces.ts +34 -0
  59. package/src/locales/en-US.json +27 -2
  60. package/src/locales/ja-JP.json +27 -2
  61. package/src/locales/zh-CN.json +27 -2
  62. package/src/renderer/hooks.ts +2 -48
  63. package/src/renderer/index.tsx +24 -1
  64. package/src/renderer/pureRenderer.tsx +26 -13
  65. package/src/renderer/specRenderer.tsx +45 -30
  66. package/src/services.ts +32 -23
  67. package/src/shadow-dom.tsx +7 -0
  68. package/src/store/commonStore.ts +29 -1
  69. package/src/store/visualSpecStore.ts +40 -24
  70. package/src/utils/save.ts +28 -1
  71. package/src/visualSettings/index.tsx +58 -7
  72. package/src/workers/buildMetricTable.worker.js +27 -0
  73. package/src/workers/buildPivotTable.ts +27 -0
@@ -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": {
@@ -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.computationFuction;
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: storeLimit,
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');
@@ -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 '../store/visualSpecStore';
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
- <SpecRenderer
106
- name={name}
107
- loading={waiting}
108
- data={viewData}
109
- ref={ref}
110
- themeKey={themeKey}
111
- dark={dark}
112
- draggableFieldState={visualState}
113
- visualConfig={visualConfig}
114
- locale={locale ?? 'en-US'}
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
- <ReactVega
143
- name={name}
144
- vegaConfig={vegaConfig}
145
- // format={format}
146
- layoutMode={size.mode}
147
- interactiveScale={interactiveScale}
148
- geomType={geoms[0]}
149
- defaultAggregate={defaultAggregated}
150
- stack={stack}
151
- dataSource={data}
152
- rows={rows}
153
- columns={columns}
154
- color={color[0]}
155
- theta={theta[0]}
156
- radius={radius[0]}
157
- shape={shape[0]}
158
- opacity={opacity[0]}
159
- size={sizeChannel[0]}
160
- details={details}
161
- text={text[0]}
162
- showActions={showActions}
163
- width={size.width - 12 * 4}
164
- height={size.height - 12 * 4}
165
- ref={ref}
166
- onGeomClick={onGeomClick}
167
- locale={locale}
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
- if (filterWorkerAutoTerminator !== null) {
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(filterWorker, {
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
- if (filterWorkerAutoTerminator !== null) {
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[],
@@ -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 }}>
@@ -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 computationFuction: IComputationFunction = async () => [];
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
- computationFuction: observable.ref,
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.computationFuction = f;
894
+ public setComputationFunction(f: IComputationFunction, timeout = 60000) {
895
+ this.computationFunction = withTimeout(f, timeout);
880
896
  }
881
897
  }