@kanaries/graphic-walker 0.3.16 → 0.4.1
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 +9 -2
- package/dist/assets/filter.worker-f09fcd6f.js.map +1 -1
- package/dist/assets/sort.worker-f77540ac.js.map +1 -0
- package/dist/assets/transform.worker-bae8e910.js.map +1 -0
- package/dist/assets/{viewQuery.worker-03404216.js.map → viewQuery.worker-bdb6477c.js.map} +1 -1
- package/dist/components/askViz/index.d.ts +6 -0
- package/dist/components/askViz/schemaTransform.d.ts +2 -0
- package/dist/components/dataTable/index.d.ts +9 -5
- package/dist/components/pivotTable/store.d.ts +0 -2
- package/dist/components/spinner.d.ts +2 -0
- package/dist/computation/clientComputation.d.ts +3 -0
- package/dist/computation/serverComputation.d.ts +8 -0
- package/dist/config.d.ts +3 -1
- package/dist/fields/filterField/tabs.d.ts +2 -1
- package/dist/graphic-walker.es.js +22268 -21682
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +139 -139
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/interfaces.d.ts +93 -4
- package/dist/lib/execExp.d.ts +4 -4
- package/dist/lib/interfaces.d.ts +1 -0
- package/dist/lib/viewQuery.d.ts +2 -2
- package/dist/renderer/hooks.d.ts +8 -4
- package/dist/renderer/index.d.ts +2 -1
- package/dist/renderer/pureRenderer.d.ts +17 -1
- package/dist/renderer/specRenderer.d.ts +1 -0
- package/dist/services.d.ts +8 -5
- package/dist/store/commonStore.d.ts +2 -2
- package/dist/store/visualSpecStore.d.ts +58 -42
- package/dist/utils/save.d.ts +10 -2
- package/dist/utils/workflow.d.ts +3 -0
- package/dist/vis/react-vega.d.ts +3 -1
- package/dist/workers/sort.d.ts +2 -2
- package/dist/workers/transform.d.ts +5 -2
- package/package.json +2 -2
- package/src/App.tsx +46 -7
- package/src/components/askViz/index.tsx +93 -0
- package/src/components/askViz/schemaTransform.ts +38 -0
- package/src/components/dataTable/index.tsx +53 -11
- package/src/components/limitSetting.tsx +8 -6
- package/src/components/pivotTable/index.tsx +0 -1
- package/src/components/pivotTable/store.tsx +0 -16
- package/src/components/sizeSetting.tsx +9 -7
- package/src/components/spinner.tsx +14 -0
- package/src/components/toggle.tsx +2 -2
- package/src/components/visualConfig/index.tsx +78 -8
- package/src/computation/clientComputation.ts +55 -0
- package/src/computation/serverComputation.ts +158 -0
- package/src/config.ts +15 -2
- package/src/dataSource/datasetConfig/index.tsx +38 -6
- package/src/dataSource/table.tsx +15 -2
- package/src/fields/filterField/filterEditDialog.tsx +11 -10
- package/src/fields/filterField/tabs.tsx +178 -77
- package/src/hooks/index.ts +8 -0
- package/src/index.tsx +2 -0
- package/src/interfaces.ts +108 -5
- package/src/lib/execExp.ts +20 -11
- package/src/lib/interfaces.ts +1 -0
- package/src/lib/op/aggregate.ts +1 -1
- package/src/lib/viewQuery.ts +2 -2
- package/src/locales/en-US.json +11 -2
- package/src/locales/ja-JP.json +11 -2
- package/src/locales/zh-CN.json +11 -2
- package/src/main.tsx +1 -1
- package/src/renderer/hooks.ts +57 -69
- package/src/renderer/index.tsx +10 -6
- package/src/renderer/pureRenderer.tsx +40 -14
- package/src/renderer/specRenderer.tsx +24 -7
- package/src/services.ts +7 -8
- package/src/store/commonStore.ts +7 -7
- package/src/store/visualSpecStore.ts +288 -192
- package/src/utils/save.ts +81 -3
- package/src/utils/workflow.ts +148 -0
- package/src/vis/react-vega.tsx +21 -6
- package/src/vis/spec/aggregate.ts +3 -2
- package/src/vis/spec/stack.ts +7 -6
- package/src/visualSettings/index.tsx +2 -4
- package/src/workers/filter.worker.js +1 -1
- package/src/workers/sort.ts +3 -4
- package/src/workers/sort.worker.ts +2 -2
- package/src/workers/transform.ts +7 -8
- package/src/workers/transform.worker.js +2 -2
- package/src/workers/viewQuery.worker.js +2 -2
- package/dist/assets/sort.worker-4299a6a0.js.map +0 -1
- package/dist/assets/transform.worker-a12fb3d8.js.map +0 -1
package/src/utils/save.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { IDataSet, IDataSource, IVisSpec, IVisSpecForExport } from "../interfaces";
|
|
1
|
+
import { DraggableFieldState, IDataSet, IDataSource, IVisSpec, IVisSpecForExport, IVisualConfig } from "../interfaces";
|
|
2
2
|
import { VisSpecWithHistory } from "../models/visSpecHistory";
|
|
3
|
+
import { toJS } from 'mobx';
|
|
4
|
+
import { GEMO_TYPES } from '../config';
|
|
3
5
|
|
|
4
6
|
export function dumpsGWPureSpec(list: VisSpecWithHistory[]): IVisSpec[] {
|
|
5
7
|
return list.map((l) => l.exportGW());
|
|
@@ -9,10 +11,86 @@ export function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[] {
|
|
|
9
11
|
return list.map((l) => new VisSpecWithHistory(l));
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
export function initVisualConfig(): IVisualConfig {
|
|
15
|
+
return {
|
|
16
|
+
defaultAggregated: true,
|
|
17
|
+
geoms: [GEMO_TYPES[0]!],
|
|
18
|
+
stack: 'stack',
|
|
19
|
+
showActions: false,
|
|
20
|
+
interactiveScale: false,
|
|
21
|
+
sorted: 'none',
|
|
22
|
+
zeroScale: true,
|
|
23
|
+
background: undefined,
|
|
24
|
+
size: {
|
|
25
|
+
mode: "auto",
|
|
26
|
+
width: 320,
|
|
27
|
+
height: 200,
|
|
28
|
+
},
|
|
29
|
+
format: {
|
|
30
|
+
numberFormat: undefined,
|
|
31
|
+
timeFormat: undefined,
|
|
32
|
+
normalizedNumberFormat: undefined,
|
|
33
|
+
},
|
|
34
|
+
resolve: {
|
|
35
|
+
x: false,
|
|
36
|
+
y: false,
|
|
37
|
+
color: false,
|
|
38
|
+
opacity: false,
|
|
39
|
+
shape: false,
|
|
40
|
+
size: false,
|
|
41
|
+
},
|
|
42
|
+
limit: -1,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
|
|
47
|
+
const updatedVisList = visList.map((visSpec) => {
|
|
48
|
+
const updatedFilters = visSpec.encodings.filters.map((filter) => {
|
|
49
|
+
if (filter.rule?.type === 'one of' && Array.isArray(filter.rule.value)) {
|
|
50
|
+
return {
|
|
51
|
+
...filter,
|
|
52
|
+
rule: {
|
|
53
|
+
...filter.rule,
|
|
54
|
+
value: new Set(filter.rule.value),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return filter;
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
...visSpec,
|
|
62
|
+
encodings: {
|
|
63
|
+
...visSpec.encodings,
|
|
64
|
+
filters: updatedFilters,
|
|
65
|
+
},
|
|
66
|
+
} as IVisSpec;
|
|
67
|
+
});
|
|
68
|
+
return updatedVisList;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>['specList']): IVisSpecForExport[] => {
|
|
72
|
+
return backwards.map((content) => ({
|
|
73
|
+
...content,
|
|
74
|
+
config: {
|
|
75
|
+
...initVisualConfig(),
|
|
76
|
+
...content.config,
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export function resolveSpecFromStoInfo(info: IStoInfo) {
|
|
82
|
+
const spec = parseGWPureSpec(visSpecDecoder(forwardVisualConfigs(info.specList)))[0];
|
|
83
|
+
return {
|
|
84
|
+
config: toJS(spec.config) as IVisualConfig,
|
|
85
|
+
encodings: toJS(spec.encodings) as DraggableFieldState,
|
|
86
|
+
name: spec.name,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
12
90
|
export interface IStoInfo {
|
|
13
91
|
datasets: IDataSet[];
|
|
14
92
|
specList: {
|
|
15
|
-
[K in keyof IVisSpecForExport]: K extends
|
|
93
|
+
[K in keyof IVisSpecForExport]: K extends 'config' ? Partial<IVisSpecForExport[K]> : IVisSpecForExport[K];
|
|
16
94
|
}[];
|
|
17
95
|
dataSources: IDataSource[];
|
|
18
96
|
}
|
|
@@ -34,7 +112,7 @@ export function download(data: string, filename: string, type: string) {
|
|
|
34
112
|
window.navigator.msSaveOrOpenBlob(file, filename);
|
|
35
113
|
else {
|
|
36
114
|
// Others
|
|
37
|
-
var a = document.createElement(
|
|
115
|
+
var a = document.createElement('a'),
|
|
38
116
|
url = URL.createObjectURL(file);
|
|
39
117
|
a.href = url;
|
|
40
118
|
a.download = filename;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { IDataQueryWorkflowStep, IExpression, IFilterWorkflowStep, ITransformWorkflowStep, IViewField, IViewWorkflowStep, IVisFilter, ISortWorkflowStep } from "../interfaces";
|
|
2
|
+
import type { VizSpecStore } from "../store/visualSpecStore";
|
|
3
|
+
import { getMeaAggKey } from ".";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const walkExpression = (expression: IExpression, each: (field: string) => void): void => {
|
|
7
|
+
for (const param of expression.params) {
|
|
8
|
+
if (param.type === 'field') {
|
|
9
|
+
each(param.value);
|
|
10
|
+
} else if (param.type === 'expression') {
|
|
11
|
+
walkExpression(param.value, each);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const treeShake = (computedFields: readonly { key: string; expression: IExpression }[], viewKeys: readonly string[]): { key: string; expression: IExpression }[] => {
|
|
17
|
+
const usedFields = new Set(viewKeys);
|
|
18
|
+
const result = computedFields.filter(f => usedFields.has(f.key));
|
|
19
|
+
let currentFields = result.slice();
|
|
20
|
+
let rest = computedFields.filter(f => !usedFields.has(f.key));
|
|
21
|
+
while (currentFields.length && rest.length) {
|
|
22
|
+
const dependencies = new Set<string>();
|
|
23
|
+
for (const f of currentFields) {
|
|
24
|
+
walkExpression(f.expression, field => dependencies.add(field));
|
|
25
|
+
}
|
|
26
|
+
const nextFields = rest.filter(f => dependencies.has(f.key));
|
|
27
|
+
currentFields = nextFields;
|
|
28
|
+
rest = rest.filter(f => !dependencies.has(f.key));
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const toWorkflow = (
|
|
34
|
+
viewFilters: VizSpecStore['viewFilters'],
|
|
35
|
+
allFields: Omit<IViewField, 'dragId'>[],
|
|
36
|
+
viewDimensions: Omit<IViewField, 'dragId'>[],
|
|
37
|
+
viewMeasures: Omit<IViewField, 'dragId'>[],
|
|
38
|
+
defaultAggregated: VizSpecStore['visualConfig']['defaultAggregated'],
|
|
39
|
+
sort: 'none' | 'ascending' | 'descending',
|
|
40
|
+
limit?: number,
|
|
41
|
+
): IDataQueryWorkflowStep[] => {
|
|
42
|
+
const viewKeys = new Set<string>([...viewDimensions, ...viewMeasures].map(f => f.fid));
|
|
43
|
+
|
|
44
|
+
let filterWorkflow: IFilterWorkflowStep | null = null;
|
|
45
|
+
let transformWorkflow: ITransformWorkflowStep | null = null;
|
|
46
|
+
let viewQueryWorkflow: IViewWorkflowStep | null = null;
|
|
47
|
+
let sortWorkflow: ISortWorkflowStep | null = null;
|
|
48
|
+
|
|
49
|
+
// TODO: apply **fold** before filter
|
|
50
|
+
|
|
51
|
+
// First, to apply filters on the detailed data
|
|
52
|
+
const filters = viewFilters.filter(f => f.rule).map<IVisFilter>(f => {
|
|
53
|
+
viewKeys.add(f.fid);
|
|
54
|
+
const rule = f.rule!;
|
|
55
|
+
if (rule.type === 'one of') {
|
|
56
|
+
return {
|
|
57
|
+
fid: f.fid,
|
|
58
|
+
rule: {
|
|
59
|
+
type: 'one of',
|
|
60
|
+
value: [...rule.value],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
} else if (rule.type === 'temporal range') {
|
|
64
|
+
const range = [new Date(rule.value[0]).getTime(), new Date(rule.value[1]).getTime()] as const;
|
|
65
|
+
return {
|
|
66
|
+
fid: f.fid,
|
|
67
|
+
rule: {
|
|
68
|
+
type: 'temporal range',
|
|
69
|
+
value: range,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
const range = [Number(rule.value[0]), Number(rule.value[1])] as const;
|
|
74
|
+
return {
|
|
75
|
+
fid: f.fid,
|
|
76
|
+
rule: {
|
|
77
|
+
type: 'range',
|
|
78
|
+
value: range,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
if (filters.length) {
|
|
84
|
+
filterWorkflow = {
|
|
85
|
+
type: 'filter',
|
|
86
|
+
filters,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Second, to transform the data by rows 1 by 1
|
|
91
|
+
const computedFields = treeShake(allFields.filter(f => f.computed && f.expression).map(f => ({
|
|
92
|
+
key: f.fid,
|
|
93
|
+
expression: f.expression!,
|
|
94
|
+
})), [...viewKeys]);
|
|
95
|
+
if (computedFields.length) {
|
|
96
|
+
transformWorkflow = {
|
|
97
|
+
type: 'transform',
|
|
98
|
+
transform: computedFields,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Finally, to apply the aggregation
|
|
103
|
+
// When aggregation is enabled, there're 2 cases:
|
|
104
|
+
// 1. If any of the measures is aggregated, then we apply the aggregation
|
|
105
|
+
// 2. If there's no measure in the view, then we apply the aggregation
|
|
106
|
+
const aggregateOn = viewMeasures.filter(f => f.aggName).map(f => [f.fid, f.aggName as string]);
|
|
107
|
+
const aggergated = defaultAggregated && (aggregateOn.length || (viewMeasures.length === 0 && viewDimensions.length > 0));
|
|
108
|
+
if (aggergated) {
|
|
109
|
+
viewQueryWorkflow = {
|
|
110
|
+
type: 'view',
|
|
111
|
+
query: [{
|
|
112
|
+
op: 'aggregate',
|
|
113
|
+
groupBy: viewDimensions.map(f => f.fid),
|
|
114
|
+
measures: viewMeasures.map((f) => ({
|
|
115
|
+
field: f.fid,
|
|
116
|
+
agg: f.aggName as any,
|
|
117
|
+
asFieldKey: getMeaAggKey(f.fid, f.aggName!),
|
|
118
|
+
})),
|
|
119
|
+
}],
|
|
120
|
+
};
|
|
121
|
+
} else {
|
|
122
|
+
viewQueryWorkflow = {
|
|
123
|
+
type: 'view',
|
|
124
|
+
query: [{
|
|
125
|
+
op: 'raw',
|
|
126
|
+
fields: [...new Set([...viewDimensions, ...viewMeasures])].map(f => f.fid),
|
|
127
|
+
}],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (sort !== "none" && limit) {
|
|
132
|
+
sortWorkflow = {
|
|
133
|
+
type: 'sort',
|
|
134
|
+
by: viewMeasures.map(f => aggergated ? getMeaAggKey(f.fid, f.aggName) : f.fid),
|
|
135
|
+
sort,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
const steps: IDataQueryWorkflowStep[] = [
|
|
141
|
+
filterWorkflow!,
|
|
142
|
+
transformWorkflow!,
|
|
143
|
+
viewQueryWorkflow!,
|
|
144
|
+
sortWorkflow!,
|
|
145
|
+
].filter(Boolean);
|
|
146
|
+
|
|
147
|
+
return steps;
|
|
148
|
+
};
|
package/src/vis/react-vega.tsx
CHANGED
|
@@ -4,10 +4,9 @@ import { Subject, Subscription } from 'rxjs'
|
|
|
4
4
|
import * as op from 'rxjs/operators';
|
|
5
5
|
import type { ScenegraphEvent } from 'vega';
|
|
6
6
|
import styled from 'styled-components';
|
|
7
|
-
|
|
7
|
+
import { NonPositionChannelConfigList, PositionChannelConfigList } from '../config';
|
|
8
8
|
import { useVegaExportApi } from '../utils/vegaApiExport';
|
|
9
9
|
import { IViewField, IRow, IStackMode, VegaGlobalConfig, IVegaChartRef } from '../interfaces';
|
|
10
|
-
import { useTranslation } from 'react-i18next';
|
|
11
10
|
import { getVegaTimeFormatRules } from './temporalFormat';
|
|
12
11
|
import { getSingleView } from './spec/view';
|
|
13
12
|
import { NULL_FIELD } from './spec/field';
|
|
@@ -29,7 +28,7 @@ interface ReactVegaProps {
|
|
|
29
28
|
name?: string;
|
|
30
29
|
rows: Readonly<IViewField[]>;
|
|
31
30
|
columns: Readonly<IViewField[]>;
|
|
32
|
-
dataSource: IRow[];
|
|
31
|
+
dataSource: readonly IRow[];
|
|
33
32
|
defaultAggregate?: boolean;
|
|
34
33
|
stack: IStackMode;
|
|
35
34
|
interactiveScale: boolean;
|
|
@@ -48,6 +47,8 @@ interface ReactVegaProps {
|
|
|
48
47
|
height: number;
|
|
49
48
|
onGeomClick?: (values: any, e: any) => void
|
|
50
49
|
vegaConfig: VegaGlobalConfig;
|
|
50
|
+
/** @default "en-US" */
|
|
51
|
+
locale?: string;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const click$ = new Subject<ScenegraphEvent>();
|
|
@@ -100,9 +101,9 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
100
101
|
// dark = 'media',
|
|
101
102
|
vegaConfig,
|
|
102
103
|
// format
|
|
104
|
+
locale = 'en-US',
|
|
103
105
|
} = props;
|
|
104
106
|
const [viewPlaceholders, setViewPlaceholders] = useState<React.MutableRefObject<HTMLDivElement>[]>([]);
|
|
105
|
-
const { i18n } = useTranslation();
|
|
106
107
|
// const mediaTheme = useCurrentMediaTheme(dark);
|
|
107
108
|
// const themeConfig = builtInThemes[themeKey]?.[mediaTheme];
|
|
108
109
|
|
|
@@ -221,8 +222,22 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
221
222
|
spec.encoding = singleView.encoding;
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
spec.resolve ||= {};
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
let resolve = vegaConfig.resolve;
|
|
228
|
+
for (let v in resolve) {
|
|
229
|
+
let value = resolve[v] ? 'independent' : 'shared';
|
|
230
|
+
// @ts-ignore
|
|
231
|
+
spec.resolve.scale = { ...spec.resolve.scale, [v]: value };
|
|
232
|
+
if((PositionChannelConfigList as string[]).includes(v)) {
|
|
233
|
+
spec.resolve.axis = { ...spec.resolve.axis, [v]: value };
|
|
234
|
+
}else if((NonPositionChannelConfigList as string[]).includes(v)){
|
|
235
|
+
spec.resolve.legend = { ...spec.resolve.legend, [v]: value };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
224
239
|
if (viewPlaceholders.length > 0 && viewPlaceholders[0].current) {
|
|
225
|
-
const task = embed(viewPlaceholders[0].current, spec, { mode: 'vega-lite', actions: showActions, timeFormatLocale: getVegaTimeFormatRules(
|
|
240
|
+
const task = embed(viewPlaceholders[0].current, spec, { mode: 'vega-lite', actions: showActions, timeFormatLocale: getVegaTimeFormatRules(locale), config: vegaConfig }).then(res => {
|
|
226
241
|
const container = res.view.container();
|
|
227
242
|
const canvas = container?.querySelector('canvas') ?? null;
|
|
228
243
|
vegaRefs.current = [{
|
|
@@ -301,7 +316,7 @@ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVe
|
|
|
301
316
|
}
|
|
302
317
|
if (node) {
|
|
303
318
|
const id = index;
|
|
304
|
-
const task = embed(node, ans, { mode: 'vega-lite', actions: showActions, timeFormatLocale: getVegaTimeFormatRules(
|
|
319
|
+
const task = embed(node, ans, { mode: 'vega-lite', actions: showActions, timeFormatLocale: getVegaTimeFormatRules(locale), config: vegaConfig }).then(res => {
|
|
305
320
|
const container = res.view.container();
|
|
306
321
|
const canvas = container?.querySelector('canvas') ?? null;
|
|
307
322
|
vegaRefs.current[id] = {
|
|
@@ -4,11 +4,12 @@ import { getMeaAggKey } from '../../utils';
|
|
|
4
4
|
|
|
5
5
|
export function channelAggregate(encoding: { [key: string]: any }, fields: IViewField[]) {
|
|
6
6
|
Object.values(encoding).forEach((c) => {
|
|
7
|
-
|
|
7
|
+
if (c.aggregate === null) return;
|
|
8
|
+
const targetField = fields.find((f) => f.fid === c.field && (f.analyticType === 'measure' || f.fid === COUNT_FIELD_ID));
|
|
8
9
|
if (targetField && targetField.fid === COUNT_FIELD_ID) {
|
|
9
10
|
c.title = 'Count';
|
|
10
11
|
c.field = getMeaAggKey(targetField.fid, targetField.aggName)
|
|
11
|
-
} else if (targetField
|
|
12
|
+
} else if (targetField) {
|
|
12
13
|
c.title = `${targetField.aggName}(${targetField.name})`;
|
|
13
14
|
c.field = getMeaAggKey(targetField.fid, targetField.aggName)
|
|
14
15
|
}
|
package/src/vis/spec/stack.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { IStackMode } from "../../interfaces";
|
|
2
2
|
|
|
3
3
|
export function channelStack(encoding: { [key: string]: any }, stackMode: IStackMode) {
|
|
4
|
-
if (stackMode === 'stack') return;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
if (stackMode === 'stack' || stackMode === 'zero') return;
|
|
5
|
+
let stackValue = stackMode === 'none' ? null : stackMode;
|
|
6
|
+
const stackableChannels = ['x', 'y', 'theta', 'radius'];
|
|
7
|
+
for (let ch of stackableChannels) {
|
|
8
|
+
if (encoding[ch] && encoding[ch].type === 'quantitative') {
|
|
9
|
+
encoding[ch].stack = stackValue
|
|
10
|
+
}
|
|
10
11
|
}
|
|
11
12
|
}
|
|
@@ -37,7 +37,6 @@ import throttle from '../utils/throttle';
|
|
|
37
37
|
import KanariesLogo from '../assets/kanaries.png';
|
|
38
38
|
import { ImageWithFallback } from '../components/timeoutImg';
|
|
39
39
|
import LimitSetting from '../components/limitSetting';
|
|
40
|
-
import { runInAction } from 'mobx';
|
|
41
40
|
|
|
42
41
|
const Invisible = styled.div`
|
|
43
42
|
clip: rect(1px, 1px, 1px, 1px);
|
|
@@ -367,6 +366,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
367
366
|
none: XMarkIcon,
|
|
368
367
|
stack: ChevronDoubleUpIcon,
|
|
369
368
|
normalize: ArrowsUpDownIcon,
|
|
369
|
+
center: ChevronUpDownIcon, // TODO: fix unsafe extends
|
|
370
370
|
}[g],
|
|
371
371
|
})),
|
|
372
372
|
value: stack,
|
|
@@ -505,9 +505,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
505
505
|
<LimitSetting
|
|
506
506
|
value={limit}
|
|
507
507
|
setValue={(v) => {
|
|
508
|
-
|
|
509
|
-
vizStore.limit = v;
|
|
510
|
-
});
|
|
508
|
+
vizStore.setLimit(v);
|
|
511
509
|
}}
|
|
512
510
|
/>
|
|
513
511
|
</FormContainer>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {import('../interfaces').IRow[]} dataSource
|
|
6
|
-
* @param {import('../interfaces').
|
|
6
|
+
* @param {import('../interfaces').IFilterFiledSimple[]} filters
|
|
7
7
|
* @return {import('../interfaces').IRow[]}
|
|
8
8
|
*/
|
|
9
9
|
const filter = (dataSource, filters) => {
|
package/src/workers/sort.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getMeaAggKey } from '../utils';
|
|
1
|
+
import { IRow } from '../interfaces';
|
|
3
2
|
|
|
4
3
|
function compareMulti(a: number[], b: number[]): number {
|
|
5
4
|
if (a.length < b.length) return -compareMulti(b, a);
|
|
@@ -11,12 +10,12 @@ function compareMulti(a: number[], b: number[]): number {
|
|
|
11
10
|
return 0;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
export function sortBy(data: IRow[], viewMeasures:
|
|
13
|
+
export function sortBy(data: IRow[], viewMeasures: string[], sort: 'ascending' | 'descending') {
|
|
15
14
|
const sortM = sort === 'ascending' ? 1 : -1;
|
|
16
15
|
return data
|
|
17
16
|
.map((x) => ({
|
|
18
17
|
data: x,
|
|
19
|
-
value: viewMeasures.map((f) => x[
|
|
18
|
+
value: viewMeasures.map((f) => x[f]),
|
|
20
19
|
}))
|
|
21
20
|
.sort((a, b) => sortM * compareMulti(a.value, b.value))
|
|
22
21
|
.map((x) => x.data);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { IRow
|
|
1
|
+
import { IRow } from '../interfaces';
|
|
2
2
|
import { sortBy } from './sort';
|
|
3
3
|
|
|
4
4
|
const main = (e: {
|
|
5
5
|
data: {
|
|
6
6
|
data: IRow[];
|
|
7
|
-
viewMeasures:
|
|
7
|
+
viewMeasures: string[];
|
|
8
8
|
sort: 'ascending' | 'descending';
|
|
9
9
|
};
|
|
10
10
|
}) => {
|
package/src/workers/transform.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ITransformWorkflowStep, IRow, IExpression } from "../interfaces";
|
|
2
2
|
import { dataframe2Dataset, dataset2DataFrame, execExpression } from "../lib/execExp";
|
|
3
3
|
|
|
4
|
-
export function transformData(data: IRow[],
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
df = execExpression(field.expression!, df, columns);
|
|
4
|
+
export function transformData(data: IRow[], trans: { key: string, expression: IExpression }[]) {
|
|
5
|
+
let df = dataset2DataFrame(data);
|
|
6
|
+
for (let i = 0; i < trans.length; i++) {
|
|
7
|
+
const field = trans[i];
|
|
8
|
+
df = execExpression(field.expression, df);
|
|
10
9
|
}
|
|
11
|
-
return dataframe2Dataset(df
|
|
10
|
+
return dataframe2Dataset(df);
|
|
12
11
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { transformData } from './transform'
|
|
2
2
|
const main = e => {
|
|
3
|
-
const { dataSource,
|
|
3
|
+
const { dataSource, trans } = e.data;
|
|
4
4
|
|
|
5
5
|
try {
|
|
6
|
-
const ans = transformData(dataSource,
|
|
6
|
+
const ans = transformData(dataSource, trans);
|
|
7
7
|
self.postMessage(ans);
|
|
8
8
|
} catch (error) {
|
|
9
9
|
self.postMessage({ error: error.message });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { queryView } from '../lib/viewQuery'
|
|
2
2
|
const main = e => {
|
|
3
3
|
try {
|
|
4
|
-
const { dataSource,
|
|
5
|
-
const ans = queryView(dataSource,
|
|
4
|
+
const { dataSource, query } = e.data;
|
|
5
|
+
const ans = queryView(dataSource, query);
|
|
6
6
|
self.postMessage(ans);
|
|
7
7
|
|
|
8
8
|
} catch (err) {
|