@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.
Files changed (44) hide show
  1. package/dist/assets/sort.worker-4299a6a0.js.map +1 -0
  2. package/dist/assets/viewQuery.worker-03404216.js.map +1 -1
  3. package/dist/components/dataTable/index.d.ts +2 -2
  4. package/dist/components/limitSetting.d.ts +5 -0
  5. package/dist/dataSource/utils.d.ts +1 -1
  6. package/dist/graphic-walker.es.js +21095 -20910
  7. package/dist/graphic-walker.es.js.map +1 -1
  8. package/dist/graphic-walker.umd.js +140 -135
  9. package/dist/graphic-walker.umd.js.map +1 -1
  10. package/dist/hooks/index.d.ts +1 -0
  11. package/dist/interfaces.d.ts +11 -0
  12. package/dist/lib/inferMeta.d.ts +1 -1
  13. package/dist/lib/viewQuery.d.ts +2 -2
  14. package/dist/renderer/hooks.d.ts +2 -0
  15. package/dist/services.d.ts +4 -3
  16. package/dist/store/visualSpecStore.d.ts +430 -2
  17. package/dist/utils/dataPrep.d.ts +2 -2
  18. package/dist/utils/is-plain-object.d.ts +2 -0
  19. package/dist/utils/save.d.ts +2 -2
  20. package/dist/workers/sort.d.ts +2 -0
  21. package/dist/workers/sort.worker.d.ts +1 -0
  22. package/package.json +1 -1
  23. package/src/components/dataTable/index.tsx +154 -74
  24. package/src/components/dataTable/pagination.tsx +1 -1
  25. package/src/components/limitSetting.tsx +38 -0
  26. package/src/dataSource/utils.ts +15 -13
  27. package/src/hooks/index.ts +10 -0
  28. package/src/interfaces.ts +16 -0
  29. package/src/lib/inferMeta.ts +7 -4
  30. package/src/lib/viewQuery.ts +2 -2
  31. package/src/locales/en-US.json +1 -0
  32. package/src/locales/ja-JP.json +1 -0
  33. package/src/locales/zh-CN.json +1 -0
  34. package/src/renderer/hooks.ts +37 -7
  35. package/src/renderer/index.tsx +22 -12
  36. package/src/renderer/pureRenderer.tsx +7 -11
  37. package/src/services.ts +27 -5
  38. package/src/store/visualSpecStore.ts +71 -7
  39. package/src/utils/dataPrep.ts +21 -28
  40. package/src/utils/is-plain-object.ts +33 -0
  41. package/src/utils/save.ts +2 -2
  42. package/src/visualSettings/index.tsx +24 -5
  43. package/src/workers/sort.ts +23 -0
  44. package/src/workers/sort.worker.ts +21 -0
@@ -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 { allFields, viewFilters, viewDimensions, viewMeasures, visualConfig, draggableFieldState, visList, visIndex } = vizStore;
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
- (values: any, e: any) => {
72
- e.stopPropagation();
73
- runInAction(() => {
74
- commonStore.showEmbededMenu([e.pageX, e.pageY]);
75
- commonStore.setFilters(values);
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 (props, ref) {
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: IMutField[]): Promise<IRow[]> => {
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: IMutField[], query: IViewQuery): Promise<IRow[]> => {
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"]): IVisSpec[] => {
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
  }
@@ -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[from];
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 (metas: IMutField[]) {
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 (data: IRow[], metas: IMutField[]): {
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
- const SPLITOR = '__'
46
- export function flatNestKeys (object: any): string[] {
47
- const keys = Object.keys(object);
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 (object: any, keyPath: string): any {
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 keys) {
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 IVisSpec]: K extends "config" ? Partial<IVisSpec[K]> : IVisSpec[K];
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);