@kanaries/graphic-walker 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +23167 -22784
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +127 -127
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/interfaces.d.ts +6 -0
- 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/package.json +1 -1
- package/src/App.tsx +2 -0
- 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 +6 -0
- 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 +11 -2
- package/src/locales/ja-JP.json +187 -178
- package/src/locales/zh-CN.json +11 -2
- 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 +12 -0
- package/src/utils/index.ts +7 -0
- 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 +11 -0
- package/dist/assets/viewQuery.worker-ffefc111.js.map +0 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { IRow } from "../../interfaces";
|
|
2
|
+
import { INestNode } from "./inteface";
|
|
3
|
+
|
|
4
|
+
const key_prefix = 'nk_';
|
|
5
|
+
|
|
6
|
+
export function insertNode (tree: INestNode, layerKeys: string[], nodeData: IRow, depth: number) {
|
|
7
|
+
if (depth >= layerKeys.length) {
|
|
8
|
+
// tree.key = nodeData[layerKeys[depth]];
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const key = nodeData[layerKeys[depth]];
|
|
12
|
+
// console.log({
|
|
13
|
+
// key,
|
|
14
|
+
// nodeData,
|
|
15
|
+
// layerKeys,
|
|
16
|
+
// depth
|
|
17
|
+
// })
|
|
18
|
+
let child = tree.children.find((c) => c.key === key);
|
|
19
|
+
if (!child) {
|
|
20
|
+
// console.log(key, tree.children.map(c => c.key))
|
|
21
|
+
// insertNode(child, layerKeys, nodeData, depth + 1);
|
|
22
|
+
// return;
|
|
23
|
+
child = {
|
|
24
|
+
key,
|
|
25
|
+
value: key,
|
|
26
|
+
fieldKey: layerKeys[depth],
|
|
27
|
+
children: [],
|
|
28
|
+
}
|
|
29
|
+
tree.children.push(child);
|
|
30
|
+
}
|
|
31
|
+
insertNode(child, layerKeys, nodeData, depth + 1);
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
const ROOT_KEY = '__root';
|
|
35
|
+
|
|
36
|
+
export function buildNestTree (layerKeys: string[], data: IRow[]): INestNode {
|
|
37
|
+
const tree: INestNode = {
|
|
38
|
+
key: ROOT_KEY,
|
|
39
|
+
value: 'root',
|
|
40
|
+
fieldKey: 'root',
|
|
41
|
+
children: [],
|
|
42
|
+
};
|
|
43
|
+
for (let row of data) {
|
|
44
|
+
insertNode(tree, layerKeys, row, 0);
|
|
45
|
+
}
|
|
46
|
+
return tree;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class NodeIterator {
|
|
50
|
+
public tree: INestNode;
|
|
51
|
+
public nodeStack: INestNode[] = [];
|
|
52
|
+
public current: INestNode | null = null;
|
|
53
|
+
constructor (tree: INestNode) {
|
|
54
|
+
this.tree = tree;
|
|
55
|
+
}
|
|
56
|
+
public first () {
|
|
57
|
+
let node = this.tree
|
|
58
|
+
this.nodeStack = [node];
|
|
59
|
+
while (node.children.length > 0) {
|
|
60
|
+
this.nodeStack.push(node.children[0])
|
|
61
|
+
node = node.children[0]
|
|
62
|
+
}
|
|
63
|
+
this.current = node;
|
|
64
|
+
return this.current;
|
|
65
|
+
}
|
|
66
|
+
public next (): INestNode | null {
|
|
67
|
+
let cursorMoved = false;
|
|
68
|
+
let counter = 0
|
|
69
|
+
while (this.nodeStack.length > 1) {
|
|
70
|
+
counter++
|
|
71
|
+
if (counter > 100) break;
|
|
72
|
+
let node = this.nodeStack[this.nodeStack.length - 1];
|
|
73
|
+
let parent = this.nodeStack[this.nodeStack.length - 2];
|
|
74
|
+
let nodeIndex = parent.children.findIndex(n => n.key === node!.key);
|
|
75
|
+
if (nodeIndex === -1) break;
|
|
76
|
+
// console.log(this.nodeStack.map(n => `${n.fieldKey}-${n.value}`))
|
|
77
|
+
if (cursorMoved) {
|
|
78
|
+
if (node.children.length > 0) {
|
|
79
|
+
this.nodeStack.push(node.children[0]);
|
|
80
|
+
continue;
|
|
81
|
+
} else {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
if (nodeIndex < parent.children.length - 1) {
|
|
86
|
+
this.nodeStack.pop();
|
|
87
|
+
this.nodeStack.push(parent.children[nodeIndex + 1])
|
|
88
|
+
cursorMoved = true
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (nodeIndex >= parent.children.length - 1) {
|
|
92
|
+
this.nodeStack.pop();
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (cursorMoved) {
|
|
98
|
+
this.current = this.nodeStack[this.nodeStack.length - 1] || null;
|
|
99
|
+
} else {
|
|
100
|
+
this.current = null;
|
|
101
|
+
}
|
|
102
|
+
// console.log(this.current)
|
|
103
|
+
return this.current;
|
|
104
|
+
}
|
|
105
|
+
public predicates (): { key: string; value: any }[] {
|
|
106
|
+
return this.nodeStack.filter(node => node.key !== ROOT_KEY).map(node => ({
|
|
107
|
+
key: node.fieldKey,
|
|
108
|
+
value: node.value
|
|
109
|
+
}))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function buildMetricTableFromNestTree (leftTree: INestNode, topTree: INestNode, data: IRow[]): (IRow | null)[][] {
|
|
114
|
+
const mat: any[][] = [];
|
|
115
|
+
const iteLeft = new NodeIterator(leftTree);
|
|
116
|
+
const iteTop = new NodeIterator(topTree);
|
|
117
|
+
// console.log(iteLeft, iteTop)
|
|
118
|
+
iteLeft.first();
|
|
119
|
+
// return mat;
|
|
120
|
+
while (iteLeft.current !== null) {
|
|
121
|
+
const vec: any[] = [];
|
|
122
|
+
iteTop.first();
|
|
123
|
+
while (iteTop.current !== null) {
|
|
124
|
+
const predicates = iteLeft.predicates().concat(iteTop.predicates());
|
|
125
|
+
const row = data.find(r => predicates.every(pre => r[pre.key] === pre.value))
|
|
126
|
+
vec.push(row)
|
|
127
|
+
iteTop.next();
|
|
128
|
+
}
|
|
129
|
+
mat.push(vec)
|
|
130
|
+
iteLeft.next();
|
|
131
|
+
}
|
|
132
|
+
return mat;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getAllChildrenSize (node: INestNode, depth: number): number {
|
|
136
|
+
if (depth === 0) {
|
|
137
|
+
return node.children.length;
|
|
138
|
+
}
|
|
139
|
+
return node.children.reduce((acc, child) => acc + getAllChildrenSize(child, depth + 1), 0)
|
|
140
|
+
|
|
141
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { observer } from 'mobx-react-lite';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { useGlobalStore } from '../../store';
|
|
4
|
+
import Modal from '../modal';
|
|
5
|
+
import { IVisualConfig } from '../../interfaces';
|
|
6
|
+
import PrimaryButton from '../button/primary';
|
|
7
|
+
import DefaultButton from '../button/default';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
|
|
10
|
+
const VisualConfigPanel: React.FC = (props) => {
|
|
11
|
+
const { commonStore, vizStore } = useGlobalStore();
|
|
12
|
+
const { showVisualConfigPanel } = commonStore;
|
|
13
|
+
const { visualConfig } = vizStore;
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const formatConfigList: (keyof IVisualConfig['format'])[] = [
|
|
16
|
+
'numberFormat',
|
|
17
|
+
'timeFormat',
|
|
18
|
+
'normalizedNumberFormat',
|
|
19
|
+
];
|
|
20
|
+
const [format, setFormat] = useState<IVisualConfig['format']>({
|
|
21
|
+
numberFormat: visualConfig.format.numberFormat,
|
|
22
|
+
timeFormat: visualConfig.format.timeFormat,
|
|
23
|
+
normalizedNumberFormat: visualConfig.format.normalizedNumberFormat,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Modal
|
|
28
|
+
show={showVisualConfigPanel}
|
|
29
|
+
onClose={() => {
|
|
30
|
+
commonStore.setShowVisualConfigPanel(false);
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<div>
|
|
34
|
+
<h2 className='text-lg mb-4'>{t('config.format')}</h2>
|
|
35
|
+
<p className='text-xs'>Format guides docs: <a target="_blank" className='underline text-blue-500' href="https://github.com/d3/d3-format#locale_format">read here</a></p>
|
|
36
|
+
{formatConfigList.map((fc) => (
|
|
37
|
+
<div className="my-2" key={fc}>
|
|
38
|
+
<label className="block text-xs font-medium leading-6 text-gray-900">{t(`config.${fc}`)}</label>
|
|
39
|
+
<div className="mt-1">
|
|
40
|
+
<input
|
|
41
|
+
type="text"
|
|
42
|
+
className="block w-full rounded-md border-0 py-1 px-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
43
|
+
value={format[fc] ?? ''}
|
|
44
|
+
onChange={(e) => {
|
|
45
|
+
setFormat((f) => ({
|
|
46
|
+
...f,
|
|
47
|
+
[fc]: e.target.value,
|
|
48
|
+
}));
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
<div className='mt-4'>
|
|
55
|
+
<PrimaryButton
|
|
56
|
+
text={t('actions.confirm')}
|
|
57
|
+
className='mr-2'
|
|
58
|
+
onClick={() => {
|
|
59
|
+
vizStore.setVisualConfig('format', format);
|
|
60
|
+
commonStore.setShowVisualConfigPanel(false);
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
<DefaultButton
|
|
64
|
+
text={t('actions.cancel')}
|
|
65
|
+
className='mr-2'
|
|
66
|
+
onClick={() => {
|
|
67
|
+
commonStore.setShowVisualConfigPanel(false);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</Modal>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default observer(VisualConfigPanel);
|
package/src/config.ts
CHANGED
|
@@ -11,7 +11,9 @@ export const GEMO_TYPES: Readonly<string[]> = [
|
|
|
11
11
|
'tick',
|
|
12
12
|
'rect',
|
|
13
13
|
'arc',
|
|
14
|
+
'text',
|
|
14
15
|
'boxplot',
|
|
16
|
+
'table'
|
|
15
17
|
] as const;
|
|
16
18
|
|
|
17
19
|
export const STACK_MODE: Readonly<IStackMode[]> = [
|
|
@@ -47,7 +49,9 @@ export const CHANNEL_LIMIT = {
|
|
|
47
49
|
size: 1,
|
|
48
50
|
shape: 1,
|
|
49
51
|
theta: 1,
|
|
50
|
-
radius: 1
|
|
52
|
+
radius: 1,
|
|
53
|
+
details: Infinity,
|
|
54
|
+
text: 1,
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export const MetaFieldKeys: Array<keyof DraggableFieldState> = [
|
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { Droppable } from "@kanaries/react-beautiful-dnd";
|
|
3
3
|
import { DRAGGABLE_STATE_KEYS } from './fieldsContext';
|
|
4
4
|
import { AestheticFieldContainer } from './components'
|
|
5
5
|
import SingleEncodeEditor from './encodeFields/singleEncodeEditor';
|
|
6
|
+
import { observer } from 'mobx-react-lite';
|
|
7
|
+
import { useGlobalStore } from '../store';
|
|
6
8
|
|
|
7
|
-
const aestheticFields = DRAGGABLE_STATE_KEYS.filter(f => ['color', 'opacity', 'size', 'shape', 'details'].includes(f.id));
|
|
9
|
+
const aestheticFields = DRAGGABLE_STATE_KEYS.filter(f => ['color', 'opacity', 'size', 'shape', 'details', 'text'].includes(f.id));
|
|
8
10
|
|
|
9
11
|
const AestheticFields: React.FC = props => {
|
|
12
|
+
const { vizStore } = useGlobalStore();
|
|
13
|
+
const { visualConfig } = vizStore;
|
|
14
|
+
const { geoms } = visualConfig;
|
|
15
|
+
|
|
16
|
+
const channels = useMemo(() => {
|
|
17
|
+
switch (geoms[0]) {
|
|
18
|
+
case 'arc':
|
|
19
|
+
case 'line':
|
|
20
|
+
case 'area':
|
|
21
|
+
case 'boxplot':
|
|
22
|
+
return aestheticFields.filter(f => f.id !== 'shape');
|
|
23
|
+
case 'text':
|
|
24
|
+
return aestheticFields.filter(f => f.id === 'text' || f.id === 'color' || f.id === 'size' || f.id === 'opacity');
|
|
25
|
+
case 'table':
|
|
26
|
+
return []
|
|
27
|
+
default:
|
|
28
|
+
return aestheticFields.filter(f => f.id !== 'text');
|
|
29
|
+
}
|
|
30
|
+
}, [geoms[0]])
|
|
10
31
|
return <div>
|
|
11
32
|
{
|
|
12
|
-
|
|
33
|
+
channels.map(dkey => <AestheticFieldContainer name={dkey.id} key={dkey.id}>
|
|
13
34
|
<Droppable droppableId={dkey.id} direction="horizontal">
|
|
14
35
|
{(provided, snapshot) => (
|
|
15
36
|
// <OBFieldContainer dkey={dkey} provided={provided} />
|
|
@@ -21,4 +42,4 @@ const AestheticFields: React.FC = props => {
|
|
|
21
42
|
</div>
|
|
22
43
|
}
|
|
23
44
|
|
|
24
|
-
export default AestheticFields;
|
|
45
|
+
export default observer(AestheticFields);
|
|
@@ -49,7 +49,8 @@ export const DRAGGABLE_STATE_KEYS: Readonly<IDraggableStateKey[]> = [
|
|
|
49
49
|
{ id: 'theta', mode: 1 },
|
|
50
50
|
{ id: 'radius', mode: 1 },
|
|
51
51
|
{ id: 'filters', mode: 1 },
|
|
52
|
-
{ id: 'details', mode: 1 }
|
|
52
|
+
{ id: 'details', mode: 1 },
|
|
53
|
+
{ id: 'text', mode: 1 },
|
|
53
54
|
] as const;
|
|
54
55
|
|
|
55
56
|
export const AGGREGATOR_LIST: Readonly<string[]> = [
|
package/src/interfaces.ts
CHANGED
|
@@ -161,6 +161,7 @@ export interface DraggableFieldState {
|
|
|
161
161
|
radius: IViewField[];
|
|
162
162
|
details: IViewField[];
|
|
163
163
|
filters: IFilterField[];
|
|
164
|
+
text: IViewField[];
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
export interface IDraggableStateKey {
|
|
@@ -191,6 +192,11 @@ export interface IVisualConfig {
|
|
|
191
192
|
showActions: boolean;
|
|
192
193
|
interactiveScale: boolean;
|
|
193
194
|
sorted: 'none' | 'ascending' | 'descending';
|
|
195
|
+
format: {
|
|
196
|
+
numberFormat?: string;
|
|
197
|
+
timeFormat?: string;
|
|
198
|
+
normalizedNumberFormat?: string;
|
|
199
|
+
};
|
|
194
200
|
size: {
|
|
195
201
|
mode: 'auto' | 'fixed';
|
|
196
202
|
width: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IMeasure, IRow } from '../../interfaces';
|
|
2
|
-
import { IPredicate, checkChildOutlier, checkMajorFactor, filterByPredicates } from '../../utils';
|
|
2
|
+
import { IPredicate, checkChildOutlier, checkMajorFactor, filterByPredicates, getMeaAggKey } from '../../utils';
|
|
3
3
|
import { aggregate } from '../op/aggregate';
|
|
4
4
|
|
|
5
5
|
export function explainByChildren(
|
|
@@ -16,7 +16,11 @@ export function explainByChildren(
|
|
|
16
16
|
const viewData = aggregate(dataSource, {
|
|
17
17
|
groupBy: dimensions,
|
|
18
18
|
op: 'aggregate',
|
|
19
|
-
|
|
19
|
+
measures: measures.map(mea => ({
|
|
20
|
+
field: mea.key,
|
|
21
|
+
agg: mea.op,
|
|
22
|
+
asFieldKey: getMeaAggKey(mea.key, mea.op)
|
|
23
|
+
}))
|
|
20
24
|
});
|
|
21
25
|
const measureIds = measures.map((m) => m.key);
|
|
22
26
|
const parentData = filterByPredicates(viewData, predicates);
|
|
@@ -27,7 +31,11 @@ export function explainByChildren(
|
|
|
27
31
|
const data = aggregate(dataSource, {
|
|
28
32
|
groupBy: dimensions.concat(extendDim),
|
|
29
33
|
op: 'aggregate',
|
|
30
|
-
|
|
34
|
+
measures: measures.map((mea) => ({
|
|
35
|
+
field: mea.key,
|
|
36
|
+
agg: mea.op,
|
|
37
|
+
asFieldKey: getMeaAggKey(mea.key, mea.op),
|
|
38
|
+
}))
|
|
31
39
|
});
|
|
32
40
|
let groups: Map<any, IRow[]> = new Map();
|
|
33
41
|
for (let record of data) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { IExplainProps, IField } from '../../interfaces';
|
|
2
|
-
import { filterByPredicates } from '../../utils';
|
|
1
|
+
import { IAggregator, IExplainProps, IField } from '../../interfaces';
|
|
2
|
+
import { filterByPredicates, getMeaAggKey } from '../../utils';
|
|
3
3
|
import { compareDistribution, normalizeWithParent } from '../../utils/normalization';
|
|
4
4
|
import { aggregate } from '../op/aggregate';
|
|
5
|
-
import { complementaryFields, groupByAnalyticTypes
|
|
5
|
+
import { complementaryFields, groupByAnalyticTypes } from './utils';
|
|
6
6
|
|
|
7
7
|
export function explainBySelection(props: IExplainProps) {
|
|
8
8
|
const { metas, dataSource, viewFields, predicates } = props;
|
|
@@ -15,12 +15,21 @@ export function explainBySelection(props: IExplainProps) {
|
|
|
15
15
|
const overallData = aggregate(dataSource, {
|
|
16
16
|
groupBy: [extendDim.fid],
|
|
17
17
|
op: 'aggregate',
|
|
18
|
-
|
|
18
|
+
measures: measInView.map((mea) => ({
|
|
19
|
+
field: mea.fid,
|
|
20
|
+
agg: (mea.aggName ?? 'sum') as IAggregator,
|
|
21
|
+
asFieldKey: getMeaAggKey(mea.fid, (mea.aggName ?? 'sum') as IAggregator),
|
|
22
|
+
})),
|
|
23
|
+
|
|
19
24
|
});
|
|
20
25
|
const viewData = aggregate(dataSource, {
|
|
21
26
|
groupBy: dimsInView.map((f) => f.fid),
|
|
22
27
|
op: 'aggregate',
|
|
23
|
-
|
|
28
|
+
measures: measInView.map((mea) => ({
|
|
29
|
+
field: mea.fid,
|
|
30
|
+
agg: (mea.aggName ?? 'sum') as IAggregator,
|
|
31
|
+
asFieldKey: getMeaAggKey(mea.fid, (mea.aggName ?? 'sum') as IAggregator),
|
|
32
|
+
}))
|
|
24
33
|
});
|
|
25
34
|
const subData = filterByPredicates(viewData, predicates);
|
|
26
35
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IAggregator, IExplainProps } from '../../interfaces';
|
|
2
|
-
import { filterByPredicates } from '../../utils';
|
|
2
|
+
import { filterByPredicates, getMeaAggKey } from '../../utils';
|
|
3
3
|
import { aggregate } from '../op/aggregate';
|
|
4
|
-
import {
|
|
4
|
+
import { groupByAnalyticTypes } from './utils';
|
|
5
5
|
|
|
6
6
|
export function explainValue(props: IExplainProps): number[] {
|
|
7
7
|
const { viewFields, dataSource, predicates } = props;
|
|
@@ -9,7 +9,14 @@ export function explainValue(props: IExplainProps): number[] {
|
|
|
9
9
|
const viewData = aggregate(dataSource, {
|
|
10
10
|
groupBy: dimsInView.map((f) => f.fid),
|
|
11
11
|
op: 'aggregate',
|
|
12
|
-
|
|
12
|
+
measures: measInView.map((mea) => {
|
|
13
|
+
const agg = (mea.aggName ?? 'sum') as IAggregator;
|
|
14
|
+
return {
|
|
15
|
+
field: mea.fid,
|
|
16
|
+
agg,
|
|
17
|
+
asFieldKey: getMeaAggKey(mea.fid, agg),
|
|
18
|
+
}
|
|
19
|
+
}),
|
|
13
20
|
});
|
|
14
21
|
const selection = filterByPredicates(viewData, predicates);
|
|
15
22
|
const cmps: number[] = [];
|
|
@@ -10,10 +10,6 @@ export function groupByAnalyticTypes(fields: IField[]) {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function meaList2AggProps(measures: IField[]): IAggQuery['agg'] {
|
|
14
|
-
return Object.fromEntries(measures.map((mea) => [mea.fid, (mea.aggName ?? 'sum') as IAggregator]));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
export function complementaryFields(props: { selection: IField[]; all: IField[] }): IField[] {
|
|
18
14
|
return props.all
|
|
19
15
|
.filter((f) => f.analyticType === 'dimension')
|
package/src/lib/interfaces.ts
CHANGED
|
@@ -3,9 +3,7 @@ import { IAggregator } from "../interfaces";
|
|
|
3
3
|
export interface IAggQuery {
|
|
4
4
|
op: 'aggregate';
|
|
5
5
|
groupBy: string[];
|
|
6
|
-
agg:
|
|
7
|
-
[field: string]: IAggregator;
|
|
8
|
-
};
|
|
6
|
+
measures: { field: string; agg: IAggregator; asFieldKey: string }[];
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
// interface IFilterQuery {
|
package/src/lib/op/aggregate.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IRow } from "../../interfaces";
|
|
2
|
+
import { getMeaAggKey } from "../../utils";
|
|
2
3
|
import { IAggQuery } from "../interfaces";
|
|
3
4
|
import { sum, mean, median, stdev, variance, max, min, count } from "./stat";
|
|
4
5
|
|
|
@@ -16,7 +17,7 @@ const aggregatorMap = {
|
|
|
16
17
|
const KEY_JOINER = '___';
|
|
17
18
|
|
|
18
19
|
export function aggregate (data: IRow[], query: IAggQuery): IRow[] {
|
|
19
|
-
const { groupBy,
|
|
20
|
+
const { groupBy, measures } = query;
|
|
20
21
|
const ans: Map<string, IRow> = new Map();
|
|
21
22
|
const groups: Map<string, IRow[]> = new Map();
|
|
22
23
|
for (let row of data) {
|
|
@@ -35,13 +36,14 @@ export function aggregate (data: IRow[], query: IAggQuery): IRow[] {
|
|
|
35
36
|
for (let k of groupBy) {
|
|
36
37
|
aggRow[k] = subGroup[0][k];
|
|
37
38
|
}
|
|
38
|
-
for (let
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
for (let mea of measures) {
|
|
40
|
+
const aggMeaKey = getMeaAggKey(mea.field, mea.agg);
|
|
41
|
+
if (aggRow[aggMeaKey] === undefined) {
|
|
42
|
+
aggRow[aggMeaKey] = 0;
|
|
41
43
|
}
|
|
42
|
-
const values: number[] = subGroup.map((r) => r[
|
|
43
|
-
const aggregator = aggregatorMap[agg
|
|
44
|
-
aggRow[
|
|
44
|
+
const values: number[] = subGroup.map((r) => r[mea.field]) ?? [];
|
|
45
|
+
const aggregator = aggregatorMap[mea.agg] ?? sum;
|
|
46
|
+
aggRow[aggMeaKey] = aggregator(values);
|
|
45
47
|
}
|
|
46
48
|
ans.set(gk, aggRow);
|
|
47
49
|
}
|
package/src/locales/en-US.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
+
"config": {
|
|
3
|
+
"format": "Format",
|
|
4
|
+
"numberFormat": "Number format",
|
|
5
|
+
"timeFormat": "Time format",
|
|
6
|
+
"normalizedNumberFormat": "Normalized number format"
|
|
7
|
+
},
|
|
2
8
|
"constant": {
|
|
3
9
|
"row_count": "Row count",
|
|
4
10
|
"analytic_type": {
|
|
@@ -23,7 +29,9 @@
|
|
|
23
29
|
"tick": "Tick",
|
|
24
30
|
"rect": "Rectangle",
|
|
25
31
|
"arc": "Arc",
|
|
26
|
-
"boxplot": "Box (Box Plot)"
|
|
32
|
+
"boxplot": "Box (Box Plot)",
|
|
33
|
+
"table": "Table",
|
|
34
|
+
"text": "Text"
|
|
27
35
|
},
|
|
28
36
|
"stack_mode": {
|
|
29
37
|
"__enum__": "Stack Mode",
|
|
@@ -59,7 +67,8 @@
|
|
|
59
67
|
"theta": "Angle",
|
|
60
68
|
"radius": "Radius",
|
|
61
69
|
"filters": "Filters",
|
|
62
|
-
"details": "Details"
|
|
70
|
+
"details": "Details",
|
|
71
|
+
"text": "Text"
|
|
63
72
|
},
|
|
64
73
|
"aggregator": {
|
|
65
74
|
"sum": "Sum",
|