@kanaries/graphic-walker 0.2.15 → 0.2.16

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 (97) hide show
  1. package/dist/App.d.ts +2 -0
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
  4. package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
  5. package/dist/components/codeExport/index.d.ts +3 -0
  6. package/dist/components/loadingLayer.d.ts +2 -0
  7. package/dist/components/tabs/defaultTab.d.ts +1 -0
  8. package/dist/components/tabs/editableTab.d.ts +1 -2
  9. package/dist/dataSource/dataSelection/config.d.ts +1 -0
  10. package/dist/dataSource/dataSelection/utils.d.ts +2 -0
  11. package/dist/datasets/tmp/test.json +1 -0
  12. package/dist/graphic-walker.es.js +23081 -22577
  13. package/dist/graphic-walker.es.js.map +1 -1
  14. package/dist/graphic-walker.umd.js +130 -130
  15. package/dist/graphic-walker.umd.js.map +1 -1
  16. package/dist/index.d.ts +3 -3
  17. package/dist/interfaces.d.ts +21 -1
  18. package/dist/lib/execExp.d.ts +8 -0
  19. package/dist/lib/interfaces.d.ts +22 -0
  20. package/dist/lib/op/aggregate.d.ts +3 -0
  21. package/dist/lib/op/bin.d.ts +3 -0
  22. package/dist/lib/op/fold.d.ts +3 -0
  23. package/dist/lib/op/stat.d.ts +8 -0
  24. package/dist/lib/viewQuery.d.ts +5 -0
  25. package/dist/models/visSpecHistory.d.ts +2 -0
  26. package/dist/renderer/index.d.ts +8 -7
  27. package/dist/renderer/specRenderer.d.ts +13 -0
  28. package/dist/services.d.ts +4 -1
  29. package/dist/store/commonStore.d.ts +6 -0
  30. package/dist/store/index.d.ts +3 -2
  31. package/dist/store/visualSpecStore.d.ts +11 -4
  32. package/dist/utils/dataPrep.d.ts +2 -0
  33. package/dist/utils/index.d.ts +3 -5
  34. package/dist/utils/save.d.ts +1 -2
  35. package/dist/vis/react-vega.d.ts +1 -22
  36. package/dist/vis/spec/aggregate.d.ts +4 -0
  37. package/dist/vis/spec/encode.d.ts +19 -0
  38. package/dist/vis/spec/field.d.ts +2 -0
  39. package/dist/vis/spec/mark.d.ts +7 -0
  40. package/dist/vis/spec/stack.d.ts +4 -0
  41. package/dist/vis/spec/view.d.ts +67 -0
  42. package/dist/workers/transform.d.ts +2 -0
  43. package/package.json +4 -3
  44. package/src/App.tsx +5 -2
  45. package/src/components/codeExport/index.tsx +114 -0
  46. package/src/components/dataTable/index.tsx +10 -10
  47. package/src/components/loadingLayer.tsx +7 -0
  48. package/src/components/tabs/defaultTab.tsx +4 -2
  49. package/src/components/tabs/editableTab.tsx +74 -39
  50. package/src/dataSource/dataSelection/config.ts +11 -0
  51. package/src/dataSource/dataSelection/csvData.tsx +71 -39
  52. package/src/dataSource/dataSelection/gwFile.tsx +2 -2
  53. package/src/dataSource/dataSelection/utils.ts +28 -0
  54. package/src/dataSource/utils.ts +8 -3
  55. package/src/fields/datasetFields/meaFields.tsx +12 -4
  56. package/src/index.css +4 -4
  57. package/src/index.tsx +22 -22
  58. package/src/interfaces.ts +26 -2
  59. package/src/lib/execExp.ts +147 -0
  60. package/src/lib/interfaces.ts +39 -0
  61. package/src/lib/op/aggregate.ts +49 -0
  62. package/src/lib/op/bin.ts +25 -0
  63. package/src/lib/op/fold.ts +17 -0
  64. package/src/lib/op/stat.ts +46 -0
  65. package/src/lib/viewQuery.ts +23 -0
  66. package/src/locales/en-US.json +4 -2
  67. package/src/locales/i18n.ts +0 -1
  68. package/src/locales/ja-JP.json +4 -2
  69. package/src/locales/zh-CN.json +4 -2
  70. package/src/main.tsx +1 -1
  71. package/src/models/visSpecHistory.ts +14 -0
  72. package/src/renderer/index.tsx +58 -126
  73. package/src/renderer/specRenderer.tsx +119 -0
  74. package/src/segments/segmentNav.tsx +3 -16
  75. package/src/segments/visNav.tsx +17 -6
  76. package/src/services.ts +37 -1
  77. package/src/store/commonStore.ts +14 -9
  78. package/src/store/index.tsx +11 -4
  79. package/src/store/visualSpecStore.ts +89 -50
  80. package/src/utils/dataPrep.ts +24 -0
  81. package/src/utils/index.ts +16 -17
  82. package/src/utils/normalization.ts +3 -1
  83. package/src/utils/save.ts +1 -2
  84. package/src/vis/react-vega.tsx +4 -340
  85. package/src/vis/spec/aggregate.ts +13 -0
  86. package/src/vis/spec/encode.ts +69 -0
  87. package/src/vis/spec/field.ts +10 -0
  88. package/src/vis/spec/mark.ts +30 -0
  89. package/src/vis/spec/stack.ts +11 -0
  90. package/src/vis/spec/view.ts +138 -0
  91. package/src/vis/theme.ts +12 -0
  92. package/src/visualSettings/index.tsx +10 -1
  93. package/src/workers/transform.ts +12 -0
  94. package/src/workers/transform.worker.js +13 -0
  95. package/src/workers/viewQuery.worker.js +16 -0
  96. package/dist/dataSource/pannel.d.ts +0 -5
  97. package/src/dataSource/pannel.tsx +0 -71
