@kanaries/graphic-walker 0.3.4 → 0.3.6

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 (62) hide show
  1. package/dist/App.d.ts +6 -0
  2. package/dist/assets/{transform.worker-5d54ff09.js.map → transform.worker-90e4f506.js.map} +1 -1
  3. package/dist/assets/viewQuery.worker-03404216.js.map +1 -0
  4. package/dist/components/pivotTable/index.d.ts +12 -0
  5. package/dist/components/pivotTable/inteface.d.ts +6 -0
  6. package/dist/components/pivotTable/leftTree.d.ts +10 -0
  7. package/dist/components/pivotTable/metricTable.d.ts +9 -0
  8. package/dist/components/pivotTable/store.d.ts +22 -0
  9. package/dist/components/pivotTable/topTree.d.ts +10 -0
  10. package/dist/components/pivotTable/utils.d.ts +6 -0
  11. package/dist/components/visualConfig/index.d.ts +3 -0
  12. package/dist/config.d.ts +2 -0
  13. package/dist/fields/aestheticFields.d.ts +2 -2
  14. package/dist/graphic-walker.es.js +23528 -23139
  15. package/dist/graphic-walker.es.js.map +1 -1
  16. package/dist/graphic-walker.umd.js +129 -129
  17. package/dist/graphic-walker.umd.js.map +1 -1
  18. package/dist/interfaces.d.ts +7 -1
  19. package/dist/lib/insights/utils.d.ts +0 -2
  20. package/dist/lib/interfaces.d.ts +5 -3
  21. package/dist/store/commonStore.d.ts +2 -0
  22. package/dist/store/visualSpecStore.d.ts +1 -0
  23. package/dist/utils/index.d.ts +1 -0
  24. package/dist/vis/react-vega.d.ts +3 -1
  25. package/dist/vis/spec/encode.d.ts +2 -0
  26. package/dist/visualSettings/index.d.ts +3 -0
  27. package/package.json +1 -1
  28. package/src/App.tsx +11 -1
  29. package/src/components/pivotTable/index.tsx +119 -0
  30. package/src/components/pivotTable/inteface.ts +8 -0
  31. package/src/components/pivotTable/leftTree.tsx +92 -0
  32. package/src/components/pivotTable/metricTable.tsx +107 -0
  33. package/src/components/pivotTable/store.tsx +66 -0
  34. package/src/components/pivotTable/topTree.tsx +77 -0
  35. package/src/components/pivotTable/utils.ts +141 -0
  36. package/src/components/visualConfig/index.tsx +76 -0
  37. package/src/config.ts +5 -1
  38. package/src/fields/aestheticFields.tsx +25 -4
  39. package/src/fields/components.tsx +2 -2
  40. package/src/fields/fieldsContext.tsx +2 -1
  41. package/src/interfaces.ts +7 -1
  42. package/src/lib/insights/explainByChildren.ts +11 -3
  43. package/src/lib/insights/explainBySelection.ts +14 -5
  44. package/src/lib/insights/explainValue.ts +10 -3
  45. package/src/lib/insights/utils.ts +0 -4
  46. package/src/lib/interfaces.ts +1 -3
  47. package/src/lib/op/aggregate.ts +9 -7
  48. package/src/locales/en-US.json +13 -3
  49. package/src/locales/ja-JP.json +188 -178
  50. package/src/locales/zh-CN.json +13 -3
  51. package/src/renderer/index.tsx +16 -2
  52. package/src/renderer/specRenderer.tsx +6 -2
  53. package/src/store/commonStore.ts +4 -0
  54. package/src/store/visualSpecStore.ts +14 -2
  55. package/src/utils/index.ts +8 -1
  56. package/src/vis/react-vega.tsx +31 -7
  57. package/src/vis/spec/aggregate.ts +3 -0
  58. package/src/vis/spec/encode.ts +5 -1
  59. package/src/vis/spec/view.ts +4 -2
  60. package/src/visualSettings/index.tsx +28 -4
  61. package/src/workers/transform.ts +1 -1
  62. package/dist/assets/viewQuery.worker-ffefc111.js.map +0 -1
