@kanaries/graphic-walker 0.3.14 → 0.3.16
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/sort.worker-4299a6a0.js.map +1 -0
- package/dist/assets/viewQuery.worker-03404216.js.map +1 -1
- package/dist/components/dataTable/index.d.ts +2 -2
- package/dist/components/limitSetting.d.ts +5 -0
- package/dist/dataSource/utils.d.ts +1 -1
- package/dist/graphic-walker.es.js +21095 -20910
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +140 -135
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/interfaces.d.ts +11 -0
- package/dist/lib/inferMeta.d.ts +1 -1
- package/dist/lib/viewQuery.d.ts +2 -2
- package/dist/renderer/hooks.d.ts +2 -0
- package/dist/services.d.ts +4 -3
- package/dist/store/visualSpecStore.d.ts +430 -2
- package/dist/utils/dataPrep.d.ts +2 -2
- package/dist/utils/is-plain-object.d.ts +2 -0
- package/dist/utils/save.d.ts +2 -2
- package/dist/workers/sort.d.ts +2 -0
- package/dist/workers/sort.worker.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/dataTable/index.tsx +154 -74
- package/src/components/dataTable/pagination.tsx +1 -1
- package/src/components/limitSetting.tsx +38 -0
- package/src/dataSource/utils.ts +15 -13
- package/src/hooks/index.ts +10 -0
- package/src/interfaces.ts +16 -0
- package/src/lib/inferMeta.ts +7 -4
- package/src/lib/viewQuery.ts +2 -2
- package/src/locales/en-US.json +1 -0
- package/src/locales/ja-JP.json +1 -0
- package/src/locales/zh-CN.json +1 -0
- package/src/renderer/hooks.ts +37 -7
- package/src/renderer/index.tsx +22 -12
- package/src/renderer/pureRenderer.tsx +7 -11
- package/src/services.ts +27 -5
- package/src/store/visualSpecStore.ts +71 -7
- package/src/utils/dataPrep.ts +21 -28
- package/src/utils/is-plain-object.ts +33 -0
- package/src/utils/save.ts +2 -2
- package/src/visualSettings/index.tsx +24 -5
- package/src/workers/sort.ts +23 -0
- package/src/workers/sort.worker.ts +21 -0
package/src/renderer/index.tsx
CHANGED
|
@@ -21,7 +21,18 @@ interface RendererProps {
|
|
|
21
21
|
const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, ref) {
|
|
22
22
|
const { themeKey, dark } = props;
|
|
23
23
|
const { vizStore, commonStore } = useGlobalStore();
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
allFields,
|
|
26
|
+
viewFilters,
|
|
27
|
+
viewDimensions,
|
|
28
|
+
viewMeasures,
|
|
29
|
+
visualConfig,
|
|
30
|
+
draggableFieldState,
|
|
31
|
+
visList,
|
|
32
|
+
visIndex,
|
|
33
|
+
sort,
|
|
34
|
+
limit,
|
|
35
|
+
} = vizStore;
|
|
25
36
|
const chart = visList[visIndex];
|
|
26
37
|
const { currentDataset } = commonStore;
|
|
27
38
|
const { dataSource } = currentDataset;
|
|
@@ -37,6 +48,8 @@ const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, r
|
|
|
37
48
|
viewMeasures,
|
|
38
49
|
filters: viewFilters,
|
|
39
50
|
defaultAggregated: visualConfig.defaultAggregated,
|
|
51
|
+
sort,
|
|
52
|
+
limit: limit,
|
|
40
53
|
});
|
|
41
54
|
|
|
42
55
|
// Dependencies that should not trigger effect individually
|
|
@@ -64,19 +77,16 @@ const Renderer = forwardRef<IReactVegaHandler, RendererProps>(function (props, r
|
|
|
64
77
|
useChartIndexControl({
|
|
65
78
|
count: visList.length,
|
|
66
79
|
index: visIndex,
|
|
67
|
-
onChange: idx => vizStore.selectVisualization(idx),
|
|
80
|
+
onChange: (idx) => vizStore.selectVisualization(idx),
|
|
68
81
|
});
|
|
69
82
|
|
|
70
|
-
const handleGeomClick = useCallback(
|
|
71
|
-
(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
[]
|
|
79
|
-
);
|
|
83
|
+
const handleGeomClick = useCallback((values: any, e: any) => {
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
runInAction(() => {
|
|
86
|
+
commonStore.showEmbededMenu([e.pageX, e.pageY]);
|
|
87
|
+
commonStore.setFilters(values);
|
|
88
|
+
});
|
|
89
|
+
}, []);
|
|
80
90
|
|
|
81
91
|
const handleChartResize = useCallback(
|
|
82
92
|
(width: number, height: number) => {
|
|
@@ -9,7 +9,6 @@ import type { IReactVegaHandler } from '../vis/react-vega';
|
|
|
9
9
|
import SpecRenderer from './specRenderer';
|
|
10
10
|
import { useRenderer } from './hooks';
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
interface IPureRendererProps {
|
|
14
13
|
name?: string;
|
|
15
14
|
themeKey?: IThemeKey;
|
|
@@ -17,25 +16,20 @@ interface IPureRendererProps {
|
|
|
17
16
|
rawData?: IRow[];
|
|
18
17
|
visualState: DraggableFieldState;
|
|
19
18
|
visualConfig: IVisualConfig;
|
|
19
|
+
sort?: 'none' | 'ascending' | 'descending';
|
|
20
|
+
limit?: number;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Render a readonly chart with given visualization schema.
|
|
24
25
|
* This is a pure component, which means it will not depend on any global state.
|
|
25
26
|
*/
|
|
26
|
-
const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function PureRenderer
|
|
27
|
-
const {
|
|
28
|
-
name,
|
|
29
|
-
themeKey,
|
|
30
|
-
dark,
|
|
31
|
-
rawData,
|
|
32
|
-
visualState,
|
|
33
|
-
visualConfig,
|
|
34
|
-
} = props;
|
|
27
|
+
const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function PureRenderer(props, ref) {
|
|
28
|
+
const { name, themeKey, dark, rawData, visualState, visualConfig, sort, limit } = props;
|
|
35
29
|
const defaultAggregated = visualConfig?.defaultAggregated ?? false;
|
|
36
30
|
|
|
37
31
|
const [viewData, setViewData] = useState<IRow[]>([]);
|
|
38
|
-
|
|
32
|
+
|
|
39
33
|
const { allFields, viewDimensions, viewMeasures, filters } = useMemo(() => {
|
|
40
34
|
const viewDimensions: IViewField[] = [];
|
|
41
35
|
const viewMeasures: IViewField[] = [];
|
|
@@ -64,6 +58,8 @@ const PureRenderer = forwardRef<IReactVegaHandler, IPureRendererProps>(function
|
|
|
64
58
|
viewMeasures,
|
|
65
59
|
filters,
|
|
66
60
|
defaultAggregated,
|
|
61
|
+
sort: sort ?? 'none',
|
|
62
|
+
limit: limit ?? -1,
|
|
67
63
|
});
|
|
68
64
|
|
|
69
65
|
// Dependencies that should not trigger effect individually
|
package/src/services.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toJS } from 'mobx';
|
|
2
|
-
import { IRow, IMutField, IFilterField, Specification } from './interfaces';
|
|
2
|
+
import { IRow, IMutField, IField, IFilterField, Specification, IViewField } from './interfaces';
|
|
3
3
|
/* eslint import/no-webpack-loader-syntax:0 */
|
|
4
4
|
// @ts-ignore
|
|
5
5
|
// eslint-disable-next-line
|
|
@@ -11,6 +11,8 @@ import { IRow, IMutField, IFilterField, Specification } from './interfaces';
|
|
|
11
11
|
import FilterWorker from './workers/filter.worker?worker&inline';
|
|
12
12
|
import TransformDataWorker from './workers/transform.worker?worker&inline';
|
|
13
13
|
import ViewQueryWorker from './workers/viewQuery.worker?worker&inline';
|
|
14
|
+
import SortWorker from './workers/sort.worker?worker&inline';
|
|
15
|
+
|
|
14
16
|
import { IViewQuery } from './lib/viewQuery';
|
|
15
17
|
|
|
16
18
|
// interface WorkerState {
|
|
@@ -140,7 +142,7 @@ export const applyFilter = async (data: IRow[], filters: readonly IFilterField[]
|
|
|
140
142
|
}
|
|
141
143
|
};
|
|
142
144
|
|
|
143
|
-
export const transformDataService = async (data: IRow[], columns:
|
|
145
|
+
export const transformDataService = async (data: IRow[], columns: IField[]): Promise<IRow[]> => {
|
|
144
146
|
if (columns.length === 0 || data.length === 0) return data;
|
|
145
147
|
const worker = new TransformDataWorker();
|
|
146
148
|
try {
|
|
@@ -154,9 +156,9 @@ export const transformDataService = async (data: IRow[], columns: IMutField[]):
|
|
|
154
156
|
} finally {
|
|
155
157
|
worker.terminate();
|
|
156
158
|
}
|
|
157
|
-
}
|
|
159
|
+
};
|
|
158
160
|
|
|
159
|
-
export const applyViewQuery = async (data: IRow[], metas:
|
|
161
|
+
export const applyViewQuery = async (data: IRow[], metas: IField[], query: IViewQuery): Promise<IRow[]> => {
|
|
160
162
|
const worker = new ViewQueryWorker();
|
|
161
163
|
try {
|
|
162
164
|
const res: IRow[] = await workerService(worker, {
|
|
@@ -170,4 +172,24 @@ export const applyViewQuery = async (data: IRow[], metas: IMutField[], query: IV
|
|
|
170
172
|
} finally {
|
|
171
173
|
worker.terminate();
|
|
172
174
|
}
|
|
173
|
-
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const applySort = async (
|
|
178
|
+
data: IRow[],
|
|
179
|
+
viewMeasures: IField[],
|
|
180
|
+
sort: 'ascending' | 'descending'
|
|
181
|
+
): Promise<IRow[]> => {
|
|
182
|
+
const worker = new SortWorker();
|
|
183
|
+
try {
|
|
184
|
+
const res: IRow[] = await workerService(worker, {
|
|
185
|
+
data,
|
|
186
|
+
viewMeasures: viewMeasures.map((x) => toJS(x)),
|
|
187
|
+
sort,
|
|
188
|
+
});
|
|
189
|
+
return res;
|
|
190
|
+
} catch (err) {
|
|
191
|
+
throw new Error('Uncaught error in ViewQueryWorker', { cause: err });
|
|
192
|
+
} finally {
|
|
193
|
+
worker.terminate();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from "mobx";
|
|
2
2
|
import produce from "immer";
|
|
3
|
-
import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisualConfig, Specification } from "../interfaces";
|
|
3
|
+
import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisSpec, IVisSpecForExport, IFilterFieldForExport, IVisualConfig, Specification } from "../interfaces";
|
|
4
4
|
import { CHANNEL_LIMIT, GEMO_TYPES, MetaFieldKeys } from "../config";
|
|
5
5
|
import { VisSpecWithHistory } from "../models/visSpecHistory";
|
|
6
6
|
import { IStoInfo, dumpsGWPureSpec, parseGWContent, parseGWPureSpec, stringifyGWContent } from "../utils/save";
|
|
@@ -88,7 +88,7 @@ type DeepReadonly<T extends Record<keyof any, any>> = {
|
|
|
88
88
|
readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]):
|
|
91
|
+
const forwardVisualConfigs = (backwards: ReturnType<typeof parseGWContent>["specList"]): IVisSpecForExport[] => {
|
|
92
92
|
return backwards.map((content) => ({
|
|
93
93
|
...content,
|
|
94
94
|
config: {
|
|
@@ -264,7 +264,7 @@ export class VizSpecStore {
|
|
|
264
264
|
*/
|
|
265
265
|
public get viewDimensions(): IViewField[] {
|
|
266
266
|
const { draggableFieldState } = this;
|
|
267
|
-
const state = toJS(draggableFieldState);
|
|
267
|
+
const { filters, ...state } = toJS(draggableFieldState);
|
|
268
268
|
const fields: IViewField[] = [];
|
|
269
269
|
(Object.keys(state) as (keyof DraggableFieldState)[])
|
|
270
270
|
.filter((dkey) => !MetaFieldKeys.includes(dkey))
|
|
@@ -278,7 +278,7 @@ export class VizSpecStore {
|
|
|
278
278
|
*/
|
|
279
279
|
public get viewMeasures(): IViewField[] {
|
|
280
280
|
const { draggableFieldState } = this;
|
|
281
|
-
const state = toJS(draggableFieldState);
|
|
281
|
+
const { filters, ...state } = toJS(draggableFieldState);
|
|
282
282
|
const fields: IViewField[] = [];
|
|
283
283
|
(Object.keys(state) as (keyof DraggableFieldState)[])
|
|
284
284
|
.filter((dkey) => !MetaFieldKeys.includes(dkey))
|
|
@@ -336,6 +336,7 @@ export class VizSpecStore {
|
|
|
336
336
|
dragId: uniqueId(),
|
|
337
337
|
fid: f.fid,
|
|
338
338
|
name: f.name || f.fid,
|
|
339
|
+
basename: f.basename || f.name || f.fid,
|
|
339
340
|
semanticType: f.semanticType,
|
|
340
341
|
analyticType: f.analyticType,
|
|
341
342
|
}));
|
|
@@ -345,6 +346,7 @@ export class VizSpecStore {
|
|
|
345
346
|
dragId: uniqueId(),
|
|
346
347
|
fid: f.fid,
|
|
347
348
|
name: f.name || f.fid,
|
|
349
|
+
basename: f.basename || f.name || f.fid,
|
|
348
350
|
analyticType: f.analyticType,
|
|
349
351
|
semanticType: f.semanticType,
|
|
350
352
|
aggName: "sum",
|
|
@@ -701,15 +703,15 @@ export class VizSpecStore {
|
|
|
701
703
|
return stringifyGWContent({
|
|
702
704
|
datasets: toJS(this.commonStore.datasets),
|
|
703
705
|
dataSources: this.commonStore.dataSources,
|
|
704
|
-
specList: pureVisList,
|
|
706
|
+
specList: this.visSpecEncoder(pureVisList),
|
|
705
707
|
});
|
|
706
708
|
}
|
|
707
709
|
public exportViewSpec() {
|
|
708
710
|
const pureVisList = dumpsGWPureSpec(this.visList);
|
|
709
|
-
return pureVisList
|
|
711
|
+
return this.visSpecEncoder(pureVisList);
|
|
710
712
|
}
|
|
711
713
|
public importStoInfo (stoInfo: IStoInfo) {
|
|
712
|
-
this.visList = parseGWPureSpec(forwardVisualConfigs(stoInfo.specList));
|
|
714
|
+
this.visList = parseGWPureSpec(this.visSpecDecoder(forwardVisualConfigs(stoInfo.specList)));
|
|
713
715
|
this.visIndex = 0;
|
|
714
716
|
this.commonStore.datasets = stoInfo.datasets;
|
|
715
717
|
this.commonStore.dataSources = stoInfo.dataSources;
|
|
@@ -719,4 +721,66 @@ export class VizSpecStore {
|
|
|
719
721
|
const content = parseGWContent(raw);
|
|
720
722
|
this.importStoInfo(content);
|
|
721
723
|
}
|
|
724
|
+
|
|
725
|
+
private visSpecEncoder(visList: IVisSpec[]): IVisSpecForExport[] {
|
|
726
|
+
const updatedVisList = visList.map((visSpec) => {
|
|
727
|
+
const updatedFilters = visSpec.encodings.filters.map((filter) => {
|
|
728
|
+
if (filter.rule?.type === "one of") {
|
|
729
|
+
const rule = {
|
|
730
|
+
...filter.rule,
|
|
731
|
+
value: Array.from(filter.rule.value)
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
...filter,
|
|
735
|
+
rule
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return filter as IFilterFieldForExport;
|
|
739
|
+
});
|
|
740
|
+
return {
|
|
741
|
+
...visSpec,
|
|
742
|
+
encodings: {
|
|
743
|
+
...visSpec.encodings,
|
|
744
|
+
filters: updatedFilters
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
return updatedVisList;
|
|
749
|
+
}
|
|
750
|
+
private visSpecDecoder(visList: IVisSpecForExport[]): IVisSpec[] {
|
|
751
|
+
const updatedVisList = visList.map((visSpec) => {
|
|
752
|
+
const updatedFilters = visSpec.encodings.filters.map((filter) => {
|
|
753
|
+
if (filter.rule?.type === "one of" && Array.isArray(filter.rule.value)) {
|
|
754
|
+
return {
|
|
755
|
+
...filter,
|
|
756
|
+
rule: {
|
|
757
|
+
...filter.rule,
|
|
758
|
+
value: new Set(filter.rule.value)
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return filter;
|
|
763
|
+
})
|
|
764
|
+
return {
|
|
765
|
+
...visSpec,
|
|
766
|
+
encodings: {
|
|
767
|
+
...visSpec.encodings,
|
|
768
|
+
filters: updatedFilters
|
|
769
|
+
}
|
|
770
|
+
} as IVisSpec;
|
|
771
|
+
});
|
|
772
|
+
return updatedVisList;
|
|
773
|
+
}
|
|
774
|
+
public limit = -1;
|
|
775
|
+
|
|
776
|
+
public get sort() {
|
|
777
|
+
const { rows, columns } = this.draggableFieldState;
|
|
778
|
+
if (rows.length && !rows.find((x) => x.analyticType === 'measure')) {
|
|
779
|
+
return rows[rows.length - 1].sort || 'none';
|
|
780
|
+
}
|
|
781
|
+
if (columns.length && !columns.find((x) => x.analyticType === 'measure')) {
|
|
782
|
+
return columns[columns.length - 1].sort || 'none';
|
|
783
|
+
}
|
|
784
|
+
return 'none';
|
|
785
|
+
}
|
|
722
786
|
}
|
package/src/utils/dataPrep.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { IMutField, IRow } from "../interfaces";
|
|
2
|
+
import { isPlainObject } from "./is-plain-object";
|
|
2
3
|
|
|
3
|
-
function updateRowKeys(data: IRow[], keyEncodeList: {from: string; to: string}[]): IRow[] {
|
|
4
|
+
function updateRowKeys(data: IRow[], keyEncodeList: { from: string[]; to: string }[]): IRow[] {
|
|
4
5
|
return data.map((row) => {
|
|
5
6
|
const newRow: IRow = {};
|
|
6
7
|
for (let k in keyEncodeList) {
|
|
7
8
|
const { from, to } = keyEncodeList[k];
|
|
8
|
-
newRow[to] = row
|
|
9
|
+
newRow[to] = getValueByKeyPath(row, from);
|
|
9
10
|
}
|
|
10
11
|
return newRow;
|
|
11
12
|
});
|
|
@@ -13,9 +14,9 @@ function updateRowKeys(data: IRow[], keyEncodeList: {from: string; to: string}[]
|
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* parse column id(key) to a safe string
|
|
16
|
-
* @param metas
|
|
17
|
+
* @param metas
|
|
17
18
|
*/
|
|
18
|
-
function parseColumnMetas
|
|
19
|
+
function parseColumnMetas(metas: IMutField[]) {
|
|
19
20
|
return metas.map((meta, i) => {
|
|
20
21
|
const safeKey = `gwc_${i}`;
|
|
21
22
|
return {
|
|
@@ -26,42 +27,34 @@ function parseColumnMetas (metas: IMutField[]) {
|
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export function guardDataKeys
|
|
30
|
+
export function guardDataKeys(
|
|
31
|
+
data: IRow[],
|
|
32
|
+
metas: IMutField[]
|
|
33
|
+
): {
|
|
30
34
|
safeData: IRow[];
|
|
31
35
|
safeMetas: IMutField[];
|
|
32
36
|
} {
|
|
33
|
-
const safeMetas = parseColumnMetas(metas)
|
|
37
|
+
const safeMetas = parseColumnMetas(metas);
|
|
34
38
|
const keyEncodeList = safeMetas.map((f, i) => ({
|
|
35
|
-
from: metas[i].fid,
|
|
36
|
-
to: f.fid
|
|
39
|
+
from: metas[i].path ?? [metas[i].fid],
|
|
40
|
+
to: f.fid,
|
|
37
41
|
}));
|
|
38
42
|
const safeData = updateRowKeys(data, keyEncodeList);
|
|
39
43
|
return {
|
|
40
44
|
safeData,
|
|
41
|
-
safeMetas
|
|
42
|
-
}
|
|
45
|
+
safeMetas,
|
|
46
|
+
};
|
|
43
47
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let flatColKeys: string[] = [];
|
|
49
|
-
for (let key of keys) {
|
|
50
|
-
if (typeof object[key] === 'object') {
|
|
51
|
-
const subKeys = flatNestKeys(object[key]);
|
|
52
|
-
flatColKeys = flatColKeys.concat(subKeys.map(k => `${key}${SPLITOR}${k}`));
|
|
53
|
-
} else {
|
|
54
|
-
flatColKeys.push(key)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return flatColKeys;
|
|
48
|
+
export function flatKeys(obj: Object, prefixKeys: string[] = []): string[][] {
|
|
49
|
+
return Object.keys(obj).flatMap((k) =>
|
|
50
|
+
isPlainObject(obj[k]) ? flatKeys(obj[k], prefixKeys.concat(k)) : [prefixKeys.concat(k)]
|
|
51
|
+
);
|
|
58
52
|
}
|
|
59
53
|
|
|
60
|
-
export function getValueByKeyPath
|
|
61
|
-
const keys = keyPath.split(SPLITOR);
|
|
54
|
+
export function getValueByKeyPath(object: any, keyPath: string[]): any {
|
|
62
55
|
let value = object;
|
|
63
|
-
for (let key of
|
|
56
|
+
for (let key of keyPath) {
|
|
64
57
|
value = value[key];
|
|
65
58
|
}
|
|
66
59
|
return value;
|
|
67
|
-
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2014-2017, Jon Schlinkert.
|
|
5
|
+
* Released under the MIT License.
|
|
6
|
+
*/
|
|
7
|
+
function isObject(o: any) {
|
|
8
|
+
return Object.prototype.toString.call(o) === '[object Object]';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isPlainObject(o: any) {
|
|
12
|
+
var ctor, prot;
|
|
13
|
+
|
|
14
|
+
if (isObject(o) === false) return false;
|
|
15
|
+
|
|
16
|
+
// If has modified constructor
|
|
17
|
+
ctor = o.constructor;
|
|
18
|
+
if (ctor === undefined) return true;
|
|
19
|
+
|
|
20
|
+
// If has modified prototype
|
|
21
|
+
prot = ctor.prototype;
|
|
22
|
+
if (isObject(prot) === false) return false;
|
|
23
|
+
|
|
24
|
+
// If constructor does not have an Object-specific method
|
|
25
|
+
if (prot.hasOwnProperty('isPrototypeOf') === false) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Most likely a plain Object
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { isPlainObject };
|
package/src/utils/save.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IDataSet, IDataSource, IVisSpec } from "../interfaces";
|
|
1
|
+
import { IDataSet, IDataSource, IVisSpec, IVisSpecForExport } from "../interfaces";
|
|
2
2
|
import { VisSpecWithHistory } from "../models/visSpecHistory";
|
|
3
3
|
|
|
4
4
|
export function dumpsGWPureSpec(list: VisSpecWithHistory[]): IVisSpec[] {
|
|
@@ -12,7 +12,7 @@ export function parseGWPureSpec(list: IVisSpec[]): VisSpecWithHistory[] {
|
|
|
12
12
|
export interface IStoInfo {
|
|
13
13
|
datasets: IDataSet[];
|
|
14
14
|
specList: {
|
|
15
|
-
[K in keyof
|
|
15
|
+
[K in keyof IVisSpecForExport]: K extends "config" ? Partial<IVisSpecForExport[K]> : IVisSpecForExport[K];
|
|
16
16
|
}[];
|
|
17
17
|
dataSources: IDataSource[];
|
|
18
18
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
LightBulbIcon,
|
|
20
20
|
CodeBracketSquareIcon,
|
|
21
21
|
Cog6ToothIcon,
|
|
22
|
+
HashtagIcon,
|
|
22
23
|
} from '@heroicons/react/24/outline';
|
|
23
24
|
import { observer } from 'mobx-react-lite';
|
|
24
25
|
import React, { SVGProps, useCallback, useMemo } from 'react';
|
|
@@ -35,6 +36,8 @@ import { useCurrentMediaTheme } from '../utils/media';
|
|
|
35
36
|
import throttle from '../utils/throttle';
|
|
36
37
|
import KanariesLogo from '../assets/kanaries.png';
|
|
37
38
|
import { ImageWithFallback } from '../components/timeoutImg';
|
|
39
|
+
import LimitSetting from '../components/limitSetting';
|
|
40
|
+
import { runInAction } from 'mobx';
|
|
38
41
|
|
|
39
42
|
const Invisible = styled.div`
|
|
40
43
|
clip: rect(1px, 1px, 1px, 1px);
|
|
@@ -73,7 +76,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
73
76
|
exclude = [],
|
|
74
77
|
}) => {
|
|
75
78
|
const { vizStore, commonStore } = useGlobalStore();
|
|
76
|
-
const { visualConfig, canUndo, canRedo } = vizStore;
|
|
79
|
+
const { visualConfig, canUndo, canRedo, limit } = vizStore;
|
|
77
80
|
const { t: tGlobal } = useTranslation();
|
|
78
81
|
const { t } = useTranslation('translation', { keyPrefix: 'main.tabpanel.settings' });
|
|
79
82
|
|
|
@@ -491,6 +494,25 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
491
494
|
commonStore.setShowCodeExportPanel(true);
|
|
492
495
|
},
|
|
493
496
|
},
|
|
497
|
+
...(extra.length === 0 ? [] : ['-', ...extra]),
|
|
498
|
+
'-',
|
|
499
|
+
{
|
|
500
|
+
key: 'limit_axis',
|
|
501
|
+
label: t('limit'),
|
|
502
|
+
icon: HashtagIcon,
|
|
503
|
+
form: (
|
|
504
|
+
<FormContainer>
|
|
505
|
+
<LimitSetting
|
|
506
|
+
value={limit}
|
|
507
|
+
setValue={(v) => {
|
|
508
|
+
runInAction(() => {
|
|
509
|
+
vizStore.limit = v;
|
|
510
|
+
});
|
|
511
|
+
}}
|
|
512
|
+
/>
|
|
513
|
+
</FormContainer>
|
|
514
|
+
),
|
|
515
|
+
},
|
|
494
516
|
'-',
|
|
495
517
|
{
|
|
496
518
|
key: 'kanaries',
|
|
@@ -513,10 +535,6 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
513
535
|
|
|
514
536
|
const items = builtInItems.filter((item) => typeof item === 'string' || !exclude.includes(item.key));
|
|
515
537
|
|
|
516
|
-
if (extra.length > 0) {
|
|
517
|
-
items.push('-', ...extra);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
538
|
return items;
|
|
521
539
|
}, [
|
|
522
540
|
vizStore,
|
|
@@ -535,6 +553,7 @@ const VisualSettings: React.FC<IVisualSettings> = ({
|
|
|
535
553
|
dark,
|
|
536
554
|
extra,
|
|
537
555
|
exclude,
|
|
556
|
+
limit,
|
|
538
557
|
]);
|
|
539
558
|
|
|
540
559
|
return (
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IViewField, IRow } from '../interfaces';
|
|
2
|
+
import { getMeaAggKey } from '../utils';
|
|
3
|
+
|
|
4
|
+
function compareMulti(a: number[], b: number[]): number {
|
|
5
|
+
if (a.length < b.length) return -compareMulti(b, a);
|
|
6
|
+
for (let i = 0; i < a.length; i++) {
|
|
7
|
+
if (!b[i]) return 1;
|
|
8
|
+
const c = a[i] - b[i];
|
|
9
|
+
if (c !== 0) return c;
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function sortBy(data: IRow[], viewMeasures: IViewField[], sort: 'ascending' | 'descending') {
|
|
15
|
+
const sortM = sort === 'ascending' ? 1 : -1;
|
|
16
|
+
return data
|
|
17
|
+
.map((x) => ({
|
|
18
|
+
data: x,
|
|
19
|
+
value: viewMeasures.map((f) => x[getMeaAggKey(f.fid, f.aggName)]),
|
|
20
|
+
}))
|
|
21
|
+
.sort((a, b) => sortM * compareMulti(a.value, b.value))
|
|
22
|
+
.map((x) => x.data);
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IRow, IViewField } from '../interfaces';
|
|
2
|
+
import { sortBy } from './sort';
|
|
3
|
+
|
|
4
|
+
const main = (e: {
|
|
5
|
+
data: {
|
|
6
|
+
data: IRow[];
|
|
7
|
+
viewMeasures: IViewField[];
|
|
8
|
+
sort: 'ascending' | 'descending';
|
|
9
|
+
};
|
|
10
|
+
}) => {
|
|
11
|
+
try {
|
|
12
|
+
const { data, viewMeasures, sort } = e.data;
|
|
13
|
+
const ans = sortBy(data, viewMeasures, sort);
|
|
14
|
+
self.postMessage(ans);
|
|
15
|
+
} catch (err: any) {
|
|
16
|
+
console.error(err.stack);
|
|
17
|
+
self.postMessage(err.stack);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
self.addEventListener('message', main, false);
|