@kanaries/graphic-walker 0.2.4 → 0.2.5

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 (71) hide show
  1. package/package.json +3 -2
  2. package/src/App.tsx +140 -0
  3. package/src/assets/kanaries.ico +0 -0
  4. package/src/components/clickMenu.tsx +29 -0
  5. package/src/components/container.tsx +16 -0
  6. package/src/components/dataTypeIcon.tsx +20 -0
  7. package/src/components/liteForm.tsx +16 -0
  8. package/src/components/modal.tsx +85 -0
  9. package/src/components/sizeSetting.tsx +95 -0
  10. package/src/components/tabs/pureTab.tsx +70 -0
  11. package/src/config.ts +57 -0
  12. package/src/constants.ts +1 -0
  13. package/src/dataSource/config.ts +62 -0
  14. package/src/dataSource/dataSelection/csvData.tsx +77 -0
  15. package/src/dataSource/dataSelection/gwFile.tsx +38 -0
  16. package/src/dataSource/dataSelection/index.tsx +57 -0
  17. package/src/dataSource/dataSelection/publicData.tsx +57 -0
  18. package/src/dataSource/index.tsx +78 -0
  19. package/src/dataSource/pannel.tsx +71 -0
  20. package/src/dataSource/table.tsx +125 -0
  21. package/src/dataSource/utils.ts +47 -0
  22. package/src/fields/aestheticFields.tsx +23 -0
  23. package/src/fields/components.tsx +159 -0
  24. package/src/fields/datasetFields/dimFields.tsx +45 -0
  25. package/src/fields/datasetFields/fieldPill.tsx +10 -0
  26. package/src/fields/datasetFields/index.tsx +28 -0
  27. package/src/fields/datasetFields/meaFields.tsx +58 -0
  28. package/src/fields/fieldsContext.tsx +59 -0
  29. package/src/fields/filterField/filterEditDialog.tsx +143 -0
  30. package/src/fields/filterField/filterPill.tsx +113 -0
  31. package/src/fields/filterField/index.tsx +61 -0
  32. package/src/fields/filterField/slider.tsx +236 -0
  33. package/src/fields/filterField/tabs.tsx +421 -0
  34. package/src/fields/obComponents/obFContainer.tsx +40 -0
  35. package/src/fields/obComponents/obPill.tsx +48 -0
  36. package/src/fields/posFields/index.tsx +33 -0
  37. package/src/fields/select.tsx +31 -0
  38. package/src/fields/utils.ts +31 -0
  39. package/src/index.css +13 -0
  40. package/src/index.tsx +12 -0
  41. package/src/insightBoard/index.tsx +30 -0
  42. package/src/insightBoard/mainBoard.tsx +203 -0
  43. package/src/insightBoard/radioGroupButtons.tsx +50 -0
  44. package/src/insightBoard/selectionSpec.ts +113 -0
  45. package/src/insightBoard/std2vegaSpec.ts +184 -0
  46. package/src/insightBoard/utils.ts +32 -0
  47. package/src/insights.ts +408 -0
  48. package/src/interfaces.ts +154 -0
  49. package/src/locales/en-US.json +140 -0
  50. package/src/locales/i18n.ts +50 -0
  51. package/src/locales/zh-CN.json +140 -0
  52. package/src/main.tsx +10 -0
  53. package/src/models/visSpecHistory.ts +129 -0
  54. package/src/renderer/index.tsx +104 -0
  55. package/src/segments/visNav.tsx +48 -0
  56. package/src/services.ts +139 -0
  57. package/src/store/commonStore.ts +158 -0
  58. package/src/store/index.tsx +53 -0
  59. package/src/store/visualSpecStore.ts +586 -0
  60. package/src/utils/autoMark.ts +34 -0
  61. package/src/utils/index.ts +251 -0
  62. package/src/utils/normalization.ts +158 -0
  63. package/src/utils/save.ts +46 -0
  64. package/src/vis/future-react-vega.tsx +193 -0
  65. package/src/vis/gen-vega.tsx +52 -0
  66. package/src/vis/react-vega.tsx +398 -0
  67. package/src/visualSettings/index.tsx +252 -0
  68. package/src/visualSettings/menubar.tsx +109 -0
  69. package/src/vite-env.d.ts +1 -0
  70. package/src/workers/explainer.worker.js +78 -0
  71. package/src/workers/filter.worker.js +70 -0
