@kanaries/graphic-walker 0.3.16 → 0.4.1
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 +9 -2
- package/dist/assets/filter.worker-f09fcd6f.js.map +1 -1
- package/dist/assets/sort.worker-f77540ac.js.map +1 -0
- package/dist/assets/transform.worker-bae8e910.js.map +1 -0
- package/dist/assets/{viewQuery.worker-03404216.js.map → viewQuery.worker-bdb6477c.js.map} +1 -1
- package/dist/components/askViz/index.d.ts +6 -0
- package/dist/components/askViz/schemaTransform.d.ts +2 -0
- package/dist/components/dataTable/index.d.ts +9 -5
- package/dist/components/pivotTable/store.d.ts +0 -2
- package/dist/components/spinner.d.ts +2 -0
- package/dist/computation/clientComputation.d.ts +3 -0
- package/dist/computation/serverComputation.d.ts +8 -0
- package/dist/config.d.ts +3 -1
- package/dist/fields/filterField/tabs.d.ts +2 -1
- package/dist/graphic-walker.es.js +22268 -21682
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +139 -139
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/interfaces.d.ts +93 -4
- package/dist/lib/execExp.d.ts +4 -4
- package/dist/lib/interfaces.d.ts +1 -0
- package/dist/lib/viewQuery.d.ts +2 -2
- package/dist/renderer/hooks.d.ts +8 -4
- package/dist/renderer/index.d.ts +2 -1
- package/dist/renderer/pureRenderer.d.ts +17 -1
- package/dist/renderer/specRenderer.d.ts +1 -0
- package/dist/services.d.ts +8 -5
- package/dist/store/commonStore.d.ts +2 -2
- package/dist/store/visualSpecStore.d.ts +58 -42
- package/dist/utils/save.d.ts +10 -2
- package/dist/utils/workflow.d.ts +3 -0
- package/dist/vis/react-vega.d.ts +3 -1
- package/dist/workers/sort.d.ts +2 -2
- package/dist/workers/transform.d.ts +5 -2
- package/package.json +2 -2
- package/src/App.tsx +46 -7
- package/src/components/askViz/index.tsx +93 -0
- package/src/components/askViz/schemaTransform.ts +38 -0
- package/src/components/dataTable/index.tsx +53 -11
- package/src/components/limitSetting.tsx +8 -6
- package/src/components/pivotTable/index.tsx +0 -1
- package/src/components/pivotTable/store.tsx +0 -16
- package/src/components/sizeSetting.tsx +9 -7
- package/src/components/spinner.tsx +14 -0
- package/src/components/toggle.tsx +2 -2
- package/src/components/visualConfig/index.tsx +78 -8
- package/src/computation/clientComputation.ts +55 -0
- package/src/computation/serverComputation.ts +158 -0
- package/src/config.ts +15 -2
- package/src/dataSource/datasetConfig/index.tsx +38 -6
- package/src/dataSource/table.tsx +15 -2
- package/src/fields/filterField/filterEditDialog.tsx +11 -10
- package/src/fields/filterField/tabs.tsx +178 -77
- package/src/hooks/index.ts +8 -0
- package/src/index.tsx +2 -0
- package/src/interfaces.ts +108 -5
- package/src/lib/execExp.ts +20 -11
- package/src/lib/interfaces.ts +1 -0
- package/src/lib/op/aggregate.ts +1 -1
- package/src/lib/viewQuery.ts +2 -2
- package/src/locales/en-US.json +11 -2
- package/src/locales/ja-JP.json +11 -2
- package/src/locales/zh-CN.json +11 -2
- package/src/main.tsx +1 -1
- package/src/renderer/hooks.ts +57 -69
- package/src/renderer/index.tsx +10 -6
- package/src/renderer/pureRenderer.tsx +40 -14
- package/src/renderer/specRenderer.tsx +24 -7
- package/src/services.ts +7 -8
- package/src/store/commonStore.ts +7 -7
- package/src/store/visualSpecStore.ts +288 -192
- package/src/utils/save.ts +81 -3
- package/src/utils/workflow.ts +148 -0
- package/src/vis/react-vega.tsx +21 -6
- package/src/vis/spec/aggregate.ts +3 -2
- package/src/vis/spec/stack.ts +7 -6
- package/src/visualSettings/index.tsx +2 -4
- package/src/workers/filter.worker.js +1 -1
- package/src/workers/sort.ts +3 -4
- package/src/workers/sort.worker.ts +2 -2
- package/src/workers/transform.ts +7 -8
- package/src/workers/transform.worker.js +2 -2
- package/src/workers/viewQuery.worker.js +2 -2
- package/dist/assets/sort.worker-4299a6a0.js.map +0 -1
- package/dist/assets/transform.worker-a12fb3d8.js.map +0 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { observer } from 'mobx-react-lite';
|
|
2
|
-
import React, { useState } from 'react';
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { useGlobalStore } from '../../store';
|
|
4
|
+
import { NonPositionChannelConfigList,PositionChannelConfigList } from '../../config';
|
|
5
|
+
|
|
4
6
|
import Modal from '../modal';
|
|
5
7
|
import { IVisualConfig } from '../../interfaces';
|
|
6
8
|
import PrimaryButton from '../button/primary';
|
|
7
9
|
import DefaultButton from '../button/default';
|
|
8
10
|
import { useTranslation } from 'react-i18next';
|
|
9
11
|
import Toggle from '../toggle';
|
|
10
|
-
import { runInAction } from 'mobx';
|
|
12
|
+
import { runInAction, toJS } from 'mobx';
|
|
11
13
|
|
|
12
14
|
const VisualConfigPanel: React.FC = (props) => {
|
|
13
15
|
const { commonStore, vizStore } = useGlobalStore();
|
|
@@ -24,7 +26,27 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
24
26
|
timeFormat: visualConfig.format.timeFormat,
|
|
25
27
|
normalizedNumberFormat: visualConfig.format.normalizedNumberFormat,
|
|
26
28
|
});
|
|
29
|
+
const [resolve, setResolve] = useState<IVisualConfig['resolve']>({
|
|
30
|
+
x: visualConfig.resolve.x,
|
|
31
|
+
y: visualConfig.resolve.y,
|
|
32
|
+
color: visualConfig.resolve.color,
|
|
33
|
+
opacity: visualConfig.resolve.opacity,
|
|
34
|
+
shape: visualConfig.resolve.shape,
|
|
35
|
+
size: visualConfig.resolve.size,
|
|
36
|
+
});
|
|
27
37
|
const [zeroScale, setZeroScale] = useState<boolean>(visualConfig.zeroScale);
|
|
38
|
+
const [background, setBackground] = useState<string | undefined>(visualConfig.background);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setZeroScale(visualConfig.zeroScale);
|
|
42
|
+
setBackground(visualConfig.background);
|
|
43
|
+
setResolve(toJS(visualConfig.resolve));
|
|
44
|
+
setFormat({
|
|
45
|
+
numberFormat: visualConfig.format.numberFormat,
|
|
46
|
+
timeFormat: visualConfig.format.timeFormat,
|
|
47
|
+
normalizedNumberFormat: visualConfig.format.normalizedNumberFormat,
|
|
48
|
+
});
|
|
49
|
+
}, [showVisualConfigPanel]);
|
|
28
50
|
|
|
29
51
|
return (
|
|
30
52
|
<Modal
|
|
@@ -36,22 +58,22 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
36
58
|
<div>
|
|
37
59
|
<h2 className="text-lg mb-4">{t('config.format')}</h2>
|
|
38
60
|
<p className="text-xs">
|
|
39
|
-
|
|
61
|
+
{t(`config.formatGuidesDocs`)}:{' '}
|
|
40
62
|
<a
|
|
41
63
|
target="_blank"
|
|
42
64
|
className="underline text-blue-500"
|
|
43
65
|
href="https://github.com/d3/d3-format#locale_format"
|
|
44
66
|
>
|
|
45
|
-
|
|
67
|
+
{t(`config.readHere`)}
|
|
46
68
|
</a>
|
|
47
69
|
</p>
|
|
48
70
|
{formatConfigList.map((fc) => (
|
|
49
71
|
<div className="my-2" key={fc}>
|
|
50
|
-
<label className="block text-xs font-medium leading-6
|
|
72
|
+
<label className="block text-xs font-medium leading-6">{t(`config.${fc}`)}</label>
|
|
51
73
|
<div className="mt-1">
|
|
52
74
|
<input
|
|
53
75
|
type="text"
|
|
54
|
-
className="block w-full rounded-md border-0 py-1 px-2
|
|
76
|
+
className="block w-full text-gray-700 dark:text-gray-200 rounded-md border-0 py-1 px-2 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-zinc-900 "
|
|
55
77
|
value={format[fc] ?? ''}
|
|
56
78
|
onChange={(e) => {
|
|
57
79
|
setFormat((f) => ({
|
|
@@ -63,9 +85,55 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
63
85
|
</div>
|
|
64
86
|
</div>
|
|
65
87
|
))}
|
|
88
|
+
<h2 className="text-lg">{t('config.background')}</h2>
|
|
89
|
+
<div className="my-2">
|
|
90
|
+
<label className="block text-xs font-medium leading-6">{t(`config.color`)}</label>
|
|
91
|
+
<div className="mt-1">
|
|
92
|
+
<input
|
|
93
|
+
type="text"
|
|
94
|
+
className="block w-full text-gray-700 dark:text-gray-200 rounded-md border-0 py-1 px-2 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-zinc-900 "
|
|
95
|
+
value={background ?? ''}
|
|
96
|
+
onChange={(e) => {
|
|
97
|
+
setBackground(e.target.value);
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<h2 className="text-lg">{t('config.independence')}</h2>
|
|
103
|
+
<div className="my-2">
|
|
104
|
+
<div className="flex space-x-6">
|
|
105
|
+
{PositionChannelConfigList.map((pc) => (
|
|
106
|
+
<Toggle
|
|
107
|
+
label={t(`config.${pc}`)}
|
|
108
|
+
key={pc}
|
|
109
|
+
enabled={resolve[pc] ?? false}
|
|
110
|
+
onChange={(e) => {
|
|
111
|
+
setResolve((r) => ({
|
|
112
|
+
...r,
|
|
113
|
+
[pc]: e,
|
|
114
|
+
}));
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
))}
|
|
118
|
+
{NonPositionChannelConfigList.map((npc) => (
|
|
119
|
+
<Toggle
|
|
120
|
+
label={t(`constant.draggable_key.${npc}`)}
|
|
121
|
+
key={npc}
|
|
122
|
+
enabled={resolve[npc] ?? false}
|
|
123
|
+
onChange={(e) => {
|
|
124
|
+
setResolve((r) => ({
|
|
125
|
+
...r,
|
|
126
|
+
[npc]: e,
|
|
127
|
+
}));
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<h2 className="text-lg">{t('config.zeroScale')}</h2>
|
|
66
134
|
<div className="my-2">
|
|
67
135
|
<Toggle
|
|
68
|
-
label=
|
|
136
|
+
label={t(`config.zeroScale`)}
|
|
69
137
|
enabled={zeroScale}
|
|
70
138
|
onChange={(en) => {
|
|
71
139
|
setZeroScale(en);
|
|
@@ -80,8 +148,10 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
80
148
|
runInAction(() => {
|
|
81
149
|
vizStore.setVisualConfig('format', format);
|
|
82
150
|
vizStore.setVisualConfig('zeroScale', zeroScale);
|
|
151
|
+
vizStore.setVisualConfig('background', background);
|
|
152
|
+
vizStore.setVisualConfig('resolve', resolve);
|
|
83
153
|
commonStore.setShowVisualConfigPanel(false);
|
|
84
|
-
})
|
|
154
|
+
});
|
|
85
155
|
}}
|
|
86
156
|
/>
|
|
87
157
|
<DefaultButton
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { IDataQueryPayload, IDataQueryWorkflowStep, IFilterFiledSimple, IRow } from "../interfaces";
|
|
2
|
+
import { applyFilter, applySort, applyViewQuery, transformDataService } from "../services";
|
|
3
|
+
|
|
4
|
+
export const dataQueryClient = async (
|
|
5
|
+
rawData: IRow[],
|
|
6
|
+
workflow: IDataQueryWorkflowStep[],
|
|
7
|
+
offset?: number,
|
|
8
|
+
limit?: number,
|
|
9
|
+
): Promise<IRow[]> => {
|
|
10
|
+
let res = rawData;
|
|
11
|
+
for await (const step of workflow) {
|
|
12
|
+
switch (step.type) {
|
|
13
|
+
case 'filter': {
|
|
14
|
+
res = await applyFilter(res, step.filters.map(filter => {
|
|
15
|
+
const res: IFilterFiledSimple = {
|
|
16
|
+
fid: filter.fid,
|
|
17
|
+
rule: null,
|
|
18
|
+
};
|
|
19
|
+
if (filter.rule.type === 'one of') {
|
|
20
|
+
res.rule = {
|
|
21
|
+
type: 'one of',
|
|
22
|
+
value: new Set(filter.rule.value),
|
|
23
|
+
};
|
|
24
|
+
} else {
|
|
25
|
+
res.rule = filter.rule;
|
|
26
|
+
}
|
|
27
|
+
return res;
|
|
28
|
+
}).filter(Boolean));
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case 'transform': {
|
|
32
|
+
res = await transformDataService(res, step.transform);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case 'view': {
|
|
36
|
+
for await (const job of step.query) {
|
|
37
|
+
res = await applyViewQuery(res, job);
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case 'sort': {
|
|
42
|
+
res = await applySort(res, step.by, step.sort);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
default: {
|
|
46
|
+
// @ts-expect-error - runtime check
|
|
47
|
+
console.warn(new Error(`Unknown step type: ${step.type}`));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return res.slice(offset ?? 0, limit ? ((offset ?? 0) + limit) : undefined);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const getComputation = (rawData: IRow[]) => (payload: IDataQueryPayload) => dataQueryClient(rawData, payload.workflow, payload.offset, payload.limit)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IComputationFunction,
|
|
3
|
+
IDataQueryPayload,
|
|
4
|
+
IDataQueryWorkflowStep,
|
|
5
|
+
IDatasetStats,
|
|
6
|
+
IFieldStats,
|
|
7
|
+
IRow,
|
|
8
|
+
} from '../interfaces';
|
|
9
|
+
|
|
10
|
+
export const datasetStatsServer = async (service: IComputationFunction): Promise<IDatasetStats> => {
|
|
11
|
+
const res = (await service({
|
|
12
|
+
workflow: [
|
|
13
|
+
{
|
|
14
|
+
type: 'view',
|
|
15
|
+
query: [
|
|
16
|
+
{
|
|
17
|
+
op: 'aggregate',
|
|
18
|
+
groupBy: [],
|
|
19
|
+
measures: [
|
|
20
|
+
{
|
|
21
|
+
field: '*',
|
|
22
|
+
agg: 'count',
|
|
23
|
+
asFieldKey: 'count',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
})) as [{ count: number }];
|
|
31
|
+
return {
|
|
32
|
+
rowCount: res[0]?.count ?? 0,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const dataReadRawServer = async (
|
|
37
|
+
service: IComputationFunction,
|
|
38
|
+
pageSize: number,
|
|
39
|
+
pageOffset = 0
|
|
40
|
+
): Promise<IRow[]> => {
|
|
41
|
+
const res = await service({
|
|
42
|
+
workflow: [
|
|
43
|
+
{
|
|
44
|
+
type: 'view',
|
|
45
|
+
query: [
|
|
46
|
+
{
|
|
47
|
+
op: 'raw',
|
|
48
|
+
fields: ['*'],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
limit: pageSize,
|
|
54
|
+
offset: pageOffset * pageSize,
|
|
55
|
+
});
|
|
56
|
+
return res;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const dataQueryServer = async (
|
|
60
|
+
service: IComputationFunction,
|
|
61
|
+
workflow: IDataQueryWorkflowStep[],
|
|
62
|
+
limit?: number,
|
|
63
|
+
): Promise<IRow[]> => {
|
|
64
|
+
if (
|
|
65
|
+
workflow.length === 1 &&
|
|
66
|
+
workflow[0].type === 'view' &&
|
|
67
|
+
workflow[0].query.length === 1 &&
|
|
68
|
+
workflow[0].query[0].op === 'raw' &&
|
|
69
|
+
workflow[0].query[0].fields.length === 0
|
|
70
|
+
) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const res = await service({
|
|
74
|
+
workflow,
|
|
75
|
+
limit,
|
|
76
|
+
});
|
|
77
|
+
return res;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const fieldStatServer = async (
|
|
81
|
+
service: IComputationFunction,
|
|
82
|
+
field: string,
|
|
83
|
+
options: { values?: boolean; range?: boolean }
|
|
84
|
+
): Promise<IFieldStats> => {
|
|
85
|
+
const { values = true, range = true } = options;
|
|
86
|
+
const COUNT_ID = `count_${field}`;
|
|
87
|
+
const MIN_ID = `min_${field}`;
|
|
88
|
+
const MAX_ID = `max_${field}`;
|
|
89
|
+
const valuesQueryPayload: IDataQueryPayload = {
|
|
90
|
+
workflow: [
|
|
91
|
+
{
|
|
92
|
+
type: 'view',
|
|
93
|
+
query: [
|
|
94
|
+
{
|
|
95
|
+
op: 'aggregate',
|
|
96
|
+
groupBy: [field],
|
|
97
|
+
measures: [
|
|
98
|
+
{
|
|
99
|
+
field,
|
|
100
|
+
agg: 'count',
|
|
101
|
+
asFieldKey: COUNT_ID,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
const valuesRes = values ? await service(valuesQueryPayload) : [];
|
|
110
|
+
const rangeQueryPayload: IDataQueryPayload = {
|
|
111
|
+
workflow: [
|
|
112
|
+
{
|
|
113
|
+
type: 'view',
|
|
114
|
+
query: [
|
|
115
|
+
{
|
|
116
|
+
op: 'aggregate',
|
|
117
|
+
groupBy: [],
|
|
118
|
+
measures: [
|
|
119
|
+
{
|
|
120
|
+
field,
|
|
121
|
+
agg: 'min',
|
|
122
|
+
asFieldKey: MIN_ID,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
field,
|
|
126
|
+
agg: 'max',
|
|
127
|
+
asFieldKey: MAX_ID,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
const [
|
|
136
|
+
rangeRes = {
|
|
137
|
+
[MIN_ID]: 0,
|
|
138
|
+
[MAX_ID]: 0,
|
|
139
|
+
},
|
|
140
|
+
] = range
|
|
141
|
+
? await service(rangeQueryPayload)
|
|
142
|
+
: [
|
|
143
|
+
{
|
|
144
|
+
[MIN_ID]: 0,
|
|
145
|
+
[MAX_ID]: 0,
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
values: valuesRes
|
|
151
|
+
.sort((a, b) => b[COUNT_ID] - a[COUNT_ID])
|
|
152
|
+
.map((row) => ({
|
|
153
|
+
value: row[field],
|
|
154
|
+
count: row[COUNT_ID],
|
|
155
|
+
})),
|
|
156
|
+
range: [rangeRes[MIN_ID], rangeRes[MAX_ID]],
|
|
157
|
+
};
|
|
158
|
+
};
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DraggableFieldState, IStackMode } from "./interfaces";
|
|
1
|
+
import { DraggableFieldState, IStackMode, IVisualConfig } from "./interfaces";
|
|
2
2
|
|
|
3
3
|
export const GEMO_TYPES: Readonly<string[]> = [
|
|
4
4
|
'auto',
|
|
@@ -19,7 +19,8 @@ export const GEMO_TYPES: Readonly<string[]> = [
|
|
|
19
19
|
export const STACK_MODE: Readonly<IStackMode[]> = [
|
|
20
20
|
'none',
|
|
21
21
|
'stack',
|
|
22
|
-
'normalize'
|
|
22
|
+
'normalize',
|
|
23
|
+
'center'
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
export const CHART_LAYOUT_TYPE: Readonly<string[]> = [
|
|
@@ -57,4 +58,16 @@ export const CHANNEL_LIMIT = {
|
|
|
57
58
|
export const MetaFieldKeys: Array<keyof DraggableFieldState> = [
|
|
58
59
|
'dimensions',
|
|
59
60
|
'measures',
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
export const PositionChannelConfigList: Array<keyof IVisualConfig['resolve']> = [
|
|
64
|
+
'x',
|
|
65
|
+
'y',
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
export const NonPositionChannelConfigList: Array<keyof IVisualConfig['resolve']> = [
|
|
69
|
+
'color',
|
|
70
|
+
'opacity',
|
|
71
|
+
'shape',
|
|
72
|
+
'size'
|
|
60
73
|
]
|
|
@@ -1,15 +1,47 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
import DatasetTable from "../../components/dataTable";
|
|
3
3
|
import { observer } from "mobx-react-lite";
|
|
4
4
|
import { useGlobalStore } from "../../store";
|
|
5
|
+
import { useComputationFunc } from "../../renderer/hooks";
|
|
6
|
+
import { datasetStatsServer } from "../../computation/serverComputation";
|
|
5
7
|
|
|
6
|
-
const DatasetConfig: React.FC = (
|
|
7
|
-
const { commonStore
|
|
8
|
+
const DatasetConfig: React.FC = () => {
|
|
9
|
+
const { commonStore } = useGlobalStore();
|
|
8
10
|
const { currentDataset } = commonStore;
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
const computationFunction = useComputationFunc();
|
|
13
|
+
|
|
14
|
+
const [count, setCount] = useState(0);
|
|
15
|
+
const taskIdRef = useRef(0);
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const taskId = ++taskIdRef.current;
|
|
20
|
+
setLoading(true);
|
|
21
|
+
datasetStatsServer(computationFunction).then(res => {
|
|
22
|
+
if (taskId !== taskIdRef.current) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
setCount(res.rowCount);
|
|
26
|
+
setLoading(false);
|
|
27
|
+
}).catch(err => {
|
|
28
|
+
if (taskId !== taskIdRef.current) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.error(err);
|
|
32
|
+
setLoading(false);
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
taskIdRef.current++;
|
|
36
|
+
};
|
|
37
|
+
}, [computationFunction]);
|
|
38
|
+
|
|
10
39
|
return (
|
|
11
|
-
<div>
|
|
12
|
-
<DatasetTable size={100}
|
|
40
|
+
<div className="relative">
|
|
41
|
+
<DatasetTable size={100}
|
|
42
|
+
total={count}
|
|
43
|
+
dataset={currentDataset}
|
|
44
|
+
loading={loading}
|
|
13
45
|
onMetaChange={(fid, fIndex, diffMeta) => {
|
|
14
46
|
commonStore.updateCurrentDatasetMetas(fid, diffMeta)
|
|
15
47
|
}}
|
package/src/dataSource/table.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite";
|
|
|
3
3
|
import { useGlobalStore } from "../store";
|
|
4
4
|
import DataTable from "../components/dataTable";
|
|
5
5
|
import { toJS } from "mobx";
|
|
6
|
+
import { getComputation } from "../computation/clientComputation";
|
|
6
7
|
|
|
7
8
|
interface TableProps {
|
|
8
9
|
size?: number;
|
|
@@ -13,11 +14,23 @@ const Table: React.FC<TableProps> = (props) => {
|
|
|
13
14
|
const { commonStore } = useGlobalStore();
|
|
14
15
|
const { tmpDSRawFields, tmpDataSource } = commonStore;
|
|
15
16
|
|
|
17
|
+
const tempDataset = React.useMemo(() => {
|
|
18
|
+
return {
|
|
19
|
+
id: "tmp",
|
|
20
|
+
name: "tmp",
|
|
21
|
+
dataSource: tmpDataSource,
|
|
22
|
+
rawFields: toJS(tmpDSRawFields),
|
|
23
|
+
};
|
|
24
|
+
}, [tmpDataSource, tmpDSRawFields]);
|
|
25
|
+
9
|
|
26
|
+
const computation = React.useMemo(() => getComputation(tempDataset.dataSource), [tempDataset])
|
|
27
|
+
|
|
16
28
|
return (
|
|
17
29
|
<DataTable
|
|
18
30
|
size={size}
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
dataset={tempDataset}
|
|
32
|
+
computation={computation}
|
|
33
|
+
total={tmpDataSource.length}
|
|
21
34
|
onMetaChange={(fid, fIndex, diffMeta) => {
|
|
22
35
|
commonStore.updateTempDatasetMetas(fid, diffMeta);
|
|
23
36
|
}}
|
|
@@ -10,27 +10,28 @@ import DefaultButton from "../../components/button/default";
|
|
|
10
10
|
import PrimaryButton from "../../components/button/primary";
|
|
11
11
|
import DropdownSelect from "../../components/dropdownSelect";
|
|
12
12
|
|
|
13
|
-
const QuantitativeRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
14
|
-
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} />;
|
|
13
|
+
const QuantitativeRuleForm: React.FC<RuleFormProps> = ({ dataset, field, onChange }) => {
|
|
14
|
+
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} dataset={dataset} />;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
const NominalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
18
|
-
return <Tabs field={field} onChange={onChange} tabs={["one of"]} />;
|
|
17
|
+
const NominalRuleForm: React.FC<RuleFormProps> = ({ dataset, field, onChange }) => {
|
|
18
|
+
return <Tabs field={field} onChange={onChange} tabs={["one of"]} dataset={dataset} />;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
const OrdinalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
22
|
-
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} />;
|
|
21
|
+
const OrdinalRuleForm: React.FC<RuleFormProps> = ({ dataset, field, onChange }) => {
|
|
22
|
+
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} dataset={dataset} />;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const TemporalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
26
|
-
return <Tabs field={field} onChange={onChange} tabs={["temporal range", "one of"]} />;
|
|
25
|
+
const TemporalRuleForm: React.FC<RuleFormProps> = ({ dataset, field, onChange }) => {
|
|
26
|
+
return <Tabs field={field} onChange={onChange} tabs={["temporal range", "one of"]} dataset={dataset} />;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const EmptyForm: React.FC<RuleFormProps> = () => <React.Fragment />;
|
|
30
30
|
|
|
31
31
|
const FilterEditDialog: React.FC = observer(() => {
|
|
32
|
-
const { vizStore } = useGlobalStore();
|
|
32
|
+
const { vizStore, commonStore } = useGlobalStore();
|
|
33
33
|
const { editingFilterIdx, draggableFieldState } = vizStore;
|
|
34
|
+
const { currentDataset } = commonStore;
|
|
34
35
|
|
|
35
36
|
const { t } = useTranslation("translation", { keyPrefix: "filters" });
|
|
36
37
|
|
|
@@ -113,7 +114,7 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
113
114
|
selectedKey={uncontrolledField.fid}
|
|
114
115
|
onSelect={handleSelectFilterField}
|
|
115
116
|
/>
|
|
116
|
-
<Form field={uncontrolledField} onChange={handleChange} />
|
|
117
|
+
<Form dataset={currentDataset} field={uncontrolledField} onChange={handleChange} />
|
|
117
118
|
<div className="mt-4">
|
|
118
119
|
<PrimaryButton
|
|
119
120
|
onClick={handleSubmit}
|