@kanaries/graphic-walker 0.3.6 → 0.3.8
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/components/pivotTable/index.d.ts +1 -1
- package/dist/components/toggle.d.ts +8 -0
- package/dist/graphic-walker.es.js +17079 -16915
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +120 -120
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/interfaces.d.ts +4 -0
- package/dist/renderer/hooks.d.ts +15 -0
- package/dist/renderer/pureRenderer.d.ts +12 -0
- package/dist/renderer/specRenderer.d.ts +7 -1
- package/dist/vis/react-vega.d.ts +2 -5
- package/dist/vis/spec/tooltip.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/pivotTable/index.tsx +1 -1
- package/src/components/toggle.tsx +40 -0
- package/src/components/visualConfig/index.tsx +31 -7
- package/src/fields/aestheticFields.tsx +2 -0
- package/src/index.tsx +2 -0
- package/src/interfaces.ts +6 -0
- package/src/renderer/hooks.ts +73 -0
- package/src/renderer/index.tsx +61 -46
- package/src/renderer/pureRenderer.tsx +90 -0
- package/src/renderer/specRenderer.tsx +63 -28
- package/src/store/visualSpecStore.ts +4 -1
- package/src/vis/react-vega.tsx +25 -29
- package/src/vis/spec/mark.ts +1 -1
- package/src/vis/spec/tooltip.ts +4 -3
- package/src/vis/spec/view.ts +1 -1
- package/dist/utils/autoMark.d.ts +0 -7
- package/src/utils/autoMark.ts +0 -30
package/dist/index.d.ts
CHANGED
package/dist/interfaces.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Config as VgConfig } from 'vega';
|
|
2
|
+
import { Config as VlConfig } from 'vega-lite';
|
|
1
3
|
export type DeepReadonly<T extends Record<keyof any, any>> = {
|
|
2
4
|
readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
|
|
3
5
|
};
|
|
@@ -164,6 +166,7 @@ export interface IVisualConfig {
|
|
|
164
166
|
showActions: boolean;
|
|
165
167
|
interactiveScale: boolean;
|
|
166
168
|
sorted: 'none' | 'ascending' | 'descending';
|
|
169
|
+
zeroScale: boolean;
|
|
167
170
|
format: {
|
|
168
171
|
numberFormat?: string;
|
|
169
172
|
timeFormat?: string;
|
|
@@ -187,3 +190,4 @@ export declare enum ISegmentKey {
|
|
|
187
190
|
}
|
|
188
191
|
export type IThemeKey = 'vega' | 'g2';
|
|
189
192
|
export type IDarkMode = 'media' | 'light' | 'dark';
|
|
193
|
+
export type VegaGlobalConfig = VgConfig | VlConfig;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DeepReadonly, IFilterField, IRow, IViewField } from '../interfaces';
|
|
2
|
+
interface UseRendererProps {
|
|
3
|
+
data: IRow[];
|
|
4
|
+
allFields: Omit<IViewField, 'dragId'>[];
|
|
5
|
+
viewDimensions: IViewField[];
|
|
6
|
+
viewMeasures: IViewField[];
|
|
7
|
+
filters: readonly DeepReadonly<IFilterField>[];
|
|
8
|
+
defaultAggregated: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface UseRendererResult {
|
|
11
|
+
viewData: IRow[];
|
|
12
|
+
loading: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const useRenderer: (props: UseRendererProps) => UseRendererResult;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { IDarkMode, IRow, IThemeKey, DraggableFieldState, IVisualConfig } from '../interfaces';
|
|
3
|
+
import type { IReactVegaHandler } from '../vis/react-vega';
|
|
4
|
+
interface IPureRendererProps {
|
|
5
|
+
themeKey?: IThemeKey;
|
|
6
|
+
dark?: IDarkMode;
|
|
7
|
+
rawData?: IRow[];
|
|
8
|
+
visualState: DraggableFieldState;
|
|
9
|
+
visualConfig: IVisualConfig;
|
|
10
|
+
}
|
|
11
|
+
declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<Pick<IPureRendererProps & React.RefAttributes<IReactVegaHandler>, "key" | keyof IPureRendererProps> & React.RefAttributes<IReactVegaHandler>>>;
|
|
12
|
+
export default _default;
|
|
@@ -7,7 +7,13 @@ interface SpecRendererProps {
|
|
|
7
7
|
data: IRow[];
|
|
8
8
|
loading: boolean;
|
|
9
9
|
draggableFieldState: DeepReadonly<DraggableFieldState>;
|
|
10
|
-
visualConfig: IVisualConfig
|
|
10
|
+
visualConfig: DeepReadonly<IVisualConfig>;
|
|
11
|
+
onGeomClick?: ((values: any, e: any) => void) | undefined;
|
|
12
|
+
onChartResize?: ((width: number, height: number) => void) | undefined;
|
|
11
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Sans-store renderer of GraphicWalker.
|
|
16
|
+
* This is a pure component, which means it will not depend on any global state.
|
|
17
|
+
*/
|
|
12
18
|
declare const SpecRenderer: React.ForwardRefExoticComponent<SpecRendererProps & React.RefAttributes<IReactVegaHandler>>;
|
|
13
19
|
export default SpecRenderer;
|
package/dist/vis/react-vega.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { IViewField, IRow, IStackMode,
|
|
2
|
+
import { IViewField, IRow, IStackMode, VegaGlobalConfig } from '../interfaces';
|
|
3
3
|
export interface IReactVegaHandler {
|
|
4
4
|
getSVGData: () => Promise<string[]>;
|
|
5
5
|
getCanvasData: () => Promise<string[]>;
|
|
@@ -7,7 +7,6 @@ export interface IReactVegaHandler {
|
|
|
7
7
|
downloadPNG: (filename?: string) => Promise<string[]>;
|
|
8
8
|
}
|
|
9
9
|
interface ReactVegaProps {
|
|
10
|
-
format: IVisualConfig['format'];
|
|
11
10
|
rows: Readonly<IViewField[]>;
|
|
12
11
|
columns: Readonly<IViewField[]>;
|
|
13
12
|
dataSource: IRow[];
|
|
@@ -28,9 +27,7 @@ interface ReactVegaProps {
|
|
|
28
27
|
width: number;
|
|
29
28
|
height: number;
|
|
30
29
|
onGeomClick?: (values: any, e: any) => void;
|
|
31
|
-
|
|
32
|
-
themeKey?: IThemeKey;
|
|
33
|
-
dark?: IDarkMode;
|
|
30
|
+
vegaConfig: VegaGlobalConfig;
|
|
34
31
|
}
|
|
35
32
|
declare const ReactVega: React.ForwardRefExoticComponent<ReactVegaProps & React.RefAttributes<IReactVegaHandler>>;
|
|
36
33
|
export default ReactVega;
|
package/package.json
CHANGED
|
@@ -39,7 +39,7 @@ interface PivotTableProps {
|
|
|
39
39
|
data: IRow[];
|
|
40
40
|
loading: boolean;
|
|
41
41
|
draggableFieldState: DeepReadonly<DraggableFieldState>;
|
|
42
|
-
visualConfig: IVisualConfig
|
|
42
|
+
visualConfig: DeepReadonly<IVisualConfig>;
|
|
43
43
|
}
|
|
44
44
|
const PivotTable: React.FC<PivotTableProps> = (props) => {
|
|
45
45
|
const { data, draggableFieldState } = props;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Switch } from '@headlessui/react';
|
|
3
|
+
|
|
4
|
+
function classNames(...classes: string[]) {
|
|
5
|
+
return classes.filter(Boolean).join(' ');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ToggleProps {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
onChange: (enabled: boolean) => void;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function Toggle(props: ToggleProps) {
|
|
15
|
+
const { enabled, onChange, label } = props;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Switch.Group as="div" className="flex items-center">
|
|
19
|
+
<Switch
|
|
20
|
+
checked={enabled}
|
|
21
|
+
onChange={onChange}
|
|
22
|
+
className={classNames(
|
|
23
|
+
enabled ? 'bg-indigo-600' : 'bg-gray-200',
|
|
24
|
+
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2'
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
<span
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
className={classNames(
|
|
30
|
+
enabled ? 'translate-x-5' : 'translate-x-0',
|
|
31
|
+
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
|
|
32
|
+
)}
|
|
33
|
+
/>
|
|
34
|
+
</Switch>
|
|
35
|
+
<Switch.Label as="span" className="ml-3 text-sm">
|
|
36
|
+
<span className="font-medium text-gray-900">{label}</span>
|
|
37
|
+
</Switch.Label>
|
|
38
|
+
</Switch.Group>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -6,6 +6,8 @@ import { IVisualConfig } from '../../interfaces';
|
|
|
6
6
|
import PrimaryButton from '../button/primary';
|
|
7
7
|
import DefaultButton from '../button/default';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
|
+
import Toggle from '../toggle';
|
|
10
|
+
import { runInAction } from 'mobx';
|
|
9
11
|
|
|
10
12
|
const VisualConfigPanel: React.FC = (props) => {
|
|
11
13
|
const { commonStore, vizStore } = useGlobalStore();
|
|
@@ -22,6 +24,7 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
22
24
|
timeFormat: visualConfig.format.timeFormat,
|
|
23
25
|
normalizedNumberFormat: visualConfig.format.normalizedNumberFormat,
|
|
24
26
|
});
|
|
27
|
+
const [zeroScale, setZeroScale] = useState<boolean>(visualConfig.zeroScale);
|
|
25
28
|
|
|
26
29
|
return (
|
|
27
30
|
<Modal
|
|
@@ -31,8 +34,17 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
31
34
|
}}
|
|
32
35
|
>
|
|
33
36
|
<div>
|
|
34
|
-
<h2 className=
|
|
35
|
-
<p className=
|
|
37
|
+
<h2 className="text-lg mb-4">{t('config.format')}</h2>
|
|
38
|
+
<p className="text-xs">
|
|
39
|
+
Format guides docs:{' '}
|
|
40
|
+
<a
|
|
41
|
+
target="_blank"
|
|
42
|
+
className="underline text-blue-500"
|
|
43
|
+
href="https://github.com/d3/d3-format#locale_format"
|
|
44
|
+
>
|
|
45
|
+
read here
|
|
46
|
+
</a>
|
|
47
|
+
</p>
|
|
36
48
|
{formatConfigList.map((fc) => (
|
|
37
49
|
<div className="my-2" key={fc}>
|
|
38
50
|
<label className="block text-xs font-medium leading-6 text-gray-900">{t(`config.${fc}`)}</label>
|
|
@@ -51,18 +63,30 @@ const VisualConfigPanel: React.FC = (props) => {
|
|
|
51
63
|
</div>
|
|
52
64
|
</div>
|
|
53
65
|
))}
|
|
54
|
-
<div className=
|
|
66
|
+
<div className="my-2">
|
|
67
|
+
<Toggle
|
|
68
|
+
label="zero scale"
|
|
69
|
+
enabled={zeroScale}
|
|
70
|
+
onChange={(en) => {
|
|
71
|
+
setZeroScale(en);
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="mt-4">
|
|
55
76
|
<PrimaryButton
|
|
56
77
|
text={t('actions.confirm')}
|
|
57
|
-
className=
|
|
78
|
+
className="mr-2"
|
|
58
79
|
onClick={() => {
|
|
59
|
-
|
|
60
|
-
|
|
80
|
+
runInAction(() => {
|
|
81
|
+
vizStore.setVisualConfig('format', format);
|
|
82
|
+
vizStore.setVisualConfig('zeroScale', zeroScale);
|
|
83
|
+
commonStore.setShowVisualConfigPanel(false);
|
|
84
|
+
})
|
|
61
85
|
}}
|
|
62
86
|
/>
|
|
63
87
|
<DefaultButton
|
|
64
88
|
text={t('actions.cancel')}
|
|
65
|
-
className=
|
|
89
|
+
className="mr-2"
|
|
66
90
|
onClick={() => {
|
|
67
91
|
commonStore.setShowVisualConfigPanel(false);
|
|
68
92
|
}}
|
package/src/index.tsx
CHANGED
package/src/interfaces.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {Config as VgConfig} from 'vega';
|
|
2
|
+
import {Config as VlConfig} from 'vega-lite';
|
|
3
|
+
|
|
1
4
|
export type DeepReadonly<T extends Record<keyof any, any>> = {
|
|
2
5
|
readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
|
|
3
6
|
};
|
|
@@ -192,6 +195,7 @@ export interface IVisualConfig {
|
|
|
192
195
|
showActions: boolean;
|
|
193
196
|
interactiveScale: boolean;
|
|
194
197
|
sorted: 'none' | 'ascending' | 'descending';
|
|
198
|
+
zeroScale: boolean;
|
|
195
199
|
format: {
|
|
196
200
|
numberFormat?: string;
|
|
197
201
|
timeFormat?: string;
|
|
@@ -218,3 +222,5 @@ export enum ISegmentKey {
|
|
|
218
222
|
|
|
219
223
|
export type IThemeKey = 'vega' | 'g2';
|
|
220
224
|
export type IDarkMode = 'media' | 'light' | 'dark';
|
|
225
|
+
|
|
226
|
+
export type VegaGlobalConfig = VgConfig | VlConfig;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { unstable_batchedUpdates } from 'react-dom';
|
|
3
|
+
import type { DeepReadonly, IFilterField, IRow, IViewField } from '../interfaces';
|
|
4
|
+
import { applyFilter, applyViewQuery, transformDataService } from '../services';
|
|
5
|
+
import { getMeaAggKey } from '../utils';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface UseRendererProps {
|
|
9
|
+
data: IRow[];
|
|
10
|
+
allFields: Omit<IViewField, 'dragId'>[];
|
|
11
|
+
viewDimensions: IViewField[];
|
|
12
|
+
viewMeasures: IViewField[];
|
|
13
|
+
filters: readonly DeepReadonly<IFilterField>[];
|
|
14
|
+
defaultAggregated: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface UseRendererResult {
|
|
18
|
+
viewData: IRow[];
|
|
19
|
+
loading: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const useRenderer = (props: UseRendererProps): UseRendererResult => {
|
|
23
|
+
const { data, allFields, viewDimensions, viewMeasures, filters, defaultAggregated } = props;
|
|
24
|
+
const [computing, setComputing] = useState(false);
|
|
25
|
+
const taskIdRef = useRef(0);
|
|
26
|
+
|
|
27
|
+
const [viewData, setViewData] = useState<IRow[]>([]);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const taskId = ++taskIdRef.current;
|
|
31
|
+
setComputing(true);
|
|
32
|
+
applyFilter(data, filters)
|
|
33
|
+
.then((data) => transformDataService(data, allFields))
|
|
34
|
+
.then((d) => {
|
|
35
|
+
// setViewData(d);
|
|
36
|
+
const dims = viewDimensions;
|
|
37
|
+
const meas = viewMeasures;
|
|
38
|
+
return applyViewQuery(d, dims.concat(meas), {
|
|
39
|
+
op: defaultAggregated ? 'aggregate' : 'raw',
|
|
40
|
+
groupBy: dims.map((f) => f.fid),
|
|
41
|
+
measures: meas.map((f) => ({ field: f.fid, agg: f.aggName as any, asFieldKey: getMeaAggKey(f.fid, f.aggName!) })),
|
|
42
|
+
});
|
|
43
|
+
})
|
|
44
|
+
.then(data => {
|
|
45
|
+
if (taskId !== taskIdRef.current) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
unstable_batchedUpdates(() => {
|
|
49
|
+
setComputing(false);
|
|
50
|
+
setViewData(data);
|
|
51
|
+
});
|
|
52
|
+
}).catch((err) => {
|
|
53
|
+
if (taskId !== taskIdRef.current) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
console.error(err);
|
|
57
|
+
unstable_batchedUpdates(() => {
|
|
58
|
+
setComputing(false);
|
|
59
|
+
setViewData([]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
return () => {
|
|
63
|
+
taskIdRef.current++;
|
|
64
|
+
};
|
|
65
|
+
}, [data, filters, viewDimensions, viewMeasures, defaultAggregated]);
|
|
66
|
+
|
|
67
|
+
return useMemo(() => {
|
|
68
|
+
return {
|
|
69
|
+
viewData,
|
|
70
|
+
loading: computing,
|
|
71
|
+
};
|
|
72
|
+
}, [viewData, computing]);
|
|
73
|
+
};
|
package/src/renderer/index.tsx
CHANGED
|
@@ -1,72 +1,85 @@
|
|
|
1
1
|
import { observer } from 'mobx-react-lite';
|
|
2
|
-
import React, { useState, useEffect, forwardRef } from 'react';
|
|
3
|
-
import { applyFilter, applyViewQuery, transformDataService } from '../services';
|
|
2
|
+
import React, { useState, useEffect, forwardRef, useRef, useCallback } from 'react';
|
|
4
3
|
import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig } from '../interfaces';
|
|
5
4
|
import SpecRenderer from './specRenderer';
|
|
6
|
-
import { toJS } from 'mobx';
|
|
5
|
+
import { runInAction, toJS } from 'mobx';
|
|
7
6
|
import { useGlobalStore } from '../store';
|
|
8
7
|
import { IReactVegaHandler } from '../vis/react-vega';
|
|
9
8
|
import { unstable_batchedUpdates } from 'react-dom';
|
|
9
|
+
import { useRenderer } from './hooks';
|
|
10
10
|
import { initEncoding, initVisualConfig } from '../store/visualSpecStore';
|
|
11
|
-
import PivotTable from '../components/pivotTable';
|
|
12
|
-
import { getMeaAggKey } from '../utils';
|
|
13
11
|
|
|
14
12
|
interface RendererProps {
|
|
15
13
|
themeKey?: IThemeKey;
|
|
16
14
|
dark?: IDarkMode;
|
|
17
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Renderer of GraphicWalker editor.
|
|
18
|
+
* Depending on global store.
|
|
19
|
+
*/
|
|
18
20
|
const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, ref) {
|
|
19
21
|
const { themeKey, dark } = props;
|
|
20
|
-
const [waiting, setWaiting] = useState<boolean>(false);
|
|
21
22
|
const { vizStore, commonStore } = useGlobalStore();
|
|
22
|
-
const { allFields, viewFilters, viewDimensions, viewMeasures } = vizStore;
|
|
23
|
+
const { allFields, viewFilters, viewDimensions, viewMeasures, visualConfig, draggableFieldState } = vizStore;
|
|
23
24
|
const { currentDataset } = commonStore;
|
|
24
25
|
const { dataSource } = currentDataset;
|
|
26
|
+
|
|
25
27
|
const [viewConfig, setViewConfig] = useState<IVisualConfig>(initVisualConfig);
|
|
26
28
|
const [encodings, setEncodings] = useState<DeepReadonly<DraggableFieldState>>(initEncoding);
|
|
27
|
-
|
|
28
29
|
const [viewData, setViewData] = useState<IRow[]>([]);
|
|
30
|
+
|
|
31
|
+
const { viewData: data, loading: waiting } = useRenderer({
|
|
32
|
+
data: dataSource,
|
|
33
|
+
allFields,
|
|
34
|
+
viewDimensions,
|
|
35
|
+
viewMeasures,
|
|
36
|
+
filters: viewFilters,
|
|
37
|
+
defaultAggregated: visualConfig.defaultAggregated,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Dependencies that should not trigger effect individually
|
|
41
|
+
const latestFromRef = useRef({
|
|
42
|
+
data,
|
|
43
|
+
draggableFieldState: toJS(draggableFieldState),
|
|
44
|
+
visualConfig: toJS(visualConfig),
|
|
45
|
+
});
|
|
46
|
+
latestFromRef.current = {
|
|
47
|
+
data,
|
|
48
|
+
draggableFieldState: toJS(draggableFieldState),
|
|
49
|
+
visualConfig: toJS(visualConfig),
|
|
50
|
+
};
|
|
51
|
+
|
|
29
52
|
useEffect(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const dims = viewDimensions;
|
|
36
|
-
const meas = viewMeasures;
|
|
37
|
-
const config = toJS(vizStore.visualConfig);
|
|
38
|
-
return applyViewQuery(d, dims.concat(meas), {
|
|
39
|
-
op: config.defaultAggregated ? 'aggregate' : 'raw',
|
|
40
|
-
groupBy: dims.map((f) => f.fid),
|
|
41
|
-
measures: meas.map((f) => ({ field: f.fid, agg: f.aggName as any, asFieldKey: getMeaAggKey(f.fid, f.aggName!) })),
|
|
42
|
-
});
|
|
43
|
-
})
|
|
44
|
-
.then((data) => {
|
|
45
|
-
unstable_batchedUpdates(() => {
|
|
46
|
-
setViewData(data);
|
|
47
|
-
setWaiting(false);
|
|
48
|
-
setEncodings(toJS(vizStore.draggableFieldState));
|
|
49
|
-
setViewConfig(toJS(vizStore.visualConfig));
|
|
50
|
-
});
|
|
51
|
-
})
|
|
52
|
-
.catch((err) => {
|
|
53
|
-
console.error(err);
|
|
54
|
-
setWaiting(false);
|
|
53
|
+
if (waiting === false) {
|
|
54
|
+
unstable_batchedUpdates(() => {
|
|
55
|
+
setViewData(latestFromRef.current.data);
|
|
56
|
+
setEncodings(latestFromRef.current.draggableFieldState);
|
|
57
|
+
setViewConfig(latestFromRef.current.visualConfig);
|
|
55
58
|
});
|
|
56
|
-
|
|
59
|
+
}
|
|
60
|
+
}, [waiting, vizStore]);
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
const handleGeomClick = useCallback(
|
|
63
|
+
(values: any, e: any) => {
|
|
64
|
+
e.stopPropagation();
|
|
65
|
+
runInAction(() => {
|
|
66
|
+
commonStore.showEmbededMenu([e.pageX, e.pageY]);
|
|
67
|
+
commonStore.setFilters(values);
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
[]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const handleChartResize = useCallback(
|
|
74
|
+
(width: number, height: number) => {
|
|
75
|
+
vizStore.setChartLayout({
|
|
76
|
+
mode: 'fixed',
|
|
77
|
+
width,
|
|
78
|
+
height,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
[vizStore]
|
|
82
|
+
);
|
|
70
83
|
|
|
71
84
|
return (
|
|
72
85
|
<SpecRenderer
|
|
@@ -77,6 +90,8 @@ const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, r
|
|
|
77
90
|
dark={dark}
|
|
78
91
|
draggableFieldState={encodings}
|
|
79
92
|
visualConfig={viewConfig}
|
|
93
|
+
onGeomClick={handleGeomClick}
|
|
94
|
+
onChartResize={handleChartResize}
|
|
80
95
|
/>
|
|
81
96
|
);
|
|
82
97
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React, { useState, useEffect, forwardRef, useMemo, useRef } from 'react';
|
|
2
|
+
import { unstable_batchedUpdates } from 'react-dom';
|
|
3
|
+
import { toJS } from 'mobx';
|
|
4
|
+
import { observer } from 'mobx-react-lite';
|
|
5
|
+
import type { IDarkMode, IViewField, IRow, IThemeKey, DraggableFieldState, IVisualConfig } from '../interfaces';
|
|
6
|
+
import type { IReactVegaHandler } from '../vis/react-vega';
|
|
7
|
+
import SpecRenderer from './specRenderer';
|
|
8
|
+
import { useRenderer } from './hooks';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
interface IPureRendererProps {
|
|
12
|
+
themeKey?: IThemeKey;
|
|
13
|
+
dark?: IDarkMode;
|
|
14
|
+
rawData?: IRow[];
|
|
15
|
+
visualState: DraggableFieldState;
|
|
16
|
+
visualConfig: IVisualConfig;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render a readonly chart with given visualization schema.
|
|
21
|
+
* This is a pure component, which means it will not depend on any global state.
|
|
22
|
+
*/
|
|
23
|
+
const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function PureRenderer (props, ref) {
|
|
24
|
+
const {
|
|
25
|
+
themeKey,
|
|
26
|
+
dark,
|
|
27
|
+
rawData,
|
|
28
|
+
visualState,
|
|
29
|
+
visualConfig,
|
|
30
|
+
} = props;
|
|
31
|
+
const defaultAggregated = visualConfig?.defaultAggregated ?? false;
|
|
32
|
+
|
|
33
|
+
const [viewData, setViewData] = useState<IRow[]>([]);
|
|
34
|
+
|
|
35
|
+
const { allFields, viewDimensions, viewMeasures, filters } = useMemo(() => {
|
|
36
|
+
const viewDimensions: IViewField[] = [];
|
|
37
|
+
const viewMeasures: IViewField[] = [];
|
|
38
|
+
|
|
39
|
+
const { dimensions, measures, filters, ...state } = toJS(visualState);
|
|
40
|
+
const allFields = [...dimensions, ...measures];
|
|
41
|
+
|
|
42
|
+
const dKeys = Object.keys(state) as (keyof DraggableFieldState)[];
|
|
43
|
+
for (const dKey of dKeys) {
|
|
44
|
+
for (const f of state[dKey]) {
|
|
45
|
+
if (f.analyticType === 'dimension') {
|
|
46
|
+
viewDimensions.push(f);
|
|
47
|
+
} else if (f.analyticType === 'measure') {
|
|
48
|
+
viewMeasures.push(f);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { allFields, viewDimensions, viewMeasures, filters };
|
|
54
|
+
}, [visualState]);
|
|
55
|
+
|
|
56
|
+
const { viewData: data, loading: waiting } = useRenderer({
|
|
57
|
+
data: rawData ?? [],
|
|
58
|
+
allFields,
|
|
59
|
+
viewDimensions,
|
|
60
|
+
viewMeasures,
|
|
61
|
+
filters,
|
|
62
|
+
defaultAggregated,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Dependencies that should not trigger effect individually
|
|
66
|
+
const latestFromRef = useRef({ data });
|
|
67
|
+
latestFromRef.current = { data };
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (waiting === false) {
|
|
71
|
+
unstable_batchedUpdates(() => {
|
|
72
|
+
setViewData(latestFromRef.current.data);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}, [waiting]);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<SpecRenderer
|
|
79
|
+
loading={waiting}
|
|
80
|
+
data={viewData}
|
|
81
|
+
ref={ref}
|
|
82
|
+
themeKey={themeKey}
|
|
83
|
+
dark={dark}
|
|
84
|
+
draggableFieldState={visualState}
|
|
85
|
+
visualConfig={visualConfig}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export default observer(PureRenderer);
|