@@ -75,7 +75,7 @@ export interface IField {
75
75
  analyticType: IAnalyticType;
76
76
  cmp?: (a: any, b: any) => number;
77
77
  computed?: boolean;
78
- expressoion?: IExpression;
78
+ expression?: IExpression;
79
79
  }
80
80
  export interface IViewField extends IField {
81
81
  dragId: string;
@@ -140,6 +140,7 @@ export interface DraggableFieldState {
140
140
  radius: IViewField[];
141
141
  details: IViewField[];
142
142
  filters: IFilterField[];
143
+ text: IViewField[];
143
144
  }
144
145
  export interface IDraggableStateKey {
145
146
  id: keyof DraggableFieldState;
@@ -163,6 +164,11 @@ export interface IVisualConfig {
163
164
  showActions: boolean;
164
165
  interactiveScale: boolean;
165
166
  sorted: 'none' | 'ascending' | 'descending';
167
+ format: {
168
+ numberFormat?: string;
169
+ timeFormat?: string;
170
+ normalizedNumberFormat?: string;
171
+ };
166
172
  size: {
167
173
  mode: 'auto' | 'fixed';
168
174
  width: number;
@@ -1,10 +1,8 @@
1
1
  import { IField } from '../../interfaces';
2
- import { IAggQuery } from '../interfaces';
3
2
  export declare function groupByAnalyticTypes(fields: IField[]): {
4
3
  dimensions: IField[];
5
4
  measures: IField[];
6
5
  };
7
- export declare function meaList2AggProps(measures: IField[]): IAggQuery['agg'];
8
6
  export declare function complementaryFields(props: {
9
7
  selection: IField[];
10
8
  all: IField[];
@@ -2,9 +2,11 @@ import { IAggregator } from "../interfaces";
2
2
  export interface IAggQuery {
3
3
  op: 'aggregate';
4
4
  groupBy: string[];
5
- agg: {
6
- [field: string]: IAggregator;
7
- };
5
+ measures: {
6
+ field: string;
7
+ agg: IAggregator;
8
+ asFieldKey: string;
9
+ }[];
8
10
  }
9
11
  export interface IFoldQuery {
10
12
  op: 'fold';
@@ -14,6 +14,7 @@ export declare class CommonStore {
14
14
  };
15
15
  showDataConfig: boolean;
16
16
  showCodeExportPanel: boolean;
17
+ showVisualConfigPanel: boolean;
17
18
  filters: Filters;
18
19
  segmentKey: ISegmentKey;
19
20
  constructor();
@@ -24,6 +25,7 @@ export declare class CommonStore {
24
25
  setShowInsightBoard(show: boolean): void;
25
26
  showEmbededMenu(position: [number, number]): void;
26
27
  setShowCodeExportPanel(show: boolean): void;
28
+ setShowVisualConfigPanel(show: boolean): void;
27
29
  closeEmbededMenu(): void;
28
30
  initTempDS(): void;
29
31
  updateTempFields(fields: IMutField[]): void;
@@ -109,6 +109,7 @@ export declare class VizSpecStore {
109
109
  setFieldSort(stateKey: keyof DraggableFieldState, index: number, sortType: "none" | "ascending" | "descending"): void;
110
110
  applyDefaultSort(sortType?: "none" | "ascending" | "descending"): void;
111
111
  appendField(destinationKey: keyof DraggableFieldState, field: IViewField | undefined): void;
112
+ setVizFormatConfig(formatKey: keyof IVisualConfig['format'], value?: string): void;
112
113
  renderSpec(spec: Specification): void;
113
114
  destroy(): void;
114
115
  exportAsRaw(): string;
@@ -20,3 +20,4 @@ export declare function createCountField(): IViewField;
20
20
  export declare function getRange(nums: number[]): [number, number];
21
21
  export declare function makeNumbersBeautiful(nums: number[]): number[];
22
22
  export declare function classNames(...classes: string[]): string;
23
+ export declare function getMeaAggKey(meaKey: string, agg?: string | undefined): string;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { IViewField, IRow, IStackMode, IDarkMode, IThemeKey } from '../interfaces';
2
+ import { IViewField, IRow, IStackMode, IDarkMode, IThemeKey, IVisualConfig } from '../interfaces';
3
3
  export interface IReactVegaHandler {
4
4
  getSVGData: () => Promise<string[]>;
5
5
  getCanvasData: () => Promise<string[]>;
@@ -7,6 +7,7 @@ export interface IReactVegaHandler {
7
7
  downloadPNG: (filename?: string) => Promise<string[]>;
8
8
  }
9
9
  interface ReactVegaProps {
10
+ format: IVisualConfig['format'];
10
11
  rows: Readonly<IViewField[]>;
11
12
  columns: Readonly<IViewField[]>;
12
13
  dataSource: IRow[];
@@ -20,6 +21,7 @@ interface ReactVegaProps {
20
21
  shape?: IViewField;
21
22
  theta?: IViewField;
22
23
  radius?: IViewField;
24
+ text?: IViewField;
23
25
  details?: Readonly<IViewField[]>;
24
26
  showActions: boolean;
25
27
  layoutMode: string;
@@ -14,7 +14,9 @@ export interface IEncodeProps {
14
14
  theta: IViewField;
15
15
  radius: IViewField;
16
16
  details: Readonly<IViewField[]>;
17
+ text: IViewField;
17
18
  }
19
+ export declare function availableChannels(geomType: string): Set<string>;
18
20
  export declare function channelEncode(props: IEncodeProps): {
19
21
  [key: string]: any;
20
22
  };
@@ -1,9 +1,12 @@
1
1
  import React from 'react';
2
2
  import { IDarkMode } from '../interfaces';
3
3
  import { IReactVegaHandler } from '../vis/react-vega';
4
+ import { ToolbarItemProps } from '../components/toolbar';
4
5
  interface IVisualSettings {
5
6
  darkModePreference: IDarkMode;
6
7
  rendererHandler?: React.RefObject<IReactVegaHandler>;
8
+ exclude?: string[];
9
+ extra?: ToolbarItemProps[];
7
10
  }
8
11
  declare const _default: React.FunctionComponent<IVisualSettings>;
9
12
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanaries/graphic-walker",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "scripts": {
5
5
  "dev:front_end": "vite --host",
6
6
  "dev": "npm run dev:front_end",
package/src/App.tsx CHANGED
@@ -18,6 +18,8 @@ import SegmentNav from './segments/segmentNav';
18
18
  import DatasetConfig from './dataSource/datasetConfig';
19
19
  import { useCurrentMediaTheme } from './utils/media';
20
20
  import CodeExport from './components/codeExport';
21
+ import VisualConfig from './components/visualConfig';
22
+ import type { ToolbarItemProps } from './components/toolbar';
21
23
 
22
24
  export interface IGWProps {
23
25
  dataSource?: IRow[];
@@ -35,6 +37,10 @@ export interface IGWProps {
35
37
  themeKey?: IThemeKey;
36
38
  dark?: IDarkMode;
37
39
  storeRef?: React.MutableRefObject<IGlobalStore | null>;
40
+ toolbar?: {
41
+ extra?: ToolbarItemProps[];
42
+ exclude?: string[];
43
+ };
38
44
  }
39
45
 
40
46
  const App = observer<IGWProps>(function App(props) {
@@ -48,6 +54,7 @@ const App = observer<IGWProps>(function App(props) {
48
54
  fieldKeyGuard = true,
49
55
  themeKey = 'vega',
50
56
  dark = 'media',
57
+ toolbar,
51
58
  } = props;
52
59
  const { commonStore, vizStore } = useGlobalStore();
53
60
 
@@ -123,8 +130,9 @@ const App = observer<IGWProps>(function App(props) {
123
130
  style={{ marginTop: '0em', borderTop: 'none' }}
124
131
  className="m-4 p-4 border border-gray-200 dark:border-gray-700"
125
132
  >
126
- <VisualSettings rendererHandler={rendererRef} darkModePreference={dark} />
133
+ <VisualSettings rendererHandler={rendererRef} darkModePreference={dark} exclude={toolbar?.exclude} extra={toolbar?.extra} />
127
134
  <CodeExport />
135
+ <VisualConfig />
128
136
  <div className="md:grid md:grid-cols-12 xl:grid-cols-6">
129
137
  <div className="md:col-span-3 xl:col-span-1">
130
138
  <DatasetFields />
@@ -185,3 +193,5 @@ const App = observer<IGWProps>(function App(props) {
185
193
  });
186
194
 
187
195
  export default App;
196
+
197
+ export type { ToolbarItemProps };
@@ -0,0 +1,119 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { StoreWrapper, useGlobalStore } from '../../store';
3
+ import { PivotTableDataProps, PivotTableStoreWrapper, usePivotTableStore } from './store';
4
+ import { observer } from 'mobx-react-lite';
5
+ import LeftTree from './leftTree';
6
+ import TopTree from './topTree';
7
+ import {
8
+ DeepReadonly,
9
+ DraggableFieldState,
10
+ IDarkMode,
11
+ IRow,
12
+ IThemeKey,
13
+ IViewField,
14
+ IVisualConfig,
15
+ } from '../../interfaces';
16
+ import { INestNode } from './inteface';
17
+ import { buildMetricTableFromNestTree, buildNestTree } from './utils';
18
+ import { unstable_batchedUpdates } from 'react-dom';
19
+ import MetricTable from './metricTable';
20
+ import { toJS } from 'mobx';
21
+
22
+ // const PTStateConnector = observer(function StateWrapper (props: PivotTableProps) {
23
+ // const store = usePivotTableStore();
24
+ // const { vizStore } = useGlobalStore();
25
+ // const { draggableFieldState } = vizStore;
26
+ // const { rows, columns } = draggableFieldState;
27
+ // return (
28
+ // <PivotTable
29
+ // {...props}
30
+ // draggableFieldState={draggableFieldState}
31
+ // visualConfig={visualConfig}
32
+ // />
33
+ // );
34
+ // })
35
+
36
+ interface PivotTableProps {
37
+ themeKey?: IThemeKey;
38
+ dark?: IDarkMode;
39
+ data: IRow[];
40
+ loading: boolean;
41
+ draggableFieldState: DeepReadonly<DraggableFieldState>;
42
+ visualConfig: IVisualConfig;
43
+ }
44
+ const PivotTable: React.FC<PivotTableProps> = (props) => {
45
+ const { data, draggableFieldState } = props;
46
+ // const store = usePivotTableStore();
47
+ // const { vizStore } = useGlobalStore();
48
+ // const { draggableFieldState } = vizStore;
49
+ const { rows, columns } = draggableFieldState;
50
+ const [leftTree, setLeftTree] = useState<INestNode | null>(null);
51
+ const [topTree, setTopTree] = useState<INestNode | null>(null);
52
+ const [metricTable, setMetricTable] = useState<any[][]>([]);
53
+
54
+ const dimsInRow = useMemo(() => {
55
+ return rows.filter((f) => f.analyticType === 'dimension');
56
+ }, [rows]);
57
+
58
+ const dimsInColumn = useMemo(() => {
59
+ return columns.filter((f) => f.analyticType === 'dimension');
60
+ }, [columns]);
61
+
62
+ const measInRow = useMemo(() => {
63
+ return rows.filter((f) => f.analyticType === 'measure');
64
+ }, [rows]);
65
+
66
+ const measInColumn = useMemo(() => {
67
+ return columns.filter((f) => f.analyticType === 'measure');
68
+ }, [columns]);
69
+
70
+ useEffect(() => {
71
+ if ((dimsInRow.length > 0 || dimsInColumn.length > 0) && data.length > 0) {
72
+ const lt = buildNestTree(
73
+ dimsInRow.map((d) => d.fid),
74
+ data
75
+ );
76
+ const tt = buildNestTree(
77
+ dimsInColumn.map((d) => d.fid),
78
+ data
79
+ );
80
+ const metric = buildMetricTableFromNestTree(lt, tt, data);
81
+ // debugger
82
+ unstable_batchedUpdates(() => {
83
+ setLeftTree(lt);
84
+ setTopTree(tt);
85
+ setMetricTable(metric);
86
+ });
87
+ }
88
+ }, [dimsInRow, dimsInColumn, data]);
89
+
90
+ // const { leftTree, topTree, metricTable } = store;
91
+ return (
92
+ <div className="flex">
93
+ <table className="border border-gray-300 border-collapse">
94
+ <thead className="border border-gray-300">
95
+ {new Array(dimsInColumn.length + (measInColumn.length > 0 ? 1 : 0)).fill(0).map((_, i) => (
96
+ <tr className="" key={i}>
97
+ <td className="p-2 m-1 text-xs text-white border border-gray-300" colSpan={dimsInRow.length + (measInRow.length > 0 ? 1 : 0)}>_</td>
98
+ </tr>
99
+ ))}
100
+ </thead>
101
+ {leftTree && <LeftTree data={leftTree} dimsInRow={dimsInRow} measInRow={measInRow} />}
102
+ </table>
103
+ <table className="border border-gray-300 border-collapse">
104
+ {topTree && <TopTree data={topTree} dimsInCol={dimsInColumn} measInCol={measInColumn} />}
105
+ {metricTable && <MetricTable matrix={metricTable} meaInColumns={measInColumn} meaInRows={measInRow} />}
106
+ </table>
107
+ </div>
108
+ );
109
+ };
110
+
111
+ export default PivotTable;
112
+
113
+ // const PivotTableApp: React.FC<PivotTableProps> = (props) => {
114
+ // return (
115
+ // <PivotTableStoreWrapper {...props}>
116
+ // <PivotTable />
117
+ // </PivotTableStoreWrapper>
118
+ // );
119
+ // };
@@ -0,0 +1,8 @@
1
+ import { IAggregator } from "../../interfaces";
2
+
3
+ export interface INestNode {
4
+ key: string;
5
+ value: string;
6
+ fieldKey: string;
7
+ children: INestNode[];
8
+ }
@@ -0,0 +1,92 @@
1
+ import React, { ReactNode, useMemo } from 'react';
2
+ import { INestNode } from './inteface';
3
+ import { IField } from '../../interfaces';
4
+
5
+ function getChildCount(node: INestNode): number {
6
+ if (node.children.length === 0) {
7
+ return 1;
8
+ }
9
+ return node.children.map(getChildCount).reduce((a, b) => a + b, 0);
10
+ }
11
+
12
+ /**
13
+ * render pivot table left tree table
14
+ * @param node
15
+ * @param dimsInRow
16
+ * @param depth
17
+ * @param cellRows
18
+ * @returns
19
+ */
20
+ function renderTree(node: INestNode, dimsInRow: IField[], depth: number, cellRows: ReactNode[][], meaNumber: number) {
21
+ const childrenSize = getChildCount(node);
22
+ if (depth > dimsInRow.length) {
23
+ return;
24
+ }
25
+ cellRows[cellRows.length - 1].push(
26
+ <td
27
+ key={`${depth}-${node.fieldKey}-${node.value}`}
28
+ className="whitespace-nowrap p-2 text-xs text-gray-500 m-1 border border-gray-300"
29
+ rowSpan={childrenSize * Math.max(meaNumber, 1)}
30
+ >
31
+ {node.value}
32
+ </td>
33
+ );
34
+ for (let i = 0; i < node.children.length; i++) {
35
+ const child = node.children[i];
36
+ renderTree(child, dimsInRow, depth + 1, cellRows, meaNumber);
37
+ if (i < node.children.length - 1) {
38
+ cellRows.push([]);
39
+ }
40
+ }
41
+ }
42
+
43
+ export interface TreeProps {
44
+ data: INestNode;
45
+ dimsInRow: IField[];
46
+ measInRow: IField[];
47
+ }
48
+ const LeftTree: React.FC<TreeProps> = (props) => {
49
+ const { data, dimsInRow, measInRow } = props;
50
+ const nodeCells: ReactNode[] = useMemo(() => {
51
+ const cellRows: ReactNode[][] = [[]];
52
+ renderTree(data, dimsInRow, 0, cellRows, measInRow.length);
53
+ cellRows[0].shift();
54
+ if (measInRow.length > 0) {
55
+ const ans: ReactNode[][] = [];
56
+ for (let row of cellRows) {
57
+ ans.push([
58
+ ...row,
59
+ <td
60
+ key={`0-${measInRow[0].fid}-${measInRow[0].aggName}`}
61
+ className="whitespace-nowrap p-2 text-xs text-gray-500 m-1 border border-gray-300"
62
+ >
63
+ {measInRow[0].aggName}({measInRow[0].name})
64
+ </td>,
65
+ ]);
66
+ for (let j = 1; j < measInRow.length; j++) {
67
+ ans.push([
68
+ <td
69
+ key={`${j}-${measInRow[j].fid}-${measInRow[j].aggName}`}
70
+ className="whitespace-nowrap p-2 text-xs text-gray-500 m-1 border border-gray-300"
71
+ >
72
+ {measInRow[j].aggName}({measInRow[j].name})
73
+ </td>,
74
+ ]);
75
+ }
76
+ }
77
+ return ans;
78
+ }
79
+ return cellRows;
80
+ }, [data, dimsInRow, measInRow]);
81
+ return (
82
+ <thead className="bg-gray-50 border border-gray-300 border border-gray-300">
83
+ {nodeCells.map((row, rIndex) => (
84
+ <tr className="border border-gray-300" key={rIndex}>
85
+ {row}
86
+ </tr>
87
+ ))}
88
+ </thead>
89
+ );
90
+ };
91
+
92
+ export default LeftTree;
@@ -0,0 +1,107 @@
1
+ import React from 'react';
2
+ import { IField, IRow } from '../../interfaces';
3
+ import { getMeaAggKey } from '../../utils';
4
+
5
+ interface MetricTableProps {
6
+ matrix: any[][];
7
+ meaInRows: IField[];
8
+ meaInColumns: IField[];
9
+ }
10
+
11
+ function getCellData (cell: IRow, measure: IField) {
12
+ const meaKey = getMeaAggKey(measure.fid, measure.aggName);
13
+ if (cell[meaKey] === undefined) {
14
+ return '--';
15
+ }
16
+ return cell[meaKey];
17
+ }
18
+ const MetricTable: React.FC<MetricTableProps> = (props) => {
19
+ const { matrix, meaInRows, meaInColumns } = props;
20
+
21
+ return (
22
+ <tbody className="bg-white border-r border-b border-gray-300">
23
+ {matrix.map((row, rIndex) => {
24
+ if (meaInRows.length !== 0) {
25
+ return meaInRows.map((rowMea, rmIndex) => {
26
+ return (
27
+ <tr className="divide-x divide-gray-200" key={`${rIndex}-${rowMea.fid}-${rowMea.aggName}`}>
28
+ {
29
+ row.flatMap((cell, cIndex) => {
30
+ cell = cell ?? {};
31
+ if (meaInColumns.length !== 0) {
32
+ return meaInColumns.map((colMea, cmIndex) => (
33
+ <td
34
+ className="whitespace-nowrap p-2 text-xs text-gray-500"
35
+ key={`${rIndex}-${cIndex}-${rowMea.fid}-${rowMea.aggName}-${colMea.fid}-${colMea.aggName}`}
36
+ >
37
+ {getCellData(cell, rowMea)} , {getCellData(cell, colMea)}
38
+ </td>
39
+ ));
40
+ }
41
+ return (
42
+ <td
43
+ className="whitespace-nowrap p-2 text-xs text-gray-500"
44
+ key={`${rIndex}-${cIndex}-${rowMea.fid}-${rowMea.aggName}`}
45
+ >
46
+ {getCellData(cell, rowMea)}
47
+ </td>
48
+ );
49
+ })
50
+ }
51
+ </tr>
52
+ );
53
+ });
54
+ }
55
+ return (
56
+ <tr className="divide-x divide-gray-200" key={rIndex}>
57
+ {row.flatMap((cell, cIndex) => {
58
+ cell = cell ?? {};
59
+ if (meaInRows.length === 0 && meaInColumns.length !== 0) {
60
+ return meaInColumns.map((colMea, cmIndex) => (
61
+ <td
62
+ className="whitespace-nowrap p-2 text-xs text-gray-500"
63
+ key={`${rIndex}-${cIndex}-${cmIndex}-${colMea.fid}-${colMea.aggName}`}
64
+ >
65
+ {
66
+ getCellData(cell, colMea)
67
+ }
68
+ </td>
69
+ ));
70
+ }else if (meaInRows.length === 0 && meaInColumns.length === 0) {
71
+ return (
72
+ <td
73
+ className="whitespace-nowrap p-2 text-xs text-gray-500"
74
+ key={`${rIndex}-${cIndex}`}
75
+ >
76
+ {`True`}
77
+ </td>
78
+ );
79
+ } else {
80
+ return meaInRows.flatMap((rowMea, rmIndex) => (
81
+ <td
82
+ className="whitespace-nowrap p-2 text-xs text-gray-500"
83
+ key={`${rIndex}-${cIndex}-${rmIndex}-${rowMea.fid}-${rowMea.aggName}`}
84
+ >
85
+ {meaInColumns.flatMap((colMea, cmIndex) => (
86
+ <td
87
+ className="whitespace-nowrap p-2 text-xs text-gray-500"
88
+ key={`${rIndex}-${cIndex}-${rmIndex}-${cmIndex}-${colMea.fid}-${colMea.aggName}`}
89
+ >
90
+ { getCellData(cell, rowMea) } , { getCellData(cell, colMea) }
91
+ </td>
92
+ ))}
93
+ </td>
94
+ ));
95
+ }
96
+ // return measures.map((mea) => (
97
+ // <td className='whitespace-nowrap p-2 text-xs text-gray-500' key={`${rIndex}-${cIndex}-${mea.fid}`}>{cell[mea.fid] ?? '--'}</td>
98
+ // ));
99
+ })}
100
+ </tr>
101
+ );
102
+ })}
103
+ </tbody>
104
+ );
105
+ };
106
+
107
+ export default MetricTable;
@@ -0,0 +1,66 @@
1
+ import { makeAutoObservable, observable } from 'mobx';
2
+ import { INestNode } from './inteface';
3
+ import { IAggQuery } from '../../lib/interfaces';
4
+ import { queryView } from '../../lib/viewQuery';
5
+ import { IField, IRow } from '../../interfaces';
6
+ import React, { createContext, useContext, useEffect } from 'react';
7
+ import { getMeaAggKey } from '../../utils';
8
+
9
+ class PivotTableStore {
10
+ public leftTree: INestNode | null = null;
11
+ public topTree: INestNode | null = null;
12
+ public metricTable: any[][] = [];
13
+ public dataSource: IRow[] = [];
14
+ public metas: IField[] = [];
15
+ public viewData: IRow[] = [];
16
+ constructor() {
17
+ makeAutoObservable(this, {
18
+ leftTree: observable.ref,
19
+ topTree: observable.ref,
20
+ metricTable: observable.ref,
21
+ dataSource: observable.ref,
22
+ metas: observable.ref,
23
+ });
24
+ }
25
+ public init(dataSource: IRow[], metas: IField[]) {
26
+ this.dataSource = dataSource ?? [];
27
+ this.metas = metas ?? [];
28
+ this.leftTree = null;
29
+ this.metricTable = [];
30
+ this.topTree = null;
31
+ this.viewData = [];
32
+ }
33
+ public async queryData(leftQuery: IAggQuery, topQuery: IAggQuery) {
34
+ const viewQuery: IAggQuery = {
35
+ op: 'aggregate',
36
+ groupBy: leftQuery.groupBy.concat(topQuery.groupBy),
37
+ measures: leftQuery.measures.concat(topQuery.measures).map(mea => ({
38
+ field: mea.field,
39
+ agg: mea.agg,
40
+ asFieldKey: getMeaAggKey(mea.field, mea.agg)
41
+ }))
42
+ };
43
+ const viewData = queryView(this.dataSource, this.metas, viewQuery);
44
+ this.viewData = viewData;
45
+ }
46
+ }
47
+
48
+ const initStore = new PivotTableStore();
49
+ const PTContext = createContext<PivotTableStore>(initStore);
50
+
51
+ export interface PivotTableDataProps {
52
+ data: IRow[];
53
+ metas: IField[];
54
+ }
55
+ export const PivotTableStoreWrapper: React.FC<PivotTableDataProps> = (props) => {
56
+ const { data, metas } = props;
57
+ useEffect(() => {
58
+ initStore.init(data, metas);
59
+ }, [data, metas]);
60
+ return <PTContext.Provider value={initStore}>{props.children}</PTContext.Provider>;
61
+ };
62
+
63
+
64
+ export function usePivotTableStore () {
65
+ return useContext(PTContext)
66
+ }
@@ -0,0 +1,77 @@
1
+ import React, { ReactNode, useMemo } from 'react';
2
+ import { INestNode } from './inteface';
3
+ import { IField } from '../../interfaces';
4
+
5
+ function getChildCount(node: INestNode): number {
6
+ if (node.children.length === 0) {
7
+ return 1;
8
+ }
9
+ return node.children.map(getChildCount).reduce((a, b) => a + b, 0);
10
+ }
11
+
12
+ /**
13
+ * render pivot table left tree table
14
+ * @param node
15
+ * @param dimsInCol
16
+ * @param depth
17
+ * @param cellRows
18
+ * @returns
19
+ */
20
+ function renderTree(node: INestNode, dimsInCol: IField[], depth: number, cellRows: ReactNode[][], meaNumber: number) {
21
+ const childrenSize = getChildCount(node);
22
+ if (depth > dimsInCol.length) {
23
+ return;
24
+ }
25
+ cellRows[depth].push(
26
+ <td
27
+ key={`${depth}-${node.fieldKey}-${node.value}`}
28
+ className="whitespace-nowrap p-2 text-xs text-gray-500 m-1 border border-gray-300"
29
+ colSpan={childrenSize * Math.max(meaNumber, 1)}
30
+ >
31
+ {node.value}
32
+ </td>
33
+ );
34
+ for (let i = 0; i < node.children.length; i++) {
35
+ const child = node.children[i];
36
+ renderTree(child, dimsInCol, depth + 1, cellRows, meaNumber);
37
+ }
38
+ }
39
+
40
+ export interface TreeProps {
41
+ data: INestNode;
42
+ dimsInCol: IField[];
43
+ measInCol: IField[];
44
+ }
45
+ const TopTree: React.FC<TreeProps> = (props) => {
46
+ const { data, dimsInCol, measInCol } = props;
47
+ const nodeCells: ReactNode[] = useMemo(() => {
48
+ const cellRows: ReactNode[][] = new Array(dimsInCol.length + 1).fill(0).map(() => []);
49
+ renderTree(data, dimsInCol, 0, cellRows, measInCol.length);
50
+ const totalChildrenSize = cellRows[cellRows.length - 1].length;
51
+ cellRows.push(
52
+ new Array(totalChildrenSize).fill(0).flatMap(() =>
53
+ measInCol.map((m) => (
54
+ <td
55
+ key={`${cellRows.length}-${m.fid}-${m.aggName}`}
56
+ className="whitespace-nowrap p-2 text-xs text-gray-500 m-1 border border-gray-300"
57
+ >
58
+ {m.aggName}({m.name})
59
+ </td>
60
+ ))
61
+ )
62
+ );
63
+ cellRows.shift();
64
+ return cellRows;
65
+ }, [data, dimsInCol, measInCol]);
66
+ return (
67
+ <thead className="border border-gray-300 bg-gray-50 border border-gray-300">
68
+ {nodeCells.map((row, rIndex) => (
69
+ <tr className="border border-gray-300" key={rIndex}>
70
+ {row}
71
+ </tr>
72
+ ))}
73
+ </thead>
74
+ );
75
+ };
76
+
77
+ export default TopTree;