@kanaries/graphic-walker 0.2.11 → 0.2.13

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 (69) hide show
  1. package/dist/App.d.ts +6 -3
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/components/button/base.d.ts +6 -0
  4. package/dist/components/button/default.d.ts +4 -0
  5. package/dist/components/button/primary.d.ts +4 -0
  6. package/dist/components/dataTable/index.d.ts +10 -0
  7. package/dist/components/dataTable/pagination.d.ts +10 -0
  8. package/dist/components/tabs/defaultTab.d.ts +14 -0
  9. package/dist/components/tabs/{pureTab.d.ts → editableTab.d.ts} +2 -2
  10. package/dist/dataSource/datasetConfig/index.d.ts +3 -0
  11. package/dist/dataSource/utils.d.ts +1 -1
  12. package/dist/graphic-walker.es.js +17347 -18050
  13. package/dist/graphic-walker.es.js.map +1 -1
  14. package/dist/graphic-walker.umd.js +140 -138
  15. package/dist/graphic-walker.umd.js.map +1 -1
  16. package/dist/index.d.ts +2 -2
  17. package/dist/interfaces.d.ts +12 -0
  18. package/dist/lib/inferMeta.d.ts +20 -0
  19. package/dist/segments/segmentNav.d.ts +3 -0
  20. package/dist/store/commonStore.d.ts +8 -2
  21. package/dist/store/index.d.ts +0 -1
  22. package/dist/store/visualSpecStore.d.ts +5 -5
  23. package/dist/utils/dataPrep.d.ts +6 -0
  24. package/dist/utils/index.d.ts +2 -2
  25. package/dist/utils/normalization.d.ts +1 -1
  26. package/dist/utils/save.d.ts +3 -3
  27. package/dist/utils/throttle.d.ts +1 -1
  28. package/dist/vis/temporalFormat.d.ts +10 -0
  29. package/package.json +1 -1
  30. package/src/App.tsx +93 -51
  31. package/src/components/button/base.ts +7 -0
  32. package/src/components/button/default.tsx +17 -0
  33. package/src/components/button/primary.tsx +17 -0
  34. package/src/components/dataTable/index.tsx +187 -0
  35. package/src/components/dataTable/pagination.tsx +44 -0
  36. package/src/components/tabs/defaultTab.tsx +43 -0
  37. package/src/components/tabs/{pureTab.tsx → editableTab.tsx} +3 -3
  38. package/src/dataSource/dataSelection/csvData.tsx +8 -10
  39. package/src/dataSource/dataSelection/index.tsx +1 -1
  40. package/src/dataSource/dataSelection/publicData.tsx +4 -4
  41. package/src/dataSource/datasetConfig/index.tsx +21 -0
  42. package/src/dataSource/index.tsx +10 -12
  43. package/src/dataSource/table.tsx +11 -142
  44. package/src/dataSource/utils.ts +30 -35
  45. package/src/fields/datasetFields/dimFields.tsx +1 -5
  46. package/src/fields/datasetFields/meaFields.tsx +1 -5
  47. package/src/fields/obComponents/obFContainer.tsx +1 -5
  48. package/src/index.tsx +3 -4
  49. package/src/interfaces.ts +14 -0
  50. package/src/lib/inferMeta.ts +88 -0
  51. package/src/locales/en-US.json +14 -0
  52. package/src/locales/zh-CN.json +14 -0
  53. package/src/main.tsx +1 -1
  54. package/src/renderer/index.tsx +1 -0
  55. package/src/segments/segmentNav.tsx +58 -0
  56. package/src/segments/visNav.tsx +2 -2
  57. package/src/store/commonStore.ts +36 -5
  58. package/src/store/index.tsx +0 -2
  59. package/src/store/visualSpecStore.ts +245 -183
  60. package/src/utils/autoMark.ts +14 -14
  61. package/src/utils/dataPrep.ts +44 -0
  62. package/src/utils/index.ts +140 -128
  63. package/src/utils/normalization.ts +59 -51
  64. package/src/utils/save.ts +22 -21
  65. package/src/utils/throttle.ts +5 -1
  66. package/src/vis/react-vega.tsx +6 -10
  67. package/src/vis/temporalFormat.ts +66 -0
  68. package/dist/pitch/dnd-offset.d.ts +0 -2
  69. package/src/pitch/dnd-offset.ts +0 -64
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
- import { EditorProps } from './App';
2
+ import { IGWProps } from './App';
3
3
  import './empty_sheet.css';