@@ -0,0 +1,104 @@
1
+ import { runInAction, toJS } from 'mobx';
2
+ import { observer } from 'mobx-react-lite';
3
+ import { Resizable } from 're-resizable';
4
+ import React, { useState, useCallback, useEffect, useRef } from 'react';
5
+ import { applyFilter } from '../services';
6
+ import { useGlobalStore } from '../store';
7
+ import ReactVega from '../vis/react-vega';
8
+
9
+
10
+ const ReactiveRenderer: React.FC = props => {
11
+ const { vizStore, commonStore } = useGlobalStore();
12
+ const { draggableFieldState, visualConfig } = vizStore;
13
+ const { geoms, interactiveScale, defaultAggregated, stack, showActions, size } = visualConfig;
14
+ const { currentDataset } = commonStore;
15
+ const { filters } = draggableFieldState;
16
+
17
+ const rows = toJS(draggableFieldState.rows)
18
+ const columns = toJS(draggableFieldState.columns)
19
+ const color = toJS(draggableFieldState.color)
20
+ const opacity = toJS(draggableFieldState.opacity)
21
+ const shape = toJS(draggableFieldState.shape)
22
+ const theta = toJS(draggableFieldState.theta)
23
+ const radius = toJS(draggableFieldState.radius)
24
+ const sizeChannel = toJS(draggableFieldState.size)
25
+
26
+ const rowLeftFacetFields = rows.slice(0, -1).filter(f => f.analyticType === 'dimension');
27
+ const colLeftFacetFields = columns.slice(0, -1).filter(f => f.analyticType === 'dimension');
28
+
29
+ const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
30
+
31
+ const onGeomClick = useCallback((values: any, e: any) => {
32
+ runInAction(() => {
33
+ commonStore.showEmbededMenu([e.pageX, e.pageY])
34
+ commonStore.setFilters(values);
35
+ })
36
+ }, [])
37
+
38
+ // apply filters
39
+ const { dataSource } = currentDataset;
40
+
41
+ const [data, setData] = useState(dataSource);
42
+ const pendingPromiseRef = useRef<Promise<typeof data> | null>(null);
43
+
44
+ useEffect(() => {
45
+ setData(dataSource);
46
+ }, [dataSource]);
47
+
48
+ useEffect(() => {
49
+ const p = filters.length === 0 ? Promise.resolve(dataSource) : applyFilter(dataSource, filters);
50
+ pendingPromiseRef.current = p;
51
+
52
+ p.then(d => {
53
+ if (p !== pendingPromiseRef.current) {
54
+ // This promise is out-of-date
55
+ return;
56
+ }
57
+
58
+ setData(d);
59
+ }).catch(err => {
60
+ console.error(err);
61
+ });
62
+
63
+ return () => {
64
+ pendingPromiseRef.current = null;
65
+ };
66
+ }, [dataSource, filters]);
67
+
68
+ return <Resizable className={(size.mode === 'fixed' && !hasFacet) ? "border-blue-400 border-2 overflow-hidden" : ""}
69
+ style={{ padding: '12px' }}
70
+ onResizeStop={(e, direction, ref, d) => {
71
+ vizStore.setChartLayout({
72
+ mode: 'fixed',
73
+ width: size.width + d.width,
74
+ height: size.height + d.height
75
+ })
76
+ }}
77
+ size={{
78
+ width: size.width + 'px',
79
+ height: size.height + 'px',
80
+ }}>
81
+ <ReactVega
82
+ layoutMode={size.mode}
83
+ interactiveScale={interactiveScale}
84
+ geomType={geoms[0]}
85
+ defaultAggregate={defaultAggregated}
86
+ stack={stack}
87
+ dataSource={data}
88
+ rows={rows}
89
+ columns={columns}
90
+ color={color[0]}
91
+ theta={theta[0]}
92
+ radius={radius[0]}
93
+ shape={shape[0]}
94
+ opacity={opacity[0]}
95
+ size={sizeChannel[0]}
96
+ onGeomClick={onGeomClick}
97
+ showActions={showActions}
98
+ width={size.width - 12 * 4}
99
+ height={size.height - 12 * 4}
100
+ />
101
+ </Resizable>
102
+ }
103
+
104
+ export default observer(ReactiveRenderer);
@@ -0,0 +1,48 @@
1
+ import React, { useCallback } from "react";
2
+ import { observer } from "mobx-react-lite";
3
+ import PureTabs, { ITabOption } from "../components/tabs/pureTab";
4
+ import { useGlobalStore } from "../store";
5
+
6
+
7
+ const ADD_KEY = '_add';
8
+
9
+ const VisNav: React.FC = (props) => {
10
+ const { vizStore, commonStore } = useGlobalStore();
11
+ const { visIndex, visList } = vizStore;
12
+ const { currentDataset } = commonStore;
13
+
14
+ const tabs: ITabOption[] = visList.map((v) => ({
15
+ key: v.visId,
16
+ label: v.name?.[0] || 'vis',
17
+ options: v.name?.[1],
18
+ }));
19
+
20
+ tabs.push({
21
+ key: ADD_KEY,
22
+ label: 'main.tablist.new'
23
+ });
24
+
25
+ const visSelectionHandler = useCallback((tabKey: string, tabIndex: number) => {
26
+ if (tabKey === ADD_KEY) {
27
+ vizStore.addVisualization();
28
+ vizStore.initMetaState(currentDataset)
29
+ } else {
30
+ vizStore.selectVisualization(tabIndex);
31
+ }
32
+ }, [currentDataset, vizStore])
33
+
34
+ const editLabelHandler = useCallback((content: string, tabIndex: number) => {
35
+ vizStore.setVisName(tabIndex, content)
36
+ }, [])
37
+
38
+ return (
39
+ <PureTabs
40
+ selectedKey={visList[visIndex].visId}
41
+ tabs={tabs}
42
+ onEditLabel={editLabelHandler}
43
+ onSelected={visSelectionHandler}
44
+ />
45
+ );
46
+ };
47
+
48
+ export default observer(VisNav);
@@ -0,0 +1,139 @@
1
+ import { toJS } from 'mobx';
2
+ import { View, Specification } from 'visual-insights';
3
+ import { IRow, Filters, SemanticType, IMeasure, IMutField, IFilterField } from './interfaces';
4
+ /* eslint import/no-webpack-loader-syntax:0 */
5
+ // @ts-ignore
6
+ // eslint-disable-next-line
7
+ // import InsightSpaceWorker from './workers/InsightService.worker?worker';
8
+ /* eslint import/no-webpack-loader-syntax:0 */
9
+ // @ts-ignore
10
+ // eslint-disable-next-line
11
+ import ExplainerWorker from './workers/explainer.worker?worker&inline';
12
+ import FilterWorker from './workers/filter.worker?worker&inline';
13
+ import { IExplaination, IMeasureWithStat } from './insights';
14
+
15
+ interface WorkerState {
16
+ eWorker: Worker | null;
17
+ }
18
+
19
+ const workerState: WorkerState = {
20
+ eWorker: null,
21
+ }
22
+
23
+ function workerService<T, R>(worker: Worker, data: R): Promise<T> {
24
+ return new Promise<T>((resolve, reject) => {
25
+ worker.postMessage(data);
26
+ worker.onmessage = (e: MessageEvent) => {
27
+ resolve(e.data);
28
+ };
29
+ worker.onerror = (e: ErrorEvent) => {
30
+ reject({
31
+ success: false,
32
+ message: e,
33
+ });
34
+ };
35
+ });
36
+ }
37
+
38
+ interface ExplainParams {
39
+ dimensions: string[];
40
+ measures: string[];
41
+ dataSource: IRow[];
42
+ filters?: Filters;
43
+ currentSpace: {
44
+ dimensions: string[];
45
+ measures: IMeasure[];
46
+ };
47
+ }
48
+ export interface IVisSpace {
49
+ dataView: IRow[];
50
+ schema: Specification;
51
+ }
52
+ interface ExplainReturns {
53
+ explainations: IExplaination[];
54
+ valueExp: IMeasureWithStat[];
55
+ visSpaces: IVisSpace[];
56
+ fieldsWithSemanticType: Array<{ key: string; type: SemanticType }>;
57
+ }
58
+ export async function getExplaination(props: ExplainParams) {
59
+ const worker = workerState.eWorker;
60
+ if (worker === null) throw new Error('init worker first.')
61
+ let result: ExplainReturns = {
62
+ explainations: [],
63
+ valueExp: [],
64
+ visSpaces: [],
65
+ fieldsWithSemanticType: [],
66
+ };
67
+ try {
68
+ result = await workerService<ExplainReturns, { type: string; data: ExplainParams }>(
69
+ worker,
70
+ {
71
+ type: 'getExplaination',
72
+ data: props
73
+ }
74
+ );
75
+ return result;
76
+ } catch (error) {
77
+ console.error(error);
78
+ }
79
+ return result;
80
+ }
81
+
82
+ interface PreAnalysisParams {
83
+ fields: IMutField[];
84
+ dataSource: IRow[];
85
+ }
86
+ export async function preAnalysis(props: PreAnalysisParams) {
87
+ if (workerState.eWorker !== null) {
88
+ workerState.eWorker.terminate();
89
+ }
90
+ try {
91
+ workerState.eWorker = new ExplainerWorker() as Worker;
92
+ const tmp = await workerService<boolean, { type: string; data: PreAnalysisParams}>(workerState.eWorker, { type: 'preAnalysis', data: props });
93
+ } catch (error) {
94
+ console.error(error)
95
+ }
96
+ }
97
+
98
+ export function destroyWorker() {
99
+ if (workerState.eWorker) {
100
+ workerState.eWorker.terminate();
101
+ workerState.eWorker = null;
102
+ }
103
+ }
104
+
105
+ let filterWorker: Worker | null = null;
106
+ let filterWorkerAutoTerminator: NodeJS.Timeout | null = null;
107
+
108
+ export const applyFilter = async (data: readonly IRow[], filters: readonly IFilterField[]): Promise<IRow[]> => {
109
+ if (filterWorkerAutoTerminator !== null) {
110
+ clearTimeout(filterWorkerAutoTerminator);
111
+ filterWorkerAutoTerminator = null;
112
+ }
113
+
114
+ if (filterWorker === null) {
115
+ filterWorker = new FilterWorker();
116
+ }
117
+
118
+ try {
119
+ const res: IRow[] = await workerService(filterWorker, {
120
+ dataSource: data,
121
+ filters: toJS(filters),
122
+ });
123
+
124
+ return res;
125
+ } catch (error) {
126
+ // @ts-ignore @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
127
+ throw new Error('Uncaught error in FilterWorker', { cause: error });
128
+ } finally {
129
+ if (filterWorkerAutoTerminator !== null) {
130
+ clearTimeout(filterWorkerAutoTerminator);
131
+ }
132
+
133
+ filterWorkerAutoTerminator = setTimeout(() => {
134
+ filterWorker?.terminate();
135
+ filterWorker = null;
136
+ filterWorkerAutoTerminator = null;
137
+ }, 60_000); // Destroy the worker when no request is received for 60 secs
138
+ }
139
+ };
@@ -0,0 +1,158 @@
1
+ import { DataSet, Filters, IDataSet, IDataSetInfo, IDataSource, IMutField, IRow } from '../interfaces';
2
+ import { makeAutoObservable, observable, toJS } from 'mobx';
3
+ import { transData } from '../dataSource/utils';
4
+ import { extendCountField } from '../utils';
5
+
6
+ export class CommonStore {
7
+ public datasets: IDataSet[] = [];
8
+ public dataSources: IDataSource[] = [];
9
+ public dsIndex: number = 0;
10
+ public tmpDSName: string = '';
11
+ public tmpDSRawFields: IMutField[] = [];
12
+ public tmpDataSource: IRow[] = [];
13
+ public showDSPanel: boolean = false;
14
+ public showInsightBoard: boolean = false;
15
+ public vizEmbededMenu: { show: boolean; position: [number, number] } = { show: false, position: [0, 0] };
16
+
17
+ public filters: Filters = {};
18
+ constructor () {
19
+ this.datasets = [];
20
+ this.dataSources = [];
21
+ makeAutoObservable(this, {
22
+ dataSources: observable.ref,
23
+ tmpDataSource: observable.ref,
24
+ filters: observable.ref
25
+ });
26
+ }
27
+ public get currentDataset (): DataSet {
28
+ const datasetIndex = this.dsIndex;
29
+ if (this.datasets.length > 0) {
30
+ const dataSourceId = this.datasets[datasetIndex].dsId;
31
+ const dataSource = this.dataSources.find(d => d.id === dataSourceId);
32
+ const rawFields = toJS(this.datasets[datasetIndex].rawFields)
33
+ const base = extendCountField((dataSource ? dataSource.data : []), rawFields)
34
+ return {
35
+ ...this.datasets[datasetIndex],
36
+ dataSource: base.dataSource,
37
+ rawFields: base.fields
38
+ }
39
+ }
40
+ return {
41
+ id: '__null_ds__',
42
+ name: 'Empty Dataset',
43
+ rawFields: [],
44
+ dataSource: []
45
+ }
46
+ }
47
+ public setShowDSPanel (show: boolean) {
48
+ this.showDSPanel = show;
49
+ }
50
+ public setShowInsightBoard (show: boolean) {
51
+ this.showInsightBoard = show;
52
+ }
53
+ public showEmbededMenu (position: [number, number]) {
54
+ this.vizEmbededMenu.show = true;
55
+ this.vizEmbededMenu.position = position;
56
+ }
57
+ public closeEmbededMenu () {
58
+ this.vizEmbededMenu.show = false;
59
+ }
60
+ public initTempDS () {
61
+ this.tmpDSName = 'New Dataset'
62
+ this.tmpDSRawFields = [];
63
+ this.tmpDataSource = [];
64
+ }
65
+ public updateTempFields (fields: IMutField[]) {
66
+ this.tmpDSRawFields = fields;
67
+ }
68
+
69
+ public updateTempFieldAnalyticType (fieldKey: string, analyticType: IMutField['analyticType']) {
70
+ const field = this.tmpDSRawFields.find(f => f.fid === fieldKey);
71
+ if (field) {
72
+ field.analyticType = analyticType;
73
+ }
74
+ }
75
+
76
+ public updateTempName (name: string) {
77
+ this.tmpDSName = name;
78
+ }
79
+
80
+ public updateTempDS (rawData: IRow[]) {
81
+ const result = transData(rawData);
82
+ // TODO: need fix web-data-loader issue #2
83
+ this.tmpDataSource = result.dataSource.slice(0, -1);
84
+ this.tmpDSRawFields = result.fields;
85
+ }
86
+
87
+ public updateTempSTDDS (dataset: IDataSetInfo) {
88
+ this.tmpDataSource = dataset.dataSource;
89
+ this.tmpDSRawFields = dataset.rawFields;
90
+ this.tmpDSName = dataset.name;
91
+ }
92
+
93
+ public commitTempDS () {
94
+ const { tmpDSName, tmpDSRawFields, tmpDataSource } = this;
95
+ this.addAndUseDS({
96
+ dataSource: tmpDataSource,
97
+ rawFields: tmpDSRawFields,
98
+ name: tmpDSName
99
+ })
100
+ this.setShowDSPanel(false);
101
+ this.initTempDS();
102
+ }
103
+
104
+ public startDSBuildingTask () {
105
+ this.initTempDS();
106
+ this.showDSPanel = true;
107
+ }
108
+ public addAndUseDS(dataset: IDataSetInfo) {
109
+ const datasetId = this.addDS(dataset);
110
+ this.dsIndex = this.datasets.length - 1;
111
+ return datasetId
112
+ }
113
+ public addDS(dataset: IDataSetInfo) {
114
+ const timestamp = new Date().getTime();
115
+ const dataSetId = `dst-${timestamp}`
116
+ const dataSourceId = `dse-${timestamp}`;
117
+ this.dataSources.push({
118
+ id: dataSourceId,
119
+ data: dataset.dataSource
120
+ })
121
+ this.datasets.push({
122
+ id: dataSetId,
123
+ name: dataset.name,
124
+ rawFields: dataset.rawFields,
125
+ dsId: dataSourceId
126
+ })
127
+ return dataSetId;
128
+ }
129
+ public removeDS(datasetId: string) {
130
+ const datasetIndex = this.datasets.findIndex(d => d.id === datasetId);
131
+ if (datasetIndex > -1) {
132
+ const dataSourceId = this.datasets[datasetIndex].dsId;
133
+ const dataSourceIndex = this.dataSources.findIndex(d => d.id === dataSourceId);
134
+ this.dataSources.splice(dataSourceIndex, 1);
135
+ this.datasets.splice(datasetIndex, 1);
136
+ }
137
+ }
138
+ public useDS(datasetId: string) {
139
+ const datasetIndex = this.datasets.findIndex(d => d.id === datasetId);
140
+ if (datasetIndex > -1) {
141
+ this.dsIndex = datasetIndex;
142
+ }
143
+ }
144
+ public createPlaceholderDS() {
145
+ this.addDS({
146
+ name: '新数据源',
147
+ dataSource: [],
148
+ rawFields: []
149
+ })
150
+ }
151
+ public setFilters (props: Filters) {
152
+ this.filters = props;
153
+ }
154
+ public destroy () {
155
+ this.dataSources = [];
156
+ this.datasets = [];
157
+ }
158
+ }
@@ -0,0 +1,53 @@
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { CommonStore } from './commonStore'
3
+ import { VizSpecStore } from './visualSpecStore'
4
+
5
+ interface GlobalStore {
6
+ commonStore: CommonStore;
7
+ vizStore: VizSpecStore;
8
+ }
9
+
10
+ const commonStore = new CommonStore();
11
+ const vizStore = new VizSpecStore(commonStore);
12
+
13
+ const initStore: GlobalStore = {
14
+ commonStore,
15
+ vizStore
16
+ }
17
+
18
+ const StoreContext = React.createContext<GlobalStore>(null!);
19
+
20
+ export function destroyGWStore() {
21
+ initStore.commonStore.destroy();
22
+ initStore.vizStore.destroy();
23
+ }
24
+
25
+ export function rebootGWStore() {
26
+ const cs = new CommonStore();
27
+ const vs = new VizSpecStore(cs);
28
+ initStore.commonStore = cs;
29
+ initStore.vizStore = vs;
30
+ }
31
+
32
+ export class StoreWrapper extends React.Component<{ keepAlive?: boolean }> {
33
+ constructor(props: { keepAlive?: boolean }) {
34
+ super(props)
35
+ if (props.keepAlive) {
36
+ rebootGWStore();
37
+ }
38
+ }
39
+ componentWillUnmount() {
40
+ if (!this.props.keepAlive) {
41
+ destroyGWStore();
42
+ }
43
+ }
44
+ render() {
45
+ return <StoreContext.Provider value={initStore}>
46
+ { this.props.children }
47
+ </StoreContext.Provider>
48
+ }
49
+ }
50
+
51
+ export function useGlobalStore() {
52
+ return useContext(StoreContext);
53
+ }