@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.
- package/dist/App.d.ts +6 -0
- package/dist/assets/{transform.worker-5d54ff09.js.map → transform.worker-90e4f506.js.map} +1 -1
- package/dist/assets/viewQuery.worker-03404216.js.map +1 -0
- package/dist/components/pivotTable/index.d.ts +12 -0
- package/dist/components/pivotTable/inteface.d.ts +6 -0
- package/dist/components/pivotTable/leftTree.d.ts +10 -0
- package/dist/components/pivotTable/metricTable.d.ts +9 -0
- package/dist/components/pivotTable/store.d.ts +22 -0
- package/dist/components/pivotTable/topTree.d.ts +10 -0
- package/dist/components/pivotTable/utils.d.ts +6 -0
- package/dist/components/visualConfig/index.d.ts +3 -0
- package/dist/config.d.ts +2 -0
- package/dist/fields/aestheticFields.d.ts +2 -2
- package/dist/graphic-walker.es.js +23528 -23139
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +129 -129
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/interfaces.d.ts +7 -1
- package/dist/lib/insights/utils.d.ts +0 -2
- package/dist/lib/interfaces.d.ts +5 -3
- package/dist/store/commonStore.d.ts +2 -0
- package/dist/store/visualSpecStore.d.ts +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/vis/react-vega.d.ts +3 -1
- package/dist/vis/spec/encode.d.ts +2 -0
- package/dist/visualSettings/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/App.tsx +11 -1
- package/src/components/pivotTable/index.tsx +119 -0
- package/src/components/pivotTable/inteface.ts +8 -0
- package/src/components/pivotTable/leftTree.tsx +92 -0
- package/src/components/pivotTable/metricTable.tsx +107 -0
- package/src/components/pivotTable/store.tsx +66 -0
- package/src/components/pivotTable/topTree.tsx +77 -0
- package/src/components/pivotTable/utils.ts +141 -0
- package/src/components/visualConfig/index.tsx +76 -0
- package/src/config.ts +5 -1
- package/src/fields/aestheticFields.tsx +25 -4
- package/src/fields/components.tsx +2 -2
- package/src/fields/fieldsContext.tsx +2 -1
- package/src/interfaces.ts +7 -1
- package/src/lib/insights/explainByChildren.ts +11 -3
- package/src/lib/insights/explainBySelection.ts +14 -5
- package/src/lib/insights/explainValue.ts +10 -3
- package/src/lib/insights/utils.ts +0 -4
- package/src/lib/interfaces.ts +1 -3
- package/src/lib/op/aggregate.ts +9 -7
- package/src/locales/en-US.json +13 -3
- package/src/locales/ja-JP.json +188 -178
- package/src/locales/zh-CN.json +13 -3
- package/src/renderer/index.tsx +16 -2
- package/src/renderer/specRenderer.tsx +6 -2
- package/src/store/commonStore.ts +4 -0
- package/src/store/visualSpecStore.ts +14 -2
- package/src/utils/index.ts +8 -1
- package/src/vis/react-vega.tsx +31 -7
- package/src/vis/spec/aggregate.ts +3 -0
- package/src/vis/spec/encode.ts +5 -1
- package/src/vis/spec/view.ts +4 -2
- package/src/visualSettings/index.tsx +28 -4
- package/src/workers/transform.ts +1 -1
- package/dist/assets/viewQuery.worker-ffefc111.js.map +0 -1
package/dist/interfaces.d.ts
CHANGED
|
@@ -75,7 +75,7 @@ export interface IField {
|
|
|
75
75
|
analyticType: IAnalyticType;
|
|
76
76
|
cmp?: (a: any, b: any) => number;
|
|
77
77
|
computed?: boolean;
|
|
78
|
-
|
|
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[];
|
package/dist/lib/interfaces.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { IAggregator } from "../interfaces";
|
|
|
2
2
|
export interface IAggQuery {
|
|
3
3
|
op: 'aggregate';
|
|
4
4
|
groupBy: string[];
|
|
5
|
-
|
|
6
|
-
|
|
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;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/vis/react-vega.d.ts
CHANGED
|
@@ -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
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,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;
|