4
4
  export declare const ShadowDomContext: React.Context<{
5
5
  root: ShadowRoot | null;
6
6
  }>;
7
- export declare const GraphicWalker: React.FC<EditorProps>;
7
+ export declare const GraphicWalker: React.FC<IGWProps>;
@@ -22,6 +22,14 @@ export interface IMutField {
22
22
  semanticType: ISemanticType;
23
23
  analyticType: IAnalyticType;
24
24
  }
25
+ export interface IUncertainMutField {
26
+ fid: string;
27
+ key?: string;
28
+ name?: string;
29
+ disable?: boolean;
30
+ semanticType: ISemanticType | '?';
31
+ analyticType: IAnalyticType | '?';
32
+ }
25
33
  export interface IField {
26
34
  /**
27
35
  * fid: key in data record
@@ -141,3 +149,7 @@ export interface IVisSpec {
141
149
  readonly encodings: DeepReadonly<DraggableFieldState>;
142
150
  readonly config: DeepReadonly<IVisualConfig>;
143
151
  }
152
+ export declare enum ISegmentKey {
153
+ vis = "vis",
154
+ data = "data"
155
+ }
@@ -0,0 +1,20 @@
1
+ import { ISemanticType } from "visual-insights";
2
+ import { IMutField, IRow, IUncertainMutField } from "../interfaces";
3
+ /**
4
+ * check if this array is a date time array based on some common date format
5
+ * @param data string array
6
+ * @returns
7
+ */
8
+ export declare function isDateTimeArray(data: string[]): boolean;
9
+ /**
10
+ * 这里目前暂时包一层,是为了解耦具体的推断实现。后续这里要调整推断的逻辑。
11
+ * 需要讨论这一层是否和交互层有关,如果没有关系,这一层包裹可以不存在这里,而是在visual-insights中。
12
+ * @param data 原始数据
13
+ * @param fid 字段id
14
+ * @returns semantic type 列表
15
+ */
16
+ export declare function inferSemanticType(data: IRow[], fid: string): ISemanticType;
17
+ export declare function inferMeta(props: {
18
+ dataSource: IRow[];
19
+ fields: IUncertainMutField[];
20
+ }): IMutField[];
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ declare const _default: React.FunctionComponent<{}>;
3
+ export default _default;
@@ -1,4 +1,4 @@
1
- import { DataSet, Filters, IDataSet, IDataSetInfo, IDataSource, IMutField, IRow } from '../interfaces';
1
+ import { DataSet, Filters, IDataSet, IDataSetInfo, IDataSource, IMutField, IRow, ISegmentKey } from '../interfaces';
2
2
  export declare class CommonStore {
3
3
  datasets: IDataSet[];
4
4
  dataSources: IDataSource[];
@@ -12,17 +12,23 @@ export declare class CommonStore {
12
12
  show: boolean;
13
13
  position: [number, number];
14
14
  };
15
- rootContainer: HTMLDivElement | null;
15
+ showDataConfig: boolean;
16
16
  filters: Filters;
17
+ segmentKey: ISegmentKey;
17
18
  constructor();
18
19
  get currentDataset(): DataSet;
20
+ setSegmentKey(sk: ISegmentKey): void;
19
21
  setShowDSPanel(show: boolean): void;
22
+ setShowDataConfig(show: boolean): void;
20
23
  setShowInsightBoard(show: boolean): void;
21
24
  showEmbededMenu(position: [number, number]): void;
22
25
  closeEmbededMenu(): void;
23
26
  initTempDS(): void;
24
27
  updateTempFields(fields: IMutField[]): void;
28
+ updateCurrentDatasetMetas(fid: string, diffMeta: Partial<IMutField>): void;
29
+ updateTempDatasetMetas(fid: string, diffMeta: Partial<IMutField>): void;
25
30
  updateTempFieldAnalyticType(fieldKey: string, analyticType: IMutField['analyticType']): void;
31
+ updateTempFieldSemanticType(fieldKey: string, semanticType: IMutField['semanticType']): void;
26
32
  updateTempName(name: string): void;
27
33
  updateTempDS(rawData: IRow[]): void;
28
34
  updateTempSTDDS(dataset: IDataSetInfo): void;
@@ -9,7 +9,6 @@ export declare function destroyGWStore(): void;
9
9
  export declare function rebootGWStore(): void;
10
10
  interface StoreWrapperProps {
11
11
  keepAlive?: boolean;
12
- rootContainer?: HTMLDivElement | null;
13
12
  }
14
13
  export declare class StoreWrapper extends React.Component<StoreWrapperProps> {
15
14
  constructor(props: StoreWrapperProps);
@@ -83,13 +83,13 @@ export declare class VizSpecStore {
83
83
  initMetaState(dataset: DataSet): void;
84
84
  clearState(): void;
85
85
  setVisualConfig<K extends keyof IVisualConfig>(configKey: K, value: IVisualConfig[K]): void;
86
- transformCoord(coord: 'cartesian' | 'polar'): void;
86
+ transformCoord(coord: "cartesian" | "polar"): void;
87
87
  setChartLayout(props: {
88
- mode: IVisualConfig['size']['mode'];
88
+ mode: IVisualConfig["size"]["mode"];
89
89
  width?: number;
90
90
  height?: number;
91
91
  }): void;
92
- setExploration(value: Partial<IVisualConfig['exploration']>): void;
92
+ setExploration(value: Partial<IVisualConfig["exploration"]>): void;
93
93
  reorderField(stateKey: keyof DraggableFieldState, sourceIndex: number, destinationIndex: number): void;
94
94
  moveField(sourceKey: keyof DraggableFieldState, sourceIndex: number, destinationKey: keyof DraggableFieldState, destinationIndex: number): void;
95
95
  removeField(sourceKey: keyof DraggableFieldState, sourceIndex: number): void;
@@ -102,8 +102,8 @@ export declare class VizSpecStore {
102
102
  createLogField(stateKey: keyof DraggableFieldState, index: number): void;
103
103
  setFieldAggregator(stateKey: keyof DraggableFieldState, index: number, aggName: string): void;
104
104
  get sortCondition(): boolean;
105
- setFieldSort(stateKey: keyof DraggableFieldState, index: number, sortType: 'none' | 'ascending' | 'descending'): void;
106
- applyDefaultSort(sortType?: 'none' | 'ascending' | 'descending'): void;
105
+ setFieldSort(stateKey: keyof DraggableFieldState, index: number, sortType: "none" | "ascending" | "descending"): void;
106
+ applyDefaultSort(sortType?: "none" | "ascending" | "descending"): void;
107
107
  appendField(destinationKey: keyof DraggableFieldState, field: IViewField | undefined): void;
108
108
  renderSpec(spec: Specification): void;
109
109
  destroy(): void;
@@ -0,0 +1,6 @@
1
+ import { IRow } from "visual-insights";
2
+ import { IMutField } from "../interfaces";
3
+ export declare function guardDataKeys(data: IRow[], metas: IMutField[]): {
4
+ safeData: IRow[];
5
+ safeMetas: IMutField[];
6
+ };
@@ -1,4 +1,4 @@
1
- import { IRow, Filters, IMutField } from '../interfaces';
1
+ import { IRow, Filters, IMutField } from "../interfaces";
2
2
  export declare function checkMajorFactor(data: IRow[], childrenData: Map<any, IRow[]>, dimensions: string[], measures: string[]): {
3
3
  majorKey: string;
4
4
  majorSum: number;
@@ -9,7 +9,7 @@ export declare function checkChildOutlier(data: IRow[], childrenData: Map<any, I
9
9
  };
10
10
  export interface IPredicate {
11
11
  key: string;
12
- type: 'discrete' | 'continuous';
12
+ type: "discrete" | "continuous";
13
13
  range: Set<any> | [number, number];
14
14
  }
15
15
  export declare function getPredicates(selection: IRow[], dimensions: string[], measures: string[]): IPredicate[];
@@ -1,4 +1,4 @@
1
- import { IRow } from '../interfaces';
1
+ import { IRow } from "../interfaces";
2
2
  export declare function normalizeWithParent(data: IRow[], parentData: IRow[], measures: string[], syncScale: boolean): {
3
3
  normalizedData: IRow[];
4
4
  normalizedParentData: IRow[];
@@ -4,9 +4,9 @@ export declare function dumpsGWPureSpec(list: VisSpecWithHistory[]): IVisSpec[];
4
4
  export declare function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[];
5
5
  interface IStoInfo {
6
6
  datasets: IDataSet[];
7
- specList: ({
8
- [K in keyof IVisSpec]: K extends 'config' ? Partial<IVisSpec[K]> : IVisSpec[K];
9
- })[];
7
+ specList: {
8
+ [K in keyof IVisSpec]: K extends "config" ? Partial<IVisSpec[K]> : IVisSpec[K];
9
+ }[];
10
10
  dataSources: IDataSource[];
11
11
  }
12
12
  export declare function stringifyGWContent(info: IStoInfo): string;
@@ -1,5 +1,5 @@
1
1
  declare const throttle: (fn: () => void, time: number, options?: Partial<{
2
2
  leading: boolean;
3
3
  trailing: boolean;
4
- }>) => () => void;
4
+ }>) => (() => void);
5
5
  export default throttle;
@@ -0,0 +1,10 @@
1
+ export declare function getVegaTimeFormatRules(lang: string): {
2
+ dateTime: string;
3
+ date: string;
4
+ time: string;
5
+ periods: string[];
6
+ days: string[];
7
+ shortDays: string[];
8
+ months: string[];
9
+ shortMonths: string[];
10
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanaries/graphic-walker",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "scripts": {
5
5
  "dev:front_end": "vite --host",
6
6
  "dev": "npm run dev:front_end",
package/src/App.tsx CHANGED
@@ -1,10 +1,10 @@
1
- import React, { useState, useEffect, useRef } from "react";
1
+ import React, { useState, useEffect, useRef, useMemo } from "react";
2
2
  import { Specification } from "visual-insights";
3
3
  import { observer } from "mobx-react-lite";
4
4
  import { LightBulbIcon } from "@heroicons/react/24/outline";
5
5
  import { toJS } from "mobx";
6
6
  import { useTranslation } from "react-i18next";
7
- import { IMutField, IRow } from "./interfaces";
7
+ import { IMutField, IRow, ISegmentKey } from "./interfaces";
8
8
  import type { IReactVegaHandler } from "./vis/react-vega";
9
9
  import VisualSettings from "./visualSettings";
10
10
  import { Container, NestContainer } from "./components/container";
@@ -20,9 +20,11 @@ import { preAnalysis, destroyWorker } from "./services";
20
20
  import VisNav from "./segments/visNav";
21
21
  import { mergeLocaleRes, setLocaleLanguage } from "./locales/i18n";
22
22
  import FilterField from "./fields/filterField";
23
- import PageNav from "./components/pageNav";
23
+ import { guardDataKeys } from "./utils/dataPrep";
24
+ import SegmentNav from "./segments/segmentNav";
25
+ import DatasetConfig from "./dataSource/datasetConfig";
24
26
 
25
- export interface EditorProps {
27
+ export interface IGWProps {
26
28
  dataSource?: IRow[];
27
29
  rawFields?: IMutField[];
28
30
  spec?: Specification;
@@ -30,15 +32,26 @@ export interface EditorProps {
30
32
  i18nLang?: string;
31
33
  i18nResources?: { [lang: string]: Record<string, string | any> };
32
34
  keepAlive?: boolean;
33
- fixContainer?: boolean;
35
+ /**
36
+ * auto parse field key into a safe string. default is true
37
+ */
38
+ fieldKeyGuard?: boolean;
34
39
  }
35
40
 
36
- const App: React.FC<EditorProps> = (props) => {
37
- const { dataSource = [], rawFields = [], spec, i18nLang = "en-US", i18nResources, hideDataSourceConfig } = props;
41
+ const App: React.FC<IGWProps> = (props) => {
42
+ const {
43
+ dataSource = [],
44
+ rawFields = [],
45
+ spec,
46
+ i18nLang = "en-US",
47
+ i18nResources,
48
+ hideDataSourceConfig,
49
+ fieldKeyGuard = true,
50
+ } = props;
38
51
  const { commonStore, vizStore } = useGlobalStore();
39
52
  const [insightReady, setInsightReady] = useState<boolean>(true);
40
53
 
41
- const { currentDataset, datasets, vizEmbededMenu } = commonStore;
54
+ const { currentDataset, datasets, vizEmbededMenu, segmentKey } = commonStore;
42
55
 
43
56
  const { t, i18n } = useTranslation();
44
57
  const curLang = i18n.language;
@@ -55,16 +68,30 @@ const App: React.FC<EditorProps> = (props) => {
55
68
  }
56
69
  }, [i18nLang, curLang]);
57
70
 
71
+ const safeDataset = useMemo(() => {
72
+ let safeData = dataSource;
73
+ let safeMetas = rawFields;
74
+ if (fieldKeyGuard) {
75
+ const { safeData: _safeData, safeMetas: _safeMetas } = guardDataKeys(dataSource, rawFields);
76
+ safeData = _safeData;
77
+ safeMetas = _safeMetas;
78
+ }
79
+ return {
80
+ safeData,
81
+ safeMetas,
82
+ };
83
+ }, [rawFields, dataSource, fieldKeyGuard]);
84
+
58
85
  // use as an embeding module, use outside datasource from props.
59
86
  useEffect(() => {
60
- if (dataSource.length > 0) {
87
+ if (safeDataset.safeData.length > 0 && safeDataset.safeMetas.length > 0) {
61
88
  commonStore.addAndUseDS({
62
89
  name: "context dataset",
63
- dataSource: dataSource,
64
- rawFields,
90
+ dataSource: safeDataset.safeData,
91
+ rawFields: safeDataset.safeMetas,
65
92
  });
66
93
  }
67
- }, [dataSource, rawFields]);
94
+ }, [safeDataset]);
68
95
 
69
96
  // do preparation analysis work when using a new dataset
70
97
  useEffect(() => {
@@ -97,48 +124,63 @@ const App: React.FC<EditorProps> = (props) => {
97
124
  <div className="">
98
125
  {!hideDataSourceConfig && <DataSourceSegment preWorkDone={insightReady} />}
99
126
  <div className="px-2 mx-2">
100
- <VisNav />
127
+ <SegmentNav />
128
+ {
129
+ segmentKey === ISegmentKey.vis && <VisNav />
130
+ }
101
131
  </div>
102
- <Container style={{ marginTop: "0em", borderTop: "none" }}>
103
- <VisualSettings rendererHandler={rendererRef} />
104
- <div className="md:grid md:grid-cols-12 xl:grid-cols-6">
105
- <div className="md:col-span-3 xl:col-span-1">
106
- <DatasetFields />
107
- </div>
108
- <div className="md:col-span-2 xl:col-span-1">
109
- <FilterField />
110
- <AestheticFields />
111
- </div>
112
- <div className="md:col-span-7 xl:col-span-4">
113
- <div>
114
- <PosFields />
132
+ {segmentKey === ISegmentKey.vis && (
133
+ <Container style={{ marginTop: "0em", borderTop: "none" }}>
134
+ <VisualSettings rendererHandler={rendererRef} />
135
+ <div className="md:grid md:grid-cols-12 xl:grid-cols-6">
136
+ <div className="md:col-span-3 xl:col-span-1">
137
+ <DatasetFields />
138
+ </div>
139
+ <div className="md:col-span-2 xl:col-span-1">
140
+ <FilterField />
141
+ <AestheticFields />
142
+ </div>
143
+ <div className="md:col-span-7 xl:col-span-4">
144
+ <div>
145
+ <PosFields />
146
+ </div>
147
+ <NestContainer
148
+ style={{ minHeight: "600px", overflow: "auto" }}
149
+ onMouseLeave={() => {
150
+ vizEmbededMenu.show && commonStore.closeEmbededMenu();
151
+ }}
152
+ onClick={() => {
153
+ vizEmbededMenu.show && commonStore.closeEmbededMenu();
154
+ }}
155
+ >
156
+ {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} />}
157
+ <InsightBoard />
158
+ {vizEmbededMenu.show && (
159
+ <ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
160
+ <div
161
+ className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100"
162
+ onClick={() => {
163
+ commonStore.closeEmbededMenu();
164
+ commonStore.setShowInsightBoard(true);
165
+ }}
166
+ >
167
+ <span className="flex-1 pr-2">
168
+ {t("App.labels.data_interpretation")}
169
+ </span>
170
+ <LightBulbIcon className="ml-1 w-3 flex-grow-0 flex-shrink-0" />
171
+ </div>
172
+ </ClickMenu>
173
+ )}
174
+ </NestContainer>
115
175
  </div>
116
- <NestContainer
117
- style={{ minHeight: "600px", overflow: "auto" }}
118
- onMouseLeave={() => {
119
- vizEmbededMenu.show && commonStore.closeEmbededMenu();
120
- }}
121
- >
122
- {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} />}
123
- <InsightBoard />
124
- {vizEmbededMenu.show && (
125
- <ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
126
- <div
127
- className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100"
128
- onClick={() => {
129
- commonStore.closeEmbededMenu();
130
- commonStore.setShowInsightBoard(true);
131
- }}
132
- >
133
- <span className="flex-1 pr-2">{t("App.labels.data_interpretation")}</span>
134
- <LightBulbIcon className="ml-1 w-3 flex-grow-0 flex-shrink-0" />
135
- </div>
136
- </ClickMenu>
137
- )}
138
- </NestContainer>
139
176
  </div>
140
- </div>
141
- </Container>
177
+ </Container>
178
+ )}
179
+ {segmentKey === ISegmentKey.data && (
180
+ <Container style={{ marginTop: "0em", borderTop: "none" }}>
181
+ <DatasetConfig />
182
+ </Container>
183
+ )}
142
184
  </div>
143
185
  </div>
144
186
  );
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ export interface ButtonBaseProps {
4
+ onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
5
+ text: string;
6
+ disabled?: boolean;
7
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { ButtonBaseProps } from "./base";
3
+
4
+ const DefaultButton: React.FC<ButtonBaseProps> = (props) => {
5
+ const { text, onClick, disabled } = props;
6
+ return (
7
+ <button
8
+ className="inline-block min-w-96 text-xs ml-2 pt-1 pb-1 pl-6 pr-6 border border-gray-500 rounded-sm hover:bg-gray-800 hover:border-gray-800 hover:text-white disabled:bg-gray-400 disabled:border-gray-400 disabled:text-white disabled:cursor-not-allowed disabled:text-white"
9
+ onClick={onClick}
10
+ disabled={disabled}
11
+ >
12
+ {text}
13
+ </button>
14
+ );
15
+ };
16
+
17
+ export default DefaultButton;
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { ButtonBaseProps } from "./base";
3
+
4
+ const PrimaryButton: React.FC<ButtonBaseProps> = (props) => {
5
+ const { text, onClick, disabled } = props;
6
+ return (
7
+ <button
8
+ className="inline-block min-w-96 text-xs ml-2 pt-1 pb-1 pl-6 pr-6 border border-gray-800 bg-gray-800 text-white rounded-sm hover:bg-white border-gray-800 hover:text-gray-800 cursor-pointer disabled:bg-gray-400 disabled:border-gray-400 disabled:text-white disabled:cursor-not-allowed disabled:text-white"
9
+ onClick={onClick}
10
+ disabled={disabled}
11
+ >
12
+ {text}
13
+ </button>
14
+ );
15
+ };
16
+
17
+ export default PrimaryButton;
@@ -0,0 +1,187 @@
1
+ import React, { useMemo, useState } from "react";
2
+ import styled from "styled-components";
3
+ import { IMutField, IRow } from "../../interfaces";
4
+ import { useTranslation } from "react-i18next";
5
+ import Pagination from "./pagination";
6
+
7
+ interface DataTableProps {
8
+ size?: number;
9
+ metas: IMutField[];
10
+ data: IRow[];
11
+ onMetaChange: (fid: string, fIndex: number, meta: Partial<IMutField>) => void;
12
+ }
13
+ const Container = styled.div`
14
+ overflow-x: auto;
15
+ max-height: 660px;
16
+ overflow-y: auto;
17
+ table {
18
+ box-sizing: content-box;
19
+ border-collapse: collapse;
20
+ font-size: 12px;
21
+ thead {
22
+ th {
23
+ }
24
+ th.number {
25
+ border-top: 3px solid #5cdbd3;
26
+ }
27
+ th.text {
28
+ border-top: 3px solid #69c0ff;
29
+ }
30
+ }
31
+ tbody {
32
+ td {
33
+ }
34
+ td.number {
35
+ text-align: right;
36
+ }
37
+ td.text {
38
+ text-align: left;
39
+ }
40
+ }
41
+ }
42
+ `;
43
+ const ANALYTIC_TYPE_LIST = ["dimension", "measure"];
44
+ const SEMANTIC_TYPE_LIST = ["nominal", "ordinal", "quantitative", "temporal"];
45
+ // function getCellType(field: IMutField): 'number' | 'text' {
46
+ // return field.dataType === 'number' || field.dataType === 'integer' ? 'number' : 'text';
47
+ // }
48
+ function getHeaderType(field: IMutField): "number" | "text" {
49
+ return field.analyticType === "dimension" ? "text" : "number";
50
+ }
51
+
52
+ function getHeaderClassNames(field: IMutField) {
53
+ return field.analyticType === "dimension" ? "border-t-4 border-blue-400" : "border-t-4 border-teal-400";
54
+ }
55
+
56
+ function getSemanticColors(field: IMutField): string {
57
+ switch (field.semanticType) {
58
+ case "nominal":
59
+ return "bg-indigo-100 text-indigo-800";
60
+ case "ordinal":
61
+ return "bg-purple-100 text-purple-800";
62
+ case "quantitative":
63
+ return "bg-green-100 text-green-800";
64
+ case "temporal":
65
+ return "bg-yellow-100 text-yellow-800";
66
+ default:
67
+ return "bg-gray-400";
68
+ }
69
+ }
70
+
71
+ const DataTable: React.FC<DataTableProps> = (props) => {
72
+ const { size = 10, data, metas, onMetaChange } = props;
73
+ const [pageIndex, setPageIndex] = useState(0);
74
+ const { t } = useTranslation();
75
+
76
+ const analyticTypeList = useMemo<{ value: string; label: string }[]>(() => {
77
+ return ANALYTIC_TYPE_LIST.map((at) => ({
78
+ value: at,
79
+ label: t(`constant.analytic_type.${at}`),
80
+ }));
81
+ }, []);
82
+
83
+ const semanticTypeList = useMemo<{ value: string; label: string }[]>(() => {
84
+ return SEMANTIC_TYPE_LIST.map((st) => ({
85
+ value: st,
86
+ label: t(`constant.semantic_type.${st}`),
87
+ }));
88
+ }, []);
89
+
90
+ const from = pageIndex * size;
91
+ const to = Math.min((pageIndex + 1) * size, data.length - 1);
92
+
93
+ return (
94
+ <Container className="rounded border-gray-200 border">
95
+ <Pagination
96
+ total={data.length}
97
+ from={from + 1}
98
+ to={to + 1}
99
+ onNext={() => {
100
+ setPageIndex(Math.min(Math.ceil(data.length / size) - 1, pageIndex + 1));
101
+ }}
102
+ onPrev={() => {
103
+ setPageIndex(Math.max(0, pageIndex - 1));
104
+ }}
105
+ />
106
+ <table className="min-w-full divide-y divide-gray-30">
107
+ <thead className="bg-gray-50">
108
+ <tr className="divide-x divide-gray-200">
109
+ {metas.map((field, fIndex) => (
110
+ <th key={field.fid} className={""}>
111
+ <div
112
+ className={
113
+ getHeaderClassNames(field) +
114
+ " whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 sm:pl-6"
115
+ }
116
+ >
117
+ <b>{field.name || field.fid}</b>
118
+ <div>
119
+ <select
120
+ className={
121
+ "px-2 py font-normal mt-2 rounded-full text-xs text-white " +
122
+ (field.analyticType === "dimension" ? "bg-blue-500" : "bg-teal-500")
123
+ }
124
+ // className="border-b border-gray-200 bg-gray-50 pl-0 mt-2 font-light"
125
+ value={field.analyticType}
126
+ onChange={(e) => {
127
+ onMetaChange(field.fid, fIndex, {
128
+ analyticType: e.target.value as IMutField["analyticType"],
129
+ });
130
+ }}
131
+ >
132
+ {analyticTypeList.map((type) => (
133
+ <option key={type.value} value={type.value}>
134
+ {type.label}
135
+ </option>
136
+ ))}
137
+ </select>
138
+ </div>
139
+ <div>
140
+ <select
141
+ className={
142
+ "inline-block px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white " +
143
+ getSemanticColors(field)
144
+ }
145
+ // className="border-b border-gray-200 bg-gray-50 pl-0 mt-2 font-light"
146
+ value={field.semanticType}
147
+ onChange={(e) => {
148
+ onMetaChange(field.fid, fIndex, {
149
+ semanticType: e.target.value as IMutField["semanticType"],
150
+ });
151
+ }}
152
+ >
153
+ {semanticTypeList.map((type) => (
154
+ <option key={type.value} value={type.value}>
155
+ {type.label}
156
+ </option>
157
+ ))}
158
+ </select>
159
+ </div>
160
+ </div>
161
+ </th>
162
+ ))}
163
+ </tr>
164
+ </thead>
165
+ <tbody className="divide-y divide-gray-200 bg-white">
166
+ {data.slice(from, to).map((row, index) => (
167
+ <tr className={"divide-x divide-gray-200 " + (index % 2 ? "bg-gray-50" : "")} key={index}>
168
+ {metas.map((field) => (
169
+ <td
170
+ key={field.fid + index}
171
+ className={
172
+ getHeaderType(field) +
173
+ " whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 sm:pl-6"
174
+ }
175
+ >
176
+ {row[field.fid]}
177
+ </td>
178
+ ))}
179
+ </tr>
180
+ ))}
181
+ </tbody>
182
+ </table>
183
+ </Container>
184
+ );
185
+ };
186
+
187
+ export default DataTable;