@@ -0,0 +1,119 @@
1
+ import { runInAction } from 'mobx';
2
+ import { Resizable } from 're-resizable';
3
+ import React, { useCallback, forwardRef, useMemo } from 'react';
4
+
5
+ import { useGlobalStore } from '../store';
6
+ import ReactVega, { IReactVegaHandler } from '../vis/react-vega';
7
+ import { DeepReadonly, DraggableFieldState, IDarkMode, IRow, IThemeKey, IVisualConfig } from '../interfaces';
8
+ import LoadingLayer from '../components/loadingLayer';
9
+
10
+ interface SpecRendererProps {
11
+ themeKey?: IThemeKey;
12
+ dark?: IDarkMode;
13
+ data: IRow[];
14
+ loading: boolean;
15
+ draggableFieldState: DeepReadonly<DraggableFieldState>;
16
+ visualConfig: IVisualConfig;
17
+ }
18
+ const SpecRenderer = forwardRef<IReactVegaHandler, SpecRendererProps>(function (
19
+ { themeKey, dark, data, loading, draggableFieldState, visualConfig },
20
+ ref
21
+ ) {
22
+ const { vizStore, commonStore } = useGlobalStore();
23
+ // const { draggableFieldState, visualConfig } = vizStore;
24
+ const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, exploration } = visualConfig;
25
+
26
+ const rows = draggableFieldState.rows;
27
+ const columns = draggableFieldState.columns;
28
+ const color = draggableFieldState.color;
29
+ const opacity = draggableFieldState.opacity;
30
+ const shape = draggableFieldState.shape;
31
+ const theta = draggableFieldState.theta;
32
+ const radius = draggableFieldState.radius;
33
+ const sizeChannel = draggableFieldState.size;
34
+
35
+ const rowLeftFacetFields = useMemo(() => rows.slice(0, -1).filter((f) => f.analyticType === 'dimension'), [rows]);
36
+ const colLeftFacetFields = useMemo(
37
+ () => columns.slice(0, -1).filter((f) => f.analyticType === 'dimension'),
38
+ [columns]
39
+ );
40
+
41
+ const hasFacet = rowLeftFacetFields.length > 0 || colLeftFacetFields.length > 0;
42
+
43
+ const shouldTriggerMenu = exploration.mode === 'none';
44
+
45
+ const handleGeomClick = useCallback(
46
+ (values: any, e: any) => {
47
+ if (shouldTriggerMenu) {
48
+ e.stopPropagation();
49
+ runInAction(() => {
50
+ commonStore.showEmbededMenu([e.pageX, e.pageY]);
51
+ commonStore.setFilters(values);
52
+ });
53
+ }
54
+ },
55
+ [shouldTriggerMenu]
56
+ );
57
+ const enableResize = size.mode === 'fixed' && !hasFacet;
58
+
59
+ return (
60
+ <Resizable
61
+ className={enableResize ? 'border-blue-400 border-2 overflow-hidden' : ''}
62
+ style={{ padding: '12px' }}
63
+ onResizeStop={(e, direction, ref, d) => {
64
+ vizStore.setChartLayout({
65
+ mode: 'fixed',
66
+ width: size.width + d.width,
67
+ height: size.height + d.height,
68
+ });
69
+ }}
70
+ enable={
71
+ enableResize
72
+ ? undefined
73
+ : {
74
+ top: false,
75
+ right: false,
76
+ bottom: false,
77
+ left: false,
78
+ topRight: false,
79
+ bottomRight: false,
80
+ bottomLeft: false,
81
+ topLeft: false,
82
+ }
83
+ }
84
+ size={{
85
+ width: size.width + 'px',
86
+ height: size.height + 'px',
87
+ }}
88
+ >
89
+ {loading && <LoadingLayer />}
90
+ <ReactVega
91
+ layoutMode={size.mode}
92
+ interactiveScale={interactiveScale}
93
+ geomType={geoms[0]}
94
+ defaultAggregate={defaultAggregated}
95
+ stack={stack}
96
+ dataSource={data}
97
+ rows={rows}
98
+ columns={columns}
99
+ color={color[0]}
100
+ theta={theta[0]}
101
+ radius={radius[0]}
102
+ shape={shape[0]}
103
+ opacity={opacity[0]}
104
+ size={sizeChannel[0]}
105
+ showActions={showActions}
106
+ width={size.width - 12 * 4}
107
+ height={size.height - 12 * 4}
108
+ ref={ref}
109
+ brushEncoding={exploration.mode === 'brush' ? exploration.brushDirection : 'none'}
110
+ selectEncoding={exploration.mode === 'point' ? 'default' : 'none'}
111
+ onGeomClick={handleGeomClick}
112
+ themeKey={themeKey}
113
+ dark={dark}
114
+ />
115
+ </Resizable>
116
+ );
117
+ });
118
+
119
+ export default SpecRenderer;
@@ -1,18 +1,14 @@
1
- import React, { Fragment, useCallback } from "react";
1
+ import React, { useCallback } from "react";
2
2
  import { observer } from "mobx-react-lite";
