@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
@@ -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
- aestheticFields.map(dkey => <AestheticFieldContainer name={dkey.id} key={dkey.id}>
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);
@@ -100,11 +100,11 @@ export const FilterFieldSegment = styled.div`
100
100
  > h4 {
101
101
  font-weight: 400;
102
102
  };
103
- },
103
+ }
104
104
 
105
105
  .flt-container {
106
106
 
107
- },
107
+ }
108
108
  `
109
109
 
110
110
  export const Pill = styled.div<{colType: 'discrete' | 'continuous'}>`
@@ -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
@@ -86,7 +86,7 @@ export interface IField {
86
86
  analyticType: IAnalyticType;
87
87
  cmp?: (a: any, b: any) => number;
88
88
  computed?: boolean;
89
- expressoion?: IExpression;
89
+ expression?: IExpression;
90
90
  }
91
91
 
92
92
  export interface IViewField extends IField {
@@ -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
- agg: Object.fromEntries(measures.map((mea) => [mea.key, mea.op])),
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
- agg: Object.fromEntries(measures.map((mea) => [mea.key, mea.op])),
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, meaList2AggProps } from './utils';
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
- agg: meaList2AggProps(measInView),
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
- agg: meaList2AggProps(measInView),
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 { complementaryFields, groupByAnalyticTypes } from './utils';
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
- agg: Object.fromEntries(measInView.map((mea) => [mea.fid, (mea.aggName ?? 'sum') as IAggregator])),
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')
@@ -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 {
@@ -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, agg } = query;
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 meaKey in agg) {
39
- if (aggRow[meaKey] === undefined) {
40
- aggRow[meaKey] = 0;
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[meaKey]) ?? [];
43
- const aggregator = aggregatorMap[agg[meaKey]] ?? sum;
44
- aggRow[meaKey] = aggregator(values);
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
  }
@@ -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",
@@ -139,7 +148,8 @@
139
148
  "descending": "Sort in Descending Order",
140
149
  "transpose": "Transpose",
141
150
  "export_chart": "Export",
142
- "export_chart_as": "Export as {{type}}"
151
+ "export_chart_as": "Export as {{type}}",
152
+ "export_code": "Export Code"
143
153
  },
144
154
  "size": "Resize",
145
155
  "size_setting": {