@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
@@ -1,10 +1,10 @@
1
- import React, { useMemo, useState } from "react";
2
- import styled from "styled-components";
3
- import { IMutField, IRow } from "../../interfaces";
4
- import { useTranslation } from "react-i18next";
5
- import Pagination from "./pagination";
6
- import { ChevronUpDownIcon } from "@heroicons/react/24/outline";
7
- import DropdownContext from "../dropdownContext";
1
+ import React, { useMemo, useState } from 'react';
2
+ import styled from 'styled-components';
3
+ import { IMutField, IRow } from '../../interfaces';
4
+ import { useTranslation } from 'react-i18next';
5
+ import Pagination from './pagination';
6
+ import { ChevronUpDownIcon } from '@heroicons/react/24/outline';
7
+ import DropdownContext from '../dropdownContext';
8
8
 
9
9
  interface DataTableProps {
10
10
  size?: number;
@@ -32,34 +32,83 @@ const Container = styled.div`
32
32
  }
33
33
  }
34
34
  `;
35
- const ANALYTIC_TYPE_LIST = ["dimension", "measure"];
36
- const SEMANTIC_TYPE_LIST = ["nominal", "ordinal", "quantitative", "temporal"];
35
+ const ANALYTIC_TYPE_LIST = ['dimension', 'measure'];
36
+ const SEMANTIC_TYPE_LIST = ['nominal', 'ordinal', 'quantitative', 'temporal'];
37
37
  // function getCellType(field: IMutField): 'number' | 'text' {
38
38
  // return field.dataType === 'number' || field.dataType === 'integer' ? 'number' : 'text';
39
39
  // }
40
- function getHeaderType(field: IMutField): "number" | "text" {
41
- return field.analyticType === "dimension" ? "text" : "number";
40
+ function getHeaderType(field: IMutField): 'number' | 'text' {
41
+ return field.analyticType === 'dimension' ? 'text' : 'number';
42
42
  }
43
43
 
44
44
  function getHeaderClassNames(field: IMutField) {
45
- return field.analyticType === "dimension" ? "border-t-4 border-blue-400" : "border-t-4 border-purple-400";
45
+ return field.analyticType === 'dimension' ? 'border-t-4 border-blue-400' : 'border-t-4 border-purple-400';
46
46
  }
47
47
 
48
48
  function getSemanticColors(field: IMutField): string {
49
49
  switch (field.semanticType) {
50
- case "nominal":
51
- return "border border-transparent bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-100 dark:border-sky-600";
52
- case "ordinal":
53
- return "border border-transparent bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100 dark:border-indigo-600";
54
- case "quantitative":
55
- return "border border-transparent bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100 dark:border-purple-600";
56
- case "temporal":
57
- return "border border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-600";
50
+ case 'nominal':
51
+ return 'border border-transparent bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-100 dark:border-sky-600';
52
+ case 'ordinal':
53
+ return 'border border-transparent bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100 dark:border-indigo-600';
54
+ case 'quantitative':
55
+ return 'border border-transparent bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100 dark:border-purple-600';
56
+ case 'temporal':
57
+ return 'border border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-600';
58
58
  default:
59
- return "border border-transparent bg-gray-400";
59
+ return 'border border-transparent bg-gray-400';
60
60
  }
61
61
  }
62
62
 
63
+ type wrapMutField = {
64
+ colSpan: number;
65
+ rowSpan: number;
66
+ } & (
67
+ | { type: 'field'; value: IMutField; fIndex: number }
68
+ | {
69
+ type: 'name';
70
+ value: string;
71
+ }
72
+ );
73
+
74
+ const getHeaders = (metas: IMutField[]): wrapMutField[][] => {
75
+ const height = metas.map((x) => x.path?.length ?? 1).reduce((a, b) => Math.max(a, b), 0);
76
+ const result: wrapMutField[][] = [...Array(height)].map(() => []);
77
+ let now = 1;
78
+ metas.forEach((x, fIndex) => {
79
+ const path = x.path ?? [x.name ?? x.fid];
80
+ if (path.length > now) {
81
+ for (let i = now - 1; i < path.length - 1; i++) {
82
+ result[i].push({
83
+ colSpan: 0,
84
+ rowSpan: 1,
85
+ type: 'name',
86
+ value: path[i],
87
+ });
88
+ }
89
+ }
90
+ now = path.length;
91
+ for (let i = 0; i < path.length - 1; i++) {
92
+ result[i][result[i].length - 1].colSpan++;
93
+ }
94
+ result[path.length - 1].push({
95
+ type: 'field',
96
+ value: x,
97
+ colSpan: 1,
98
+ rowSpan: height - path.length + 1,
99
+ fIndex,
100
+ });
101
+ });
102
+ return result;
103
+ };
104
+
105
+ const getHeaderKey = (f: wrapMutField) => {
106
+ if (f.type === 'name') {
107
+ return f.value;
108
+ }
109
+ return f.value.name ?? f.value.fid;
110
+ };
111
+
63
112
  const DataTable: React.FC<DataTableProps> = (props) => {
64
113
  const { size = 10, data, metas, onMetaChange } = props;
65
114
  const [pageIndex, setPageIndex] = useState(0);
@@ -82,6 +131,8 @@ const DataTable: React.FC<DataTableProps> = (props) => {
82
131
  const from = pageIndex * size;
83
132
  const to = Math.min((pageIndex + 1) * size, data.length - 1);
84
133
 
134
+ const headers = useMemo(() => getHeaders(metas), [metas]);
135
+
85
136
  return (
86
137
  <Container className="rounded border-gray-200 dark:border-gray-700 border">
87
138
  <Pagination
@@ -97,70 +148,99 @@ const DataTable: React.FC<DataTableProps> = (props) => {
97
148
  />
98
149
  <table className="min-w-full divide-y">
99
150
  <thead className="bg-gray-50 dark:bg-gray-900">
100
- <tr className="divide-x divide-gray-200 dark:divide-gray-700">
101
- {metas.map((field, fIndex) => (
102
- <th key={field.fid} className={""}>
103
- <div
104
- className={
105
- getHeaderClassNames(field) +
106
- " whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 dark:text-gray-50 sm:pl-6"
107
- }
151
+ {headers.map((row) => (
152
+ <tr
153
+ className="divide-x divide-gray-200 dark:divide-gray-700"
154
+ key={`row_${getHeaderKey(row[0])}`}
155
+ >
156
+ {row.map((f) => (
157
+ <th
158
+ colSpan={f.colSpan}
159
+ rowSpan={f.rowSpan}
160
+ key={getHeaderKey(f)}
161
+ className={'align-top'}
108
162
  >
109
- <b>{field.name || field.fid}</b>
110
- <div>
111
- <DropdownContext
112
- options={analyticTypeList}
113
- onSelect={(value) => {
114
- onMetaChange(field.fid, fIndex, {
115
- analyticType: value as IMutField["analyticType"],
116
- });
117
- }}
163
+ {f.type === 'name' && (
164
+ <div
165
+ className={
166
+ 'border-t-4 border-yellow-400 whitespace-nowrap py-3.5 text-left text-xs font-semibold text-gray-900 dark:text-gray-50'
167
+ }
118
168
  >
119
- <span
120
- className={
121
- "cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white " +
122
- (field.analyticType === "dimension" ? "bg-blue-500" : "bg-purple-500")
123
- }
124
- >
125
- {field.analyticType}
126
- <ChevronUpDownIcon className="ml-2 w-3" />
127
- </span>
128
- </DropdownContext>
129
- </div>
130
- <div>
131
- <DropdownContext
132
- options={semanticTypeList}
133
- onSelect={(value) => {
134
- onMetaChange(field.fid, fIndex, {
135
- semanticType: value as IMutField["semanticType"],
136
- });
137
- }}
169
+ <b className="sticky inset-x-0 w-fit px-6 sm:pl-6">{f.value}</b>
170
+ </div>
171
+ )}
172
+ {f.type === 'field' && (
173
+ <div
174
+ className={
175
+ getHeaderClassNames(f.value) +
176
+ ' whitespace-nowrap py-3.5 px-6 text-left text-xs font-semibold text-gray-900 dark:text-gray-50 sm:pl-6'
177
+ }
138
178
  >
139
- <span
140
- className={
141
- "cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs " +
142
- getSemanticColors(field)
143
- }
144
- >
145
- {field.semanticType}
146
- <ChevronUpDownIcon className="ml-2 w-3" />
147
- </span>
148
- </DropdownContext>
149
- </div>
150
- </div>
151
- </th>
152
- ))}
153
- </tr>
179
+ <b>{f.value.basename || f.value.name || f.value.fid}</b>
180
+ <div>
181
+ <DropdownContext
182
+ options={analyticTypeList}
183
+ onSelect={(value) => {
184
+ onMetaChange(f.value.fid, f.fIndex, {
185
+ analyticType: value as IMutField['analyticType'],
186
+ });
187
+ }}
188
+ >
189
+ <span
190
+ className={
191
+ 'cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs text-white ' +
192
+ (f.value.analyticType === 'dimension'
193
+ ? 'bg-blue-500'
194
+ : 'bg-purple-500')
195
+ }
196
+ >
197
+ {f.value.analyticType}
198
+ <ChevronUpDownIcon className="ml-2 w-3" />
199
+ </span>
200
+ </DropdownContext>
201
+ </div>
202
+ <div>
203
+ <DropdownContext
204
+ options={semanticTypeList}
205
+ onSelect={(value) => {
206
+ onMetaChange(f.value.fid, f.fIndex, {
207
+ semanticType: value as IMutField['semanticType'],
208
+ });
209
+ }}
210
+ >
211
+ <span
212
+ className={
213
+ 'cursor-pointer inline-flex px-2.5 py-0.5 text-xs font-medium mt-1 rounded-full text-xs ' +
214
+ getSemanticColors(f.value)
215
+ }
216
+ >
217
+ {f.value.semanticType}
218
+ <ChevronUpDownIcon className="ml-2 w-3" />
219
+ </span>
220
+ </DropdownContext>
221
+ </div>
222
+ </div>
223
+ )}
224
+ </th>
225
+ ))}
226
+ </tr>
227
+ ))}
154
228
  </thead>
155
229
  <tbody className="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-zinc-900">
156
230
  {data.slice(from, to + 1).map((row, index) => (
157
- <tr className={"divide-x divide-gray-200 dark:divide-gray-700 " + (index % 2 ? "bg-gray-50 dark:bg-gray-900" : "")} key={index}>
231
+ <tr
232
+ className={
233
+ 'divide-x divide-gray-200 dark:divide-gray-700 ' +
234
+ (index % 2 ? 'bg-gray-50 dark:bg-gray-900' : '')
235
+ }
236
+ key={index}
237
+ >
158
238
  {metas.map((field) => (
159
239
  <td
160
240
  key={field.fid + index}
161
241
  className={
162
242
  getHeaderType(field) +
163
- " whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6"
243
+ ' whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6'
164
244
  }
165
245
  >
166
246
  {`${row[field.fid]}`}
@@ -12,7 +12,7 @@ export default function Pagination(props: PaginationProps) {
12
12
  const { t } = useTranslation();
13
13
  return (
14
14
  <nav
15
- className="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-zinc-900 px-4 py-3 sm:px-6"
15
+ className="sticky inset-x-0 flex items-center justify-between border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-zinc-900 px-4 py-3 sm:px-6"
16
16
  aria-label="Pagination"
17
17
  >
18
18
  <div className="hidden sm:block">
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+
4
+ export default function LimitSetting(props: { value: number; setValue: (v: number) => void }) {
5
+ const { t } = useTranslation('translation', { keyPrefix: 'main.tabpanel.settings' });
6
+
7
+ return (
8
+ <div className=" mt-2">
9
+ <input
10
+ className="w-full h-2 bg-blue-100 appearance-none"
11
+ type="range"
12
+ name="limit"
13
+ value={props.value > 0 ? props.value : 0}
14
+ min="1"
15
+ max="50"
16
+ disabled={props.value < 0}
17
+ step="1"
18
+ onChange={(e) => {
19
+ const v = parseInt(e.target.value);
20
+ if (!isNaN(v)) {
21
+ props.setValue(v);
22
+ }
23
+ }}
24
+ />
25
+ <output className="text-sm ml-1" htmlFor="height">
26
+ <input
27
+ type="checkbox"
28
+ className="h-4 w-4 mr-1 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
29
+ checked={props.value > 0}
30
+ onChange={(e) => {
31
+ props.setValue(e.target.checked ? 30 : -1);
32
+ }}
33
+ ></input>
34
+ {`${t('limit')}${props.value > 0 ? `: ${props.value}` : ''}`}
35
+ </output>
36
+ </div>
37
+ );
38
+ }
@@ -1,7 +1,6 @@
1
- import { IRow, IMutField } from "../interfaces";
2
- import { inferMeta } from "../lib/inferMeta";
3
- import { guardDataKeys } from "../utils/dataPrep";
4
-
1
+ import { IRow, IMutField } from '../interfaces';
2
+ import { inferMeta } from '../lib/inferMeta';
3
+ import { flatKeys, guardDataKeys } from '../utils/dataPrep';
5
4
 
6
5
  export function transData(dataSource: IRow[]): {
7
6
  dataSource: IRow[];
@@ -17,31 +16,34 @@ export function transData(dataSource: IRow[]): {
17
16
  // const rawKeys = Object.keys(sampleRecord);
18
17
  // let flatColKeys: string[] = flatNestKeys(sampleRecord);
19
18
 
20
- const keys = Object.keys(sampleRecord);
19
+ const keys = flatKeys(sampleRecord);
21
20
  const metas = inferMeta({
22
21
  dataSource,
23
22
  fields: keys.map((k) => ({
24
- fid: k,
25
- key: k,
26
- analyticType: "?",
27
- semanticType: "?",
23
+ fid: k[k.length - 1],
24
+ key: k[k.length - 1],
25
+ analyticType: '?',
26
+ semanticType: '?',
27
+ path: k,
28
+ basename: k[k.length - 1],
29
+ name: k.join('.'),
28
30
  })),
29
- })
31
+ });
30
32
  const { safeData, safeMetas } = guardDataKeys(dataSource, metas);
31
33
  const finalData: IRow[] = [];
32
34
  for (let record of safeData) {
33
35
  const newRecord: IRow = {};
34
36
  for (let field of safeMetas) {
35
- if (field.semanticType === "quantitative") {
37
+ if (field.semanticType === 'quantitative') {
36
38
  newRecord[field.fid] = Number(record[field.fid]);
37
39
  } else {
38
- newRecord[field.fid] = record[field.fid];//getValueByKeyPath(record, field.fid);// record[field.fid];
40
+ newRecord[field.fid] = record[field.fid]; //getValueByKeyPath(record, field.fid);// record[field.fid];
39
41
  }
40
42
  }
41
43
  finalData.push(newRecord);
42
44
  }
43
45
  return {
44
46
  dataSource: finalData,
45
- fields: safeMetas
47
+ fields: safeMetas,
46
48
  };
47
49
  }
@@ -0,0 +1,10 @@
1
+ import { useState, useEffect } from 'react';
2
+
3
+ export function useDebounceValue<T>(value: T, timeout = 200): T {
4
+ const [innerValue, setInnerValue] = useState(value);
5
+ useEffect(() => {
6
+ const handler = setTimeout(() => setInnerValue(value), timeout);
7
+ return () => clearTimeout(handler);
8
+ }, [value]);
9
+ return innerValue;
10
+ }
package/src/interfaces.ts CHANGED
@@ -34,18 +34,22 @@ export interface IMutField {
34
34
  fid: string;
35
35
  key?: string;
36
36
  name?: string;
37
+ basename?: string;
37
38
  disable?: boolean;
38
39
  semanticType: ISemanticType;
39
40
  analyticType: IAnalyticType;
41
+ path?: string[];
40
42
  }
41
43
 
42
44
  export interface IUncertainMutField {
43
45
  fid: string;
44
46
  key?: string;
45
47
  name?: string;
48
+ basename?: string;
46
49
  disable?: boolean;
47
50
  semanticType: ISemanticType | '?';
48
51
  analyticType: IAnalyticType | '?';
52
+ path: string[];
49
53
  }
50
54
 
51
55
  export type IExpParamter =
@@ -90,6 +94,8 @@ export interface IField {
90
94
  cmp?: (a: any, b: any) => number;
91
95
  computed?: boolean;
92
96
  expression?: IExpression;
97
+ basename?: string;
98
+ path?: [],
93
99
  }
94
100
 
95
101
  export interface IViewField extends IField {
@@ -215,6 +221,16 @@ export interface IVisSpec {
215
221
  readonly config: DeepReadonly<IVisualConfig>;
216
222
  }
217
223
 
224
+ export type SetToArray<T> = (
225
+ T extends object ? (
226
+ T extends Set<infer U> ? Array<U> : { [K in keyof T]: SetToArray<T[K]> }
227
+ ) : T
228
+ );
229
+
230
+ export type IVisSpecForExport = SetToArray<IVisSpec>;
231
+
232
+ export type IFilterFieldForExport = SetToArray<IFilterField>;
233
+
218
234
  export enum ISegmentKey {
219
235
  vis = 'vis',
220
236
  data = 'data',
@@ -1,4 +1,5 @@
1
1
  import { IAnalyticType, IMutField, IRow, ISemanticType, IUncertainMutField } from '../interfaces';
2
+ import { getValueByKeyPath } from '../utils/dataPrep';
2
3
 
3
4
  const COMMON_TIME_FORMAT: RegExp[] = [
4
5
  /^\d{4}-\d{2}-\d{2}$/, // YYYY-MM-DD
@@ -55,12 +56,12 @@ function inferAnalyticTypeFromSemanticType(semanticType: ISemanticType): IAnalyt
55
56
  }
56
57
  }
57
58
 
58
- export function inferSemanticType(data: IRow[], fid: string): ISemanticType {
59
- const values = data.map((row) => row[fid]);
59
+ export function inferSemanticType(data: IRow[], path: string[]): ISemanticType {
60
+ const values = data.map((row) => getValueByKeyPath(row, path));
60
61
 
61
62
  let st: ISemanticType = isNumericArray(values) ? 'quantitative' : 'nominal';
62
63
  if (st === 'nominal') {
63
- if (isDateTimeArray(data.map((row) => `${row[fid]}`))) st = 'temporal';
64
+ if (isDateTimeArray(data.map((row) => `${getValueByKeyPath(row, path)}`))) st = 'temporal';
64
65
  }
65
66
  return st;
66
67
  }
@@ -70,7 +71,7 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
70
71
  const finalFieldMetas: IMutField[] = [];
71
72
  for (let field of fields) {
72
73
  let semanticType: ISemanticType =
73
- field.semanticType === '?' ? inferSemanticType(dataSource, field.fid) : field.semanticType;
74
+ field.semanticType === '?' ? inferSemanticType(dataSource, field.path) : field.semanticType;
74
75
  let analyticType: IAnalyticType = inferAnalyticTypeFromSemanticType(semanticType);
75
76
 
76
77
  finalFieldMetas.push({
@@ -79,6 +80,8 @@ export function inferMeta(props: { dataSource: IRow[]; fields: IUncertainMutFiel
79
80
  name: field.name ? field.name : field.fid,
80
81
  analyticType,
81
82
  semanticType,
83
+ basename: field.basename || field.name || field.fid,
84
+ path: field.path,
82
85
  });
83
86
  }
84
87
  return finalFieldMetas;
@@ -1,4 +1,4 @@
1
- import { IMutField, IRow } from "../interfaces";
1
+ import { IField, IRow } from "../interfaces";
2
2
  import { aggregate } from "./op/aggregate";
3
3
  import { fold } from "./op/fold";
4
4
  import { IAggQuery, IBinQuery, IFoldQuery, IRawQuery } from "./interfaces";
@@ -6,7 +6,7 @@ import { bin } from "./op/bin";
6
6
 
7
7
  export type IViewQuery = IAggQuery | IFoldQuery | IBinQuery | IRawQuery;
8
8
 
9
- export function queryView (rawData: IRow[], metas: IMutField[], query: IViewQuery) {
9
+ export function queryView (rawData: IRow[], metas: IField[], query: IViewQuery) {
10
10
  switch (query.op) {
11
11
  case 'aggregate':
12
12
  return aggregate(rawData, query);
@@ -154,6 +154,7 @@
154
154
  "export_chart_as": "Export as {{type}}",
155
155
  "export_code": "Export Code"
156
156
  },
157
+ "limit": "Limit",
157
158
  "size": "Resize",
158
159
  "size_setting": {
159
160
  "width": "Width",
@@ -153,6 +153,7 @@
153
153
  "export_chart_as": "{{type}}としてエクスポート",
154
154
  "export_code": "コードをエクスポート"
155
155
  },
156
+ "limit": "上限",
156
157
  "size": "サイズ変更",
157
158
  "size_setting": {
158
159
  "width": "幅",
@@ -154,6 +154,7 @@
154
154
  "export_chart_as": "导出 {{type}}",
155
155
  "export_code": "导出代码"
156
156
  },
157
+ "limit": "上限",
157
158
  "size": "调整尺寸",
158
159
  "size_setting": {
159
160
  "width": "宽度",
@@ -1,10 +1,10 @@
1
1
  import { useState, useEffect, useMemo, useRef } from 'react';
2
2
  import { unstable_batchedUpdates } from 'react-dom';
3
3
  import type { DeepReadonly, IFilterField, IRow, IViewField } from '../interfaces';
4
- import { applyFilter, applyViewQuery, transformDataService } from '../services';
4
+ import { applyFilter, applySort, applyViewQuery, transformDataService } from '../services';
5
5
  import { getMeaAggKey } from '../utils';
6
6
  import { useAppRootContext } from '../components/appRoot';
7
-
7
+ import { useDebounceValue } from '../hooks';
8
8
 
9
9
  interface UseRendererProps {
10
10
  data: IRow[];
@@ -13,6 +13,8 @@ interface UseRendererProps {
13
13
  viewMeasures: IViewField[];
14
14
  filters: readonly DeepReadonly<IFilterField>[];
15
15
  defaultAggregated: boolean;
16
+ sort: 'none' | 'ascending' | 'descending';
17
+ limit: number;
16
18
  }
17
19
 
18
20
  interface UseRendererResult {
@@ -21,10 +23,21 @@ interface UseRendererResult {
21
23
  }
22
24
 
23
25
  export const useRenderer = (props: UseRendererProps): UseRendererResult => {
24
- const { data, allFields, viewDimensions, viewMeasures, filters, defaultAggregated } = props;
26
+ const {
27
+ data,
28
+ allFields,
29
+ viewDimensions,
30
+ viewMeasures,
31
+ filters,
32
+ defaultAggregated,
33
+ sort,
34
+ limit: storeLimit,
35
+ } = props;
25
36
  const [computing, setComputing] = useState(false);
26
37
  const taskIdRef = useRef(0);
27
38
 
39
+ const limit = useDebounceValue(storeLimit);
40
+
28
41
  const [viewData, setViewData] = useState<IRow[]>([]);
29
42
 
30
43
  const appRef = useAppRootContext();
@@ -50,10 +63,26 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
50
63
  return applyViewQuery(d, dims.concat(meas), {
51
64
  op: defaultAggregated ? 'aggregate' : 'raw',
52
65
  groupBy: dims.map((f) => f.fid),
53
- measures: meas.map((f) => ({ field: f.fid, agg: f.aggName as any, asFieldKey: getMeaAggKey(f.fid, f.aggName!) })),
66
+ measures: meas.map((f) => ({
67
+ field: f.fid,
68
+ agg: f.aggName as any,
69
+ asFieldKey: getMeaAggKey(f.fid, f.aggName!),
70
+ })),
54
71
  });
55
72
  })
56
- .then(data => {
73
+ .then((data) => {
74
+ if (limit > 0 && sort !== 'none' && viewMeasures.length > 0) {
75
+ return applySort(data, viewMeasures, sort);
76
+ }
77
+ return data;
78
+ })
79
+ .then((data) => {
80
+ if (limit > 0) {
81
+ return data.slice(0, limit);
82
+ }
83
+ return data;
84
+ })
85
+ .then((data) => {
57
86
  if (taskId !== taskIdRef.current) {
58
87
  return;
59
88
  }
@@ -62,7 +91,8 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
62
91
  setComputing(false);
63
92
  setViewData(data);
64
93
  });
65
- }).catch((err) => {
94
+ })
95
+ .catch((err) => {
66
96
  if (taskId !== taskIdRef.current) {
67
97
  return;
68
98
  }
@@ -76,7 +106,7 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
76
106
  return () => {
77
107
  taskIdRef.current++;
78
108
  };
79
- }, [data, filters, viewDimensions, viewMeasures, defaultAggregated]);
109
+ }, [data, filters, viewDimensions, viewMeasures, defaultAggregated, limit]);
80
110
 
81
111
  return useMemo(() => {
82
112
  return {