3
3
  import DefaultTab, { ITabOption } from "../components/tabs/defaultTab";
4
4
  import { useGlobalStore } from "../store";
5
- import { ChartBarIcon, ChartPieIcon, CircleStackIcon } from "@heroicons/react/24/outline";
5
+ import { ChartPieIcon, CircleStackIcon } from "@heroicons/react/24/outline";
6
6
  import { ISegmentKey } from "../interfaces";
7
7
  import { useTranslation } from "react-i18next";
8
8
 
9
-
10
- const ADD_KEY = '_add';
11
-
12
9
  const SegmentNav: React.FC = (props) => {
13
10
  const { vizStore, commonStore } = useGlobalStore();
14
- const { visIndex, visList } = vizStore;
15
- const { currentDataset, segmentKey } = commonStore;
11
+ const { segmentKey } = commonStore;
16
12
  const { t } = useTranslation();
17
13
 
18
14
  const tabs: ITabOption[] = [
@@ -30,15 +26,6 @@ const SegmentNav: React.FC = (props) => {
30
26
  }
31
27
  ]
32
28
 
33
- const visSelectionHandler = useCallback((tabKey: string, tabIndex: number) => {
34
- if (tabKey === ADD_KEY) {
35
- vizStore.addVisualization();
36
- vizStore.initMetaState(currentDataset)
37
- } else {
38
- vizStore.selectVisualization(tabIndex);
39
- }
40
- }, [currentDataset, vizStore])
41
-
42
29
  const editLabelHandler = useCallback((content: string, tabIndex: number) => {
43
30
  vizStore.setVisName(tabIndex, content)
44
31
  }, [])
@@ -1,5 +1,6 @@
1
- import React, { useCallback } from "react";
1
+ import React, { useCallback, useEffect } from "react";
2
2
  import { observer } from "mobx-react-lite";
3
+ import { useTranslation } from "react-i18next";
3
4
  import EditableTabs, { ITabOption } from "../components/tabs/editableTab";
4
5
  import { useGlobalStore } from "../store";
5
6
 
@@ -11,25 +12,35 @@ const VisNav: React.FC = (props) => {
11
12
  const { visIndex, visList } = vizStore;
12
13
  const { currentDataset } = commonStore;
13
14
 
15
+ const { t } = useTranslation();
16
+
14
17
  const tabs: ITabOption[] = visList.map((v) => ({
15
18
  key: v.visId,
16
- label: v.name?.[0] || 'vis',
17
- options: v.name?.[1],
19
+ label: v.name ?? 'vis',
20
+ editable: true
18
21
  }));
19
22
 
20
23
  tabs.push({
21
24
  key: ADD_KEY,
22
- label: 'main.tablist.new'
25
+ label: t('main.tablist.new')
23
26
  });
24
27
 
28
+ useEffect(() => {
29
+ if (visList.length === 1) {
30
+ // should set the first vis name when the component is mounted
31
+ vizStore.setVisName(0, t('main.tablist.auto_title', { idx: 1 }));
32
+ }
33
+ // no need to add deps here
34
+ }, [])
35
+
25
36
  const visSelectionHandler = useCallback((tabKey: string, tabIndex: number) => {
26
37
  if (tabKey === ADD_KEY) {
27
- vizStore.addVisualization();
38
+ vizStore.addVisualization(t('main.tablist.auto_title', { idx: visList.length + 1 }));
28
39
  vizStore.initMetaState(currentDataset)
29
40
  } else {
30
41
  vizStore.selectVisualization(tabIndex);
31
42
  }
32
- }, [currentDataset, vizStore])
43
+ }, [currentDataset, vizStore, visList.length])
33
44
 
34
45
  const editLabelHandler = useCallback((content: string, tabIndex: number) => {
35
46
  vizStore.setVisName(tabIndex, content)
package/src/services.ts CHANGED
@@ -10,7 +10,10 @@ import { IRow, Filters, SemanticType, IMeasure, IMutField, IFilterField } from '
10
10
  // eslint-disable-next-line
11
11
  import ExplainerWorker from './workers/explainer.worker?worker&inline';
12
12
  import FilterWorker from './workers/filter.worker?worker&inline';
13
+ import TransformDataWorker from './workers/transform.worker?worker&inline';
14
+ import ViewQueryWorker from './workers/viewQuery.worker?worker&inline';
13
15
  import { IExplaination, IMeasureWithStat } from './insights';
16
+ import { IViewQuery, queryView } from './lib/viewQuery';
14
17
 
15
18
  interface WorkerState {
16
19
  eWorker: Worker | null;
@@ -105,7 +108,8 @@ export function destroyWorker() {
105
108
  let filterWorker: Worker | null = null;
106
109
  let filterWorkerAutoTerminator: NodeJS.Timeout | null = null;
107
110
 
108
- export const applyFilter = async (data: readonly IRow[], filters: readonly IFilterField[]): Promise<IRow[]> => {
111
+ export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]): Promise<IRow[]> => {
112
+ if (filters.length === 0) return data;
109
113
  if (filterWorkerAutoTerminator !== null) {
110
114
  clearTimeout(filterWorkerAutoTerminator);
111
115
  filterWorkerAutoTerminator = null;
@@ -137,3 +141,35 @@ export const applyFilter = async (data: readonly IRow[], filters: readonly IFilt
137
141
  }, 60_000); // Destroy the worker when no request is received for 60 secs
138
142
  }
139
143
  };
144
+
145
+ export const transformDataService = async (data: IRow[], columns: IMutField[]): Promise<IRow[]> => {
146
+ if (columns.length === 0 || data.length === 0) return data;
147
+ const worker = new TransformDataWorker();
148
+ try {
149
+ const res: IRow[] = await workerService(worker, {
150
+ dataSource: data,
151
+ columns: toJS(columns),
152
+ });
153
+ return res;
154
+ } catch (error) {
155
+ throw new Error('Uncaught error in TransformDataWorker', { cause: error });
156
+ } finally {
157
+ worker.terminate();
158
+ }
159
+ }
160
+
161
+ export const applyViewQuery = async (data: IRow[], metas: IMutField[], query: IViewQuery): Promise<IRow[]> => {
162
+ const worker = new ViewQueryWorker();
163
+ try {
164
+ const res: IRow[] = await workerService(worker, {
165
+ dataSource: data,
166
+ metas: toJS(metas),
167
+ query: toJS(query),
168
+ });
169
+ return res;
170
+ } catch (err) {
171
+ throw new Error('Uncaught error in ViewQueryWorker', { cause: err });
172
+ } finally {
173
+ worker.terminate();
174
+ }
175
+ }
@@ -1,7 +1,6 @@
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 { extendCountField } from '../utils';
5
4
 
6
5
  export class CommonStore {
7
6
  public datasets: IDataSet[] = [];
@@ -14,6 +13,7 @@ export class CommonStore {
14
13
  public showInsightBoard: boolean = false;
15
14
  public vizEmbededMenu: { show: boolean; position: [number, number] } = { show: false, position: [0, 0] };
16
15
  public showDataConfig: boolean = false;
16
+ public showCodeExportPanel: boolean = false;
17
17
  public filters: Filters = {};
18
18
  public segmentKey: ISegmentKey = ISegmentKey.vis;
19
19
  constructor () {
@@ -30,12 +30,12 @@ export class CommonStore {
30
30
  if (this.datasets.length > 0) {
31
31
  const dataSourceId = this.datasets[datasetIndex].dsId;
32
32
  const dataSource = this.dataSources.find(d => d.id === dataSourceId);
33
- const rawFields = toJS(this.datasets[datasetIndex].rawFields)
34
- const base = extendCountField((dataSource ? dataSource.data : []), rawFields)
33
+ const rawFields = toJS(this.datasets[datasetIndex].rawFields)//.concat(createCountField())
34
+ // const base = extendCountField((dataSource ? dataSource.data : []), rawFields)
35
35
  return {
36
36
  ...this.datasets[datasetIndex],
37
- dataSource: base.dataSource,
38
- rawFields: base.fields
37
+ dataSource: dataSource?.data ?? [],
38
+ rawFields
39
39
  }
40
40
  }
41
41
  return {
@@ -61,6 +61,9 @@ export class CommonStore {
61
61
  this.vizEmbededMenu.show = true;
62
62
  this.vizEmbededMenu.position = position;
63
63
  }
64
+ public setShowCodeExportPanel (show: boolean) {
65
+ this.showCodeExportPanel = show;
66
+ }
64
67
  public closeEmbededMenu () {
65
68
  this.vizEmbededMenu.show = false;
66
69
  }
@@ -112,11 +115,13 @@ export class CommonStore {
112
115
 
113
116
  public updateTempDS (rawData: IRow[]) {
114
117
  const result = transData(rawData);
115
- // TODO: need fix web-data-loader issue #2
116
- this.tmpDataSource = result.dataSource.slice(0, -1);
118
+ this.tmpDataSource = result.dataSource;
117
119
  this.tmpDSRawFields = result.fields;
118
120
  }
119
-
121
+ /**
122
+ * update temp dataset (standard) with dataset info
123
+ * @param dataset
124
+ */
120
125
  public updateTempSTDDS (dataset: IDataSetInfo) {
121
126
  this.tmpDataSource = dataset.dataSource;
122
127
  this.tmpDSRawFields = dataset.rawFields;
@@ -176,7 +181,7 @@ export class CommonStore {
176
181
  }
177
182
  public createPlaceholderDS() {
178
183
  this.addDS({
179
- name: '新数据源',
184
+ name: 'new dataset',
180
185
  dataSource: [],
181
186
  rawFields: []
182
187
  })
@@ -1,8 +1,8 @@
1
- import React, { useContext, useEffect } from 'react';
1
+ import React, { useContext } from 'react';
2
2
  import { CommonStore } from './commonStore'
3
3
  import { VizSpecStore } from './visualSpecStore'
4
4
 
5
- interface GlobalStore {
5
+ export interface IGlobalStore {
6
6
  commonStore: CommonStore;
7
7
  vizStore: VizSpecStore;
8
8
  }
@@ -10,12 +10,12 @@ interface GlobalStore {
10
10
  const commonStore = new CommonStore();
11
11
  const vizStore = new VizSpecStore(commonStore);
12
12
 
13
- const initStore: GlobalStore = {
13
+ const initStore: IGlobalStore = {
14
14
  commonStore,
15
15
  vizStore
16
16
  }
17
17
 
18
- const StoreContext = React.createContext<GlobalStore>(null!);
18
+ const StoreContext = React.createContext<IGlobalStore>(null!);
19
19
 
20
20
  export function destroyGWStore() {
21
21
  initStore.commonStore.destroy();
@@ -31,16 +31,23 @@ export function rebootGWStore() {
31
31
 
32
32
  interface StoreWrapperProps {
33
33
  keepAlive?: boolean;
34
+ storeRef?: React.MutableRefObject<IGlobalStore | null>;
34
35
  }
35
36
  export class StoreWrapper extends React.Component<StoreWrapperProps> {
36
37
  constructor(props: StoreWrapperProps) {
37
38
  super(props)
39
+ if (props.storeRef) {
40
+ props.storeRef.current = initStore;
41
+ }
38
42
  if (props.keepAlive) {
39
43
  rebootGWStore();
40
44
  }
41
45
  }
42
46
  componentWillUnmount() {
43
47
  if (!this.props.keepAlive) {
48
+ if (this.props.storeRef) {
49
+ this.props.storeRef.current = null;
50
+ }
44
51
  destroyGWStore();
45
52
  }
46
53
  }
@@ -2,12 +2,14 @@ import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } fro
2
2
  import produce from "immer";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
  import { Specification } from "visual-insights";
5
- import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisualConfig } from "../interfaces";
5
+ import { DataSet, DraggableFieldState, IExpression, IFilterRule, IViewField, IVisSpec, IVisualConfig } from "../interfaces";
6
6
  import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
7
7
  import { makeBinField, makeLogField } from "../utils/normalization";
8
8
  import { VisSpecWithHistory } from "../models/visSpecHistory";
9
- import { dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
9
+ import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
10
10
  import { CommonStore } from "./commonStore";
11
+ import { createCountField } from "../utils";
12
+ import { IViewQuery } from "../lib/viewQuery";
11
13
 
12
14
  function getChannelSizeLimit(channel: string): number {
13
15
  if (typeof CHANNEL_LIMIT[channel] === "undefined") return Infinity;
@@ -40,7 +42,7 @@ function geomAdapter(geom: string) {
40
42
  }
41
43
  }
42
44
 
43
- function initEncoding(): DraggableFieldState {
45
+ export function initEncoding(): DraggableFieldState {
44
46
  return {
45
47
  dimensions: [],
46
48
  measures: [],
@@ -58,7 +60,7 @@ function initEncoding(): DraggableFieldState {
58
60
  };
59
61
  }
60
62
 
61
- function initVisualConfig(): IVisualConfig {
63
+ export function initVisualConfig(): IVisualConfig {
62
64
  return {
63
65
  defaultAggregated: true,
64
66
  geoms: [GEMO_TYPES[0]!],
@@ -92,6 +94,10 @@ const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["spec
92
94
  }));
93
95
  };
94
96
 
97
+ function isDraggableStateEmpty(state: DeepReadonly<DraggableFieldState>): boolean {
98
+ return Object.values(state).every((value) => value.length === 0);
99
+ }
100
+
95
101
  export class VizSpecStore {
96
102
  // public fields: IViewField[] = [];
97
103
  private commonStore: CommonStore;
@@ -139,7 +145,7 @@ export class VizSpecStore {
139
145
  this.visualConfig = initVisualConfig();
140
146
  this.visList.push(
141
147
  new VisSpecWithHistory({
142
- name: ["main.tablist.autoTitle", { idx: 1 }],
148
+ name: 'Chart 1',
143
149
  visId: uuidv4(),
144
150
  config: this.visualConfig,
145
151
  encodings: this.draggableFieldState,
@@ -151,13 +157,6 @@ export class VizSpecStore {
151
157
  reactions: false,
152
158
  });
153
159
  this.reactions.push(
154
- reaction(
155
- () => commonStore.currentDataset,
156
- (dataset) => {
157
- // this.initState();
158
- this.initMetaState(dataset);
159
- }
160
- ),
161
160
  reaction(
162
161
  () => this.visList[this.visIndex],
163
162
  (frame) => {
@@ -168,6 +167,15 @@ export class VizSpecStore {
168
167
  this.canUndo = frame.canUndo;
169
168
  this.canRedo = frame.canRedo;
170
169
  }
170
+ ),
171
+ reaction(
172
+ () => commonStore.currentDataset,
173
+ (dataset) => {
174
+ // this.initState();
175
+ if (isDraggableStateEmpty(this.draggableFieldState) && dataset.dataSource.length > 0 && dataset.rawFields.length > 0) {
176
+ this.initMetaState(dataset);
177
+ }
178
+ }
171
179
  )
172
180
  );
173
181
  }
@@ -275,10 +283,24 @@ export class VizSpecStore {
275
283
  });
276
284
  return fields;
277
285
  }
278
- public addVisualization() {
286
+ public get allFields(): IViewField[] {
287
+ const { draggableFieldState } = this;
288
+ const dimensions = toJS(draggableFieldState.dimensions);
289
+ const measures = toJS(draggableFieldState.measures);
290
+ return [...dimensions, ...measures];
291
+ }
292
+ public get viewFilters() {
293
+ const { draggableFieldState } = this;
294
+ const state = toJS(draggableFieldState);
295
+ return state.filters;
296
+ }
297
+
298
+
299
+ public addVisualization(defaultName?: string) {
300
+ const name = defaultName || 'Chart ' + (this.visList.length + 1);
279
301
  this.visList.push(
280
302
  new VisSpecWithHistory({
281
- name: ["main.tablist.autoTitle", { idx: this.visList.length + 1 }],
303
+ name,
282
304
  visId: uuidv4(),
283
305
  config: initVisualConfig(),
284
306
  encodings: initEncoding(),
@@ -290,9 +312,10 @@ export class VizSpecStore {
290
312
  this.visIndex = visIndex;
291
313
  }
292
314
  public setVisName(visIndex: number, name: string) {
293
- this.useMutable(() => {
294
- this.visList[visIndex].name = [name];
295
- });
315
+ this.visList[visIndex] = this.visList[visIndex].clone();
316
+ this.visList[visIndex].updateLatest({
317
+ name
318
+ })
296
319
  }
297
320
  public initState() {
298
321
  this.useMutable((tab) => {
@@ -301,6 +324,7 @@ export class VizSpecStore {
301
324
  });
302
325
  }
303
326
  public initMetaState(dataset: DataSet) {
327
+ const countField = createCountField();
304
328
  this.useMutable(({ encodings }) => {
305
329
  encodings.fields = dataset.rawFields.map((f) => ({
306
330
  dragId: uuidv4(),
@@ -309,7 +333,8 @@ export class VizSpecStore {
309
333
  aggName: f.analyticType === "measure" ? "sum" : undefined,
310
334
  analyticType: f.analyticType,
311
335
  semanticType: f.semanticType,
312
- }));
336
+ }))//.concat(countField);
337
+ encodings.fields.push(countField);
313
338
  encodings.dimensions = dataset.rawFields
314
339
  .filter((f) => f.analyticType === "dimension")
315
340
  .map((f) => ({
@@ -329,17 +354,10 @@ export class VizSpecStore {
329
354
  semanticType: f.semanticType,
330
355
  aggName: "sum",
331
356
  }));
357
+ encodings.measures.push(countField);
332
358
  });
333
359
 
334
360
  this.freezeHistory();
335
- // this.draggableFieldState.measures.push({
336
- // dragId: uuidv4(),
337
- // fid: COUNT_FIELD_ID,
338
- // name: '记录数',
339
- // analyticType: 'measure',
340
- // semanticType: 'quantitative',
341
- // aggName: 'count'
342
- // })
343
361
  }
344
362
  public clearState() {
345
363
  this.useMutable(({ encodings }) => {
@@ -489,44 +507,59 @@ export class VizSpecStore {
489
507
  encodings.rows = fieldsInCup as typeof encodings.rows; // assume this as writable
490
508
  });
491
509
  }
492
- public createBinField(stateKey: keyof DraggableFieldState, index: number) {
510
+ public createBinField(stateKey: keyof DraggableFieldState, index: number, binType: 'bin' | 'binCount') {
493
511
  this.useMutable(({ encodings }) => {
494
512
  const originField = encodings[stateKey][index];
513
+ const newVarKey = uuidv4();
495
514
  const binField: IViewField = {
496
- fid: uuidv4(),
497
- dragId: uuidv4(),
498
- name: `bin(${originField.name})`,
515
+ fid: newVarKey,
516
+ dragId: newVarKey,
517
+ name: `${binType}(${originField.name})`,
499
518
  semanticType: "ordinal",
500
519
  analyticType: "dimension",
520
+ computed: true,
521
+ expressoion: {
522
+ op: binType,
523
+ as: newVarKey,
524
+ params: [
525
+ {
526
+ type: 'field',
527
+ value: originField.fid
528
+ }
529
+ ]
530
+ }
501
531
  };
502
532
  encodings.dimensions.push(binField);
503
- this.commonStore.currentDataset.dataSource = makeBinField(
504
- this.commonStore.currentDataset.dataSource,
505
- originField.fid,
506
- binField.fid
507
- );
508
533
  });
509
534
  }
510
- public createLogField(stateKey: keyof DraggableFieldState, index: number) {
535
+ public createLogField(stateKey: keyof DraggableFieldState, index: number, scaleType: 'log10' | 'log2') {
511
536
  if (stateKey === "filters") {
512
537
  return;
513
538
  }
514
539
 
515
540
  this.useMutable(({ encodings }) => {
516
541
  const originField = encodings[stateKey][index];
542
+ const newVarKey = uuidv4();
517
543
  const logField: IViewField = {
518
- fid: uuidv4(),
519
- dragId: uuidv4(),
520
- name: `log10(${originField.name})`,
544
+ fid: newVarKey,
545
+ dragId: newVarKey,
546
+ name: `${scaleType}(${originField.name})`,
521
547
  semanticType: "quantitative",
522
548
  analyticType: originField.analyticType,
549
+ aggName: 'sum',
550
+ computed: true,
551
+ expressoion: {
552
+ op: scaleType,
553
+ as: newVarKey,
554
+ params: [
555
+ {
556
+ type: 'field',
557
+ value: originField.fid
558
+ }
559
+ ]
560
+ }
523
561
  };
524
562
  encodings[stateKey].push(logField);
525
- this.commonStore.currentDataset.dataSource = makeLogField(
526
- this.commonStore.currentDataset.dataSource,
527
- originField.fid,
528
- logField.fid
529
- );
530
563
  });
531
564
  }
532
565
  public setFieldAggregator(stateKey: keyof DraggableFieldState, index: number, aggName: string) {
@@ -677,13 +710,19 @@ export class VizSpecStore {
677
710
  specList: pureVisList,
678
711
  });
679
712
  }
713
+ public exportViewSpec() {
714
+ const pureVisList = dumpsGWPureSpec(this.visList);
715
+ return pureVisList
716
+ }
717
+ public importStoInfo (stoInfo: IStoInfo) {
718
+ this.visList = parseGWPureSpec(forwardVisualConfigs(stoInfo.specList));
719
+ this.visIndex = 0;
720
+ this.commonStore.datasets = stoInfo.datasets;
721
+ this.commonStore.dataSources = stoInfo.dataSources;
722
+ this.commonStore.dsIndex = Math.max(stoInfo.datasets.length - 1, 0);
723
+ }
680
724
  public importRaw(raw: string) {
681
725
  const content = parseGWContent(raw);
682
- this.commonStore.datasets = content.datasets;
683
- this.commonStore.dataSources = content.dataSources;
684
- this.commonStore.dsIndex = Math.max(content.datasets.length - 1, 0);
685
- // 补上初始化新版本特性
686
- this.visList = parseGWPureSpec(forwardVisualConfigs(content.specList));
687
- this.visIndex = 0;
726
+ this.importStoInfo(content);
688
727
  }
689
728
  }
@@ -41,4 +41,28 @@ export function guardDataKeys (data: IRow[], metas: IMutField[]): {
41
41
  safeData,
42
42
  safeMetas
43
43
  }
44
+ }
45
+
46
+ const SPLITOR = '__'
47
+ export function flatNestKeys (object: any): string[] {
48
+ const keys = Object.keys(object);
49
+ let flatColKeys: string[] = [];
50
+ for (let key of keys) {
51
+ if (typeof object[key] === 'object') {
52
+ const subKeys = flatNestKeys(object[key]);
53
+ flatColKeys = flatColKeys.concat(subKeys.map(k => `${key}${SPLITOR}${k}`));
54
+ } else {
55
+ flatColKeys.push(key)
56
+ }
57
+ }
58
+ return flatColKeys;
59
+ }
60
+
61
+ export function getValueByKeyPath (object: any, keyPath: string): any {
62
+ const keys = keyPath.split(SPLITOR);
63
+ let value = object;
64
+ for (let key of keys) {
65
+ value = value[key];
66
+ }
67
+ return value;
44
68
  }